614 lines
20 KiB
Dart
614 lines
20 KiB
Dart
import 'package:swagger_generator_flutter/core/models.dart';
|
|
import 'package:swagger_generator_flutter/generators/optimized_retrofit_generator.dart';
|
|
import 'package:swagger_generator_flutter/generators/performance_generator.dart';
|
|
import 'package:swagger_generator_flutter/generators/retrofit_api_generator.dart';
|
|
import 'package:test/test.dart';
|
|
|
|
void main() {
|
|
group('Comprehensive Generator Tests', () {
|
|
late SwaggerDocument testDocument;
|
|
|
|
setUp(() {
|
|
testDocument = const SwaggerDocument(
|
|
title: 'Test API',
|
|
version: '1.0.0',
|
|
description: 'A comprehensive test API',
|
|
servers: [
|
|
ApiServer(
|
|
url: 'https://api.example.com',
|
|
description: 'Production server',
|
|
),
|
|
],
|
|
components: ApiComponents(
|
|
schemas: {},
|
|
securitySchemes: {
|
|
'bearerAuth': const ApiSecurityScheme(
|
|
type: SecuritySchemeType.http,
|
|
description: 'Bearer token',
|
|
scheme: 'bearer',
|
|
bearerFormat: 'JWT',
|
|
),
|
|
'apiKey': const ApiSecurityScheme(
|
|
type: SecuritySchemeType.apiKey,
|
|
description: 'API Key',
|
|
name: 'X-API-Key',
|
|
location: ApiKeyLocation.header,
|
|
),
|
|
},
|
|
),
|
|
paths: {
|
|
'/users': const ApiPath(
|
|
path: '/users',
|
|
method: HttpMethod.get,
|
|
summary: 'Get all users',
|
|
description: 'Retrieve a list of all users',
|
|
operationId: 'getUsers',
|
|
tags: ['users'],
|
|
parameters: [
|
|
ApiParameter(
|
|
name: 'page',
|
|
location: ParameterLocation.query,
|
|
required: false,
|
|
type: PropertyType.integer,
|
|
description: 'Page number',
|
|
),
|
|
ApiParameter(
|
|
name: 'limit',
|
|
location: ParameterLocation.query,
|
|
required: false,
|
|
type: PropertyType.integer,
|
|
description: 'Items per page',
|
|
),
|
|
],
|
|
responses: {
|
|
'200': const ApiResponse(
|
|
code: '200',
|
|
description: 'Successful response',
|
|
content: {
|
|
'application/json': const ApiMediaType(
|
|
schema: {
|
|
'type': 'array',
|
|
'items': {
|
|
'\$ref': '#/components/schemas/User',
|
|
},
|
|
},
|
|
),
|
|
},
|
|
),
|
|
'400': const ApiResponse(
|
|
code: '400',
|
|
description: 'Bad request',
|
|
),
|
|
'401': const ApiResponse(
|
|
code: '401',
|
|
description: 'Unauthorized',
|
|
),
|
|
},
|
|
security: [
|
|
ApiSecurityRequirement(
|
|
requirements: {'bearerAuth': []},
|
|
),
|
|
],
|
|
),
|
|
'/users/{id}': const ApiPath(
|
|
path: '/users/{id}',
|
|
method: HttpMethod.get,
|
|
summary: 'Get user by ID',
|
|
description: 'Retrieve a specific user by their ID',
|
|
operationId: 'getUserById',
|
|
tags: ['users'],
|
|
parameters: [
|
|
ApiParameter(
|
|
name: 'id',
|
|
location: ParameterLocation.path,
|
|
required: true,
|
|
type: PropertyType.integer,
|
|
description: 'User ID',
|
|
),
|
|
],
|
|
responses: {
|
|
'200': const ApiResponse(
|
|
code: '200',
|
|
description: 'User found',
|
|
content: {
|
|
'application/json': const ApiMediaType(
|
|
schema: {
|
|
'\$ref': '#/components/schemas/User',
|
|
},
|
|
),
|
|
},
|
|
),
|
|
'404': const ApiResponse(
|
|
code: '404',
|
|
description: 'User not found',
|
|
),
|
|
},
|
|
),
|
|
'/users/create': const ApiPath(
|
|
path: '/users/create',
|
|
method: HttpMethod.post,
|
|
summary: 'Create user',
|
|
description: 'Create a new user',
|
|
operationId: 'createUser',
|
|
tags: ['users'],
|
|
parameters: [],
|
|
requestBody: ApiRequestBody(
|
|
description: 'User data',
|
|
required: true,
|
|
content: {
|
|
'application/json': const ApiMediaType(
|
|
schema: {
|
|
'\$ref': '#/components/schemas/CreateUserRequest',
|
|
},
|
|
),
|
|
},
|
|
),
|
|
responses: {
|
|
'201': const ApiResponse(
|
|
code: '201',
|
|
description: 'User created',
|
|
content: {
|
|
'application/json': const ApiMediaType(
|
|
schema: {
|
|
'\$ref': '#/components/schemas/User',
|
|
},
|
|
),
|
|
},
|
|
),
|
|
'400': const ApiResponse(
|
|
code: '400',
|
|
description: 'Invalid input',
|
|
),
|
|
},
|
|
),
|
|
'/files/upload': const ApiPath(
|
|
path: '/files/upload',
|
|
method: HttpMethod.post,
|
|
summary: 'Upload file',
|
|
description: 'Upload a file to the server',
|
|
operationId: 'uploadFile',
|
|
tags: ['files'],
|
|
parameters: [],
|
|
requestBody: ApiRequestBody(
|
|
description: 'File to upload',
|
|
required: true,
|
|
content: {
|
|
'multipart/form-data': const ApiMediaType(
|
|
schema: {
|
|
'type': 'object',
|
|
'properties': {
|
|
'file': {
|
|
'type': 'string',
|
|
'format': 'binary',
|
|
},
|
|
'description': {
|
|
'type': 'string',
|
|
},
|
|
},
|
|
},
|
|
),
|
|
},
|
|
),
|
|
responses: {
|
|
'200': const ApiResponse(
|
|
code: '200',
|
|
description: 'File uploaded successfully',
|
|
content: {
|
|
'application/json': const ApiMediaType(
|
|
schema: {
|
|
'\$ref': '#/components/schemas/FileUploadResult',
|
|
},
|
|
),
|
|
},
|
|
),
|
|
},
|
|
),
|
|
},
|
|
models: {
|
|
'User': const ApiModel(
|
|
name: 'User',
|
|
description: 'User model',
|
|
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,
|
|
),
|
|
'email': const ApiProperty(
|
|
name: 'email',
|
|
type: PropertyType.string,
|
|
description: 'User email',
|
|
required: true,
|
|
),
|
|
'createdAt': const ApiProperty(
|
|
name: 'createdAt',
|
|
type: PropertyType.string,
|
|
description: 'Creation timestamp',
|
|
required: false,
|
|
),
|
|
},
|
|
required: ['id', 'name', 'email'],
|
|
),
|
|
'CreateUserRequest': const ApiModel(
|
|
name: 'CreateUserRequest',
|
|
description: 'Request model for creating a user',
|
|
properties: {
|
|
'name': const ApiProperty(
|
|
name: 'name',
|
|
type: PropertyType.string,
|
|
description: 'User name',
|
|
required: true,
|
|
),
|
|
'email': const ApiProperty(
|
|
name: 'email',
|
|
type: PropertyType.string,
|
|
description: 'User email',
|
|
required: true,
|
|
),
|
|
},
|
|
required: ['name', 'email'],
|
|
),
|
|
'FileUploadResult': const ApiModel(
|
|
name: 'FileUploadResult',
|
|
description: 'Result of file upload',
|
|
properties: {
|
|
'url': const ApiProperty(
|
|
name: 'url',
|
|
type: PropertyType.string,
|
|
description: 'File URL',
|
|
required: true,
|
|
),
|
|
'filename': const ApiProperty(
|
|
name: 'filename',
|
|
type: PropertyType.string,
|
|
description: 'Original filename',
|
|
required: true,
|
|
),
|
|
'size': const ApiProperty(
|
|
name: 'size',
|
|
type: PropertyType.integer,
|
|
description: 'File size in bytes',
|
|
required: true,
|
|
),
|
|
},
|
|
required: ['url', 'filename', 'size'],
|
|
),
|
|
},
|
|
controllers: {},
|
|
security: [
|
|
ApiSecurityRequirement(
|
|
requirements: {'bearerAuth': []},
|
|
),
|
|
],
|
|
);
|
|
});
|
|
|
|
group('RetrofitApiGenerator', () {
|
|
test('generates basic Retrofit API', () {
|
|
final generator = RetrofitApiGenerator(
|
|
className: 'TestApiService',
|
|
splitByTags: false,
|
|
);
|
|
|
|
final result = generator.generateFromDocument(testDocument);
|
|
|
|
expect(result, isNotEmpty);
|
|
expect(result, contains('abstract class TestApiService'));
|
|
expect(result, contains('@RestApi()'));
|
|
expect(result, contains('factory TestApiService(Dio dio'));
|
|
expect(result, contains('@GET(\'/users\')'));
|
|
expect(result, contains('@POST(\'/users\')'));
|
|
expect(result, contains('@Path(\'id\')'));
|
|
expect(result, contains('@Query(\'page\')'));
|
|
expect(result, contains('@Body()'));
|
|
});
|
|
|
|
test('generates split APIs by tags', () {
|
|
final generator = RetrofitApiGenerator(
|
|
className: 'ApiService',
|
|
splitByTags: true,
|
|
);
|
|
|
|
final result = generator.generateFromDocument(testDocument);
|
|
|
|
expect(result, isNotEmpty);
|
|
expect(result, contains('UsersApi'));
|
|
expect(result, contains('FilesApi'));
|
|
expect(result, contains('class ApiService'));
|
|
expect(result, contains('late final UsersApi users'));
|
|
expect(result, contains('late final FilesApi files'));
|
|
});
|
|
|
|
test('handles file upload endpoints', () {
|
|
final generator = RetrofitApiGenerator();
|
|
|
|
final result = generator.generateFromDocument(testDocument);
|
|
|
|
expect(result, contains('@POST(\'/files/upload\')'));
|
|
expect(result, contains('@MultiPart()'));
|
|
expect(result, contains('MultipartFile'));
|
|
});
|
|
|
|
test('generates proper parameter annotations', () {
|
|
final generator = RetrofitApiGenerator();
|
|
|
|
final result = generator.generateFromDocument(testDocument);
|
|
|
|
// Path parameters
|
|
expect(result, contains('@Path(\'id\') int id'));
|
|
|
|
// Query parameters
|
|
expect(result, contains('@Query(\'page\') int? page'));
|
|
expect(result, contains('@Query(\'limit\') int? limit'));
|
|
|
|
// Body parameters
|
|
expect(result, contains('@Body() CreateUserRequest body'));
|
|
});
|
|
|
|
test('generates security annotations', () {
|
|
final generator = RetrofitApiGenerator();
|
|
|
|
final result = generator.generateFromDocument(testDocument);
|
|
|
|
// Should include headers for authentication
|
|
expect(
|
|
result, anyOf([contains('Authorization'), contains('X-API-Key')]));
|
|
});
|
|
});
|
|
|
|
group('OptimizedRetrofitGenerator', () {
|
|
test('generates optimized code with base types', () {
|
|
final generator = OptimizedRetrofitGenerator(
|
|
className: 'OptimizedApiService',
|
|
generateModularApis: true,
|
|
generateBaseResult: true,
|
|
generatePagination: true,
|
|
generateFileUpload: true,
|
|
);
|
|
|
|
final result = generator.generateFromDocument(testDocument);
|
|
|
|
expect(result, isNotEmpty);
|
|
expect(result, contains('class BaseResult<T>'));
|
|
expect(result, contains('class BasePageResult<T>'));
|
|
expect(result, contains('class FileUploadRequest'));
|
|
expect(result, contains('class FileUploadResult'));
|
|
expect(result, contains('class ApiUtils'));
|
|
});
|
|
|
|
test('generates modular APIs', () {
|
|
final generator = OptimizedRetrofitGenerator(
|
|
generateModularApis: true,
|
|
);
|
|
|
|
final result = generator.generateFromDocument(testDocument);
|
|
|
|
expect(result, contains('class UsersApi'));
|
|
expect(result, contains('class FilesApi'));
|
|
expect(result, contains('class ApiService'));
|
|
});
|
|
|
|
test('generates single API when modular is disabled', () {
|
|
final generator = OptimizedRetrofitGenerator(
|
|
generateModularApis: false,
|
|
className: 'SingleApiService',
|
|
);
|
|
|
|
final result = generator.generateFromDocument(testDocument);
|
|
|
|
expect(result, contains('abstract class SingleApiService'));
|
|
expect(result, isNot(contains('class UsersApi')));
|
|
expect(result, isNot(contains('class FilesApi')));
|
|
});
|
|
|
|
test('includes utility methods', () {
|
|
final generator = OptimizedRetrofitGenerator(
|
|
generateFileUpload: true,
|
|
generatePagination: true,
|
|
);
|
|
|
|
final result = generator.generateFromDocument(testDocument);
|
|
|
|
expect(result, contains('class ApiUtils'));
|
|
expect(result, contains('createFileUpload'));
|
|
expect(result, contains('createPageParam'));
|
|
});
|
|
});
|
|
|
|
group('PerformanceGenerator', () {
|
|
test('generates code with performance tracking', () async {
|
|
final generator = PerformanceGenerator(
|
|
maxConcurrency: 2,
|
|
enableCaching: true,
|
|
enableParallel: true,
|
|
);
|
|
|
|
final result = await generator.generateFromDocument(testDocument);
|
|
|
|
expect(result, isNotEmpty);
|
|
expect(result, contains('Generated API for Test API'));
|
|
expect(result, contains('class User'));
|
|
expect(result, contains('CommonApi'));
|
|
|
|
final stats = generator.getStats();
|
|
expect(stats.totalTasks, greaterThan(0));
|
|
expect(stats.completedTasks, greaterThan(0));
|
|
});
|
|
|
|
test('caching improves performance', () async {
|
|
final generator = PerformanceGenerator(
|
|
enableCaching: true,
|
|
);
|
|
|
|
// First generation
|
|
final stopwatch1 = Stopwatch()..start();
|
|
await generator.generateFromDocument(testDocument);
|
|
stopwatch1.stop();
|
|
|
|
// Second generation (should use cache)
|
|
final stopwatch2 = Stopwatch()..start();
|
|
await generator.generateFromDocument(testDocument);
|
|
stopwatch2.stop();
|
|
|
|
// Second should be faster due to caching
|
|
expect(stopwatch2.elapsedMicroseconds,
|
|
lessThan(stopwatch1.elapsedMicroseconds));
|
|
|
|
final cacheStats = generator.getCacheStats();
|
|
expect(cacheStats.hits, greaterThan(0));
|
|
});
|
|
});
|
|
|
|
group('Code Quality', () {
|
|
test('generated code is valid Dart syntax', () {
|
|
final generator = RetrofitApiGenerator();
|
|
final result = generator.generateFromDocument(testDocument);
|
|
|
|
// Basic syntax checks
|
|
expect(result, isNot(contains(';;'))); // No double semicolons
|
|
expect(result, isNot(contains(',,'))); // No double commas
|
|
expect(result,
|
|
isNot(contains(' '))); // No double spaces (basic formatting)
|
|
|
|
// Check for proper imports
|
|
expect(result, contains('import \'package:dio/dio.dart\';'));
|
|
expect(result, contains('import \'package:retrofit/retrofit.dart\';'));
|
|
|
|
// Check for proper class structure
|
|
final classMatches = RegExp(r'class \w+').allMatches(result);
|
|
final abstractClassMatches =
|
|
RegExp(r'abstract class \w+').allMatches(result);
|
|
expect(
|
|
classMatches.length + abstractClassMatches.length, greaterThan(0));
|
|
});
|
|
|
|
test('handles special characters in names', () {
|
|
const specialDocument = SwaggerDocument(
|
|
title: 'API with Special-Characters_and.dots',
|
|
version: '1.0.0',
|
|
description: 'Test API',
|
|
servers: [],
|
|
components: ApiComponents(schemas: {}, securitySchemes: {}),
|
|
paths: {
|
|
'/special-endpoint_with.dots': const ApiPath(
|
|
path: '/special-endpoint_with.dots',
|
|
method: HttpMethod.get,
|
|
summary: 'Special endpoint',
|
|
description: 'Endpoint with special characters',
|
|
operationId: 'getSpecialEndpoint',
|
|
tags: ['special-tag_with.dots'],
|
|
parameters: [],
|
|
responses: {
|
|
'200': const ApiResponse(
|
|
code: '200',
|
|
description: 'Success',
|
|
),
|
|
},
|
|
),
|
|
},
|
|
models: {},
|
|
controllers: {},
|
|
security: [],
|
|
);
|
|
|
|
final generator = RetrofitApiGenerator();
|
|
final result = generator.generateFromDocument(specialDocument);
|
|
|
|
expect(result, isNotEmpty);
|
|
// Should handle special characters in class names
|
|
expect(result, contains('class'));
|
|
});
|
|
|
|
test('generates proper JSON annotations', () {
|
|
final generator = OptimizedRetrofitGenerator(
|
|
generateBaseResult: true,
|
|
);
|
|
|
|
final result = generator.generateFromDocument(testDocument);
|
|
|
|
expect(result, contains('@JsonSerializable()'));
|
|
expect(result, contains('fromJson'));
|
|
expect(result, contains('toJson'));
|
|
expect(result, contains('_\$'));
|
|
});
|
|
|
|
test('handles nullable and required fields correctly', () {
|
|
final generator = RetrofitApiGenerator();
|
|
final result = generator.generateFromDocument(testDocument);
|
|
|
|
// Required path parameters should not be nullable
|
|
expect(result, contains('@Path(\'id\') int id'));
|
|
|
|
// Optional query parameters should be nullable
|
|
expect(result, contains('@Query(\'page\') int? page'));
|
|
expect(result, contains('@Query(\'limit\') int? limit'));
|
|
});
|
|
});
|
|
|
|
group('Error Handling', () {
|
|
test('handles empty document gracefully', () {
|
|
const emptyDocument = SwaggerDocument(
|
|
title: 'Empty API',
|
|
version: '1.0.0',
|
|
description: 'Empty test API',
|
|
servers: [],
|
|
components: ApiComponents(schemas: {}, securitySchemes: {}),
|
|
paths: {},
|
|
models: {},
|
|
controllers: {},
|
|
security: [],
|
|
);
|
|
|
|
final generator = RetrofitApiGenerator();
|
|
final result = generator.generateFromDocument(emptyDocument);
|
|
|
|
expect(result, isNotEmpty);
|
|
expect(result, contains('Empty API'));
|
|
// Should still generate basic structure even with no paths
|
|
});
|
|
|
|
test('handles missing operation IDs', () {
|
|
const documentWithoutOperationIds = SwaggerDocument(
|
|
title: 'Test API',
|
|
version: '1.0.0',
|
|
description: 'Test',
|
|
servers: [],
|
|
components: ApiComponents(schemas: {}, securitySchemes: {}),
|
|
paths: {
|
|
'/test': const ApiPath(
|
|
path: '/test',
|
|
method: HttpMethod.get,
|
|
summary: 'Test endpoint',
|
|
description: 'Test',
|
|
operationId: '', // Empty operation ID
|
|
tags: [],
|
|
parameters: [],
|
|
responses: {
|
|
'200': const ApiResponse(
|
|
code: '200',
|
|
description: 'Success',
|
|
),
|
|
},
|
|
),
|
|
},
|
|
models: {},
|
|
controllers: {},
|
|
security: [],
|
|
);
|
|
|
|
final generator = RetrofitApiGenerator();
|
|
expect(
|
|
() => generator.generateFromDocument(documentWithoutOperationIds),
|
|
returnsNormally);
|
|
});
|
|
});
|
|
});
|
|
}
|