import 'dart:io'; import 'package:koi_swagger_parser/koi_swagger_parser.dart'; import 'package:koi_swagger_parser/src/openapi/openapi.dart'; import 'package:path/path.dart' as p; import 'package:test/test.dart'; void main() { group('servers and security metadata', () { test('解析 server variables、operation override 与鉴权元数据', () async { final tempDir = await Directory.systemTemp.createTemp( 'koi_swagger_parser_server_security_', ); addTearDown(() => tempDir.delete(recursive: true)); final spec = File(p.join(tempDir.path, 'openapi.json')); await spec.writeAsString(r''' { "openapi": "3.1.0", "info": {"title": "servers and auth", "version": "1"}, "servers": [ { "url": "https://{environment}.example.com/{version}", "variables": { "environment": { "default": "api", "enum": ["api", "staging"] }, "version": {"default": "v1"} } } ], "security": [ {"bearerAuth": []}, {"apiKeyAuth": []} ], "paths": { "/accounts": { "get": { "tags": ["accounts"], "operationId": "list_accounts", "responses": {"204": {"description": "ok"}} } }, "/upload": { "post": { "tags": ["accounts"], "operationId": "upload_account", "servers": [{"url": "https://upload.example.com"}], "security": [], "responses": {"204": {"description": "ok"}} } } }, "components": { "securitySchemes": { "bearerAuth": { "type": "http", "scheme": "bearer", "bearerFormat": "JWT" }, "basicAuth": { "type": "http", "scheme": "basic" }, "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "X-API-Key" }, "oauth": { "type": "oauth2", "flows": { "clientCredentials": { "tokenUrl": "https://auth.example.com/token", "scopes": { "accounts:read": "read accounts" } } } } } } } '''); 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 '''); await const KoiSwaggerParser().generate(configPath: config.path); final api = File( p.join(outputDir, 'api', 'v1', 'accounts_api.dart'), ).readAsStringSync(); expect( api, contains( "@RestApi(baseUrl: 'https://api.example.com/v1', " 'parser: Parser.JsonSerializable)', ), ); expect(api, contains('Security: bearerAuth OR apiKeyAuth')); expect(api, contains("@POST('https://upload.example.com/upload')")); final client = File( p.join(outputDir, 'api', 'api_client.dart'), ).readAsStringSync(); expect( client, contains("baseUrl = baseUrl ?? 'https://api.example.com/v1'"), ); expect(client, contains("'bearerAuth': {")); expect(client, contains("'scheme': 'bearer'")); expect(client, contains("'apiKeyAuth': {")); expect(client, contains("'in': 'header'")); expect(client, contains("'scopes': ['accounts:read']")); expect(client, contains("'GET /accounts':")); expect(client, contains("'POST /upload': >>[]")); }); test('不存在的 security scheme 会结构化拒绝', () { final config = _testConfig(); expect( () => OpenApiDocumentParser(config).parse( LoadedOpenApiDocument( document: { 'openapi': '3.1.0', 'info': { 'title': 'invalid auth', 'version': '1', }, 'security': [ {'missing': []}, ], 'paths': { '/value': { 'get': { 'responses': { '204': {'description': 'ok'}, }, }, }, }, }, sources: const ['memory://invalid-auth'], ), ), throwsA( isA().having( (error) => error.diagnostics.map((item) => item.code), 'codes', contains('openapi.unknown_security_scheme'), ), ), ); }); }); } KoiSwaggerConfig _testConfig() { return KoiSwaggerConfig( configPath: null, workingDirectory: Directory.current.path, input: const InputConfig(swaggerUrls: []), output: OutputConfig( baseDir: Directory.current.path, apiDir: p.join(Directory.current.path, 'api'), modelsDir: p.join(Directory.current.path, 'models'), splitByTags: true, extractVersionFromUrl: false, ), generation: GenerationConfig( preset: GeneratorPresetRegistry.koiFlutter, api: const ApiGenerationConfig( enabled: true, useRetrofit: true, cancelTokenSupport: false, client: ClientConfig(className: 'ApiClient', fileName: 'api_client'), responseWrapper: ResponseWrapperConfig( mode: ResponseWrapperMode.passthrough, successDataField: 'data', detectStandardEnvelope: false, ), ), models: const ModelGenerationConfig( enabled: true, classPrefix: 'Koi', defaultNonNullableListToEmpty: true, jsonSerializable: JsonSerializableConfig( checked: true, explicitToJson: true, includeIfNull: false, ), ), formatting: const FormattingConfig( runDartFormat: false, runDartFix: false, ), ), ); }