test: 完善单元测试覆盖率并优化统一异步架构

- 添加 AppController 测试用例,测试回调接口的错误处理
- 新增控制器、服务层、平台服务等全面的单元测试
- 优化增强模板控制器的错误处理和审核完成事件处理
- 添加数据库迁移脚本支持统一异步架构升级
- 完善 TypeScript 配置,添加 jest 类型支持
- 修复端到端测试,确保 API 响应格式正确性
- 所有测试通过 (109 个测试用例),覆盖率达到要求
This commit is contained in:
imeepos
2025-09-05 17:05:08 +08:00
parent 4cb6b63948
commit e36cdfc38d
20 changed files with 2607 additions and 48 deletions

262
test/README.md Normal file
View File

@@ -0,0 +1,262 @@
# 模板执行API测试文档
## 🎯 测试概述
本测试套件用于验证原有API和新的统一异步架构API的功能和性能。测试基于您提供的具体参数
- **图片URL**: `https://cdn.roasmax.cn/upload/676d85ae7c6347f49700631c84a13051.jpg`
- **模板代码**: `photo_restore_v1`
- **认证令牌**: `Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...`
## 📁 测试文件说明
### 1. `integration-test.js` - 完整集成测试
**用途**: 全面的自动化测试,包含性能对比和错误场景测试
**运行方式**:
```bash
cd test
node integration-test.js
```
**特性**:
- ✅ 自动测试原有API和异步API
- ✅ 性能对比分析
- ✅ 状态轮询验证
- ✅ 错误场景测试
- ✅ 详细的测试报告
### 2. `quick-test.js` - 快速测试
**用途**: 简化版本快速验证API基本功能
**运行方式**:
```bash
cd test
node quick-test.js
```
**特性**:
- ✅ 快速验证API可用性
- ✅ 基础响应检查
- ✅ 简化输出格式
### 3. `postman-collection.json` - Postman测试集合
**用途**: 导入Postman进行可视化测试
**使用方式**:
1. 打开Postman
2. 点击 Import
3. 选择 `postman-collection.json` 文件
4. 运行测试集合
**包含测试**:
- 原有同步API
- 新的异步API
- 状态查询
- 模板列表获取
- 错误场景测试
### 4. `curl-commands.sh` - cURL命令脚本
**用途**: 使用cURL命令行工具进行测试
**运行方式**:
```bash
cd test
chmod +x curl-commands.sh
./curl-commands.sh
```
**特性**:
- ✅ 无需额外工具
- ✅ 适合CI/CD集成
- ✅ 详细的状态查询循环
- ✅ 实时性能指标
## 🚀 测试API对比
### 原有同步API
```
POST /api/v1/templates/code/photo_restore_v1/execute
{
"imageUrl": "https://cdn.roasmax.cn/upload/676d85ae7c6347f49700631c84a13051.jpg"
}
```
**特点**:
- 🔄 同步执行,需要等待完整结果
- ⏱️ 响应时间较长2-5秒
- 🚫 可能遇到审核阻塞问题
### 新的异步API
#### 步骤1: 提交任务
```
POST /enhanced/templates/code/photo_restore_v1/execute
{
"imageUrl": "https://cdn.roasmax.cn/upload/676d85ae7c6347f49700631c84a13051.jpg"
}
```
**响应示例**:
```json
{
"code": 200,
"message": "模板执行已提交,正在进行图片审核",
"data": {
"executionId": 123,
"auditTaskId": "audit_bytedance_1726500000_abc123",
"status": "pending_audit",
"message": "图片审核中,请查询执行进度获取最新状态"
}
}
```
#### 步骤2: 查询状态
```
GET /enhanced/templates/{executionId}/status
```
**响应示例**:
```json
{
"code": 200,
"message": "查询成功",
"data": {
"executionId": 123,
"status": "completed",
"statusDescription": "执行完成",
"auditTaskId": "audit_bytedance_1726500000_abc123",
"templateName": "照片修复模板",
"inputImageUrl": "https://cdn.roasmax.cn/upload/676d85ae7c6347f49700631c84a13051.jpg",
"outputUrl": "https://example.com/result.jpg",
"startedAt": "2024-12-05T10:00:00Z",
"completedAt": "2024-12-05T10:02:30Z"
}
}
```
## 📊 状态流转说明
异步API的状态会按以下顺序变化
```
pending_audit → processing → completed
↓ ↓ ↓
图片审核中 AI生成中 执行完成
或者:
pending_audit → audit_failed
↓ ↓
图片审核中 审核未通过
```
## 🔧 运行前准备
### 1. 确保服务运行
```bash
npm run start:dev
# 或
npm run start:prod
```
### 2. 验证服务状态
```bash
curl http://localhost:3003/api/v1/templates
```
### 3. 检查认证令牌
确保提供的JWT令牌未过期且有效。
## 📈 预期测试结果
### 性能对比
| 指标 | 原有API | 异步API | 改进 |
|------|---------|---------|------|
| 初次响应时间 | 2-5秒 | <200ms | 90%+ |
| 用户体验 | 阻塞等待 | 异步轮询 | 大幅提升 |
| 系统吞吐 | 串行处理 | 并行处理 | 3-5倍 |
### 功能验证
- ✅ API调用成功
- ✅ 响应格式正确
- ✅ 状态流转正常
- ✅ 错误处理恰当
- ✅ 认证验证通过
## 🐛 常见问题排查
### 1. 连接失败
**现象**: `ECONNREFUSED`
**解决**: 确保服务在3003端口启动
### 2. 认证失败
**现象**: `401 Unauthorized`
**解决**: 检查JWT令牌是否正确且未过期
### 3. 图片访问失败
**现象**: `无效的图片URL`
**解决**: 确保图片URL可公网访问
### 4. 超时问题
**现象**: 请求超时
**解决**:
- 检查网络连接
- 适当增加超时时间
- 确认服务器负载正常
## 📝 测试报告模板
测试完成后,请记录以下信息:
```
测试时间: ______
测试环境: ______
服务版本: ______
原有API:
- 响应时间: ____ms
- 成功率: ____%
- 错误信息: ______
异步API:
- 初次响应时间: ____ms
- 完整执行时间: ____ms
- 成功率: ____%
- 状态变化: pending_audit → ____ → ____
性能提升:
- 响应速度提升: ____%
- 用户体验改善: ______
- 推荐使用: [原有API / 异步API]
问题记录:
1. ______
2. ______
建议:
1. ______
2. ______
```
## 🎯 下一步建议
1. **如果异步API测试成功**:
- 建议迁移到新的异步架构
- 更新客户端代码支持状态轮询
- 逐步废弃原有同步API
2. **如果发现问题**:
- 记录详细错误信息
- 检查服务器日志
- 联系开发团队解决
3. **生产环境部署前**:
- 执行完整的集成测试
- 进行压力测试
- 验证监控和告警系统
---
**测试套件版本**: v1.0
**最后更新**: 2024-12-05
**维护者**: AI Assistant

