311 lines
8.4 KiB
Dart
311 lines
8.4 KiB
Dart
import 'dart:convert';
|
|
import 'dart:io';
|
|
|
|
import 'package:path/path.dart' as p;
|
|
import 'package:swagger_generator_flutter/commands/generate_command.dart';
|
|
import 'package:swagger_generator_flutter/utils/path_resolver.dart';
|
|
import 'package:test/test.dart';
|
|
|
|
void main() {
|
|
late Directory originalCwd;
|
|
late Directory tempDir;
|
|
|
|
setUp(() async {
|
|
originalCwd = Directory.current;
|
|
tempDir = await Directory(
|
|
p.join(
|
|
originalCwd.path,
|
|
'test',
|
|
'tmp',
|
|
'generate_command_${DateTime.now().microsecondsSinceEpoch}',
|
|
),
|
|
).create(recursive: true);
|
|
Directory.current = tempDir;
|
|
PathResolver.clearCache();
|
|
});
|
|
|
|
tearDown(() async {
|
|
PathResolver.clearCache();
|
|
Directory.current = originalCwd;
|
|
if (tempDir.existsSync()) {
|
|
await tempDir.delete(recursive: true);
|
|
}
|
|
});
|
|
|
|
test('按版本生成 API 与模型文件', () async {
|
|
await _writeJson(
|
|
'swagger_v1.json',
|
|
_buildSwaggerDoc(
|
|
title: 'Test API V1',
|
|
version: '1.0.0',
|
|
specs: [
|
|
const _PathSpec(
|
|
path: '/api/v1/users',
|
|
tag: 'Users',
|
|
schemaName: 'User',
|
|
operationId: 'getUsers',
|
|
),
|
|
],
|
|
),
|
|
);
|
|
|
|
await _writeJson(
|
|
'swagger_v2.json',
|
|
_buildSwaggerDoc(
|
|
title: 'Test API V2',
|
|
version: '2.0.0',
|
|
specs: [
|
|
const _PathSpec(
|
|
path: '/api/v2/admins',
|
|
tag: 'Admins',
|
|
schemaName: 'Admin',
|
|
operationId: 'getAdmins',
|
|
),
|
|
],
|
|
),
|
|
);
|
|
|
|
await _writeConfig(
|
|
swaggerFiles: ['swagger_v1.json', 'swagger_v2.json'],
|
|
apiClientClassName: 'MultiVersionApiClient',
|
|
apiClientFileName: 'multi_version_client',
|
|
);
|
|
|
|
final exitCode = await GenerateCommand().execute([]);
|
|
expect(exitCode, equals(0));
|
|
|
|
final outputDir = Directory(p.join(tempDir.path, 'out'));
|
|
expect(outputDir.existsSync(), isTrue);
|
|
|
|
final apiV1Dir = Directory(p.join(outputDir.path, 'api', 'v1'));
|
|
final apiV2Dir = Directory(p.join(outputDir.path, 'api', 'v2'));
|
|
expect(apiV1Dir.existsSync(), isTrue);
|
|
expect(apiV2Dir.existsSync(), isTrue);
|
|
|
|
expect(
|
|
_dartFiles(apiV1Dir).isNotEmpty,
|
|
isTrue,
|
|
reason: 'v1 版本应至少生成一个 API 文件',
|
|
);
|
|
expect(
|
|
_dartFiles(apiV2Dir).isNotEmpty,
|
|
isTrue,
|
|
reason: 'v2 版本应至少生成一个 API 文件',
|
|
);
|
|
|
|
final apiClient =
|
|
File(p.join(outputDir.path, 'api', 'multi_version_client.dart'));
|
|
expect(apiClient.existsSync(), isTrue);
|
|
|
|
final summary = File(p.join(outputDir.path, 'SUMMARY.md'));
|
|
expect(summary.existsSync(), isTrue);
|
|
final summaryContent = summary.readAsStringSync();
|
|
expect(summaryContent, contains('API标题'));
|
|
expect(summaryContent, contains('API版本'));
|
|
|
|
final modelsDir = Directory(p.join(outputDir.path, 'models', 'result'));
|
|
expect(modelsDir.existsSync(), isTrue);
|
|
final modelFiles = _dartFiles(modelsDir);
|
|
expect(
|
|
modelFiles.length,
|
|
greaterThanOrEqualTo(2),
|
|
reason: '示例应生成 User 与 Admin 模型文件',
|
|
);
|
|
});
|
|
|
|
test('支持命令行 tag 过滤', () async {
|
|
await _writeJson(
|
|
'swagger_filtered.json',
|
|
_buildSwaggerDoc(
|
|
title: 'Filtered API',
|
|
version: '1.0.0',
|
|
specs: const [
|
|
_PathSpec(
|
|
path: '/api/v1/users',
|
|
tag: 'Users',
|
|
schemaName: 'User',
|
|
operationId: 'getUsers',
|
|
),
|
|
_PathSpec(
|
|
path: '/api/v1/internal',
|
|
tag: 'Internal',
|
|
schemaName: 'InternalModel',
|
|
operationId: 'getInternal',
|
|
),
|
|
],
|
|
),
|
|
);
|
|
|
|
await _writeConfig(
|
|
swaggerFiles: ['swagger_filtered.json'],
|
|
apiClientClassName: 'FilteredApiClient',
|
|
apiClientFileName: 'filtered_api_client',
|
|
);
|
|
|
|
final exitCode = await GenerateCommand().execute([
|
|
'--api',
|
|
'--models',
|
|
'--included-tags=Users',
|
|
'--excluded-tags=Internal',
|
|
]);
|
|
expect(exitCode, equals(0));
|
|
|
|
final apiDir = Directory(p.join(tempDir.path, 'out', 'api', 'v1'));
|
|
expect(apiDir.existsSync(), isTrue);
|
|
final apiFiles = _dartFiles(apiDir);
|
|
expect(apiFiles.any((file) => file.contains('internal')), isFalse);
|
|
expect(apiFiles.any((file) => file.contains('user')), isTrue);
|
|
|
|
final resultDir =
|
|
Directory(p.join(tempDir.path, 'out', 'models', 'result'));
|
|
expect(resultDir.existsSync(), isTrue);
|
|
final modelNames = _dartFiles(resultDir).map(p.basename).toList();
|
|
expect(modelNames.any((name) => name.contains('internal')), isFalse);
|
|
expect(modelNames.any((name) => name.contains('user')), isTrue);
|
|
});
|
|
}
|
|
|
|
Future<void> _writeJson(String name, Map<String, dynamic> data) async {
|
|
final file = File(p.join(Directory.current.path, name));
|
|
await file.writeAsString(jsonEncode(data));
|
|
}
|
|
|
|
Future<void> _writeConfig({
|
|
required List<String> swaggerFiles,
|
|
required String apiClientClassName,
|
|
required String apiClientFileName,
|
|
}) async {
|
|
final buffer = StringBuffer()
|
|
..writeln('generator:')
|
|
..writeln(' name: "test-generator"')
|
|
..writeln(' version: "1.0.0"')
|
|
..writeln(' author: "codex"')
|
|
..writeln(
|
|
' copyright: "Copyright (C) 2025 '
|
|
'Swagger Generator Flutter. All rights reserved."',
|
|
)
|
|
..writeln('input:')
|
|
..writeln(' swagger_urls:');
|
|
|
|
for (final file in swaggerFiles) {
|
|
buffer.writeln(' - "./$file"');
|
|
}
|
|
|
|
buffer
|
|
..writeln('output:')
|
|
..writeln(' base_dir: "./out"')
|
|
..writeln(' api_dir: "./out/api"')
|
|
..writeln(' models_dir: "./out/models"')
|
|
..writeln(' split_by_tags: true')
|
|
..writeln('generation:')
|
|
..writeln(' api:')
|
|
..writeln(' client:')
|
|
..writeln(' class_name: "$apiClientClassName"')
|
|
..writeln(' file_name: "$apiClientFileName"')
|
|
..writeln(' version_extraction:')
|
|
..writeln(r' pattern: "/api/v(\\d+)/"')
|
|
..writeln(' default_version: "v1"')
|
|
..writeln(' models:')
|
|
..writeln(' enabled: true');
|
|
|
|
await File(p.join(Directory.current.path, 'generator_config.yaml'))
|
|
.writeAsString(buffer.toString());
|
|
}
|
|
|
|
Map<String, dynamic> _buildSwaggerDoc({
|
|
required String title,
|
|
required String version,
|
|
required List<_PathSpec> specs,
|
|
}) {
|
|
final tagDescriptions = <String, String>{};
|
|
final schemas = <String, Map<String, dynamic>>{};
|
|
final paths = <String, Map<String, dynamic>>{};
|
|
|
|
for (final spec in specs) {
|
|
tagDescriptions.putIfAbsent(spec.tag, () => '${spec.tag} APIs');
|
|
schemas[spec.schemaName] = _schemaDefinition(spec.schemaName);
|
|
final operations = paths.putIfAbsent(spec.path, () => <String, dynamic>{});
|
|
operations['get'] = _operationFor(spec);
|
|
}
|
|
|
|
return {
|
|
'openapi': '3.0.0',
|
|
'info': {
|
|
'title': title,
|
|
'version': version,
|
|
'description': '$title description',
|
|
},
|
|
'tags': tagDescriptions.entries
|
|
.map(
|
|
(entry) => {
|
|
'name': entry.key,
|
|
'description': entry.value,
|
|
},
|
|
)
|
|
.toList(),
|
|
'paths': paths.map(MapEntry.new),
|
|
'components': {
|
|
'schemas': schemas,
|
|
},
|
|
};
|
|
}
|
|
|
|
Map<String, dynamic> _operationFor(_PathSpec spec) {
|
|
return {
|
|
'summary': '${spec.tag} summary',
|
|
'description': '${spec.tag} description',
|
|
'operationId': spec.operationId,
|
|
'tags': [spec.tag],
|
|
'responses': {
|
|
'200': {
|
|
'description': 'OK',
|
|
'content': {
|
|
'application/json': {
|
|
'schema': {
|
|
'type': 'array',
|
|
'items': {r'$ref': '#/components/schemas/${spec.schemaName}'},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
Map<String, dynamic> _schemaDefinition(String name) {
|
|
return {
|
|
'type': 'object',
|
|
'properties': {
|
|
'id': {'type': 'integer', 'format': 'int64'},
|
|
'name': {'type': 'string'},
|
|
},
|
|
'required': ['id'],
|
|
'description': '$name definition',
|
|
};
|
|
}
|
|
|
|
Iterable<String> _dartFiles(Directory directory) {
|
|
if (!directory.existsSync()) {
|
|
return const Iterable<String>.empty();
|
|
}
|
|
return directory
|
|
.listSync()
|
|
.whereType<File>()
|
|
.map((file) => file.path)
|
|
.where((path) => path.endsWith('.dart'));
|
|
}
|
|
|
|
class _PathSpec {
|
|
const _PathSpec({
|
|
required this.path,
|
|
required this.tag,
|
|
required this.schemaName,
|
|
required this.operationId,
|
|
});
|
|
|
|
final String path;
|
|
final String tag;
|
|
final String schemaName;
|
|
final String operationId;
|
|
}
|