swagger_generator_flutter/test/reference_resolver_test.dart

328 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': [
{r'$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': {r'$ref': '#/components/schemas/Node'},
'children': {
'type': 'array',
'items': {r'$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': {r'$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': [
{r'$ref': '#/components/schemas/Cat'},
{r'$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, '解析失败的模型');
});
});
}