578 lines
19 KiB
Dart
578 lines
19 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/pipeline/generate/apis.dart';
|
||
import 'package:swagger_generator_flutter/pipeline/validate/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': {
|
||
r'$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': {
|
||
r'$ref': '#/components/schemas/CreateUserRequest',
|
||
},
|
||
},
|
||
},
|
||
},
|
||
'responses': {
|
||
'201': {
|
||
'description': 'User created',
|
||
'content': {
|
||
'application/json': {
|
||
'schema': {
|
||
r'$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': {
|
||
r'$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': {
|
||
r'$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, greaterThanOrEqualTo(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();
|
||
|
||
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: false, // 不拆分,生成单个文件以便测试
|
||
);
|
||
|
||
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. 性能验证
|
||
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',
|
||
);
|
||
|
||
// 验证性能指标
|
||
expect(
|
||
parseStats.totalTime.inMilliseconds,
|
||
lessThan(2000),
|
||
); // 解析应在2秒内完成
|
||
expect(retrofitCode.length, greaterThan(1000)); // 应生成足够的代码
|
||
});
|
||
|
||
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, // 禁用并行解析
|
||
),
|
||
);
|
||
|
||
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}');
|
||
|
||
// 生成代码(使用 RetrofitApiGenerator)
|
||
final generator = RetrofitApiGenerator(
|
||
className: 'OAMobileApiService',
|
||
);
|
||
|
||
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(1000)); // 至少生成1KB代码
|
||
});
|
||
});
|
||
|
||
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()..validateDocument(document);
|
||
|
||
// The validator should run without throwing
|
||
// Validation results may vary based on implementation
|
||
expect(validator.errorReporter.errors, isNotNull);
|
||
});
|
||
});
|
||
|
||
group('Performance Integration', () {
|
||
test('handles large documents efficiently', () async {
|
||
// 创建大型文档
|
||
final paths = <String, dynamic>{};
|
||
final schemas = <String, dynamic>{};
|
||
|
||
for (var 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': {
|
||
r'$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, // 禁用并行解析
|
||
),
|
||
);
|
||
|
||
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秒内完成
|
||
|
||
// 测试生成性能(使用 RetrofitApiGenerator)
|
||
final generator = RetrofitApiGenerator(splitByTags: false);
|
||
|
||
final genStopwatch = Stopwatch()..start();
|
||
final generatedCode = generator.generateFromDocument(document);
|
||
genStopwatch.stop();
|
||
|
||
expect(generatedCode.length, greaterThan(1000)); // 至少1KB代码
|
||
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',
|
||
);
|
||
});
|
||
});
|
||
});
|
||
}
|
||
// 忽略测试构造数据的类型推断与打印告警,便于保持示例简洁
|
||
// ignore_for_file: inference_failure_on_collection_literal, avoid_print
|