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.
This commit is contained in:
@@ -0,0 +1,214 @@
|
||||
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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user