326 lines
9.6 KiB
Dart
326 lines
9.6 KiB
Dart
import 'package:swagger_generator_flutter/core/models.dart';
|
|
import 'package:swagger_generator_flutter/utils/reference_resolver.dart';
|
|
import 'package:test/test.dart';
|
|
|
|
void main() {
|
|
group('ReferenceResolver', () {
|
|
late ReferenceResolver resolver;
|
|
|
|
setUp(() {
|
|
resolver = ReferenceResolver();
|
|
});
|
|
|
|
tearDown(() {
|
|
resolver.clearCache();
|
|
});
|
|
|
|
test('resolves simple models without references', () {
|
|
final componentsJson = {
|
|
'schemas': {
|
|
'User': {
|
|
'type': 'object',
|
|
'description': 'User model',
|
|
'properties': {
|
|
'id': {'type': 'integer'},
|
|
'name': {'type': 'string'},
|
|
},
|
|
'required': ['id', 'name'],
|
|
},
|
|
'Product': {
|
|
'type': 'object',
|
|
'description': 'Product model',
|
|
'properties': {
|
|
'id': {'type': 'integer'},
|
|
'title': {'type': 'string'},
|
|
'price': {'type': 'number'},
|
|
},
|
|
'required': ['id', 'title'],
|
|
},
|
|
},
|
|
};
|
|
|
|
final models = resolver.resolveModels(componentsJson);
|
|
|
|
expect(models.length, 2);
|
|
expect(models['User'], isNotNull);
|
|
expect(models['User']!.name, 'User');
|
|
expect(models['User']!.properties.length, 2);
|
|
expect(models['User']!.required, ['id', 'name']);
|
|
|
|
expect(models['Product'], isNotNull);
|
|
expect(models['Product']!.name, 'Product');
|
|
expect(models['Product']!.properties.length, 3);
|
|
expect(models['Product']!.required, ['id', 'title']);
|
|
});
|
|
|
|
test('resolves models with allOf composition', () {
|
|
final componentsJson = {
|
|
'schemas': {
|
|
'BaseEntity': {
|
|
'type': 'object',
|
|
'properties': {
|
|
'id': {'type': 'integer'},
|
|
'createdAt': {'type': 'string', 'format': 'date-time'},
|
|
},
|
|
'required': ['id'],
|
|
},
|
|
'User': {
|
|
'allOf': [
|
|
{'\$ref': '#/components/schemas/BaseEntity'},
|
|
{
|
|
'type': 'object',
|
|
'properties': {
|
|
'name': {'type': 'string'},
|
|
'email': {'type': 'string'},
|
|
},
|
|
'required': ['name'],
|
|
},
|
|
],
|
|
},
|
|
},
|
|
};
|
|
|
|
final models = resolver.resolveModels(componentsJson);
|
|
|
|
expect(models.length, 2);
|
|
expect(models['User'], isNotNull);
|
|
expect(models['User']!.isAllOf, true);
|
|
expect(models['User']!.allOf.length, 2);
|
|
|
|
// 检查合并的属性
|
|
expect(
|
|
models['User']!.properties.length, 4); // id, createdAt, name, email
|
|
expect(models['User']!.properties['id'], isNotNull);
|
|
expect(models['User']!.properties['name'], isNotNull);
|
|
expect(models['User']!.properties['email'], isNotNull);
|
|
expect(models['User']!.properties['createdAt'], isNotNull);
|
|
});
|
|
|
|
test('detects and handles circular references', () {
|
|
final componentsJson = {
|
|
'schemas': {
|
|
'Node': {
|
|
'type': 'object',
|
|
'properties': {
|
|
'id': {'type': 'integer'},
|
|
'parent': {'\$ref': '#/components/schemas/Node'},
|
|
'children': {
|
|
'type': 'array',
|
|
'items': {'\$ref': '#/components/schemas/Node'},
|
|
},
|
|
},
|
|
'required': ['id'],
|
|
},
|
|
},
|
|
};
|
|
|
|
final models = resolver.resolveModels(componentsJson);
|
|
|
|
expect(models.length, 1);
|
|
expect(models['Node'], isNotNull);
|
|
expect(models['Node']!.name, 'Node');
|
|
expect(models['Node']!.properties.length, 3);
|
|
|
|
// 循环引用应该被正确处理,不会导致无限递归
|
|
expect(models['Node']!.properties['parent'], isNotNull);
|
|
expect(models['Node']!.properties['children'], isNotNull);
|
|
});
|
|
|
|
test('handles complex nested structures', () {
|
|
final componentsJson = {
|
|
'schemas': {
|
|
'Address': {
|
|
'type': 'object',
|
|
'properties': {
|
|
'street': {'type': 'string'},
|
|
'city': {'type': 'string'},
|
|
'coordinates': {
|
|
'type': 'object',
|
|
'properties': {
|
|
'lat': {'type': 'number'},
|
|
'lng': {'type': 'number'},
|
|
},
|
|
'required': ['lat', 'lng'],
|
|
},
|
|
},
|
|
'required': ['street', 'city'],
|
|
},
|
|
'User': {
|
|
'type': 'object',
|
|
'properties': {
|
|
'id': {'type': 'integer'},
|
|
'name': {'type': 'string'},
|
|
'addresses': {
|
|
'type': 'array',
|
|
'items': {'\$ref': '#/components/schemas/Address'},
|
|
},
|
|
},
|
|
'required': ['id', 'name'],
|
|
},
|
|
},
|
|
};
|
|
|
|
final models = resolver.resolveModels(componentsJson);
|
|
|
|
expect(models.length, 2);
|
|
expect(models['User'], isNotNull);
|
|
expect(models['Address'], isNotNull);
|
|
|
|
final userModel = models['User']!;
|
|
expect(userModel.properties['addresses'], isNotNull);
|
|
expect(userModel.properties['addresses']!.type, PropertyType.array);
|
|
|
|
final addressModel = models['Address']!;
|
|
expect(addressModel.properties['coordinates'], isNotNull);
|
|
expect(addressModel.properties['coordinates']!.type, PropertyType.object);
|
|
});
|
|
|
|
test('handles enum models', () {
|
|
final componentsJson = {
|
|
'schemas': {
|
|
'Status': {
|
|
'type': 'string',
|
|
'enum': ['active', 'inactive', 'pending'],
|
|
'description': 'User status',
|
|
},
|
|
'Priority': {
|
|
'type': 'integer',
|
|
'enum': [1, 2, 3, 4, 5],
|
|
'description': 'Priority level',
|
|
},
|
|
},
|
|
};
|
|
|
|
final models = resolver.resolveModels(componentsJson);
|
|
|
|
expect(models.length, 2);
|
|
|
|
final statusModel = models['Status']!;
|
|
expect(statusModel.isEnum, true);
|
|
expect(statusModel.enumValues, ['active', 'inactive', 'pending']);
|
|
expect(statusModel.enumType, PropertyType.string);
|
|
|
|
final priorityModel = models['Priority']!;
|
|
expect(priorityModel.isEnum, true);
|
|
expect(priorityModel.enumValues, [1, 2, 3, 4, 5]);
|
|
expect(priorityModel.enumType, PropertyType.integer);
|
|
});
|
|
|
|
test('handles oneOf with discriminator', () {
|
|
final componentsJson = {
|
|
'schemas': {
|
|
'Pet': {
|
|
'oneOf': [
|
|
{'\$ref': '#/components/schemas/Cat'},
|
|
{'\$ref': '#/components/schemas/Dog'},
|
|
],
|
|
'discriminator': {
|
|
'propertyName': 'petType',
|
|
'mapping': {
|
|
'cat': '#/components/schemas/Cat',
|
|
'dog': '#/components/schemas/Dog',
|
|
},
|
|
},
|
|
},
|
|
'Cat': {
|
|
'type': 'object',
|
|
'properties': {
|
|
'petType': {'type': 'string'},
|
|
'meowSound': {'type': 'string'},
|
|
},
|
|
'required': ['petType'],
|
|
},
|
|
'Dog': {
|
|
'type': 'object',
|
|
'properties': {
|
|
'petType': {'type': 'string'},
|
|
'barkSound': {'type': 'string'},
|
|
},
|
|
'required': ['petType'],
|
|
},
|
|
},
|
|
};
|
|
|
|
final models = resolver.resolveModels(componentsJson);
|
|
|
|
expect(models.length, 3);
|
|
|
|
final petModel = models['Pet']!;
|
|
expect(petModel.isOneOf, true);
|
|
expect(petModel.hasDiscriminator, true);
|
|
expect(petModel.discriminator?.propertyName, 'petType');
|
|
expect(petModel.discriminator?.mapping.length, 2);
|
|
|
|
expect(models['Cat'], isNotNull);
|
|
expect(models['Dog'], isNotNull);
|
|
});
|
|
|
|
test('respects maximum depth limit', () {
|
|
final resolver = ReferenceResolver(maxDepth: 3);
|
|
|
|
final componentsJson = {
|
|
'schemas': {
|
|
'DeepNested': {
|
|
'type': 'object',
|
|
'properties': {
|
|
'level1': {
|
|
'type': 'object',
|
|
'properties': {
|
|
'level2': {
|
|
'type': 'object',
|
|
'properties': {
|
|
'level3': {
|
|
'type': 'object',
|
|
'properties': {
|
|
'level4': {'type': 'string'},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
final models = resolver.resolveModels(componentsJson);
|
|
|
|
expect(models.length, 1);
|
|
expect(models['DeepNested'], isNotNull);
|
|
|
|
// 应该能够解析,但深度受限
|
|
final model = models['DeepNested']!;
|
|
expect(model.properties['level1'], isNotNull);
|
|
});
|
|
|
|
test('handles parsing errors gracefully', () {
|
|
final componentsJson = {
|
|
'schemas': {
|
|
'ValidModel': {
|
|
'type': 'object',
|
|
'properties': {
|
|
'id': {'type': 'integer'},
|
|
},
|
|
},
|
|
'InvalidModel': {
|
|
// 故意创建一个可能导致解析错误的结构
|
|
'properties': 'invalid_properties_value', // 这会导致类型错误
|
|
'required': 123, // 这也会导致类型错误
|
|
},
|
|
},
|
|
};
|
|
|
|
final models = resolver.resolveModels(componentsJson);
|
|
|
|
expect(models.length, 2);
|
|
expect(models['ValidModel'], isNotNull);
|
|
expect(models['InvalidModel'], isNotNull);
|
|
|
|
// 无效模型应该被创建为后备模型
|
|
final invalidModel = models['InvalidModel']!;
|
|
expect(invalidModel.description, '解析失败的模型');
|
|
});
|
|
});
|
|
}
|