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,312 @@
|
||||
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('enum mappings', () {
|
||||
test('按配置和扩展字段优先级生成名称、描述与 unknown fallback', () async {
|
||||
final tempDir = await Directory.systemTemp.createTemp(
|
||||
'koi_swagger_parser_enum_mappings_',
|
||||
);
|
||||
addTearDown(() => tempDir.delete(recursive: true));
|
||||
|
||||
final specFile = File(p.join(tempDir.path, 'openapi.json'));
|
||||
await specFile.writeAsString(r'''
|
||||
{
|
||||
"openapi": "3.1.0",
|
||||
"info": {
|
||||
"title": "Enum mappings",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"paths": {
|
||||
"/api/v1/deliveries": {
|
||||
"get": {
|
||||
"tags": ["deliveries"],
|
||||
"operationId": "list_deliveries",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "ok",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/DeliveryResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"DeliveryResponse": {
|
||||
"type": "object",
|
||||
"required": ["state"],
|
||||
"properties": {
|
||||
"state": {
|
||||
"$ref": "#/components/schemas/DeliveryState"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DeliveryState": {
|
||||
"type": "integer",
|
||||
"enum": [0, 1, 2, 3, 4],
|
||||
"x-enumNames": [null, "ONE_FROM_NAMES"],
|
||||
"x-enum-varnames": [
|
||||
"ZERO_FROM_VARNAMES",
|
||||
"ONE_FROM_VARNAMES",
|
||||
"TWO_FROM_VARNAMES"
|
||||
],
|
||||
"x-enumDescriptions": [null, "来自 x-enumDescriptions"],
|
||||
"x-enum-descriptions": [
|
||||
"来自 x-enum-descriptions",
|
||||
"旧描述",
|
||||
"第二项描述"
|
||||
],
|
||||
"x-enum-values": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
{
|
||||
"value": 3,
|
||||
"name": "THREE_FROM_VALUES",
|
||||
"description": "来自 x-enum-values"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
''');
|
||||
|
||||
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}
|
||||
enabled: true
|
||||
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
|
||||
enum_unknown_fallback: true
|
||||
future_option: true
|
||||
enum_key_mappings:
|
||||
DeliveryState:
|
||||
- value: 0
|
||||
name: OLD_ZERO
|
||||
enum_mappings:
|
||||
DeliveryState:
|
||||
- value: 0
|
||||
name: CONFIG_ZERO
|
||||
description: 来自 enum_mappings
|
||||
formatting:
|
||||
run_dart_format: false
|
||||
''');
|
||||
|
||||
final result = await const KoiSwaggerParser().generate(
|
||||
configPath: configFile.path,
|
||||
);
|
||||
|
||||
final enumFile = File(
|
||||
p.join(outputDir, 'api_models', 'enums', 'delivery_state.dart'),
|
||||
).readAsStringSync();
|
||||
expect(enumFile, contains('CONFIG_ZERO,'));
|
||||
expect(enumFile, isNot(contains('OLD_ZERO,')));
|
||||
expect(enumFile, contains('ONE_FROM_NAMES,'));
|
||||
expect(enumFile, contains('TWO_FROM_VARNAMES,'));
|
||||
expect(enumFile, contains('THREE_FROM_VALUES,'));
|
||||
expect(enumFile, contains('value4,'));
|
||||
expect(enumFile, contains('unknown,'));
|
||||
expect(enumFile, contains('/// 来自 enum_mappings'));
|
||||
expect(enumFile, contains('/// 来自 x-enumDescriptions'));
|
||||
expect(enumFile, contains('/// 第二项描述'));
|
||||
expect(enumFile, contains('/// 来自 x-enum-values'));
|
||||
|
||||
final responseFile = File(
|
||||
p.join(outputDir, 'api_models', 'result', 'delivery_response.dart'),
|
||||
).readAsStringSync();
|
||||
expect(
|
||||
responseFile,
|
||||
contains('unknownEnumValue: KoiDeliveryState.unknown'),
|
||||
);
|
||||
|
||||
expect(
|
||||
result.diagnostics.map((diagnostic) => diagnostic.code),
|
||||
containsAll(<String>[
|
||||
'config.unknown_key',
|
||||
'config.enum_mapping_overridden',
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
test('无效映射通过结构化配置错误拒绝', () async {
|
||||
final tempDir = await Directory.systemTemp.createTemp(
|
||||
'koi_swagger_parser_invalid_enum_mapping_',
|
||||
);
|
||||
addTearDown(() => tempDir.delete(recursive: true));
|
||||
|
||||
final configFile = File(p.join(tempDir.path, 'koi_swagger_parser.yaml'));
|
||||
await configFile.writeAsString('''
|
||||
input:
|
||||
swagger_urls:
|
||||
- url: ./openapi.json
|
||||
generation:
|
||||
api:
|
||||
response_wrapper:
|
||||
mode: passthrough
|
||||
models:
|
||||
enum_mappings:
|
||||
Status:
|
||||
- value: 1
|
||||
''');
|
||||
|
||||
await expectLater(
|
||||
KoiSwaggerConfig.load(configPath: configFile.path),
|
||||
throwsA(
|
||||
isA<KoiDiagnosticException>().having(
|
||||
(error) => error.diagnostics.map((diagnostic) => diagnostic.code),
|
||||
'diagnostic codes',
|
||||
contains('config.invalid_enum_mapping'),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('OpenAPI diagnostics', () {
|
||||
test('extract_version_from_url 从单一 source 文件名提取版本', () async {
|
||||
final tempDir = await Directory.systemTemp.createTemp(
|
||||
'koi_swagger_parser_source_version_',
|
||||
);
|
||||
addTearDown(() => tempDir.delete(recursive: true));
|
||||
|
||||
final specFile = File(p.join(tempDir.path, 'catalog-v4.openapi.json'));
|
||||
await specFile.writeAsString(r'''
|
||||
{
|
||||
"openapi": "3.1.0",
|
||||
"info": {
|
||||
"title": "Source version",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"paths": {
|
||||
"/catalog": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "no content"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
''');
|
||||
|
||||
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')}
|
||||
extract_version_from_url: true
|
||||
generation:
|
||||
api:
|
||||
response_wrapper:
|
||||
mode: passthrough
|
||||
formatting:
|
||||
run_dart_format: false
|
||||
''');
|
||||
|
||||
final result = await const KoiSwaggerParser().generate(
|
||||
configPath: configFile.path,
|
||||
);
|
||||
|
||||
expect(
|
||||
File(p.join(outputDir, 'api', 'v4', 'default_api.dart')).existsSync(),
|
||||
isTrue,
|
||||
);
|
||||
expect(
|
||||
result.diagnostics.map((diagnostic) => diagnostic.code),
|
||||
isNot(contains('config.source_version_not_found')),
|
||||
);
|
||||
});
|
||||
|
||||
test('unresolved ref 不再静默退化为 dynamic', () async {
|
||||
final tempDir = await Directory.systemTemp.createTemp(
|
||||
'koi_swagger_parser_unresolved_ref_',
|
||||
);
|
||||
addTearDown(() => tempDir.delete(recursive: true));
|
||||
|
||||
final specFile = File(p.join(tempDir.path, 'openapi.json'));
|
||||
await specFile.writeAsString(r'''
|
||||
{
|
||||
"openapi": "3.1.0",
|
||||
"info": {
|
||||
"title": "Broken refs",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"paths": {
|
||||
"/api/v1/items": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "ok",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/MissingModel"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {}
|
||||
}
|
||||
}
|
||||
''');
|
||||
|
||||
final configFile = File(p.join(tempDir.path, 'koi_swagger_parser.yaml'));
|
||||
await configFile.writeAsString('''
|
||||
input:
|
||||
swagger_urls:
|
||||
- url: ${specFile.path}
|
||||
output:
|
||||
base_dir: ${p.join(tempDir.path, 'lib', 'src')}
|
||||
generation:
|
||||
api:
|
||||
response_wrapper:
|
||||
mode: passthrough
|
||||
''');
|
||||
|
||||
await expectLater(
|
||||
const KoiSwaggerParser().generate(configPath: configFile.path),
|
||||
throwsA(
|
||||
isA<KoiDiagnosticException>().having(
|
||||
(error) => error.diagnostics.map((diagnostic) => diagnostic.code),
|
||||
'diagnostic codes',
|
||||
contains('openapi.unresolved_ref'),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user