swagger_generator_flutter/test/comprehensive_parser_test.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