swagger_generator_flutter/test/models_test.dart

1754 lines
51 KiB
Dart

import 'package:swagger_generator_flutter/core/models.dart';
import 'package:test/test.dart';
void main() {
group('ApiPath', () {
test('creates ApiPath with required fields', () {
const path = ApiPath(
path: '/api/users',
method: HttpMethod.get,
summary: 'Get users',
description: 'Retrieve all users',
operationId: 'getUsers',
tags: ['User'],
parameters: [],
responses: {},
);
expect(path.path, '/api/users');
expect(path.method, HttpMethod.get);
expect(path.summary, 'Get users');
expect(path.description, 'Retrieve all users');
expect(path.tags, ['User']);
expect(path.parameters, isEmpty);
expect(path.responses, isEmpty);
expect(path.requestBody, isNull);
});
test('creates ApiPath with all fields', () {
final parameters = [
const ApiParameter(
name: 'id',
location: ParameterLocation.path,
required: true,
type: PropertyType.integer,
description: 'User ID',
),
];
final responses = {
'200': const ApiResponse(
code: '200',
description: 'Success',
content: {
'application/json': ApiMediaType(
schema: {'type': 'object'},
),
},
),
};
const requestBody = ApiRequestBody(
description: 'User data',
required: true,
content: {
'application/json': ApiMediaType(
schema: {'type': 'object'},
),
},
);
final path = ApiPath(
path: '/api/users/{id}',
method: HttpMethod.put,
summary: 'Update user',
description: 'Update user by ID',
operationId: 'updateUser',
tags: ['User'],
parameters: parameters,
responses: responses,
requestBody: requestBody,
);
expect(path.parameters, hasLength(1));
expect(path.responses, hasLength(1));
expect(path.requestBody, isNotNull);
});
});
group('SwaggerDocument', () {
test('provides default server when missing', () {
final json = {
'info': {
'title': 'Test API',
'version': '1.0.0',
},
'paths': {
'/ping': {
'get': {
'summary': 'Ping',
'operationId': 'ping',
'responses': {
'200': {
'description': 'Success',
},
},
},
},
},
'components': {
'schemas': {
'PingResponse': {
'type': 'object',
'properties': {
'message': {'type': 'string'},
},
},
},
},
};
final document = SwaggerDocument.fromJson(json);
expect(document.title, 'Test API');
expect(document.version, '1.0.0');
expect(document.servers, isNotEmpty);
expect(document.servers.first.url, '/');
expect(document.models.containsKey('PingResponse'), isTrue);
expect(document.findPath('/ping', HttpMethod.get), isNotNull);
});
test('parses components into strongly typed maps', () {
final json = <String, dynamic>{
'info': <String, dynamic>{'title': 'Test', 'version': '0.0.1'},
'servers': <Map<String, dynamic>>[
<String, dynamic>{
'url': 'https://api.example.com',
'description': 'Prod',
'variables': <String, dynamic>{
'region': <String, dynamic>{
'default': 'us-east-1',
'enum': <String>['us-east-1'],
},
},
},
],
'paths': <String, dynamic>{},
'components': <String, dynamic>{
'schemas': <String, dynamic>{
'User': <String, dynamic>{
'type': 'object',
'properties': <String, dynamic>{
'id': <String, dynamic>{'type': 'integer'},
},
},
},
'responses': <String, dynamic>{
'NotFound': <String, dynamic>{
'description': 'Not found',
},
},
'parameters': <String, dynamic>{
'UserId': <String, dynamic>{
'name': 'id',
'in': 'path',
'required': true,
'schema': <String, dynamic>{'type': 'integer'},
},
},
'requestBodies': <String, dynamic>{
'UserBody': <String, dynamic>{
'content': <String, dynamic>{
'application/json': <String, dynamic>{
'schema': <String, dynamic>{
r'$ref': '#/components/schemas/User',
},
},
},
},
},
'securitySchemes': <String, dynamic>{
'bearer': <String, dynamic>{
'type': 'http',
'scheme': 'bearer',
},
},
},
};
final document = SwaggerDocument.fromJson(json);
expect(document.components.schemas.keys, contains('User'));
expect(document.components.responses.keys, contains('NotFound'));
expect(document.components.parameters.keys, contains('UserId'));
expect(document.components.requestBodies.keys, contains('UserBody'));
expect(
document.components.securitySchemes.keys,
contains('bearer'),
);
expect(document.models['User']?.name, 'User');
expect(document.servers.single.url, 'https://api.example.com');
expect(
document.servers.single.variables.keys,
contains('region'),
);
});
test('keeps separate entries for same path with different methods', () {
final json = {
'info': {'title': 'Test', 'version': '1.0.0'},
'paths': {
'/users': {
'get': {
'operationId': 'getUsers',
'responses': {
'200': {'description': 'OK'},
},
},
'post': {
'operationId': 'createUser',
'responses': {
'201': {'description': 'Created'},
},
},
},
},
};
final document = SwaggerDocument.fromJson(json);
expect(
document.findPath('/users', HttpMethod.get)?.operationId,
'getUsers',
);
expect(
document.findPath('/users', HttpMethod.post)?.operationId,
'createUser',
);
expect(document.operationsFor('/users'), hasLength(2));
});
});
group('ApiParameter', () {
test('creates ApiParameter with required fields', () {
const param = ApiParameter(
name: 'id',
location: ParameterLocation.path,
required: true,
type: PropertyType.integer,
description: 'User ID',
);
expect(param.name, 'id');
expect(param.location, ParameterLocation.path);
expect(param.required, true);
expect(param.type, PropertyType.integer);
expect(param.description, 'User ID');
});
test('creates ApiParameter with optional fields', () {
const param = ApiParameter(
name: 'page',
location: ParameterLocation.query,
required: false,
type: PropertyType.integer,
description: 'Page number',
format: 'int32',
example: 1,
defaultValue: 1,
);
expect(param.format, 'int32');
expect(param.example, 1);
expect(param.defaultValue, 1);
});
test('creates ApiParameter from JSON', () {
final json = {
'name': 'id',
'in': 'path',
'required': true,
'type': 'integer',
'description': 'User ID',
'format': 'int64',
'example': 123,
'default': 1,
};
final param = ApiParameter.fromJson(json);
expect(param.name, 'id');
expect(param.location, ParameterLocation.path);
expect(param.required, true);
expect(param.type, PropertyType.integer);
expect(param.description, 'User ID');
expect(param.format, 'int64');
expect(param.example, 123);
expect(param.defaultValue, 1);
});
});
group('ApiResponse', () {
test('creates ApiResponse with required fields', () {
const response = ApiResponse(
code: '200',
description: 'Success',
);
expect(response.code, '200');
expect(response.description, 'Success');
expect(response.content, isEmpty);
expect(response.headers, isEmpty);
expect(response.links, isEmpty);
});
test('creates ApiResponse with content', () {
final content = {
'application/json': const ApiMediaType(
schema: {
'type': 'object',
'properties': {
'id': {'type': 'integer'},
'name': {'type': 'string'},
},
},
),
};
final response = ApiResponse(
code: '200',
description: 'Success',
content: content,
);
expect(response.content.length, 1);
expect(response.content['application/json']?.schema?['type'], 'object');
expect(response.supportedMediaTypes, contains('application/json'));
expect(response.supportsMediaType('application/json'), true);
expect(response.hasHeaders, false);
expect(response.hasLinks, false);
});
test('creates ApiResponse from JSON', () {
final json = {
'description': 'Success',
'headers': {
'X-Rate-Limit': {
'description': 'Rate limit',
'schema': {'type': 'integer'},
},
},
'content': {
'application/json': {
'schema': {'type': 'object'},
'example': {'id': 1, 'name': 'test'},
},
},
'links': {
'GetUserByName': {
'operationId': 'getUserByName',
'parameters': {'username': r'$response.body#/username'},
},
},
};
final response = ApiResponse.fromJson('200', json);
expect(response.code, '200');
expect(response.description, 'Success');
expect(response.content.length, 1);
expect(response.content['application/json']?.schema?['type'], 'object');
expect(response.headers.length, 1);
expect(response.headers['X-Rate-Limit']?.description, 'Rate limit');
expect(response.links.length, 1);
expect(response.links['GetUserByName']?.operationId, 'getUserByName');
expect(response.hasHeaders, true);
expect(response.hasLinks, true);
});
});
group('ApiRequestBody', () {
test('creates ApiRequestBody with required fields', () {
const requestBody = ApiRequestBody(
description: 'User data',
required: true,
);
expect(requestBody.description, 'User data');
expect(requestBody.required, true);
expect(requestBody.content, isEmpty);
});
test('creates ApiRequestBody with content', () {
final content = {
'application/json': const ApiMediaType(
schema: {
'type': 'object',
'properties': {
'name': {'type': 'string'},
'email': {'type': 'string'},
},
},
),
};
final requestBody = ApiRequestBody(
description: 'User data',
required: true,
content: content,
);
expect(requestBody.content.length, 1);
expect(
requestBody.content['application/json']?.schema?['type'],
'object',
);
expect(requestBody.supportedMediaTypes, contains('application/json'));
expect(requestBody.supportsMediaType('application/json'), true);
});
test('creates ApiRequestBody from JSON', () {
final json = {
'description': 'User data',
'required': true,
'content': {
'application/json': {
'schema': {'type': 'object'},
},
},
};
final requestBody = ApiRequestBody.fromJson(json);
expect(requestBody.description, 'User data');
expect(requestBody.required, true);
expect(requestBody.content, isNotNull);
});
});
group('ApiModel', () {
test('creates ApiModel with required fields', () {
const model = ApiModel(
name: 'User',
description: 'User model',
properties: {},
required: [],
);
expect(model.name, 'User');
expect(model.description, 'User model');
expect(model.properties, isEmpty);
expect(model.required, isEmpty);
expect(model.isEnum, false);
expect(model.enumValues, isEmpty);
expect(model.enumType, isNull);
});
test('creates ApiModel with properties', () {
final properties = {
'id': const ApiProperty(
name: 'id',
type: PropertyType.integer,
description: 'User ID',
required: true,
),
'name': const ApiProperty(
name: 'name',
type: PropertyType.string,
description: 'User name',
required: true,
),
};
final model = ApiModel(
name: 'User',
description: 'User model',
properties: properties,
required: ['id', 'name'],
);
expect(model.properties, hasLength(2));
expect(model.required, ['id', 'name']);
});
test('creates enum ApiModel', () {
const model = ApiModel(
name: 'UserStatus',
description: 'User status enum',
properties: {},
required: [],
isEnum: true,
enumValues: ['active', 'inactive', 'pending'],
enumType: PropertyType.string,
);
expect(model.isEnum, true);
expect(model.enumValues, ['active', 'inactive', 'pending']);
expect(model.enumType, PropertyType.string);
});
test('creates ApiModel from JSON', () {
final json = {
'description': 'User model',
'properties': {
'id': {
'type': 'integer',
'description': 'User ID',
},
'name': {
'type': 'string',
'description': 'User name',
},
},
'required': ['id', 'name'],
};
final model = ApiModel.fromJson('User', json);
expect(model.name, 'User');
expect(model.description, 'User model');
expect(model.properties, hasLength(2));
expect(model.required, ['id', 'name']);
});
test('creates enum ApiModel from JSON', () {
final json = {
'description': 'User status enum',
'enum': ['active', 'inactive', 'pending'],
'type': 'string',
};
final model = ApiModel.fromJson('UserStatus', json);
expect(model.isEnum, true);
expect(model.enumValues, ['active', 'inactive', 'pending']);
expect(model.enumType, PropertyType.string);
});
});
group('ApiProperty', () {
test('creates ApiProperty with required fields', () {
const property = ApiProperty(
name: 'id',
type: PropertyType.integer,
description: 'User ID',
required: true,
);
expect(property.name, 'id');
expect(property.type, PropertyType.integer);
expect(property.description, 'User ID');
expect(property.required, true);
expect(property.nullable, false);
});
test('creates ApiProperty with optional fields', () {
const property = ApiProperty(
name: 'name',
type: PropertyType.string,
description: 'User name',
required: false,
nullable: true,
format: 'string',
example: 'John Doe',
defaultValue: 'Unknown',
reference: 'User',
);
expect(property.nullable, true);
expect(property.format, 'string');
expect(property.example, 'John Doe');
expect(property.defaultValue, 'Unknown');
expect(property.reference, 'User');
});
test('creates ApiProperty from JSON', () {
final json = {
'type': 'string',
'description': 'User name',
'nullable': true,
'format': 'string',
'example': 'John Doe',
'default': 'Unknown',
};
final property = ApiProperty.fromJson('name', json, []);
expect(property.name, 'name');
expect(property.type, PropertyType.string);
expect(property.description, 'User name');
expect(property.nullable, true);
expect(property.format, 'string');
expect(property.example, 'John Doe');
expect(property.defaultValue, 'Unknown');
});
test('creates ApiProperty with reference', () {
final json = {
'type': 'object',
'description': 'User object',
r'$ref': '#/components/schemas/User',
};
final property = ApiProperty.fromJson('user', json, []);
expect(property.type, PropertyType.reference);
expect(property.reference, 'User');
});
test('creates ApiProperty with array items', () {
final json = {
'type': 'array',
'description': 'User list',
'items': {
'type': 'object',
r'$ref': '#/components/schemas/User',
},
};
final property = ApiProperty.fromJson('users', json, []);
expect(property.type, PropertyType.array);
expect(property.items, isNotNull);
expect(property.items!.name, 'User');
});
});
group('PropertyType', () {
test('converts string to PropertyType', () {
expect(PropertyType.fromString('string'), PropertyType.string);
expect(PropertyType.fromString('integer'), PropertyType.integer);
expect(PropertyType.fromString('number'), PropertyType.number);
expect(PropertyType.fromString('boolean'), PropertyType.boolean);
expect(PropertyType.fromString('array'), PropertyType.array);
expect(PropertyType.fromString('object'), PropertyType.object);
expect(PropertyType.fromString('unknown'), PropertyType.unknown);
});
test('gets PropertyType value', () {
expect(PropertyType.string.value, 'string');
expect(PropertyType.integer.value, 'integer');
expect(PropertyType.number.value, 'number');
expect(PropertyType.boolean.value, 'boolean');
expect(PropertyType.array.value, 'array');
expect(PropertyType.object.value, 'object');
expect(PropertyType.reference.value, 'reference');
});
});
group('ParameterLocation', () {
test('converts string to ParameterLocation', () {
expect(ParameterLocation.fromString('query'), ParameterLocation.query);
expect(ParameterLocation.fromString('path'), ParameterLocation.path);
expect(ParameterLocation.fromString('header'), ParameterLocation.header);
expect(ParameterLocation.fromString('cookie'), ParameterLocation.cookie);
expect(ParameterLocation.fromString('unknown'), ParameterLocation.query);
});
});
group('HttpMethod', () {
test('converts string to HttpMethod', () {
expect(HttpMethod.fromString('GET'), HttpMethod.get);
expect(HttpMethod.fromString('POST'), HttpMethod.post);
expect(HttpMethod.fromString('PUT'), HttpMethod.put);
expect(HttpMethod.fromString('DELETE'), HttpMethod.delete);
expect(HttpMethod.fromString('PATCH'), HttpMethod.patch);
expect(HttpMethod.fromString('HEAD'), HttpMethod.head);
expect(HttpMethod.fromString('OPTIONS'), HttpMethod.options);
});
test('handles case insensitive input', () {
expect(HttpMethod.fromString('get'), HttpMethod.get);
expect(HttpMethod.fromString('Post'), HttpMethod.post);
expect(HttpMethod.fromString('put'), HttpMethod.put);
});
test('returns default for unknown method', () {
expect(HttpMethod.fromString('UNKNOWN'), HttpMethod.get);
expect(HttpMethod.fromString(''), HttpMethod.get);
});
test('gets HttpMethod value', () {
expect(HttpMethod.get.value, 'GET');
expect(HttpMethod.post.value, 'POST');
expect(HttpMethod.put.value, 'PUT');
expect(HttpMethod.delete.value, 'DELETE');
expect(HttpMethod.patch.value, 'PATCH');
expect(HttpMethod.head.value, 'HEAD');
expect(HttpMethod.options.value, 'OPTIONS');
});
});
group('SwaggerDocument', () {
test('creates SwaggerDocument with required fields', () {
const document = SwaggerDocument(
title: 'Test API',
version: '1.0.0',
description: 'Test API description',
servers: [
ApiServer(
url: 'https://api.example.com/api',
description: 'Production server',
),
],
components: ApiComponents(
schemas: {
'User': ApiModel(
name: 'User',
description: 'User model',
properties: {},
required: [],
),
},
),
paths: {},
models: {},
controllers: {},
);
expect(document.title, 'Test API');
expect(document.version, '1.0.0');
expect(document.description, 'Test API description');
expect(document.servers.length, 1);
expect(document.servers.first.url, 'https://api.example.com/api');
expect(document.servers.first.description, 'Production server');
expect(document.components.schemas.length, 1);
expect(document.components.schemas['User']?.name, 'User');
expect(document.paths, isEmpty);
expect(document.models, isEmpty);
expect(document.controllers, isEmpty);
});
test('creates SwaggerDocument from JSON with all fields', () {
final json = {
'info': {
'title': 'Test API',
'version': '1.0.0',
'description': 'Test API description',
},
'servers': [
{
'url': 'https://api.example.com/api',
'description': 'Production server',
'variables': {
'version': {
'default': 'v1',
'enum': ['v1', 'v2'],
'description': 'API version',
},
},
},
{
'url': 'http://localhost:3000',
'description': 'Development server',
},
],
'components': {
'schemas': {
'User': {
'type': 'object',
'description': 'User model',
'properties': {
'id': {'type': 'integer'},
'name': {'type': 'string'},
},
'required': ['id', 'name'],
},
},
'responses': {
'NotFound': {
'description': 'Resource not found',
'content': {
'application/json': {
'schema': {
'type': 'object',
'properties': {
'error': {'type': 'string'},
},
},
},
},
},
},
},
};
final document = SwaggerDocument.fromJson(json);
expect(document.title, 'Test API');
expect(document.version, '1.0.0');
expect(document.description, 'Test API description');
expect(document.servers.length, 2);
expect(document.servers.first.url, 'https://api.example.com/api');
expect(document.servers.first.description, 'Production server');
expect(document.servers.first.variables.length, 1);
expect(document.servers.first.variables['version']?.defaultValue, 'v1');
expect(document.components.schemas.length, 1);
expect(document.components.schemas['User']?.name, 'User');
expect(document.components.responses.length, 1);
expect(
document.components.responses['NotFound']?.description,
'Resource not found',
);
});
test('creates SwaggerDocument with composition schemas', () {
final json = {
'info': {
'title': 'Test API',
'version': '1.0.0',
},
'components': {
'schemas': {
'Pet': {
'type': 'object',
'properties': {
'name': {'type': 'string'},
'type': {'type': 'string'},
},
'required': ['name'],
},
'Dog': {
'allOf': [
{r'$ref': '#/components/schemas/Pet'},
{
'type': 'object',
'properties': {
'breed': {'type': 'string'},
},
},
],
},
'Animal': {
'oneOf': [
{r'$ref': '#/components/schemas/Pet'},
{
'type': 'object',
'properties': {
'species': {'type': 'string'},
},
},
],
},
},
},
};
final document = SwaggerDocument.fromJson(json);
expect(document.components.schemas.length, 3);
// 测试 allOf 组合
final dogModel = document.components.schemas['Dog'];
expect(dogModel?.isAllOf, true);
expect(dogModel?.allOf.length, 2);
expect(dogModel?.allOf.first.reference, 'Pet');
// 测试 oneOf 组合
final animalModel = document.components.schemas['Animal'];
expect(animalModel?.isOneOf, true);
expect(animalModel?.oneOf.length, 2);
expect(animalModel?.oneOf.first.reference, 'Pet');
});
test('throws when info object is missing', () {
final json = <String, dynamic>{};
expect(
() => SwaggerDocument.fromJson(json),
throwsA(isA<FormatException>()),
);
});
test('throws when info is null', () {
final json = {'info': null};
expect(
() => SwaggerDocument.fromJson(json),
throwsA(isA<FormatException>()),
);
});
});
group('ApiController', () {
test('creates ApiController with required fields', () {
const controller = ApiController(
name: 'UserController',
description: 'User management controller',
paths: [],
);
expect(controller.name, 'UserController');
expect(controller.description, 'User management controller');
expect(controller.paths, isEmpty);
});
test('creates ApiController with paths', () {
final paths = [
const ApiPath(
path: '/api/users',
method: HttpMethod.get,
summary: 'Get users',
description: 'Retrieve all users',
operationId: 'getUsers',
tags: ['User'],
parameters: [],
responses: {},
),
const ApiPath(
path: '/api/users/{id}',
method: HttpMethod.post,
summary: 'Create user',
description: 'Create a new user',
operationId: 'createUser',
tags: ['User'],
parameters: [],
responses: {},
),
];
final controller = ApiController(
name: 'UserController',
description: 'User management controller',
paths: paths,
);
expect(controller.paths, hasLength(2));
expect(controller.paths[0].path, '/api/users');
expect(controller.paths[1].path, '/api/users/{id}');
});
test('creates ApiController from paths', () {
final paths = [
const ApiPath(
path: '/api/users',
method: HttpMethod.get,
summary: 'Get users',
description: 'Retrieve all users',
operationId: 'getUsers',
tags: ['User'],
parameters: [],
responses: {},
),
];
final controller = ApiController.fromPaths('UserController', paths);
expect(controller.name, 'UserController');
expect(controller.description, 'UserController');
expect(controller.paths, hasLength(1));
});
});
group('ApiPath fromJson', () {
test('creates ApiPath from JSON with all fields', () {
final json = {
'summary': 'Get users',
'description': 'Retrieve all users',
'operationId': 'getUsers',
'tags': ['User'],
'parameters': [
<String, dynamic>{
'name': 'id',
'in': 'path',
'required': true,
'type': 'integer',
'description': 'User ID',
}
],
'responses': <String, dynamic>{
'200': <String, dynamic>{
'description': 'Success',
'schema': <String, dynamic>{'type': 'object'},
},
},
'requestBody': <String, dynamic>{
'description': 'User data',
'required': true,
'content': <String, dynamic>{
'application/json': <String, dynamic>{
'schema': <String, dynamic>{'type': 'object'},
},
},
},
'deprecated': true,
};
final path = ApiPath.fromJson('/api/users/{id}', HttpMethod.put, json);
expect(path.path, '/api/users/{id}');
expect(path.method, HttpMethod.put);
expect(path.summary, 'Get users');
expect(path.description, 'Retrieve all users');
expect(path.operationId, 'getUsers');
expect(path.tags, ['User']);
expect(path.parameters, hasLength(1));
expect(path.responses, hasLength(1));
expect(path.requestBody, isNotNull);
expect(path.deprecated, true);
});
test('creates ApiPath from JSON with minimal fields', () {
final json = <String, dynamic>{};
final path = ApiPath.fromJson('/api/users', HttpMethod.get, json);
expect(path.path, '/api/users');
expect(path.method, HttpMethod.get);
expect(path.summary, '');
expect(path.description, '');
expect(path.operationId, '');
expect(path.tags, isEmpty);
expect(path.parameters, isEmpty);
expect(path.responses, isEmpty);
expect(path.requestBody, isNull);
expect(path.deprecated, false);
});
});
group('ApiModel fromJson edge cases', () {
test('creates ApiModel with nullable properties', () {
final json = {
'description': 'User model',
'properties': {
'id': {
'type': 'integer',
'description': 'User ID',
'nullable': true,
},
'name': {
'type': 'string',
'description': 'User name',
'nullable': false,
},
},
};
final model = ApiModel.fromJson('User', json);
expect(model.properties['id']!.nullable, true);
expect(model.properties['id']!.required, false);
expect(model.properties['name']!.nullable, false);
expect(model.properties['name']!.required, true);
expect(model.required, ['name']);
});
test('creates ApiModel with explicit required fields', () {
final json = {
'description': 'User model',
'properties': {
'id': {
'type': 'integer',
'description': 'User ID',
'nullable': true,
},
'name': {
'type': 'string',
'description': 'User name',
'nullable': false,
},
},
'required': ['id', 'name'],
};
final model = ApiModel.fromJson('User', json);
expect(model.properties['id']!.required, true);
expect(model.properties['name']!.required, true);
expect(model.required, ['id', 'name']);
});
});
group('ApiProperty complex types', () {
test('creates ApiProperty with nested object', () {
final json = {
'type': 'object',
'description': 'User address',
'properties': {
'street': {'type': 'string'},
'city': {'type': 'string'},
},
};
final property = ApiProperty.fromJson('address', json, []);
expect(property.type, PropertyType.object);
expect(property.description, 'User address');
expect(property.required, false);
});
test('creates ApiProperty with array of primitives', () {
final json = {
'type': 'array',
'description': 'User tags',
'items': {
'type': 'string',
},
};
final property = ApiProperty.fromJson('tags', json, []);
expect(property.type, PropertyType.array);
expect(property.description, 'User tags');
expect(property.items, isNotNull);
expect(property.items!.name, 'string');
});
test('creates ApiProperty with array of objects', () {
final json = {
'type': 'array',
'description': 'User list',
'items': {
'type': 'object',
r'$ref': '#/components/schemas/User',
},
};
final property = ApiProperty.fromJson('users', json, []);
expect(property.type, PropertyType.array);
expect(property.description, 'User list');
expect(property.items, isNotNull);
expect(property.items!.name, 'User');
});
test('creates ApiProperty with file type', () {
final json = {
'type': 'file',
'description': 'User avatar',
'format': 'binary',
};
final property = ApiProperty.fromJson('avatar', json, []);
expect(property.type, PropertyType.file);
expect(property.description, 'User avatar');
expect(property.format, 'binary');
});
test('creates ApiProperty with date type', () {
final json = {
'type': 'string',
'format': 'date',
'description': 'User birth date',
};
final property = ApiProperty.fromJson('birthDate', json, []);
expect(property.type, PropertyType.string);
expect(property.format, 'date');
expect(property.description, 'User birth date');
});
test('creates ApiProperty with date-time type', () {
final json = {
'type': 'string',
'format': 'date-time',
'description': 'User created at',
};
final property = ApiProperty.fromJson('createdAt', json, []);
expect(property.type, PropertyType.string);
expect(property.format, 'date-time');
expect(property.description, 'User created at');
});
});
group('Error handling and edge cases', () {
test('ApiParameter fromJson handles missing fields gracefully', () {
final json = <String, dynamic>{};
final param = ApiParameter.fromJson(json);
expect(param.name, '');
expect(param.location, ParameterLocation.query);
expect(param.required, false);
expect(param.type, PropertyType.string);
expect(param.description, '');
expect(param.format, isNull);
expect(param.example, isNull);
expect(param.defaultValue, isNull);
});
test('ApiResponse fromJson handles missing fields gracefully', () {
final json = <String, dynamic>{};
final response = ApiResponse.fromJson('200', json);
expect(response.code, '200');
expect(response.description, '');
expect(response.content, isEmpty);
expect(response.headers, isEmpty);
expect(response.links, isEmpty);
expect(response.hasHeaders, false);
expect(response.hasLinks, false);
});
test('ApiRequestBody fromJson handles missing fields gracefully', () {
final json = <String, dynamic>{};
final requestBody = ApiRequestBody.fromJson(json);
expect(requestBody.description, '');
expect(requestBody.required, false);
expect(requestBody.content, isEmpty);
});
test('PropertyType fromString handles unknown types', () {
expect(PropertyType.fromString('unknown'), PropertyType.unknown);
expect(PropertyType.fromString(''), PropertyType.unknown);
expect(PropertyType.fromString('CUSTOM_TYPE'), PropertyType.unknown);
});
test('ParameterLocation fromString handles unknown locations', () {
expect(ParameterLocation.fromString('unknown'), ParameterLocation.query);
expect(ParameterLocation.fromString(''), ParameterLocation.query);
expect(
ParameterLocation.fromString('CUSTOM_LOCATION'),
ParameterLocation.query,
);
});
test('HttpMethod fromString handles unknown methods', () {
expect(HttpMethod.fromString('unknown'), HttpMethod.get);
expect(HttpMethod.fromString(''), HttpMethod.get);
expect(HttpMethod.fromString('CUSTOM_METHOD'), HttpMethod.get);
});
});
group('ApiSchema', () {
test('creates ApiSchema with basic properties', () {
const schema = ApiSchema(
type: 'object',
description: 'Test schema',
properties: {
'name': ApiProperty(
name: 'name',
type: PropertyType.string,
description: 'Name property',
required: true,
),
},
required: ['name'],
);
expect(schema.type, 'object');
expect(schema.description, 'Test schema');
expect(schema.properties.length, 1);
expect(schema.required, ['name']);
expect(schema.isObject, true);
expect(schema.isComposition, false);
});
test('creates ApiSchema with composition patterns', () {
const schema = ApiSchema(
allOf: [
ApiSchema(
reference: 'BaseModel',
),
ApiSchema(
type: 'object',
properties: {
'extra': ApiProperty(
name: 'extra',
type: PropertyType.string,
description: 'Extra property',
required: false,
),
},
),
],
);
expect(schema.isComposition, true);
expect(schema.isAllOf, true);
expect(schema.allOf.length, 2);
expect(schema.allOf.first.isReference, true);
expect(schema.allOf.first.reference, 'BaseModel');
});
test('creates ApiSchema from JSON with allOf', () {
final json = {
'allOf': [
{r'$ref': '#/components/schemas/Pet'},
{
'type': 'object',
'properties': {
'breed': {'type': 'string'},
},
'required': ['breed'],
},
],
};
final schema = ApiSchema.fromJson(json);
expect(schema.isComposition, true);
expect(schema.isAllOf, true);
expect(schema.allOf.length, 2);
expect(schema.allOf.first.reference, 'Pet');
expect(schema.allOf.last.type, 'object');
expect(schema.allOf.last.properties.length, 1);
expect(schema.allOf.last.required, ['breed']);
});
test('creates ApiSchema from JSON with oneOf', () {
final json = {
'oneOf': [
{r'$ref': '#/components/schemas/Cat'},
{r'$ref': '#/components/schemas/Dog'},
],
};
final schema = ApiSchema.fromJson(json);
expect(schema.isComposition, true);
expect(schema.isOneOf, true);
expect(schema.oneOf.length, 2);
expect(schema.oneOf.first.reference, 'Cat');
expect(schema.oneOf.last.reference, 'Dog');
});
test('creates ApiSchema from JSON with validation constraints', () {
final json = {
'type': 'string',
'minLength': 1,
'maxLength': 100,
'pattern': r'^[a-zA-Z]+$',
'example': 'example',
'nullable': true,
};
final schema = ApiSchema.fromJson(json);
expect(schema.type, 'string');
expect(schema.minLength, 1);
expect(schema.maxLength, 100);
expect(schema.pattern, r'^[a-zA-Z]+$');
expect(schema.example, 'example');
expect(schema.nullable, true);
});
test('creates ApiSchema from JSON with discriminator', () {
final json = {
'oneOf': [
{r'$ref': '#/components/schemas/Cat'},
{r'$ref': '#/components/schemas/Dog'},
],
'discriminator': {
'propertyName': 'petType',
'mapping': {
'cat': '#/components/schemas/Cat',
'dog': '#/components/schemas/Dog',
},
},
};
final schema = ApiSchema.fromJson(json);
expect(schema.isComposition, true);
expect(schema.isOneOf, true);
expect(schema.hasDiscriminator, true);
expect(schema.discriminator?.propertyName, 'petType');
expect(schema.discriminator?.hasMapping, true);
expect(schema.discriminator?.mapping.length, 2);
expect(
schema.discriminator?.getSchemaForValue('cat'),
'#/components/schemas/Cat',
);
expect(
schema.discriminator?.getSchemaForValue('dog'),
'#/components/schemas/Dog',
);
});
});
group('ApiDiscriminator', () {
test('creates ApiDiscriminator with required fields', () {
const discriminator = ApiDiscriminator(
propertyName: 'type',
);
expect(discriminator.propertyName, 'type');
expect(discriminator.mapping, isEmpty);
expect(discriminator.hasMapping, false);
});
test('creates ApiDiscriminator with mapping', () {
const discriminator = ApiDiscriminator(
propertyName: 'petType',
mapping: {
'cat': '#/components/schemas/Cat',
'dog': '#/components/schemas/Dog',
},
);
expect(discriminator.propertyName, 'petType');
expect(discriminator.mapping.length, 2);
expect(discriminator.hasMapping, true);
expect(
discriminator.getSchemaForValue('cat'),
'#/components/schemas/Cat',
);
expect(
discriminator.getSchemaForValue('dog'),
'#/components/schemas/Dog',
);
expect(discriminator.getSchemaForValue('bird'), isNull);
});
test('creates ApiDiscriminator from JSON', () {
final json = {
'propertyName': 'objectType',
'mapping': {
'user': '#/components/schemas/User',
'admin': '#/components/schemas/Admin',
},
};
final discriminator = ApiDiscriminator.fromJson(json);
expect(discriminator.propertyName, 'objectType');
expect(discriminator.mapping.length, 2);
expect(discriminator.hasMapping, true);
expect(
discriminator.getSchemaForValue('user'),
'#/components/schemas/User',
);
expect(
discriminator.getSchemaForValue('admin'),
'#/components/schemas/Admin',
);
});
test('creates ApiDiscriminator from JSON with minimal fields', () {
final json = {
'propertyName': 'type',
};
final discriminator = ApiDiscriminator.fromJson(json);
expect(discriminator.propertyName, 'type');
expect(discriminator.mapping, isEmpty);
expect(discriminator.hasMapping, false);
});
});
group('Complex Nested Types', () {
test('creates ApiProperty with nested object properties', () {
final json = {
'type': 'object',
'description': 'User profile',
'properties': {
'name': {'type': 'string'},
'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'],
},
},
'required': ['name'],
};
final property = ApiProperty.fromJson('profile', json, []);
expect(property.type, PropertyType.object);
expect(property.hasNestedProperties, true);
expect(property.nestedProperties.length, 2);
expect(property.nestedProperties['name']?.type, PropertyType.string);
expect(property.nestedProperties['address']?.type, PropertyType.object);
expect(property.nestedProperties['address']?.hasNestedProperties, true);
// 检查深层嵌套
final addressProperty = property.nestedProperties['address']!;
expect(addressProperty.nestedProperties.length, 3);
expect(
addressProperty.nestedProperties['coordinates']?.type,
PropertyType.object,
);
final coordinatesProperty =
addressProperty.nestedProperties['coordinates']!;
expect(coordinatesProperty.nestedProperties.length, 2);
expect(
coordinatesProperty.nestedProperties['lat']?.type,
PropertyType.number,
);
expect(
coordinatesProperty.nestedProperties['lng']?.type,
PropertyType.number,
);
});
test('creates ApiProperty with array of nested objects', () {
final json = {
'type': 'array',
'description': 'List of users',
'items': {
'type': 'object',
'properties': {
'id': {'type': 'integer'},
'name': {'type': 'string'},
'tags': {
'type': 'array',
'items': {'type': 'string'},
},
},
'required': ['id', 'name'],
},
};
final property = ApiProperty.fromJson('users', json, []);
expect(property.type, PropertyType.array);
expect(property.items, isNotNull);
expect(property.items!.name, 'usersItem');
expect(property.items!.properties.length, 3);
expect(property.items!.properties['id']?.type, PropertyType.integer);
expect(property.items!.properties['tags']?.type, PropertyType.array);
});
test('handles maximum depth limit', () {
final json = {
'type': 'object',
'properties': {
'level1': {
'type': 'object',
'properties': {
'level2': {
'type': 'object',
'properties': {
'level3': {'type': 'string'},
},
},
},
},
},
};
final property = ApiProperty.fromJson('deep', json, [], maxDepth: 2);
expect(property.type, PropertyType.object);
expect(property.hasNestedProperties, true);
// 第一层应该正常解析
final level1 = property.nestedProperties['level1']!;
expect(level1.type, PropertyType.object);
expect(level1.hasNestedProperties, true);
// 第二层应该达到深度限制
final level2 = level1.nestedProperties['level2']!;
expect(level2.type, PropertyType.object);
expect(level2.description, '达到最大嵌套深度的对象');
});
test('creates ApiProperty with complex schema composition', () {
final json = {
'allOf': [
{
'type': 'object',
'properties': {
'id': {'type': 'integer'},
},
},
{
'type': 'object',
'properties': {
'name': {'type': 'string'},
},
},
],
};
final property = ApiProperty.fromJson('composed', json, []);
expect(property.hasComplexSchema, true);
expect(property.schema?.isAllOf, true);
expect(property.schema?.allOf.length, 2);
});
});
group('Advanced Schema Features', () {
test('creates ApiSchema with additionalProperties as boolean', () {
final json = {
'type': 'object',
'properties': {
'name': {'type': 'string'},
},
'additionalProperties': false,
};
final schema = ApiSchema.fromJson(json);
expect(schema.type, 'object');
expect(schema.additionalProperties, false);
expect(schema.allowsAdditionalProperties, false);
expect(schema.additionalPropertiesSchema, isNull);
});
test('creates ApiSchema with additionalProperties as schema', () {
final json = {
'type': 'object',
'properties': {
'name': {'type': 'string'},
},
'additionalProperties': {
'type': 'string',
'pattern': r'^[a-zA-Z]+$',
},
};
final schema = ApiSchema.fromJson(json);
expect(schema.type, 'object');
expect(schema.allowsAdditionalProperties, true);
expect(schema.additionalPropertiesSchema, isNotNull);
expect(schema.additionalPropertiesSchema?.type, 'string');
expect(schema.additionalPropertiesSchema?.pattern, r'^[a-zA-Z]+$');
});
test('creates ApiSchema with patternProperties', () {
final json = {
'type': 'object',
'patternProperties': {
'^S_': {'type': 'string'},
'^I_': {'type': 'integer'},
},
};
final schema = ApiSchema.fromJson(json);
expect(schema.hasPatternProperties, true);
expect(schema.patternProperties.length, 2);
expect(schema.patternProperties['^S_']?.type, 'string');
expect(schema.patternProperties['^I_']?.type, 'integer');
});
test('creates ApiSchema with propertyNames constraint', () {
final json = {
'type': 'object',
'propertyNames': {
'pattern': r'^[A-Za-z_][A-Za-z0-9_]*$',
},
};
final schema = ApiSchema.fromJson(json);
expect(schema.hasPropertyNames, true);
expect(schema.propertyNames?.pattern, r'^[A-Za-z_][A-Za-z0-9_]*$');
});
test('creates ApiSchema with dependencies', () {
final json = {
'type': 'object',
'properties': {
'name': {'type': 'string'},
'credit_card': {'type': 'string'},
'billing_address': {'type': 'string'},
},
'dependencies': {
'credit_card': ['billing_address'],
},
};
final schema = ApiSchema.fromJson(json);
expect(schema.hasDependencies, true);
expect(schema.dependencies.length, 1);
expect(schema.dependencies['credit_card'], ['billing_address']);
});
test('creates ApiSchema with const value', () {
final json = {
'const': 'United States of America',
};
final schema = ApiSchema.fromJson(json);
expect(schema.hasConstValue, true);
expect(schema.constValue, 'United States of America');
});
test('creates ApiSchema with conditional schema (if/then/else)', () {
final json = {
'type': 'object',
'properties': {
'street_address': {'type': 'string'},
'country': {'type': 'string'},
'postal_code': {'type': 'string'},
},
'if': {
'properties': {
'country': {'const': 'United States of America'},
},
},
'then': {
'properties': {
'postal_code': {'pattern': '[0-9]{5}(-[0-9]{4})?'},
},
},
'else': {
'properties': {
'postal_code': {'pattern': '[A-Z][0-9][A-Z] [0-9][A-Z][0-9]'},
},
},
};
final schema = ApiSchema.fromJson(json);
expect(schema.hasConditionalSchema, true);
expect(schema.ifSchema, isNotNull);
expect(schema.thenSchema, isNotNull);
expect(schema.elseSchema, isNotNull);
// 检查基本结构
expect(schema.ifSchema?.properties.length, 1);
expect(schema.thenSchema?.properties.length, 1);
expect(schema.elseSchema?.properties.length, 1);
// 检查属性存在性
expect(schema.ifSchema?.properties.containsKey('country'), true);
expect(schema.thenSchema?.properties.containsKey('postal_code'), true);
expect(schema.elseSchema?.properties.containsKey('postal_code'), true);
});
test('creates ApiSchema with multiple advanced features', () {
final json = {
'type': 'object',
'properties': {
'name': {'type': 'string'},
},
'additionalProperties': {'type': 'string'},
'patternProperties': {
'^prefix_': {'type': 'integer'},
},
'propertyNames': {
'pattern': r'^[a-z]+$',
},
'dependencies': {
'name': ['id'],
},
};
final schema = ApiSchema.fromJson(json);
expect(schema.allowsAdditionalProperties, true);
expect(schema.hasPatternProperties, true);
expect(schema.hasPropertyNames, true);
expect(schema.hasDependencies, true);
expect(schema.patternProperties.length, 1);
expect(schema.dependencies.length, 1);
});
});
}