import 'dart:io'; import 'package:koi_swagger_parser/koi_swagger_parser.dart'; import 'package:path/path.dart' as p; import 'package:test/test.dart'; void main() { group('enum mappings', () { test('按配置和扩展字段优先级生成名称、描述与 unknown fallback', () async { final tempDir = await Directory.systemTemp.createTemp( 'koi_swagger_parser_enum_mappings_', ); addTearDown(() => tempDir.delete(recursive: true)); final specFile = File(p.join(tempDir.path, 'openapi.json')); await specFile.writeAsString(r''' { "openapi": "3.1.0", "info": { "title": "Enum mappings", "version": "1.0.0" }, "paths": { "/api/v1/deliveries": { "get": { "tags": ["deliveries"], "operationId": "list_deliveries", "responses": { "200": { "description": "ok", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DeliveryResponse" } } } } } } } }, "components": { "schemas": { "DeliveryResponse": { "type": "object", "required": ["state"], "properties": { "state": { "$ref": "#/components/schemas/DeliveryState" } } }, "DeliveryState": { "type": "integer", "enum": [0, 1, 2, 3, 4], "x-enumNames": [null, "ONE_FROM_NAMES"], "x-enum-varnames": [ "ZERO_FROM_VARNAMES", "ONE_FROM_VARNAMES", "TWO_FROM_VARNAMES" ], "x-enumDescriptions": [null, "来自 x-enumDescriptions"], "x-enum-descriptions": [ "来自 x-enum-descriptions", "旧描述", "第二项描述" ], "x-enum-values": [ null, null, null, { "value": 3, "name": "THREE_FROM_VALUES", "description": "来自 x-enum-values" } ] } } } } '''); final outputDir = p.join(tempDir.path, 'lib', 'src'); final configFile = File(p.join(tempDir.path, 'koi_swagger_parser.yaml')); await configFile.writeAsString(''' input: swagger_urls: - url: ${specFile.path} enabled: true output: base_dir: $outputDir api_dir: ${p.join(outputDir, 'api')} models_dir: ${p.join(outputDir, 'api_models')} generation: api: response_wrapper: mode: passthrough models: class_prefix: Koi enum_unknown_fallback: true future_option: true enum_key_mappings: DeliveryState: - value: 0 name: OLD_ZERO enum_mappings: DeliveryState: - value: 0 name: CONFIG_ZERO description: 来自 enum_mappings formatting: run_dart_format: false '''); final result = await const KoiSwaggerParser().generate( configPath: configFile.path, ); final enumFile = File( p.join(outputDir, 'api_models', 'enums', 'delivery_state.dart'), ).readAsStringSync(); expect(enumFile, contains('CONFIG_ZERO,')); expect(enumFile, isNot(contains('OLD_ZERO,'))); expect(enumFile, contains('ONE_FROM_NAMES,')); expect(enumFile, contains('TWO_FROM_VARNAMES,')); expect(enumFile, contains('THREE_FROM_VALUES,')); expect(enumFile, contains('value4,')); expect(enumFile, contains('unknown,')); expect(enumFile, contains('/// 来自 enum_mappings')); expect(enumFile, contains('/// 来自 x-enumDescriptions')); expect(enumFile, contains('/// 第二项描述')); expect(enumFile, contains('/// 来自 x-enum-values')); final responseFile = File( p.join(outputDir, 'api_models', 'result', 'delivery_response.dart'), ).readAsStringSync(); expect( responseFile, contains('unknownEnumValue: KoiDeliveryState.unknown'), ); expect( result.diagnostics.map((diagnostic) => diagnostic.code), containsAll([ 'config.unknown_key', 'config.enum_mapping_overridden', ]), ); }); test('无效映射通过结构化配置错误拒绝', () async { final tempDir = await Directory.systemTemp.createTemp( 'koi_swagger_parser_invalid_enum_mapping_', ); addTearDown(() => tempDir.delete(recursive: true)); final configFile = File(p.join(tempDir.path, 'koi_swagger_parser.yaml')); await configFile.writeAsString(''' input: swagger_urls: - url: ./openapi.json generation: api: response_wrapper: mode: passthrough models: enum_mappings: Status: - value: 1 '''); await expectLater( KoiSwaggerConfig.load(configPath: configFile.path), throwsA( isA().having( (error) => error.diagnostics.map((diagnostic) => diagnostic.code), 'diagnostic codes', contains('config.invalid_enum_mapping'), ), ), ); }); }); group('OpenAPI diagnostics', () { test('extract_version_from_url 从单一 source 文件名提取版本', () async { final tempDir = await Directory.systemTemp.createTemp( 'koi_swagger_parser_source_version_', ); addTearDown(() => tempDir.delete(recursive: true)); final specFile = File(p.join(tempDir.path, 'catalog-v4.openapi.json')); await specFile.writeAsString(r''' { "openapi": "3.1.0", "info": { "title": "Source version", "version": "1.0.0" }, "paths": { "/catalog": { "get": { "responses": { "204": { "description": "no content" } } } } } } '''); final outputDir = p.join(tempDir.path, 'lib', 'src'); final configFile = File(p.join(tempDir.path, 'koi_swagger_parser.yaml')); await configFile.writeAsString(''' input: swagger_urls: - url: ${specFile.path} output: base_dir: $outputDir api_dir: ${p.join(outputDir, 'api')} models_dir: ${p.join(outputDir, 'api_models')} extract_version_from_url: true generation: api: response_wrapper: mode: passthrough formatting: run_dart_format: false '''); final result = await const KoiSwaggerParser().generate( configPath: configFile.path, ); expect( File(p.join(outputDir, 'api', 'v4', 'default_api.dart')).existsSync(), isTrue, ); expect( result.diagnostics.map((diagnostic) => diagnostic.code), isNot(contains('config.source_version_not_found')), ); }); test('unresolved ref 不再静默退化为 dynamic', () async { final tempDir = await Directory.systemTemp.createTemp( 'koi_swagger_parser_unresolved_ref_', ); addTearDown(() => tempDir.delete(recursive: true)); final specFile = File(p.join(tempDir.path, 'openapi.json')); await specFile.writeAsString(r''' { "openapi": "3.1.0", "info": { "title": "Broken refs", "version": "1.0.0" }, "paths": { "/api/v1/items": { "get": { "responses": { "200": { "description": "ok", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MissingModel" } } } } } } } }, "components": { "schemas": {} } } '''); final configFile = File(p.join(tempDir.path, 'koi_swagger_parser.yaml')); await configFile.writeAsString(''' input: swagger_urls: - url: ${specFile.path} output: base_dir: ${p.join(tempDir.path, 'lib', 'src')} generation: api: response_wrapper: mode: passthrough '''); await expectLater( const KoiSwaggerParser().generate(configPath: configFile.path), throwsA( isA().having( (error) => error.diagnostics.map((diagnostic) => diagnostic.code), 'diagnostic codes', contains('openapi.unresolved_ref'), ), ), ); }); }); }