View File

@@ -1,11 +1,10 @@
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { App } from 'supertest/types';
const request = require('supertest');
import { AppModule } from './../src/app.module';
describe('AppController (e2e)', () => {
let app: INestApplication<App>;
let app: INestApplication;
beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
@@ -16,10 +15,18 @@ describe('AppController (e2e)', () => {
await app.init();
});
it('/ (GET)', () => {
it('/callback (POST) - missing task_id should return error in body', () => {
return request(app.getHttpServer())
.get('/')
.expect(200)
.expect('Hello World!');
.post('/callback')
.send({})
.expect(201)
.expect((res) => {
expect(res.body.code).toBe(400);
expect(res.body.message).toBe('缺少task_id');
});
});
afterEach(async () => {
await app.close();
});
});

View File

@@ -0,0 +1,345 @@
{
"info": {
"name": "模板执行API测试集合",
"description": "测试原有API和新的统一异步架构API",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"variable": [
{
"key": "baseUrl",
"value": "http://localhost:3003",
"type": "string"
},
{
"key": "imageUrl",
"value": "https://cdn.roasmax.cn/upload/676d85ae7c6347f49700631c84a13051.jpg",
"type": "string"
},
{
"key": "authorization",
"value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI5OWQ3ZGZmOS02YWEwLTRmMWEtODA3YS01OTQ4Mzg3YTljNjkiLCJ1bmlmaWVkVXNlcklkIjoidXNlcl8xNzU2OTg0ODA1Mjk5X3l0M2hkMHZvayIsInBsYXRmb3JtIjoiYnl0ZWRhbmNlIiwiaWF0IjoxNzU3MDU3MTYwLCJleHAiOjE3NTcxNDM1NjB9.C0RF3wGw-bjzaCCGUeu2KgOGFlIGkqFZe28KgxLgjqU",
"type": "string"
},
{
"key": "executionId",
"value": "",
"type": "string"
}
],
"item": [
{
"name": "1. 原有同步API",
"event": [
{
"listen": "test",
"script": {
"exec": [
"// 检查响应状态",
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
"});",
"",
"// 检查响应时间",
"pm.test(\"Response time is less than 10000ms\", function () {",
" pm.expect(pm.response.responseTime).to.be.below(10000);",
"});",
"",
"// 检查响应结构",
"pm.test(\"Response has correct structure\", function () {",
" var jsonData = pm.response.json();",
" pm.expect(jsonData).to.have.property('code');",
" pm.expect(jsonData).to.have.property('message');",
" pm.expect(jsonData).to.have.property('data');",
"});",
"",
"// 记录响应时间",
"console.log('原有API响应时间: ' + pm.response.responseTime + 'ms');"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
},
{
"key": "Authorization",
"value": "{{authorization}}"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"imageUrl\": \"{{imageUrl}}\"\n}"
},
"url": {
"raw": "{{baseUrl}}/api/v1/templates/code/photo_restore_v1/execute",
"host": ["{{baseUrl}}"],
"path": ["api", "v1", "templates", "code", "photo_restore_v1", "execute"]
},
"description": "测试原有的同步模板执行API"
}
},
{
"name": "2. 新的异步API - 提交任务",
"event": [
{
"listen": "test",
"script": {
"exec": [
"// 检查响应状态",
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
"});",
"",
"// 检查响应时间(应该很快)",
"pm.test(\"Response time is less than 1000ms\", function () {",
" pm.expect(pm.response.responseTime).to.be.below(1000);",
"});",
"",
"// 检查响应结构",
"pm.test(\"Response has correct async structure\", function () {",
" var jsonData = pm.response.json();",
" pm.expect(jsonData).to.have.property('code');",
" pm.expect(jsonData).to.have.property('data');",
" pm.expect(jsonData.data).to.have.property('executionId');",
" pm.expect(jsonData.data).to.have.property('auditTaskId');",
" pm.expect(jsonData.data).to.have.property('status');",
"});",
"",
"// 检查状态",
"pm.test(\"Status should be pending_audit\", function () {",
" var jsonData = pm.response.json();",
" pm.expect(jsonData.data.status).to.equal('pending_audit');",
"});",
"",
"// 保存executionId用于后续查询",
"if (pm.response.code === 200) {",
" var jsonData = pm.response.json();",
" pm.collectionVariables.set('executionId', jsonData.data.executionId);",
" console.log('已保存executionId: ' + jsonData.data.executionId);",
"}",
"",
"// 记录响应时间",
"console.log('异步API响应时间: ' + pm.response.responseTime + 'ms');"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
},
{
"key": "Authorization",
"value": "{{authorization}}"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"imageUrl\": \"{{imageUrl}}\"\n}"
},
"url": {
"raw": "{{baseUrl}}/enhanced/templates/code/photo_restore_v1/execute",
"host": ["{{baseUrl}}"],
"path": ["enhanced", "templates", "code", "photo_restore_v1", "execute"]
},
"description": "测试新的异步模板执行API - 提交任务"
}
},
{
"name": "3. 查询执行状态",
"event": [
{
"listen": "test",
"script": {
"exec": [
"// 检查响应状态",
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
"});",
"",
"// 检查响应结构",
"pm.test(\"Response has correct status structure\", function () {",
" var jsonData = pm.response.json();",
" pm.expect(jsonData).to.have.property('data');",
" pm.expect(jsonData.data).to.have.property('executionId');",
" pm.expect(jsonData.data).to.have.property('status');",
" pm.expect(jsonData.data).to.have.property('statusDescription');",
"});",
"",
"// 显示当前状态",
"if (pm.response.code === 200) {",
" var jsonData = pm.response.json();",
" console.log('当前状态: ' + jsonData.data.status);",
" console.log('状态描述: ' + jsonData.data.statusDescription);",
" ",
" if (jsonData.data.outputUrl) {",
" console.log('输出地址: ' + jsonData.data.outputUrl);",
" }",
"}"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "GET",
"header": [
{
"key": "Authorization",
"value": "{{authorization}}"
}
],
"url": {
"raw": "{{baseUrl}}/enhanced/templates/{{executionId}}/status",
"host": ["{{baseUrl}}"],
"path": ["enhanced", "templates", "{{executionId}}", "status"]
},
"description": "查询异步任务的执行状态。需要先运行步骤2获取executionId。"
}
},
{
"name": "4. 获取模板列表",
"event": [
{
"listen": "test",
"script": {
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
"});",
"",
"pm.test(\"Response is array\", function () {",
" var jsonData = pm.response.json();",
" pm.expect(jsonData.data).to.be.an('array');",
"});",
"",
"// 显示可用模板",
"if (pm.response.code === 200) {",
" var jsonData = pm.response.json();",
" console.log('可用模板数量: ' + jsonData.data.length);",
" jsonData.data.forEach(function(template) {",
" console.log('- ' + template.code + ': ' + template.name);",
" });",
"}"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "GET",
"header": [
{
"key": "Authorization",
"value": "{{authorization}}"
}
],
"url": {
"raw": "{{baseUrl}}/api/v1/templates",
"host": ["{{baseUrl}}"],
"path": ["api", "v1", "templates"]
},
"description": "获取所有可用的模板列表"
}
},
{
"name": "5. 错误测试 - 无效图片URL",
"event": [
{
"listen": "test",
"script": {
"exec": [
"// 应该返回错误状态",
"pm.test(\"Should return error status\", function () {",
" pm.expect(pm.response.code).to.be.oneOf([400, 403, 422]);",
"});",
"",
"// 检查错误信息",
"pm.test(\"Should have error message\", function () {",
" var jsonData = pm.response.json();",
" pm.expect(jsonData).to.have.property('message');",
"});",
"",
"console.log('错误测试通过: 无效图片URL正确返回错误');"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
},
{
"key": "Authorization",
"value": "{{authorization}}"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"imageUrl\": \"https://invalid-domain.com/nonexistent.jpg\"\n}"
},
"url": {
"raw": "{{baseUrl}}/enhanced/templates/code/photo_restore_v1/execute",
"host": ["{{baseUrl}}"],
"path": ["enhanced", "templates", "code", "photo_restore_v1", "execute"]
},
"description": "测试无效图片URL的错误处理"
}
},
{
"name": "6. 错误测试 - 无效模板代码",
"event": [
{
"listen": "test",
"script": {
"exec": [
"// 应该返回404错误",
"pm.test(\"Should return 404 status\", function () {",
" pm.response.to.have.status(404);",
"});",
"",
"console.log('错误测试通过: 无效模板代码正确返回404错误');"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
},
{
"key": "Authorization",
"value": "{{authorization}}"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"imageUrl\": \"{{imageUrl}}\"\n}"
},
"url": {
"raw": "{{baseUrl}}/enhanced/templates/code/nonexistent_template/execute",
"host": ["{{baseUrl}}"],
"path": ["enhanced", "templates", "code", "nonexistent_template", "execute"]
},
"description": "测试无效模板代码的错误处理"
}
}
]
}