486 lines
16 KiB
Dart
486 lines
16 KiB
Dart
import 'package:swagger_generator_flutter/core/models.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(
|
|
securitySchemes: {
|
|
'bearerAuth': ApiSecurityScheme(
|
|
type: SecuritySchemeType.http,
|
|
description: 'Bearer token',
|
|
scheme: 'bearer',
|
|
bearerFormat: 'JWT',
|
|
),
|
|
'apiKey': ApiSecurityScheme(
|
|
type: SecuritySchemeType.apiKey,
|
|
description: 'API Key',
|
|
name: 'X-API-Key',
|
|
location: ApiKeyLocation.header,
|
|
),
|
|
},
|
|
),
|
|
paths: {
|
|
'/users': 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': ApiResponse(
|
|
code: '200',
|
|
description: 'Successful response',
|
|
content: {
|
|
'application/json': ApiMediaType(
|
|
schema: {
|
|
'type': 'array',
|
|
'items': {
|
|
r'$ref': '#/components/schemas/User',
|
|
},
|
|
},
|
|
),
|
|
},
|
|
),
|
|
'400': ApiResponse(
|
|
code: '400',
|
|
description: 'Bad request',
|
|
),
|
|
'401': ApiResponse(
|
|
code: '401',
|
|
description: 'Unauthorized',
|
|
),
|
|
},
|
|
security: [
|
|
ApiSecurityRequirement(
|
|
requirements: {'bearerAuth': []},
|
|
),
|
|
],
|
|
),
|
|
'/users/{id}': 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': ApiResponse(
|
|
code: '200',
|
|
description: 'User found',
|
|
content: {
|
|
'application/json': ApiMediaType(
|
|
schema: {
|
|
r'$ref': '#/components/schemas/User',
|
|
},
|
|
),
|
|
},
|
|
),
|
|
'404': ApiResponse(
|
|
code: '404',
|
|
description: 'User not found',
|
|
),
|
|
},
|
|
),
|
|
'/users/create': 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': ApiMediaType(
|
|
schema: {
|
|
r'$ref': '#/components/schemas/CreateUserRequest',
|
|
},
|
|
),
|
|
},
|
|
),
|
|
responses: {
|
|
'201': ApiResponse(
|
|
code: '201',
|
|
description: 'User created',
|
|
content: {
|
|
'application/json': ApiMediaType(
|
|
schema: {
|
|
r'$ref': '#/components/schemas/User',
|
|
},
|
|
),
|
|
},
|
|
),
|
|
'400': ApiResponse(
|
|
code: '400',
|
|
description: 'Invalid input',
|
|
),
|
|
},
|
|
),
|
|
'/files/upload': 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': ApiMediaType(
|
|
schema: {
|
|
'type': 'object',
|
|
'properties': {
|
|
'file': {
|
|
'type': 'string',
|
|
'format': 'binary',
|
|
},
|
|
'description': {
|
|
'type': 'string',
|
|
},
|
|
},
|
|
},
|
|
),
|
|
},
|
|
),
|
|
responses: {
|
|
'200': ApiResponse(
|
|
code: '200',
|
|
description: 'File uploaded successfully',
|
|
content: {
|
|
'application/json': ApiMediaType(
|
|
schema: {
|
|
r'$ref': '#/components/schemas/FileUploadResult',
|
|
},
|
|
),
|
|
},
|
|
),
|
|
},
|
|
),
|
|
},
|
|
models: {
|
|
'User': ApiModel(
|
|
name: 'User',
|
|
description: 'User model',
|
|
properties: {
|
|
'id': ApiProperty(
|
|
name: 'id',
|
|
type: PropertyType.integer,
|
|
description: 'User ID',
|
|
required: true,
|
|
),
|
|
'name': ApiProperty(
|
|
name: 'name',
|
|
type: PropertyType.string,
|
|
description: 'User name',
|
|
required: true,
|
|
),
|
|
'email': ApiProperty(
|
|
name: 'email',
|
|
type: PropertyType.string,
|
|
description: 'User email',
|
|
required: true,
|
|
),
|
|
'createdAt': ApiProperty(
|
|
name: 'createdAt',
|
|
type: PropertyType.string,
|
|
description: 'Creation timestamp',
|
|
required: false,
|
|
),
|
|
},
|
|
required: ['id', 'name', 'email'],
|
|
),
|
|
'CreateUserRequest': ApiModel(
|
|
name: 'CreateUserRequest',
|
|
description: 'Request model for creating a user',
|
|
properties: {
|
|
'name': ApiProperty(
|
|
name: 'name',
|
|
type: PropertyType.string,
|
|
description: 'User name',
|
|
required: true,
|
|
),
|
|
'email': ApiProperty(
|
|
name: 'email',
|
|
type: PropertyType.string,
|
|
description: 'User email',
|
|
required: true,
|
|
),
|
|
},
|
|
required: ['name', 'email'],
|
|
),
|
|
'FileUploadResult': ApiModel(
|
|
name: 'FileUploadResult',
|
|
description: 'Result of file upload',
|
|
properties: {
|
|
'url': ApiProperty(
|
|
name: 'url',
|
|
type: PropertyType.string,
|
|
description: 'File URL',
|
|
required: true,
|
|
),
|
|
'filename': ApiProperty(
|
|
name: 'filename',
|
|
type: PropertyType.string,
|
|
description: 'Original filename',
|
|
required: true,
|
|
),
|
|
'size': 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',
|
|
);
|
|
|
|
final result = generator.generateFromDocument(testDocument);
|
|
|
|
expect(result, isNotEmpty);
|
|
// The main file should contain the aggregator class
|
|
expect(result, contains('class ApiService'));
|
|
// It should have getters for the individual API services
|
|
expect(result, contains('UsersApi get users => _usersApi;'));
|
|
expect(result, contains('FilesApi get files => _filesApi;'));
|
|
// It should import the tag-based API files
|
|
expect(result, contains("import 'users_api.dart';"));
|
|
expect(result, contains("import 'files_api.dart';"));
|
|
});
|
|
|
|
test('handles file upload endpoints', () {
|
|
final generator = RetrofitApiGenerator(splitByTags: false);
|
|
|
|
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(splitByTags: false);
|
|
|
|
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 request'));
|
|
});
|
|
|
|
test('generates security annotations', () {
|
|
final generator = RetrofitApiGenerator(splitByTags: false);
|
|
|
|
final result = generator.generateFromDocument(testDocument);
|
|
|
|
// Should include headers for authentication
|
|
expect(
|
|
result, anyOf([contains('Authorization'), contains('X-API-Key')]),);
|
|
});
|
|
});
|
|
|
|
group('Code Quality', () {
|
|
test('generated code is valid Dart syntax', () {
|
|
final generator = RetrofitApiGenerator(splitByTags: false);
|
|
final result = generator.generateFromDocument(testDocument);
|
|
|
|
// Basic syntax checks
|
|
expect(result, isNot(contains(';;'))); // No double semicolons
|
|
expect(result, isNot(contains(',,'))); // No double commas
|
|
|
|
// 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',
|
|
paths: {
|
|
'/special-endpoint_with.dots': 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': ApiResponse(
|
|
code: '200',
|
|
description: 'Success',
|
|
),
|
|
},
|
|
),
|
|
},
|
|
models: {},
|
|
controllers: {},
|
|
);
|
|
|
|
final generator = RetrofitApiGenerator(splitByTags: false);
|
|
final result = generator.generateFromDocument(specialDocument);
|
|
|
|
expect(result, isNotEmpty);
|
|
// Should handle special characters in class names
|
|
expect(result, contains('class'));
|
|
});
|
|
|
|
test('handles nullable and required fields correctly', () {
|
|
final generator = RetrofitApiGenerator(splitByTags: false);
|
|
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',
|
|
paths: {},
|
|
models: {},
|
|
controllers: {},
|
|
);
|
|
|
|
final generator = RetrofitApiGenerator();
|
|
final result = generator.generateFromDocument(emptyDocument);
|
|
|
|
expect(result, isNotEmpty);
|
|
|
|
// 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',
|
|
paths: {
|
|
'/test': ApiPath(
|
|
path: '/test',
|
|
method: HttpMethod.get,
|
|
summary: 'Test endpoint',
|
|
description: 'Test',
|
|
operationId: '', // Empty operation ID
|
|
tags: [],
|
|
parameters: [],
|
|
responses: {
|
|
'200': ApiResponse(
|
|
code: '200',
|
|
description: 'Success',
|
|
),
|
|
},
|
|
),
|
|
},
|
|
models: {},
|
|
controllers: {},
|
|
);
|
|
|
|
final generator = RetrofitApiGenerator(splitByTags: false);
|
|
expect(
|
|
() => generator.generateFromDocument(documentWithoutOperationIds),
|
|
returnsNormally,);
|
|
});
|
|
});
|
|
});
|
|
}
|