feat: Initial commit of PDF Tools project

This commit is contained in:
2025-08-25 02:29:48 +08:00
parent af6827cd9e
commit 30180e50a2
48 changed files with 36364 additions and 1 deletions

308
server/tests/api.test.js Normal file
View File

@@ -0,0 +1,308 @@
const request = require('supertest');
const app = require('../index');
describe('PDF转换工具 API 测试', () => {
describe('系统健康检查', () => {
test('GET /health 应该返回系统健康状态', async () => {
const response = await request(app)
.get('/health')
.expect(200);
expect(response.body).toHaveProperty('status', 'OK');
expect(response.body).toHaveProperty('timestamp');
expect(response.body).toHaveProperty('uptime');
expect(response.body).toHaveProperty('memory');
});
});
describe('系统信息 API', () => {
test('GET /api/system/health 应该返回详细健康信息', async () => {
const response = await request(app)
.get('/api/system/health')
.expect(200);
expect(response.body).toHaveProperty('status');
expect(response.body).toHaveProperty('timestamp');
expect(response.body).toHaveProperty('system');
});
test('GET /api/system/stats 应该返回系统统计信息', async () => {
const response = await request(app)
.get('/api/system/stats')
.expect(200);
expect(response.body.success).toBe(true);
expect(response.body.data).toHaveProperty('files');
expect(response.body.data).toHaveProperty('conversions');
expect(response.body.data).toHaveProperty('users');
});
test('GET /api/system/formats 应该返回支持的格式信息', async () => {
const response = await request(app)
.get('/api/system/formats')
.expect(200);
expect(response.body.success).toBe(true);
expect(response.body.data).toHaveProperty('input');
expect(response.body.data).toHaveProperty('output');
expect(Array.isArray(response.body.data.input)).toBe(true);
expect(Array.isArray(response.body.data.output)).toBe(true);
});
test('GET /api/system/config 应该返回系统配置信息', async () => {
const response = await request(app)
.get('/api/system/config')
.expect(200);
expect(response.body.success).toBe(true);
expect(response.body.data).toHaveProperty('upload');
expect(response.body.data).toHaveProperty('conversion');
expect(response.body.data).toHaveProperty('security');
});
});
describe('用户认证 API', () => {
const testUser = {
email: 'test@example.com',
username: 'testuser',
password: 'password123'
};
test('POST /api/users/register 应该成功注册新用户', async () => {
const response = await request(app)
.post('/api/users/register')
.send(testUser)
.expect(201);
expect(response.body.success).toBe(true);
expect(response.body.data).toHaveProperty('user');
expect(response.body.data).toHaveProperty('token');
expect(response.body.data.user.email).toBe(testUser.email);
});
test('POST /api/users/register 应该拒绝重复邮箱', async () => {
// 先注册一次
await request(app)
.post('/api/users/register')
.send(testUser);
// 再次注册相同邮箱
const response = await request(app)
.post('/api/users/register')
.send(testUser)
.expect(400);
expect(response.body.success).toBe(false);
expect(response.body.message).toContain('邮箱已被注册');
});
test('POST /api/users/login 应该成功登录', async () => {
// 先注册用户
await request(app)
.post('/api/users/register')
.send(testUser);
// 登录
const response = await request(app)
.post('/api/users/login')
.send({
email: testUser.email,
password: testUser.password
})
.expect(200);
expect(response.body.success).toBe(true);
expect(response.body.data).toHaveProperty('user');
expect(response.body.data).toHaveProperty('token');
});
test('POST /api/users/login 应该拒绝错误密码', async () => {
// 先注册用户
await request(app)
.post('/api/users/register')
.send(testUser);
// 使用错误密码登录
const response = await request(app)
.post('/api/users/login')
.send({
email: testUser.email,
password: 'wrongpassword'
})
.expect(401);
expect(response.body.success).toBe(false);
expect(response.body.message).toContain('邮箱或密码错误');
});
});
describe('转换任务 API', () => {
test('POST /api/convert/start 应该创建转换任务', async () => {
const conversionData = {
fileId: 'test-file-id',
outputFormat: 'docx',
options: {
preserveLayout: true,
includeImages: true
}
};
const response = await request(app)
.post('/api/convert/start')
.send(conversionData)
.expect(200);
expect(response.body.success).toBe(true);
expect(response.body.data).toHaveProperty('taskId');
expect(response.body.data).toHaveProperty('status', 'pending');
expect(response.body.data).toHaveProperty('progress', 0);
});
test('POST /api/convert/start 应该验证必要参数', async () => {
const response = await request(app)
.post('/api/convert/start')
.send({})
.expect(400);
expect(response.body.success).toBe(false);
expect(response.body.message).toContain('缺少必要参数');
});
test('POST /api/convert/start 应该验证输出格式', async () => {
const response = await request(app)
.post('/api/convert/start')
.send({
fileId: 'test-file-id',
outputFormat: 'invalid-format'
})
.expect(400);
expect(response.body.success).toBe(false);
expect(response.body.message).toContain('不支持的输出格式');
});
test('GET /api/convert/status/:taskId 应该返回任务状态', async () => {
// 先创建任务
const createResponse = await request(app)
.post('/api/convert/start')
.send({
fileId: 'test-file-id',
outputFormat: 'docx'
});
const taskId = createResponse.body.data.taskId;
// 查询状态
const response = await request(app)
.get(`/api/convert/status/${taskId}`)
.expect(200);
expect(response.body.success).toBe(true);
expect(response.body.data).toHaveProperty('taskId', taskId);
expect(response.body.data).toHaveProperty('status');
expect(response.body.data).toHaveProperty('progress');
});
test('GET /api/convert/status/:taskId 应该处理不存在的任务', async () => {
const response = await request(app)
.get('/api/convert/status/non-existent-task')
.expect(404);
expect(response.body.success).toBe(false);
expect(response.body.message).toContain('转换任务未找到');
});
});
describe('文件管理 API', () => {
test('GET /api/files/:fileId 应该返回文件信息', async () => {
const response = await request(app)
.get('/api/files/test-file-id')
.expect(200);
expect(response.body.success).toBe(true);
expect(response.body.data).toHaveProperty('fileId');
expect(response.body.data).toHaveProperty('originalName');
expect(response.body.data).toHaveProperty('fileSize');
});
test('DELETE /api/files/:fileId 应该删除文件', async () => {
const response = await request(app)
.delete('/api/files/test-file-id')
.expect(200);
expect(response.body.success).toBe(true);
expect(response.body.message).toContain('文件删除成功');
});
});
describe('错误处理', () => {
test('不存在的路由应该返回404', async () => {
const response = await request(app)
.get('/api/non-existent-route')
.expect(404);
expect(response.body.success).toBe(false);
expect(response.body.message).toContain('接口不存在');
});
test('无效的JSON应该返回400', async () => {
const response = await request(app)
.post('/api/convert/start')
.send('invalid json')
.set('Content-Type', 'application/json')
.expect(400);
});
});
describe('请求限流', () => {
test('短时间内大量请求应该被限制', async () => {
const requests = [];
// 发送多个并发请求
for (let i = 0; i < 20; i++) {
requests.push(
request(app)
.get('/api/system/health')
);
}
const responses = await Promise.all(requests);
// 应该有一些请求被限制
const rateLimitedResponses = responses.filter(res => res.status === 429);
expect(rateLimitedResponses.length).toBeGreaterThan(0);
}, 10000);
});
});
// 测试工具函数
describe('工具函数测试', () => {
test('应该正确格式化文件大小', () => {
const formatBytes = require('../utils/formatBytes');
expect(formatBytes(0)).toBe('0 Bytes');
expect(formatBytes(1024)).toBe('1.0 KB');
expect(formatBytes(1048576)).toBe('1.0 MB');
expect(formatBytes(1073741824)).toBe('1.0 GB');
});
test('应该正确验证邮箱格式', () => {
const validateEmail = require('../utils/validateEmail');
expect(validateEmail('test@example.com')).toBe(true);
expect(validateEmail('invalid-email')).toBe(false);
expect(validateEmail('test@')).toBe(false);
expect(validateEmail('@example.com')).toBe(false);
});
test('应该正确生成UUID', () => {
const { v4: uuidv4 } = require('uuid');
const uuid1 = uuidv4();
const uuid2 = uuidv4();
expect(uuid1).not.toBe(uuid2);
expect(uuid1).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i);
});
});