swagger_generator_flutter/tests/comprehensive_generator_tes...

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);
});
});
});
}