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