1754 lines
51 KiB
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);
|
|
});
|
|
});
|
|
}
|