636 lines
20 KiB
Dart
636 lines
20 KiB
Dart
import 'package:swagger_generator_flutter/core/models.dart';
|
|
import 'package:test/test.dart';
|
|
|
|
void main() {
|
|
group('Comprehensive Parser Tests', () {
|
|
group('OpenAPI 3.0 Core Features', () {
|
|
test('parses basic OpenAPI 3.0 document', () {
|
|
final json = <String, dynamic>{
|
|
'openapi': '3.0.3',
|
|
'info': {
|
|
'title': 'Test API',
|
|
'version': '1.0.0',
|
|
'description': 'A test API',
|
|
},
|
|
'servers': [
|
|
{
|
|
'url': 'https://api.example.com',
|
|
'description': 'Production server',
|
|
},
|
|
],
|
|
'paths': {
|
|
'/users': {
|
|
'get': {
|
|
'summary': 'Get users',
|
|
'operationId': 'getUsers',
|
|
'responses': {
|
|
'200': {
|
|
'description': 'Success',
|
|
'content': {
|
|
'application/json': {
|
|
'schema': {
|
|
'type': 'array',
|
|
'items': {
|
|
r'$ref': '#/components/schemas/User',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
'components': {
|
|
'schemas': {
|
|
'User': {
|
|
'type': 'object',
|
|
'properties': {
|
|
'id': {
|
|
'type': 'integer',
|
|
'format': 'int64',
|
|
},
|
|
'name': {
|
|
'type': 'string',
|
|
},
|
|
},
|
|
'required': ['id', 'name'],
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
final document = SwaggerDocument.fromJson(json);
|
|
|
|
expect(document.title, equals('Test API'));
|
|
expect(document.version, equals('1.0.0'));
|
|
expect(document.description, equals('A test API'));
|
|
expect(document.servers, hasLength(1));
|
|
expect(document.servers.first.url, equals('https://api.example.com'));
|
|
expect(document.paths, hasLength(1));
|
|
expect(document.models, hasLength(1));
|
|
expect(document.models.containsKey('User'), isTrue);
|
|
});
|
|
|
|
test('parses servers with variables', () {
|
|
final json = <String, dynamic>{
|
|
'openapi': '3.0.3',
|
|
'info': {'title': 'Test', 'version': '1.0.0'},
|
|
'servers': [
|
|
{
|
|
'url': 'https://{environment}.example.com/{basePath}',
|
|
'description': 'Configurable server',
|
|
'variables': {
|
|
'environment': {
|
|
'default': 'api',
|
|
'enum': ['api', 'staging', 'dev'],
|
|
'description': 'Environment name',
|
|
},
|
|
'basePath': {
|
|
'default': 'v1',
|
|
'description': 'API version',
|
|
},
|
|
},
|
|
},
|
|
],
|
|
'paths': {},
|
|
};
|
|
|
|
final document = SwaggerDocument.fromJson(json);
|
|
final server = document.servers.first;
|
|
|
|
expect(
|
|
server.url,
|
|
equals('https://{environment}.example.com/{basePath}'),
|
|
);
|
|
expect(server.variables, hasLength(2));
|
|
expect(server.variables.containsKey('environment'), isTrue);
|
|
expect(server.variables['environment']!.defaultValue, equals('api'));
|
|
expect(server.variables['environment']!.enumValues, hasLength(3));
|
|
});
|
|
|
|
test('parses complex request body', () {
|
|
final json = <String, dynamic>{
|
|
'openapi': '3.0.3',
|
|
'info': {'title': 'Test', 'version': '1.0.0'},
|
|
'paths': {
|
|
'/users': {
|
|
'post': {
|
|
'summary': 'Create user',
|
|
'requestBody': {
|
|
'description': 'User to create',
|
|
'required': true,
|
|
'content': {
|
|
'application/json': {
|
|
'schema': {
|
|
r'$ref': '#/components/schemas/User',
|
|
},
|
|
'examples': {
|
|
'user1': {
|
|
'summary': 'Example user',
|
|
'value': {
|
|
'name': 'John Doe',
|
|
'email': 'john@example.com',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
'application/xml': {
|
|
'schema': {
|
|
r'$ref': '#/components/schemas/User',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
'responses': {
|
|
'201': {'description': 'Created'},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
'components': {
|
|
'schemas': {
|
|
'User': {
|
|
'type': 'object',
|
|
'properties': {
|
|
'name': {'type': 'string'},
|
|
'email': {'type': 'string'},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
final document = SwaggerDocument.fromJson(json);
|
|
final path = document.paths['/users']!;
|
|
|
|
expect(path.requestBody, isNotNull);
|
|
expect(path.requestBody!.required, isTrue);
|
|
expect(path.requestBody!.content, hasLength(2));
|
|
expect(
|
|
path.requestBody!.content.containsKey('application/json'),
|
|
isTrue,
|
|
);
|
|
expect(
|
|
path.requestBody!.content.containsKey('application/xml'),
|
|
isTrue,
|
|
);
|
|
|
|
final jsonContent = path.requestBody!.content['application/json']!;
|
|
expect(jsonContent.examples, hasLength(1));
|
|
expect(jsonContent.examples.containsKey('user1'), isTrue);
|
|
});
|
|
|
|
test('parses complex responses with headers and links', () {
|
|
final json = <String, dynamic>{
|
|
'openapi': '3.0.3',
|
|
'info': {'title': 'Test', 'version': '1.0.0'},
|
|
'paths': {
|
|
'/users/{id}': {
|
|
'get': {
|
|
'summary': 'Get user',
|
|
'parameters': [
|
|
{
|
|
'name': 'id',
|
|
'in': 'path',
|
|
'required': true,
|
|
'schema': {'type': 'integer'},
|
|
},
|
|
],
|
|
'responses': {
|
|
'200': {
|
|
'description': 'User found',
|
|
'headers': {
|
|
'X-Rate-Limit': {
|
|
'description': 'Rate limit',
|
|
'schema': {'type': 'integer'},
|
|
},
|
|
},
|
|
'content': {
|
|
'application/json': {
|
|
'schema': {
|
|
r'$ref': '#/components/schemas/User',
|
|
},
|
|
},
|
|
},
|
|
'links': {
|
|
'getUserPosts': {
|
|
'operationId': 'getUserPosts',
|
|
'parameters': {
|
|
'userId': r'$response.body#/id',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
'404': {
|
|
'description': 'User not found',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
'components': {
|
|
'schemas': {
|
|
'User': {
|
|
'type': 'object',
|
|
'properties': {
|
|
'id': {'type': 'integer'},
|
|
'name': {'type': 'string'},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
final document = SwaggerDocument.fromJson(json);
|
|
final path = document.paths['/users/{id}']!;
|
|
|
|
expect(path.parameters, hasLength(1));
|
|
expect(path.parameters.first.name, equals('id'));
|
|
expect(path.parameters.first.location, equals(ParameterLocation.path));
|
|
expect(path.parameters.first.required, isTrue);
|
|
|
|
expect(path.responses, hasLength(2));
|
|
final successResponse = path.responses['200']!;
|
|
expect(successResponse.headers, hasLength(1));
|
|
expect(successResponse.headers.containsKey('X-Rate-Limit'), isTrue);
|
|
expect(successResponse.links, hasLength(1));
|
|
expect(successResponse.links.containsKey('getUserPosts'), isTrue);
|
|
});
|
|
|
|
test('parses security schemes and requirements', () {
|
|
final json = <String, dynamic>{
|
|
'openapi': '3.0.3',
|
|
'info': {'title': 'Test', 'version': '1.0.0'},
|
|
'security': [
|
|
{'bearerAuth': []},
|
|
{'apiKey': []},
|
|
],
|
|
'paths': {
|
|
'/protected': {
|
|
'get': {
|
|
'summary': 'Protected endpoint',
|
|
'security': [
|
|
{
|
|
'bearerAuth': ['read:users'],
|
|
},
|
|
],
|
|
'responses': {
|
|
'200': {'description': 'Success'},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
'components': {
|
|
'securitySchemes': {
|
|
'bearerAuth': {
|
|
'type': 'http',
|
|
'scheme': 'bearer',
|
|
'bearerFormat': 'JWT',
|
|
},
|
|
'apiKey': {
|
|
'type': 'apiKey',
|
|
'in': 'header',
|
|
'name': 'X-API-Key',
|
|
},
|
|
'oauth2': {
|
|
'type': 'oauth2',
|
|
'flows': {
|
|
'authorizationCode': {
|
|
'authorizationUrl': 'https://example.com/oauth/authorize',
|
|
'tokenUrl': 'https://example.com/oauth/token',
|
|
'scopes': {
|
|
'read:users': 'Read user data',
|
|
'write:users': 'Write user data',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
final document = SwaggerDocument.fromJson(json);
|
|
|
|
expect(document.security, hasLength(2));
|
|
expect(document.components.securitySchemes, hasLength(3));
|
|
|
|
final bearerAuth = document.components.securitySchemes['bearerAuth']!;
|
|
expect(bearerAuth.type, equals(SecuritySchemeType.http));
|
|
expect(bearerAuth.scheme, equals('bearer'));
|
|
expect(bearerAuth.bearerFormat, equals('JWT'));
|
|
|
|
final apiKey = document.components.securitySchemes['apiKey']!;
|
|
expect(apiKey.type, equals(SecuritySchemeType.apiKey));
|
|
expect(apiKey.location, equals(ApiKeyLocation.header));
|
|
expect(apiKey.name, equals('X-API-Key'));
|
|
|
|
final oauth2 = document.components.securitySchemes['oauth2']!;
|
|
expect(oauth2.type, equals(SecuritySchemeType.oauth2));
|
|
expect(oauth2.flows, isNotNull);
|
|
expect(oauth2.flows!.authorizationCode, isNotNull);
|
|
expect(oauth2.flows!.authorizationCode!.scopes, hasLength(2));
|
|
|
|
final path = document.paths['/protected']!;
|
|
expect(path.security, hasLength(1));
|
|
expect(path.security.first.schemeNames, contains('bearerAuth'));
|
|
});
|
|
});
|
|
|
|
group('Schema Validation', () {
|
|
test('parses allOf composition', () {
|
|
final json = <String, dynamic>{
|
|
'openapi': '3.0.3',
|
|
'info': {'title': 'Test', 'version': '1.0.0'},
|
|
'paths': {},
|
|
'components': {
|
|
'schemas': {
|
|
'Pet': {
|
|
'type': 'object',
|
|
'properties': {
|
|
'name': {'type': 'string'},
|
|
},
|
|
'required': ['name'],
|
|
},
|
|
'Dog': {
|
|
'allOf': [
|
|
{r'$ref': '#/components/schemas/Pet'},
|
|
{
|
|
'type': 'object',
|
|
'properties': {
|
|
'breed': {'type': 'string'},
|
|
},
|
|
'required': ['breed'],
|
|
},
|
|
],
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
final document = SwaggerDocument.fromJson(json);
|
|
expect(document.models, hasLength(2));
|
|
expect(document.models.containsKey('Pet'), isTrue);
|
|
expect(document.models.containsKey('Dog'), isTrue);
|
|
|
|
final dog = document.models['Dog']!;
|
|
expect(dog.name, equals('Dog'));
|
|
// allOf 处理逻辑会在实际实现中更复杂
|
|
});
|
|
|
|
test('parses oneOf and anyOf', () {
|
|
final json = <String, dynamic>{
|
|
'openapi': '3.0.3',
|
|
'info': {'title': 'Test', 'version': '1.0.0'},
|
|
'paths': {},
|
|
'components': {
|
|
'schemas': {
|
|
'StringOrNumber': {
|
|
'oneOf': [
|
|
{'type': 'string'},
|
|
{'type': 'number'},
|
|
],
|
|
},
|
|
'FlexibleType': {
|
|
'anyOf': [
|
|
{'type': 'string'},
|
|
{'type': 'integer'},
|
|
{'type': 'boolean'},
|
|
],
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
final document = SwaggerDocument.fromJson(json);
|
|
expect(document.models, hasLength(2));
|
|
expect(document.models.containsKey('StringOrNumber'), isTrue);
|
|
expect(document.models.containsKey('FlexibleType'), isTrue);
|
|
});
|
|
|
|
test('parses discriminator', () {
|
|
final json = <String, dynamic>{
|
|
'openapi': '3.0.3',
|
|
'info': {'title': 'Test', 'version': '1.0.0'},
|
|
'paths': {},
|
|
'components': {
|
|
'schemas': {
|
|
'Pet': {
|
|
'type': 'object',
|
|
'discriminator': {
|
|
'propertyName': 'petType',
|
|
'mapping': {
|
|
'dog': '#/components/schemas/Dog',
|
|
'cat': '#/components/schemas/Cat',
|
|
},
|
|
},
|
|
'properties': {
|
|
'petType': {'type': 'string'},
|
|
'name': {'type': 'string'},
|
|
},
|
|
'required': ['petType', 'name'],
|
|
},
|
|
'Dog': {
|
|
'allOf': [
|
|
{r'$ref': '#/components/schemas/Pet'},
|
|
{
|
|
'type': 'object',
|
|
'properties': {
|
|
'breed': {'type': 'string'},
|
|
},
|
|
},
|
|
],
|
|
},
|
|
'Cat': {
|
|
'allOf': [
|
|
{r'$ref': '#/components/schemas/Pet'},
|
|
{
|
|
'type': 'object',
|
|
'properties': {
|
|
'color': {'type': 'string'},
|
|
},
|
|
},
|
|
],
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
final document = SwaggerDocument.fromJson(json);
|
|
expect(document.models, hasLength(3));
|
|
|
|
final pet = document.models['Pet']!;
|
|
expect(pet.name, equals('Pet'));
|
|
// discriminator 处理逻辑会在实际实现中更复杂
|
|
});
|
|
});
|
|
|
|
group('Error Handling', () {
|
|
test('handles missing required fields gracefully', () {
|
|
final json = <String, dynamic>{
|
|
'openapi': '3.0.3',
|
|
// Missing info object
|
|
'paths': {},
|
|
};
|
|
|
|
expect(
|
|
() => SwaggerDocument.fromJson(json),
|
|
throwsA(isA<FormatException>()),
|
|
);
|
|
});
|
|
|
|
test('handles invalid OpenAPI version', () {
|
|
final json = <String, dynamic>{
|
|
'openapi': '2.0', // Invalid version
|
|
'info': {'title': 'Test', 'version': '1.0.0'},
|
|
'paths': {},
|
|
};
|
|
|
|
// Should still parse but might have warnings
|
|
expect(() => SwaggerDocument.fromJson(json), returnsNormally);
|
|
});
|
|
|
|
test('handles malformed paths', () {
|
|
final json = <String, dynamic>{
|
|
'openapi': '3.0.3',
|
|
'info': {'title': 'Test', 'version': '1.0.0'},
|
|
'paths': {
|
|
'/valid': {
|
|
'get': {
|
|
'responses': {
|
|
'200': {'description': 'OK'},
|
|
},
|
|
},
|
|
},
|
|
'/invalid': {
|
|
'invalidMethod': {
|
|
'responses': {
|
|
'200': {'description': 'OK'},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
final document = SwaggerDocument.fromJson(json);
|
|
// Should parse valid paths and skip invalid ones
|
|
expect(document.paths.length, greaterThanOrEqualTo(1));
|
|
});
|
|
|
|
test('handles circular references', () {
|
|
final json = <String, dynamic>{
|
|
'openapi': '3.0.3',
|
|
'info': {'title': 'Test', 'version': '1.0.0'},
|
|
'paths': {},
|
|
'components': {
|
|
'schemas': {
|
|
'Node': {
|
|
'type': 'object',
|
|
'properties': {
|
|
'value': {'type': 'string'},
|
|
'children': {
|
|
'type': 'array',
|
|
'items': {r'$ref': '#/components/schemas/Node'},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
// Should handle circular references without infinite recursion
|
|
expect(() => SwaggerDocument.fromJson(json), returnsNormally);
|
|
final document = SwaggerDocument.fromJson(json);
|
|
expect(document.models.containsKey('Node'), isTrue);
|
|
});
|
|
});
|
|
|
|
group('Edge Cases', () {
|
|
test('handles empty document', () {
|
|
final json = <String, dynamic>{
|
|
'openapi': '3.0.3',
|
|
'info': {'title': 'Empty API', 'version': '1.0.0'},
|
|
'paths': {},
|
|
};
|
|
|
|
final document = SwaggerDocument.fromJson(json);
|
|
expect(document.title, equals('Empty API'));
|
|
expect(document.paths, isEmpty);
|
|
expect(document.models, isEmpty);
|
|
});
|
|
|
|
test('handles very large documents', () {
|
|
final paths = <String, dynamic>{};
|
|
final schemas = <String, dynamic>{};
|
|
|
|
// Create a large number of paths and schemas
|
|
for (var i = 0; i < 1000; i++) {
|
|
paths['/resource$i'] = {
|
|
'get': {
|
|
'summary': 'Get resource $i',
|
|
'responses': {
|
|
'200': {'description': 'Success'},
|
|
},
|
|
},
|
|
};
|
|
|
|
schemas['Model$i'] = {
|
|
'type': 'object',
|
|
'properties': {
|
|
'id': {'type': 'integer'},
|
|
'name': {'type': 'string'},
|
|
},
|
|
};
|
|
}
|
|
|
|
final json = <String, dynamic>{
|
|
'openapi': '3.0.3',
|
|
'info': {'title': 'Large API', 'version': '1.0.0'},
|
|
'paths': paths,
|
|
'components': {'schemas': schemas},
|
|
};
|
|
|
|
final stopwatch = Stopwatch()..start();
|
|
final document = SwaggerDocument.fromJson(json);
|
|
stopwatch.stop();
|
|
|
|
expect(document.paths.length, greaterThan(500));
|
|
expect(document.models.length, greaterThan(500));
|
|
expect(
|
|
stopwatch.elapsedMilliseconds,
|
|
lessThan(10000),
|
|
); // Should complete within 10 seconds
|
|
});
|
|
|
|
test('handles unicode and special characters', () {
|
|
final json = <String, dynamic>{
|
|
'openapi': '3.0.3',
|
|
'info': {
|
|
'title': 'API with 中文 and émojis 🚀',
|
|
'version': '1.0.0',
|
|
'description': 'Supports unicode: αβγ, 日本語, العربية',
|
|
},
|
|
'paths': {
|
|
'/测试': {
|
|
'get': {
|
|
'summary': 'Test with unicode path',
|
|
'responses': {
|
|
'200': {'description': 'Success'},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
final document = SwaggerDocument.fromJson(json);
|
|
expect(document.title, contains('中文'));
|
|
expect(document.title, contains('🚀'));
|
|
expect(document.description, contains('日本語'));
|
|
expect(document.paths.containsKey('/测试'), isTrue);
|
|
});
|
|
});
|
|
});
|
|
}
|
|
// 忽略测试构造数据的类型推断告警,便于保持示例简洁
|
|
// ignore_for_file: inference_failure_on_collection_literal
|