590 lines
20 KiB
Dart
590 lines
20 KiB
Dart
import 'dart:convert';
|
|
import 'dart:io';
|
|
|
|
import 'package:swagger_generator_flutter/core/error_reporter.dart';
|
|
import 'package:swagger_generator_flutter/core/performance_parser.dart';
|
|
import 'package:swagger_generator_flutter/generators/optimized_retrofit_generator.dart';
|
|
import 'package:swagger_generator_flutter/generators/retrofit_api_generator.dart';
|
|
import 'package:swagger_generator_flutter/validators/enhanced_validator.dart';
|
|
import 'package:test/test.dart';
|
|
|
|
void main() {
|
|
group('Integration Tests', () {
|
|
group('End-to-End Workflow', () {
|
|
test('complete workflow from JSON to generated code', () async {
|
|
// 1. 准备测试数据
|
|
final testApiJson = {
|
|
'openapi': '3.0.3',
|
|
'info': {
|
|
'title': 'Integration Test API',
|
|
'version': '1.0.0',
|
|
'description': 'API for integration testing',
|
|
},
|
|
'servers': [
|
|
{
|
|
'url': 'https://api.example.com',
|
|
'description': 'Production server',
|
|
},
|
|
],
|
|
'paths': {
|
|
'/users': {
|
|
'get': {
|
|
'summary': 'Get users',
|
|
'operationId': 'getUsers',
|
|
'tags': ['users'],
|
|
'parameters': [
|
|
{
|
|
'name': 'page',
|
|
'in': 'query',
|
|
'required': false,
|
|
'schema': {'type': 'integer', 'default': 1},
|
|
'description': 'Page number',
|
|
},
|
|
{
|
|
'name': 'limit',
|
|
'in': 'query',
|
|
'required': false,
|
|
'schema': {'type': 'integer', 'default': 20},
|
|
'description': 'Items per page',
|
|
},
|
|
],
|
|
'responses': {
|
|
'200': {
|
|
'description': 'Success',
|
|
'content': {
|
|
'application/json': {
|
|
'schema': {
|
|
'type': 'object',
|
|
'properties': {
|
|
'data': {
|
|
'type': 'array',
|
|
'items': {
|
|
'\$ref': '#/components/schemas/User',
|
|
},
|
|
},
|
|
'total': {'type': 'integer'},
|
|
'page': {'type': 'integer'},
|
|
'limit': {'type': 'integer'},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
'400': {
|
|
'description': 'Bad request',
|
|
},
|
|
},
|
|
},
|
|
'post': {
|
|
'summary': 'Create user',
|
|
'operationId': 'createUser',
|
|
'tags': ['users'],
|
|
'requestBody': {
|
|
'required': true,
|
|
'content': {
|
|
'application/json': {
|
|
'schema': {
|
|
'\$ref': '#/components/schemas/CreateUserRequest',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
'responses': {
|
|
'201': {
|
|
'description': 'User created',
|
|
'content': {
|
|
'application/json': {
|
|
'schema': {
|
|
'\$ref': '#/components/schemas/User',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
'400': {
|
|
'description': 'Invalid input',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
'/users/{id}': {
|
|
'get': {
|
|
'summary': 'Get user by ID',
|
|
'operationId': 'getUserById',
|
|
'tags': ['users'],
|
|
'parameters': [
|
|
{
|
|
'name': 'id',
|
|
'in': 'path',
|
|
'required': true,
|
|
'schema': {'type': 'integer'},
|
|
'description': 'User ID',
|
|
},
|
|
],
|
|
'responses': {
|
|
'200': {
|
|
'description': 'User found',
|
|
'content': {
|
|
'application/json': {
|
|
'schema': {
|
|
'\$ref': '#/components/schemas/User',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
'404': {
|
|
'description': 'User not found',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
'/files/upload': {
|
|
'post': {
|
|
'summary': 'Upload file',
|
|
'operationId': 'uploadFile',
|
|
'tags': ['files'],
|
|
'requestBody': {
|
|
'required': true,
|
|
'content': {
|
|
'multipart/form-data': {
|
|
'schema': {
|
|
'type': 'object',
|
|
'properties': {
|
|
'file': {
|
|
'type': 'string',
|
|
'format': 'binary',
|
|
},
|
|
'description': {
|
|
'type': 'string',
|
|
},
|
|
},
|
|
'required': ['file'],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
'responses': {
|
|
'200': {
|
|
'description': 'File uploaded',
|
|
'content': {
|
|
'application/json': {
|
|
'schema': {
|
|
'\$ref': '#/components/schemas/FileUploadResult',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
'components': {
|
|
'schemas': {
|
|
'User': {
|
|
'type': 'object',
|
|
'properties': {
|
|
'id': {
|
|
'type': 'integer',
|
|
'format': 'int64',
|
|
},
|
|
'name': {
|
|
'type': 'string',
|
|
'maxLength': 100,
|
|
},
|
|
'email': {
|
|
'type': 'string',
|
|
'format': 'email',
|
|
},
|
|
'createdAt': {
|
|
'type': 'string',
|
|
'format': 'date-time',
|
|
},
|
|
},
|
|
'required': ['id', 'name', 'email'],
|
|
},
|
|
'CreateUserRequest': {
|
|
'type': 'object',
|
|
'properties': {
|
|
'name': {
|
|
'type': 'string',
|
|
'maxLength': 100,
|
|
},
|
|
'email': {
|
|
'type': 'string',
|
|
'format': 'email',
|
|
},
|
|
},
|
|
'required': ['name', 'email'],
|
|
},
|
|
'FileUploadResult': {
|
|
'type': 'object',
|
|
'properties': {
|
|
'url': {
|
|
'type': 'string',
|
|
'format': 'uri',
|
|
},
|
|
'filename': {
|
|
'type': 'string',
|
|
},
|
|
'size': {
|
|
'type': 'integer',
|
|
},
|
|
'contentType': {
|
|
'type': 'string',
|
|
},
|
|
},
|
|
'required': ['url', 'filename', 'size'],
|
|
},
|
|
},
|
|
'securitySchemes': {
|
|
'bearerAuth': {
|
|
'type': 'http',
|
|
'scheme': 'bearer',
|
|
'bearerFormat': 'JWT',
|
|
},
|
|
},
|
|
},
|
|
'security': [
|
|
{
|
|
'bearerAuth': [],
|
|
},
|
|
],
|
|
};
|
|
|
|
final jsonString = jsonEncode(testApiJson);
|
|
|
|
// 2. 解析 JSON 为 SwaggerDocument
|
|
final parser = PerformanceParser(
|
|
config: const ParseConfig(
|
|
enablePerformanceStats: true,
|
|
enableParallelParsing: false, // 禁用并行解析避免类型转换问题
|
|
),
|
|
);
|
|
|
|
final document = await parser.parseDocument(jsonString);
|
|
|
|
// 验证解析结果
|
|
expect(document.title, equals('Integration Test API'));
|
|
expect(document.version, equals('1.0.0'));
|
|
expect(document.paths.length, equals(3));
|
|
expect(document.models.length, equals(3));
|
|
expect(document.servers.length, equals(1));
|
|
|
|
// 检查解析性能
|
|
final parseStats = parser.lastStats;
|
|
expect(parseStats, isNotNull);
|
|
expect(parseStats!.totalTime.inMilliseconds, lessThan(5000));
|
|
|
|
// 3. 验证文档
|
|
final validator = EnhancedValidator(
|
|
includeWarnings: true,
|
|
);
|
|
|
|
final isValid = validator.validateDocument(document);
|
|
expect(isValid, isTrue);
|
|
|
|
final errors = validator.errorReporter.errors;
|
|
final criticalErrors = errors
|
|
.where((e) =>
|
|
e.severity == ErrorSeverity.error ||
|
|
e.severity == ErrorSeverity.critical)
|
|
.toList();
|
|
|
|
expect(criticalErrors, isEmpty,
|
|
reason:
|
|
'Document should not have critical errors: ${criticalErrors.map((e) => e.title).join(", ")}');
|
|
|
|
// 4. 生成 Retrofit API 代码
|
|
final retrofitGenerator = RetrofitApiGenerator(
|
|
className: 'IntegrationTestApi',
|
|
splitByTags: true,
|
|
);
|
|
|
|
final retrofitCode = retrofitGenerator.generateFromDocument(document);
|
|
|
|
// 验证生成的代码
|
|
expect(retrofitCode, isNotEmpty);
|
|
expect(retrofitCode, contains('IntegrationTestApi'));
|
|
expect(retrofitCode, contains('@GET(\'/users\')'));
|
|
expect(retrofitCode, contains('@POST(\'/users\')'));
|
|
expect(retrofitCode, contains('@GET(\'/users/{id}\')'));
|
|
expect(retrofitCode, contains('@POST(\'/files/upload\')'));
|
|
expect(retrofitCode, contains('@Path(\'id\')'));
|
|
expect(retrofitCode, contains('@Query(\'page\')'));
|
|
expect(retrofitCode, contains('@MultiPart()'));
|
|
|
|
// 5. 生成优化的 API 代码
|
|
final optimizedGenerator = OptimizedRetrofitGenerator(
|
|
className: 'OptimizedIntegrationApi',
|
|
generateModularApis: true,
|
|
generateBaseResult: true,
|
|
generatePagination: true,
|
|
generateFileUpload: true,
|
|
);
|
|
|
|
final optimizedCode = optimizedGenerator.generateFromDocument(document);
|
|
|
|
// 验证优化代码
|
|
expect(optimizedCode, isNotEmpty);
|
|
expect(optimizedCode, contains('class BaseResult'));
|
|
expect(optimizedCode, contains('class BasePageResult'));
|
|
expect(optimizedCode, contains('class FileUploadRequest'));
|
|
expect(optimizedCode, contains('class ApiUtils'));
|
|
expect(optimizedCode, contains('UsersApi'));
|
|
expect(optimizedCode, contains('FilesApi'));
|
|
|
|
// 6. 性能验证
|
|
print('Integration Test Performance Summary:');
|
|
print(' Parse Time: ${parseStats.totalTime.inMilliseconds}ms');
|
|
print(
|
|
' Document Size: ${(jsonString.length / 1024).toStringAsFixed(2)}KB');
|
|
print(' Paths Parsed: ${parseStats.pathCount}');
|
|
print(' Schemas Parsed: ${parseStats.schemaCount}');
|
|
print(
|
|
' Retrofit Code Size: ${(retrofitCode.length / 1024).toStringAsFixed(2)}KB');
|
|
print(
|
|
' Optimized Code Size: ${(optimizedCode.length / 1024).toStringAsFixed(2)}KB');
|
|
|
|
// 验证性能指标
|
|
expect(
|
|
parseStats.totalTime.inMilliseconds, lessThan(2000)); // 解析应在2秒内完成
|
|
expect(retrofitCode.length, greaterThan(1000)); // 应生成足够的代码
|
|
expect(optimizedCode.length,
|
|
greaterThan(retrofitCode.length)); // 优化版本应该更丰富
|
|
});
|
|
|
|
test('handles real project swagger.json', () async {
|
|
final file = File('swagger.json');
|
|
if (!file.existsSync()) {
|
|
print(
|
|
'swagger.json not found, skipping real project integration test');
|
|
return;
|
|
}
|
|
|
|
final jsonString = await file.readAsString();
|
|
print(
|
|
'Real project swagger.json size: ${(jsonString.length / 1024).toStringAsFixed(2)}KB');
|
|
|
|
// 解析
|
|
final parser = PerformanceParser(
|
|
config: const ParseConfig(
|
|
enablePerformanceStats: true,
|
|
enableParallelParsing: false, // 禁用并行解析
|
|
maxConcurrency: 4,
|
|
),
|
|
);
|
|
|
|
final parseStopwatch = Stopwatch()..start();
|
|
final document = await parser.parseDocument(jsonString);
|
|
parseStopwatch.stop();
|
|
|
|
expect(document, isNotNull);
|
|
expect(document.paths.isNotEmpty, isTrue);
|
|
|
|
print('Real project parsing results:');
|
|
print(' Parse Time: ${parseStopwatch.elapsedMilliseconds}ms');
|
|
print(' Paths: ${document.paths.length}');
|
|
print(' Models: ${document.models.length}');
|
|
print(' Servers: ${document.servers.length}');
|
|
|
|
// 验证
|
|
final validator = EnhancedValidator(includeWarnings: false);
|
|
final isValid = validator.validateDocument(document);
|
|
|
|
final errors =
|
|
validator.errorReporter.getErrorsBySeverity(ErrorSeverity.error);
|
|
final criticalErrors =
|
|
validator.errorReporter.getErrorsBySeverity(ErrorSeverity.critical);
|
|
|
|
print('Validation results:');
|
|
print(' Valid: $isValid');
|
|
print(' Errors: ${errors.length}');
|
|
print(' Critical: ${criticalErrors.length}');
|
|
|
|
// 生成代码
|
|
final generator = OptimizedRetrofitGenerator(
|
|
className: 'OAMobileApiService',
|
|
generateModularApis: true,
|
|
generateBaseResult: true,
|
|
generatePagination: true,
|
|
generateFileUpload: true,
|
|
);
|
|
|
|
final genStopwatch = Stopwatch()..start();
|
|
final generatedCode = generator.generateFromDocument(document);
|
|
genStopwatch.stop();
|
|
|
|
expect(generatedCode, isNotEmpty);
|
|
expect(generatedCode, contains('OAMobileApiService'));
|
|
|
|
print('Code generation results:');
|
|
print(' Generation Time: ${genStopwatch.elapsedMilliseconds}ms');
|
|
print(
|
|
' Generated Code Size: ${(generatedCode.length / 1024).toStringAsFixed(2)}KB');
|
|
print(' Generated Lines: ${generatedCode.split('\n').length}');
|
|
|
|
// 性能要求
|
|
expect(parseStopwatch.elapsedMilliseconds, lessThan(15000)); // 15秒内解析完成
|
|
expect(genStopwatch.elapsedMilliseconds, lessThan(10000)); // 10秒内生成完成
|
|
expect(generatedCode.length, greaterThan(5000)); // 至少生成5KB代码
|
|
});
|
|
});
|
|
|
|
group('Error Handling Integration', () {
|
|
test('handles malformed JSON gracefully', () async {
|
|
const malformedJson =
|
|
'{"openapi": "3.0.3", "info": {"title": "Test"'; // 缺少闭合括号
|
|
|
|
final parser = PerformanceParser();
|
|
|
|
expect(() => parser.parseDocument(malformedJson),
|
|
throwsA(isA<FormatException>()));
|
|
});
|
|
|
|
test('handles invalid OpenAPI document', () async {
|
|
final invalidDoc = {
|
|
'openapi': '3.0.3',
|
|
'info': {
|
|
'title': 'Invalid API',
|
|
// 缺少 version
|
|
},
|
|
'paths': {}, // 空路径
|
|
};
|
|
|
|
final jsonString = jsonEncode(invalidDoc);
|
|
final parser = PerformanceParser();
|
|
|
|
expect(() => parser.parseDocument(jsonString),
|
|
throwsA(isA<FormatException>()));
|
|
});
|
|
|
|
test('validation catches common errors', () async {
|
|
final problematicDoc = {
|
|
'openapi': '3.0.3',
|
|
'info': {
|
|
'title': 'Problematic API',
|
|
'version': '1.0.0',
|
|
},
|
|
'paths': {
|
|
'/users/{id}': {
|
|
'get': {
|
|
'summary': 'Get user',
|
|
'responses': {
|
|
'200': {
|
|
'description': 'Success',
|
|
},
|
|
},
|
|
// 缺少路径参数声明
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
final jsonString = jsonEncode(problematicDoc);
|
|
final parser = PerformanceParser();
|
|
final document = await parser.parseDocument(jsonString);
|
|
|
|
final validator = EnhancedValidator();
|
|
final isValid = validator.validateDocument(document);
|
|
|
|
expect(isValid, isFalse);
|
|
|
|
final errors = validator.errorReporter.errors;
|
|
expect(errors.any((e) => e.id == 'UNDECLARED_PATH_PARAMETER'), isTrue);
|
|
});
|
|
});
|
|
|
|
group('Performance Integration', () {
|
|
test('handles large documents efficiently', () async {
|
|
// 创建大型文档
|
|
final paths = <String, dynamic>{};
|
|
final schemas = <String, dynamic>{};
|
|
|
|
for (int i = 0; i < 200; i++) {
|
|
paths['/resource$i'] = {
|
|
'get': {
|
|
'summary': 'Get resource $i',
|
|
'operationId': 'getResource$i',
|
|
'tags': ['resources'],
|
|
'responses': {
|
|
'200': {
|
|
'description': 'Success',
|
|
'content': {
|
|
'application/json': {
|
|
'schema': {
|
|
'\$ref': '#/components/schemas/Resource$i',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
schemas['Resource$i'] = {
|
|
'type': 'object',
|
|
'properties': {
|
|
'id': {'type': 'integer'},
|
|
'name': {'type': 'string'},
|
|
'value$i': {'type': 'string'},
|
|
},
|
|
'required': ['id', 'name'],
|
|
};
|
|
}
|
|
|
|
final largeDoc = {
|
|
'openapi': '3.0.3',
|
|
'info': {
|
|
'title': 'Large API',
|
|
'version': '1.0.0',
|
|
},
|
|
'paths': paths,
|
|
'components': {
|
|
'schemas': schemas,
|
|
},
|
|
};
|
|
|
|
final jsonString = jsonEncode(largeDoc);
|
|
print(
|
|
'Large document size: ${(jsonString.length / 1024).toStringAsFixed(2)}KB');
|
|
|
|
// 测试解析性能
|
|
final parser = PerformanceParser(
|
|
config: const ParseConfig(
|
|
enablePerformanceStats: true,
|
|
enableParallelParsing: false, // 禁用并行解析
|
|
maxConcurrency: 4,
|
|
),
|
|
);
|
|
|
|
final parseStopwatch = Stopwatch()..start();
|
|
final document = await parser.parseDocument(jsonString);
|
|
parseStopwatch.stop();
|
|
|
|
expect(document.paths.length, greaterThan(100));
|
|
expect(document.models.length, greaterThan(100));
|
|
expect(parseStopwatch.elapsedMilliseconds, lessThan(10000)); // 10秒内完成
|
|
|
|
// 测试生成性能
|
|
final generator = OptimizedRetrofitGenerator(
|
|
generateModularApis: true,
|
|
);
|
|
|
|
final genStopwatch = Stopwatch()..start();
|
|
final generatedCode = generator.generateFromDocument(document);
|
|
genStopwatch.stop();
|
|
|
|
expect(generatedCode.length, greaterThan(10000)); // 至少10KB代码
|
|
expect(genStopwatch.elapsedMilliseconds, lessThan(15000)); // 15秒内完成
|
|
|
|
print('Large document performance:');
|
|
print(' Parse Time: ${parseStopwatch.elapsedMilliseconds}ms');
|
|
print(' Generation Time: ${genStopwatch.elapsedMilliseconds}ms');
|
|
print(' Paths: ${document.paths.length}');
|
|
print(' Models: ${document.models.length}');
|
|
print(
|
|
' Generated Code: ${(generatedCode.length / 1024).toStringAsFixed(2)}KB');
|
|
});
|
|
});
|
|
});
|
|
}
|