Files
koi_swagger_parser/test/server_security_test.dart
mr.koi 454e6c51d7 Release 0.2.0
Complete OpenAPI 3.0/3.1 generation, diagnostics, lifecycle safety, enum and naming mappings, bilingual documentation, examples, and pub.dev release validation.
2026-07-31 16:47:39 +08:00

215 lines
6.1 KiB
Dart

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': <String, Object?>{"));
expect(client, contains("'scheme': 'bearer'"));
expect(client, contains("'apiKeyAuth': <String, Object?>{"));
expect(client, contains("'in': 'header'"));
expect(client, contains("'scopes': <String>['accounts:read']"));
expect(client, contains("'GET /accounts':"));
expect(client, contains("'POST /upload': <Map<String, List<String>>>[]"));
});
test('不存在的 security scheme 会结构化拒绝', () {
final config = _testConfig();
expect(
() => OpenApiDocumentParser(config).parse(
LoadedOpenApiDocument(
document: <String, dynamic>{
'openapi': '3.1.0',
'info': <String, dynamic>{
'title': 'invalid auth',
'version': '1',
},
'security': <dynamic>[
<String, dynamic>{'missing': <String>[]},
],
'paths': <String, dynamic>{
'/value': <String, dynamic>{
'get': <String, dynamic>{
'responses': <String, dynamic>{
'204': <String, dynamic>{'description': 'ok'},
},
},
},
},
},
sources: const <String>['memory://invalid-auth'],
),
),
throwsA(
isA<KoiDiagnosticException>().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: <String>[]),
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,
),
),
);
}