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('HTTP wire semantics', () { test( '生成 style/explode/cookie/allowReserved transport adapter 与表单', () async { final tempDir = await Directory.systemTemp.createTemp( 'koi_swagger_parser_http_semantics_', ); addTearDown(() => tempDir.delete(recursive: true)); final specFile = File(p.join(tempDir.path, 'openapi.json')); await specFile.writeAsString(r''' { "openapi": "3.1.0", "info": {"title": "HTTP Semantics", "version": "1.0.0"}, "paths": { "/api/v1/items/{coordinates}": { "post": { "tags": ["items"], "operationId": "search_items", "parameters": [ { "name": "coordinates", "in": "path", "required": true, "style": "matrix", "explode": false, "schema": {"type": "array", "items": {"type": "integer"}} }, { "name": "filter", "in": "query", "style": "deepObject", "explode": true, "schema": { "type": "object", "properties": { "role": {"type": "string"}, "active": {"type": "boolean"} } } }, { "name": "sort", "in": "query", "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string"}} }, { "name": "fields", "in": "query", "style": "pipeDelimited", "schema": {"type": "array", "items": {"type": "string"}} }, { "name": "redirect", "in": "query", "allowReserved": true, "schema": {"type": "string"} }, { "name": "X-Flags", "in": "header", "schema": {"type": "array", "items": {"type": "string"}} }, { "name": "session", "in": "cookie", "required": true, "schema": {"type": "string"} }, { "name": "features", "in": "cookie", "explode": true, "schema": {"type": "array", "items": {"type": "string"}} } ], "requestBody": { "required": true, "content": { "application/x-www-form-urlencoded": { "schema": { "type": "object", "required": ["name", "state"], "properties": { "name": {"type": "string"}, "state": { "type": "string", "enum": ["draft", "published"] }, "labels": { "type": "array", "items": {"type": "string"} } } } } } }, "responses": {"204": {"description": "updated"}} } } } } '''); 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')} generation: api: response_wrapper: mode: passthrough models: class_prefix: Koi formatting: run_dart_format: false '''); await const KoiSwaggerParser().generate(configPath: configFile.path); final apiFile = File(p.join(outputDir, 'api', 'v1', 'items_api.dart')); final api = await apiFile.readAsString(); expect(api, contains('class ItemsApi {')); expect(api, contains('final _ItemsApiRetrofit _delegate;')); expect(api, contains('required List coordinates')); expect(api, contains('@RestApi(parser: Parser.JsonSerializable)')); expect(api, contains('abstract class _ItemsApiRetrofit')); expect( api, contains("@POST('/api/v1/items/{coordinates}{reservedQuery}')"), ); expect( api, contains("@Path('coordinates') required String coordinates"), ); expect(api, contains("@Query('filter') required")); expect(api, contains("@Query('sort') required String? sort")); expect(api, contains("@Header('X-Flags') required String? xFlags")); expect( api, contains("@Header('Cookie') required String? cookieHeader"), ); expect( api, contains("@Path('reservedQuery') required String reservedQuery"), ); expect(api, contains('@FormUrlEncoded()')); expect(api, contains("@Field('state') required Object? state")); expect(api, contains('_openApiCookieHeader(')); expect(api, contains('_openApiReservedQuery(')); expect(api, contains("case 'matrix':")); expect(api, contains("'pipeDelimited' => '|'")); final format = await Process.run(Platform.resolvedExecutable, [ 'format', '--output=none', apiFile.path, ]); expect( format.exitCode, 0, reason: '${format.stdout}\n${format.stderr}', ); }, ); test('可选 JSON body 生成 nullable 类型与 nullToAbsent', () async { final config = 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, ), ), ); final parsed = OpenApiDocumentParser(config).parse( LoadedOpenApiDocument( document: { 'openapi': '3.1.0', 'info': {'title': 'optional body', 'version': '1'}, 'paths': { '/api/v1/items': { 'post': { 'tags': ['items'], 'requestBody': { 'required': false, 'content': { 'application/json': { 'schema': { 'type': 'object', 'properties': { 'name': {'type': 'string'}, }, }, }, }, }, 'responses': { '204': {'description': 'ok'}, }, }, }, }, }, sources: const ['memory://optional-body'], ), ); expect(parsed.operations.single.requestBody!.type!.nullable, isTrue); }); }); }