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 _writeJson(String name, Map data) async { final file = File(p.join(Directory.current.path, name)); await file.writeAsString(jsonEncode(data)); } Future _writeConfig({ required List 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 _buildSwaggerDoc({ required String title, required String version, required List<_PathSpec> specs, }) { final tagDescriptions = {}; final schemas = >{}; final paths = >{}; for (final spec in specs) { tagDescriptions.putIfAbsent(spec.tag, () => '${spec.tag} APIs'); schemas[spec.schemaName] = _schemaDefinition(spec.schemaName); final operations = paths.putIfAbsent(spec.path, () => {}); 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 _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 _schemaDefinition(String name) { return { 'type': 'object', 'properties': { 'id': {'type': 'integer', 'format': 'int64'}, 'name': {'type': 'string'}, }, 'required': ['id'], 'description': '$name definition', }; } Iterable _dartFiles(Directory directory) { if (!directory.existsSync()) { return const Iterable.empty(); } return directory .listSync() .whereType() .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; }