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('schema semantics', () { test( '区分 required/nullable/absent,按方向拆分 readOnly/writeOnly,并保留 additionalProperties', () async { final tempDir = await Directory.systemTemp.createTemp( 'koi_swagger_parser_schema_semantics_', ); addTearDown(() => tempDir.delete(recursive: true)); final spec = File(p.join(tempDir.path, 'openapi.json')); await spec.writeAsString(r''' { "openapi": "3.1.0", "info": {"title": "schema semantics", "version": "1"}, "paths": { "/api/v1/accounts": { "post": { "tags": ["accounts"], "operationId": "create_account", "requestBody": { "required": true, "content": { "application/json": { "schema": {"$ref": "#/components/schemas/Account"} } } }, "responses": { "200": { "description": "created", "content": { "application/json": { "schema": {"$ref": "#/components/schemas/Account"} } } } } } } }, "components": { "schemas": { "Account": { "type": "object", "required": ["id", "secret", "display_name", "settings"], "properties": { "id": {"type": "integer", "readOnly": true}, "secret": {"type": "string", "writeOnly": true}, "display_name": {"type": ["string", "null"]}, "nickname": {"type": "string"}, "settings": {"$ref": "#/components/schemas/FlexibleSettings"} } }, "FlexibleSettings": { "type": "object", "required": ["theme"], "properties": { "theme": {"type": "string"} }, "additionalProperties": {"type": "integer"} } } } } '''); final outputDir = p.join(tempDir.path, 'lib', 'src'); final config = File(p.join(tempDir.path, 'koi_swagger_parser.yaml')); await config.writeAsString(''' input: swagger_urls: - url: ${spec.path} output: base_dir: $outputDir api_dir: ${p.join(outputDir, 'api')} models_dir: ${p.join(outputDir, 'models')} generation: api: response_wrapper: mode: passthrough models: class_prefix: Koi formatting: run_dart_format: false '''); await const KoiSwaggerParser().generate(configPath: config.path); final request = File( p.join(outputDir, 'models', 'request', 'account_request.dart'), ).readAsStringSync(); final response = File( p.join(outputDir, 'models', 'result', 'account_response.dart'), ).readAsStringSync(); expect(request, contains('class KoiAccountRequest')); expect(request, isNot(contains('required int id'))); expect(request, contains('required String secret')); expect(request, contains('required String? displayName')); expect(request, contains('includeIfNull: true')); expect(request, contains('String? nickname')); expect(request, contains('disallowNullValue: true')); expect(response, contains('class KoiAccountResponse')); expect(response, contains('required int id')); expect(response, isNot(contains('required String secret'))); expect(response, contains('required String? displayName')); final settings = File( p.join(outputDir, 'models', 'shared', 'flexible_settings.dart'), ).readAsStringSync(); expect(settings, contains('Map additionalProperties')); expect(settings, contains('ReadAdditionalProperties')); expect(settings, contains('createToJson: false')); expect(settings, contains('result[entry.key]')); expect(settings, contains("result['theme']")); final api = File( p.join(outputDir, 'api', 'v1', 'accounts_api.dart'), ).readAsStringSync(); expect(api, contains('required KoiAccountRequest body')); expect(api, contains('Future createAccount')); }, ); test('同一模型属性名冲突时确定性后缀并给出诊断', () async { final tempDir = await Directory.systemTemp.createTemp( 'koi_swagger_parser_property_collision_', ); addTearDown(() => tempDir.delete(recursive: true)); final spec = File(p.join(tempDir.path, 'openapi.json')); await spec.writeAsString(r''' { "openapi": "3.1.0", "info": {"title": "collision", "version": "1"}, "paths": { "/api/v1/value": { "get": { "tags": ["value"], "responses": { "200": { "description": "ok", "content": { "application/json": { "schema": { "type": "object", "properties": { "some-value": {"type": "string"}, "some_value": {"type": "string"} } } } } } } } } } } '''); final outputDir = p.join(tempDir.path, 'lib', 'src'); final config = File(p.join(tempDir.path, 'koi_swagger_parser.yaml')); await config.writeAsString(''' input: swagger_urls: - url: ${spec.path} output: base_dir: $outputDir api_dir: ${p.join(outputDir, 'api')} models_dir: ${p.join(outputDir, 'models')} generation: api: response_wrapper: mode: passthrough formatting: run_dart_format: false '''); final result = await const KoiSwaggerParser().generate( configPath: config.path, ); expect( result.diagnostics.map((diagnostic) => diagnostic.code), contains('openapi.property_name_collision'), ); final modelPath = result.writtenFiles.singleWhere( (path) => path.endsWith('response.dart'), ); final model = File(modelPath).readAsStringSync(); expect(model, contains('String? someValue')); expect(model, contains('String? someValue2')); }); }); }