@qq33357486/oh-my-task 1.4.4 → 1.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/package.json +2 -2
  2. package/dist/__tests__/auth-admin.test.d.ts +0 -2
  3. package/dist/__tests__/auth-admin.test.d.ts.map +0 -1
  4. package/dist/__tests__/auth-admin.test.js +0 -440
  5. package/dist/__tests__/auth-admin.test.js.map +0 -1
  6. package/dist/__tests__/auth-login-logout.test.d.ts +0 -2
  7. package/dist/__tests__/auth-login-logout.test.d.ts.map +0 -1
  8. package/dist/__tests__/auth-login-logout.test.js +0 -400
  9. package/dist/__tests__/auth-login-logout.test.js.map +0 -1
  10. package/dist/__tests__/auth-password.test.d.ts +0 -2
  11. package/dist/__tests__/auth-password.test.d.ts.map +0 -1
  12. package/dist/__tests__/auth-password.test.js +0 -419
  13. package/dist/__tests__/auth-password.test.js.map +0 -1
  14. package/dist/__tests__/auth-register.test.d.ts +0 -2
  15. package/dist/__tests__/auth-register.test.d.ts.map +0 -1
  16. package/dist/__tests__/auth-register.test.js +0 -342
  17. package/dist/__tests__/auth-register.test.js.map +0 -1
  18. package/dist/__tests__/auth-tokens.test.d.ts +0 -2
  19. package/dist/__tests__/auth-tokens.test.d.ts.map +0 -1
  20. package/dist/__tests__/auth-tokens.test.js +0 -392
  21. package/dist/__tests__/auth-tokens.test.js.map +0 -1
  22. package/dist/__tests__/db-schema.test.d.ts +0 -2
  23. package/dist/__tests__/db-schema.test.d.ts.map +0 -1
  24. package/dist/__tests__/db-schema.test.js +0 -245
  25. package/dist/__tests__/db-schema.test.js.map +0 -1
  26. package/dist/__tests__/express-server.test.d.ts +0 -2
  27. package/dist/__tests__/express-server.test.d.ts.map +0 -1
  28. package/dist/__tests__/express-server.test.js +0 -119
  29. package/dist/__tests__/express-server.test.js.map +0 -1
  30. package/dist/__tests__/fix-hcaptcha-dev-bypass.test.d.ts +0 -2
  31. package/dist/__tests__/fix-hcaptcha-dev-bypass.test.d.ts.map +0 -1
  32. package/dist/__tests__/fix-hcaptcha-dev-bypass.test.js +0 -85
  33. package/dist/__tests__/fix-hcaptcha-dev-bypass.test.js.map +0 -1
  34. package/dist/__tests__/mcp/mcp-tools.test.d.ts +0 -2
  35. package/dist/__tests__/mcp/mcp-tools.test.d.ts.map +0 -1
  36. package/dist/__tests__/mcp/mcp-tools.test.js +0 -694
  37. package/dist/__tests__/mcp/mcp-tools.test.js.map +0 -1
  38. package/dist/__tests__/projects.test.d.ts +0 -2
  39. package/dist/__tests__/projects.test.d.ts.map +0 -1
  40. package/dist/__tests__/projects.test.js +0 -406
  41. package/dist/__tests__/projects.test.js.map +0 -1
  42. package/dist/__tests__/schedule.test.d.ts +0 -2
  43. package/dist/__tests__/schedule.test.d.ts.map +0 -1
  44. package/dist/__tests__/schedule.test.js +0 -587
  45. package/dist/__tests__/schedule.test.js.map +0 -1
  46. package/dist/__tests__/tasks-crud.test.d.ts +0 -2
  47. package/dist/__tests__/tasks-crud.test.d.ts.map +0 -1
  48. package/dist/__tests__/tasks-crud.test.js +0 -617
  49. package/dist/__tests__/tasks-crud.test.js.map +0 -1
  50. package/dist/__tests__/tasks-lifecycle.test.d.ts +0 -2
  51. package/dist/__tests__/tasks-lifecycle.test.d.ts.map +0 -1
  52. package/dist/__tests__/tasks-lifecycle.test.js +0 -712
  53. package/dist/__tests__/tasks-lifecycle.test.js.map +0 -1
  54. package/dist/__tests__/versions.test.d.ts +0 -2
  55. package/dist/__tests__/versions.test.d.ts.map +0 -1
  56. package/dist/__tests__/versions.test.js +0 -641
  57. package/dist/__tests__/versions.test.js.map +0 -1
@@ -1,641 +0,0 @@
1
- import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest';
2
- import request from 'supertest';
3
- import Database from 'better-sqlite3';
4
- import { mkdirSync, rmSync, existsSync, readFileSync } from 'fs';
5
- import { join } from 'path';
6
- import { tmpdir } from 'os';
7
- // 每个测试用唯一的临时目录,避免并行测试冲突
8
- let TEST_DIR;
9
- let TEST_DB_PATH;
10
- let app;
11
- // 测试用户 cookie
12
- let user1Cookie;
13
- let user2Cookie;
14
- let user1Id;
15
- let user2Id;
16
- const DEFAULT_DUE_DATE = '2026-05-30';
17
- beforeAll(async () => {
18
- TEST_DIR = join(tmpdir(), `omt-versions-test-${Date.now()}`);
19
- TEST_DB_PATH = join(TEST_DIR, 'data', 'data.db');
20
- mkdirSync(join(TEST_DIR, 'data'), { recursive: true });
21
- process.env.DB_PATH = TEST_DB_PATH;
22
- // 初始化数据库
23
- const db = new Database(TEST_DB_PATH);
24
- db.pragma('journal_mode = WAL');
25
- db.pragma('foreign_keys = ON');
26
- const schemaSql = readFileSync(join(process.cwd(), 'src', 'db', 'schema.sql'), 'utf-8');
27
- db.exec(schemaSql);
28
- db.close();
29
- // 动态导入 app(需要在数据库初始化后)
30
- const serverModule = await import('../api/server.js');
31
- app = serverModule.default;
32
- });
33
- afterAll(() => {
34
- try {
35
- if (existsSync(TEST_DIR)) {
36
- rmSync(TEST_DIR, { recursive: true, force: true });
37
- }
38
- }
39
- catch {
40
- // Windows 可能因文件锁定无法立即删除
41
- }
42
- delete process.env.DB_PATH;
43
- });
44
- beforeEach(async () => {
45
- // 注册用户1(admin)
46
- const res1 = await request(app)
47
- .post('/api/auth/register')
48
- .send({
49
- name: 'User1',
50
- email: `user1-${Date.now()}@test.com`,
51
- password: 'UserPass123'
52
- });
53
- user1Cookie = res1.headers['set-cookie']?.[0] || '';
54
- user1Id = res1.body.data?.user?.id || '';
55
- // 注册用户2(member)
56
- const res2 = await request(app)
57
- .post('/api/auth/register')
58
- .send({
59
- name: 'User2',
60
- email: `user2-${Date.now()}@test.com`,
61
- password: 'UserPass123'
62
- });
63
- user2Cookie = res2.headers['set-cookie']?.[0] || '';
64
- user2Id = res2.body.data?.user?.id || '';
65
- });
66
- // 辅助函数:创建项目
67
- async function createProject(cookie, name) {
68
- const res = await request(app)
69
- .post('/api/projects')
70
- .set('Cookie', cookie)
71
- .send({ name });
72
- return res.body.data;
73
- }
74
- // 辅助函数:创建版本
75
- async function createVersion(cookie, projectId, name) {
76
- const res = await request(app)
77
- .post('/api/versions')
78
- .set('Cookie', cookie)
79
- .send({ project_id: projectId, name, due_date: DEFAULT_DUE_DATE });
80
- return res.body.data;
81
- }
82
- // 辅助函数:创建任务
83
- async function createTask(cookie, projectId, title, versionId) {
84
- const body = { project_id: projectId, title };
85
- if (versionId)
86
- body.version_id = versionId;
87
- const res = await request(app)
88
- .post('/api/tasks')
89
- .set('Cookie', cookie)
90
- .send(body);
91
- return res.body.data;
92
- }
93
- describe('POST /api/versions', () => {
94
- it('VAL-CORE-010: 创建版本成功,返回 { id, name, project_id }', async () => {
95
- const project = await createProject(user1Cookie, '测试项目');
96
- const res = await request(app)
97
- .post('/api/versions')
98
- .set('Cookie', user1Cookie)
99
- .send({ project_id: project.id, name: 'v1.0', due_date: DEFAULT_DUE_DATE });
100
- expect(res.status).toBe(201);
101
- expect(res.body.success).toBe(true);
102
- expect(res.body.data).toHaveProperty('id');
103
- expect(res.body.data.name).toBe('v1.0');
104
- expect(res.body.data.project_id).toBe(project.id);
105
- expect(res.body.data.locked_at).toBeNull();
106
- expect(res.body.data.archived_at).toBeNull();
107
- });
108
- it('名称为空时返回 400', async () => {
109
- const project = await createProject(user1Cookie, '测试项目');
110
- const res = await request(app)
111
- .post('/api/versions')
112
- .set('Cookie', user1Cookie)
113
- .send({ project_id: project.id, name: '' });
114
- expect(res.status).toBe(400);
115
- expect(res.body.success).toBe(false);
116
- });
117
- it('缺少名称时返回 400', async () => {
118
- const project = await createProject(user1Cookie, '测试项目');
119
- const res = await request(app)
120
- .post('/api/versions')
121
- .set('Cookie', user1Cookie)
122
- .send({ project_id: project.id });
123
- expect(res.status).toBe(400);
124
- expect(res.body.success).toBe(false);
125
- });
126
- it('缺少 project_id 时返回 400', async () => {
127
- const res = await request(app)
128
- .post('/api/versions')
129
- .set('Cookie', user1Cookie)
130
- .send({ name: 'v1.0', due_date: DEFAULT_DUE_DATE });
131
- expect(res.status).toBe(400);
132
- expect(res.body.success).toBe(false);
133
- });
134
- it('项目不存在时返回 404', async () => {
135
- const res = await request(app)
136
- .post('/api/versions')
137
- .set('Cookie', user1Cookie)
138
- .send({ project_id: 'nonexistent', name: 'v1.0', due_date: DEFAULT_DUE_DATE });
139
- expect(res.status).toBe(404);
140
- expect(res.body.success).toBe(false);
141
- });
142
- it('他人项目返回 404', async () => {
143
- const project = await createProject(user1Cookie, '私有项目');
144
- const res = await request(app)
145
- .post('/api/versions')
146
- .set('Cookie', user2Cookie)
147
- .send({ project_id: project.id, name: 'v1.0', due_date: DEFAULT_DUE_DATE });
148
- expect(res.status).toBe(404);
149
- expect(res.body.success).toBe(false);
150
- });
151
- it('创建版本时可附带描述', async () => {
152
- const project = await createProject(user1Cookie, '测试项目');
153
- const res = await request(app)
154
- .post('/api/versions')
155
- .set('Cookie', user1Cookie)
156
- .send({ project_id: project.id, name: 'v1.0', description: '第一个版本', due_date: DEFAULT_DUE_DATE });
157
- expect(res.status).toBe(201);
158
- expect(res.body.data.description).toBe('第一个版本');
159
- });
160
- it('未认证时返回 401', async () => {
161
- const res = await request(app)
162
- .post('/api/versions')
163
- .send({ project_id: 'xxx', name: 'v1.0' });
164
- expect(res.status).toBe(401);
165
- });
166
- });
167
- describe('GET /api/versions', () => {
168
- it('VAL-CORE-011: 返回项目的未归档版本列表', async () => {
169
- const project = await createProject(user1Cookie, '测试项目');
170
- const v1 = await createVersion(user1Cookie, project.id, 'v1.0');
171
- await request(app)
172
- .post(`/api/versions/${v1.id}/start`)
173
- .set('Cookie', user1Cookie);
174
- const task = await createTask(user1Cookie, project.id, 'v1任务', v1.id);
175
- await request(app)
176
- .post(`/api/tasks/${task.id}/complete`)
177
- .set('Cookie', user1Cookie);
178
- await request(app)
179
- .post(`/api/versions/${v1.id}/complete`)
180
- .set('Cookie', user1Cookie);
181
- await createVersion(user1Cookie, project.id, 'v2.0');
182
- const res = await request(app)
183
- .get(`/api/versions?project_id=${project.id}`)
184
- .set('Cookie', user1Cookie);
185
- expect(res.status).toBe(200);
186
- expect(res.body.success).toBe(true);
187
- expect(Array.isArray(res.body.data)).toBe(true);
188
- expect(res.body.data.length).toBe(2);
189
- });
190
- it('归档版本不出现在列表中', async () => {
191
- const project = await createProject(user1Cookie, '测试项目');
192
- const v1 = await createVersion(user1Cookie, project.id, 'v1.0');
193
- await request(app)
194
- .post(`/api/versions/${v1.id}/start`)
195
- .set('Cookie', user1Cookie);
196
- const task = await createTask(user1Cookie, project.id, 'v1任务', v1.id);
197
- await request(app)
198
- .post(`/api/tasks/${task.id}/complete`)
199
- .set('Cookie', user1Cookie);
200
- await request(app)
201
- .post(`/api/versions/${v1.id}/complete`)
202
- .set('Cookie', user1Cookie);
203
- await createVersion(user1Cookie, project.id, 'v2.0');
204
- // 归档 v1
205
- await request(app)
206
- .post(`/api/versions/${v1.id}/archive`)
207
- .set('Cookie', user1Cookie);
208
- const res = await request(app)
209
- .get(`/api/versions?project_id=${project.id}`)
210
- .set('Cookie', user1Cookie);
211
- expect(res.status).toBe(200);
212
- expect(res.body.data.length).toBe(1);
213
- expect(res.body.data[0].name).toBe('v2.0');
214
- });
215
- it('缺少 project_id 时返回 400', async () => {
216
- const res = await request(app)
217
- .get('/api/versions')
218
- .set('Cookie', user1Cookie);
219
- expect(res.status).toBe(400);
220
- expect(res.body.success).toBe(false);
221
- });
222
- it('他人项目的版本返回 404', async () => {
223
- const project = await createProject(user1Cookie, '私有项目');
224
- await createVersion(user1Cookie, project.id, 'v1.0');
225
- const res = await request(app)
226
- .get(`/api/versions?project_id=${project.id}`)
227
- .set('Cookie', user2Cookie);
228
- expect(res.status).toBe(404);
229
- });
230
- });
231
- describe('PUT /api/versions/:id', () => {
232
- it('VAL-CORE-012: 更新版本名称成功', async () => {
233
- const project = await createProject(user1Cookie, '测试项目');
234
- const version = await createVersion(user1Cookie, project.id, 'v1.0');
235
- const res = await request(app)
236
- .put(`/api/versions/${version.id}`)
237
- .set('Cookie', user1Cookie)
238
- .send({ name: 'v1.1' });
239
- expect(res.status).toBe(200);
240
- expect(res.body.success).toBe(true);
241
- expect(res.body.data.name).toBe('v1.1');
242
- // 通过 GET 确认
243
- const getRes = await request(app)
244
- .get(`/api/versions/${version.id}`)
245
- .set('Cookie', user1Cookie);
246
- expect(getRes.body.data.name).toBe('v1.1');
247
- });
248
- it('更新版本描述成功', async () => {
249
- const project = await createProject(user1Cookie, '测试项目');
250
- const version = await createVersion(user1Cookie, project.id, 'v1.0');
251
- const res = await request(app)
252
- .put(`/api/versions/${version.id}`)
253
- .set('Cookie', user1Cookie)
254
- .send({ description: '新描述' });
255
- expect(res.status).toBe(200);
256
- expect(res.body.data.description).toBe('新描述');
257
- });
258
- it('更新他人版本返回 404', async () => {
259
- const project = await createProject(user1Cookie, '私有项目');
260
- const version = await createVersion(user1Cookie, project.id, 'v1.0');
261
- const res = await request(app)
262
- .put(`/api/versions/${version.id}`)
263
- .set('Cookie', user2Cookie)
264
- .send({ name: '试图修改' });
265
- expect(res.status).toBe(404);
266
- });
267
- it('更新不存在的版本返回 404', async () => {
268
- const res = await request(app)
269
- .put('/api/versions/nonexistent')
270
- .set('Cookie', user1Cookie)
271
- .send({ name: '不存在' });
272
- expect(res.status).toBe(404);
273
- });
274
- });
275
- describe('POST /api/versions/:id/start', () => {
276
- it('VAL-CORE-013: 开始版本成功,设置 locked_at', async () => {
277
- const project = await createProject(user1Cookie, '测试项目');
278
- const version = await createVersion(user1Cookie, project.id, 'v1.0');
279
- const res = await request(app)
280
- .post(`/api/versions/${version.id}/start`)
281
- .set('Cookie', user1Cookie);
282
- expect(res.status).toBe(200);
283
- expect(res.body.success).toBe(true);
284
- expect(res.body.data.locked_at).not.toBeNull();
285
- });
286
- it('VAL-CORE-014: 未结束版本时不能创建下一个版本', async () => {
287
- const project = await createProject(user1Cookie, '测试项目');
288
- await createVersion(user1Cookie, project.id, 'v1.0');
289
- const res = await request(app)
290
- .post('/api/versions')
291
- .set('Cookie', user1Cookie)
292
- .send({ project_id: project.id, name: 'v2.0', due_date: DEFAULT_DUE_DATE });
293
- expect(res.status).toBe(409);
294
- expect(res.body.success).toBe(false);
295
- expect(res.body.error).toContain('未结束版本');
296
- });
297
- it('已锁定的版本再次 start 返回幂等成功', async () => {
298
- const project = await createProject(user1Cookie, '测试项目');
299
- const version = await createVersion(user1Cookie, project.id, 'v1.0');
300
- // 第一次 start
301
- const res1 = await request(app)
302
- .post(`/api/versions/${version.id}/start`)
303
- .set('Cookie', user1Cookie);
304
- expect(res1.status).toBe(200);
305
- // 第二次 start(幂等)
306
- const res2 = await request(app)
307
- .post(`/api/versions/${version.id}/start`)
308
- .set('Cookie', user1Cookie);
309
- expect(res2.status).toBe(200);
310
- });
311
- it('已完成并归档的版本后可创建并启动新版本', async () => {
312
- const project = await createProject(user1Cookie, '测试项目');
313
- const v1 = await createVersion(user1Cookie, project.id, 'v1.0');
314
- // 启动 v1
315
- await request(app)
316
- .post(`/api/versions/${v1.id}/start`)
317
- .set('Cookie', user1Cookie);
318
- // 完成 v1 前先创建任务并完成
319
- const task = await createTask(user1Cookie, project.id, '任务1', v1.id);
320
- await request(app)
321
- .post(`/api/tasks/${task.id}/complete`)
322
- .set('Cookie', user1Cookie);
323
- // 完成 v1
324
- await request(app)
325
- .post(`/api/versions/${v1.id}/complete`)
326
- .set('Cookie', user1Cookie);
327
- // 归档 v1
328
- await request(app)
329
- .post(`/api/versions/${v1.id}/archive`)
330
- .set('Cookie', user1Cookie);
331
- // 现在可以创建并启动 v2
332
- const v2 = await createVersion(user1Cookie, project.id, 'v2.0');
333
- const res = await request(app)
334
- .post(`/api/versions/${v2.id}/start`)
335
- .set('Cookie', user1Cookie);
336
- expect(res.status).toBe(200);
337
- expect(res.body.success).toBe(true);
338
- });
339
- it('他人版本返回 404', async () => {
340
- const project = await createProject(user1Cookie, '私有项目');
341
- const version = await createVersion(user1Cookie, project.id, 'v1.0');
342
- const res = await request(app)
343
- .post(`/api/versions/${version.id}/start`)
344
- .set('Cookie', user2Cookie);
345
- expect(res.status).toBe(404);
346
- });
347
- it('不存在的版本返回 404', async () => {
348
- const res = await request(app)
349
- .post('/api/versions/nonexistent/start')
350
- .set('Cookie', user1Cookie);
351
- expect(res.status).toBe(404);
352
- });
353
- });
354
- describe('POST /api/versions/:id/complete', () => {
355
- it('VAL-CORE-015: 所有任务完成时,版本完成成功', async () => {
356
- const project = await createProject(user1Cookie, '测试项目');
357
- const version = await createVersion(user1Cookie, project.id, 'v1.0');
358
- // 启动版本
359
- await request(app)
360
- .post(`/api/versions/${version.id}/start`)
361
- .set('Cookie', user1Cookie);
362
- // 创建任务
363
- const task = await createTask(user1Cookie, project.id, '任务1', version.id);
364
- // 完成任务
365
- await request(app)
366
- .post(`/api/tasks/${task.id}/complete`)
367
- .set('Cookie', user1Cookie);
368
- // 完成版本
369
- const res = await request(app)
370
- .post(`/api/versions/${version.id}/complete`)
371
- .set('Cookie', user1Cookie);
372
- expect(res.status).toBe(200);
373
- expect(res.body.success).toBe(true);
374
- expect(res.body.data.completed_at).not.toBeNull();
375
- });
376
- it('VAL-CORE-016: 存在未完成任务时返回 400', async () => {
377
- const project = await createProject(user1Cookie, '测试项目');
378
- const version = await createVersion(user1Cookie, project.id, 'v1.0');
379
- // 启动版本
380
- await request(app)
381
- .post(`/api/versions/${version.id}/start`)
382
- .set('Cookie', user1Cookie);
383
- // 创建任务但不完成
384
- await createTask(user1Cookie, project.id, '未完成任务', version.id);
385
- // 尝试完成版本
386
- const res = await request(app)
387
- .post(`/api/versions/${version.id}/complete`)
388
- .set('Cookie', user1Cookie);
389
- expect(res.status).toBe(400);
390
- expect(res.body.success).toBe(false);
391
- expect(res.body.error).toContain('未完成');
392
- });
393
- it('VAL-CORE-045: 空版本(无任务)完成返回 400', async () => {
394
- const project = await createProject(user1Cookie, '测试项目');
395
- const version = await createVersion(user1Cookie, project.id, 'v1.0');
396
- // 启动版本
397
- await request(app)
398
- .post(`/api/versions/${version.id}/start`)
399
- .set('Cookie', user1Cookie);
400
- // 不创建任何任务,直接尝试完成版本
401
- const res = await request(app)
402
- .post(`/api/versions/${version.id}/complete`)
403
- .set('Cookie', user1Cookie);
404
- expect(res.status).toBe(400);
405
- expect(res.body.success).toBe(false);
406
- });
407
- it('未启动的版本不能完成', async () => {
408
- const project = await createProject(user1Cookie, '测试项目');
409
- const version = await createVersion(user1Cookie, project.id, 'v1.0');
410
- const res = await request(app)
411
- .post(`/api/versions/${version.id}/complete`)
412
- .set('Cookie', user1Cookie);
413
- // 未启动的版本应该返回 400
414
- expect(res.status).toBe(400);
415
- expect(res.body.success).toBe(false);
416
- });
417
- it('他人版本返回 404', async () => {
418
- const project = await createProject(user1Cookie, '私有项目');
419
- const version = await createVersion(user1Cookie, project.id, 'v1.0');
420
- const res = await request(app)
421
- .post(`/api/versions/${version.id}/complete`)
422
- .set('Cookie', user2Cookie);
423
- expect(res.status).toBe(404);
424
- });
425
- it('不存在的版本返回 404', async () => {
426
- const res = await request(app)
427
- .post('/api/versions/nonexistent/complete')
428
- .set('Cookie', user1Cookie);
429
- expect(res.status).toBe(404);
430
- });
431
- });
432
- describe('POST /api/versions/:id/archive', () => {
433
- it('VAL-CORE-017: 归档版本成功', async () => {
434
- const project = await createProject(user1Cookie, '测试项目');
435
- const version = await createVersion(user1Cookie, project.id, 'v1.0');
436
- const res = await request(app)
437
- .post(`/api/versions/${version.id}/archive`)
438
- .set('Cookie', user1Cookie);
439
- expect(res.status).toBe(200);
440
- expect(res.body.success).toBe(true);
441
- });
442
- it('归档后 list 不包含该版本', async () => {
443
- const project = await createProject(user1Cookie, '测试项目');
444
- const version = await createVersion(user1Cookie, project.id, 'v1.0');
445
- // 归档
446
- await request(app)
447
- .post(`/api/versions/${version.id}/archive`)
448
- .set('Cookie', user1Cookie);
449
- // 列表不应包含
450
- const res = await request(app)
451
- .get(`/api/versions?project_id=${project.id}`)
452
- .set('Cookie', user1Cookie);
453
- expect(res.status).toBe(200);
454
- const ids = res.body.data.map((v) => v.id);
455
- expect(ids).not.toContain(version.id);
456
- });
457
- it('归档活跃版本后可启动新版本 VAL-CORE-046 + VAL-CROSS-010', async () => {
458
- const project = await createProject(user1Cookie, '测试项目');
459
- const v1 = await createVersion(user1Cookie, project.id, 'v1.0');
460
- // 启动 v1
461
- const startRes1 = await request(app)
462
- .post(`/api/versions/${v1.id}/start`)
463
- .set('Cookie', user1Cookie);
464
- expect(startRes1.status).toBe(200);
465
- // 完成 v1 任务并结束版本
466
- const task = await createTask(user1Cookie, project.id, '任务1', v1.id);
467
- await request(app)
468
- .post(`/api/tasks/${task.id}/complete`)
469
- .set('Cookie', user1Cookie);
470
- await request(app)
471
- .post(`/api/versions/${v1.id}/complete`)
472
- .set('Cookie', user1Cookie);
473
- // 归档 v1
474
- const archiveRes = await request(app)
475
- .post(`/api/versions/${v1.id}/archive`)
476
- .set('Cookie', user1Cookie);
477
- expect(archiveRes.status).toBe(200);
478
- // 现在可以创建并启动 v2
479
- const v2 = await createVersion(user1Cookie, project.id, 'v2.0');
480
- const startRes3 = await request(app)
481
- .post(`/api/versions/${v2.id}/start`)
482
- .set('Cookie', user1Cookie);
483
- expect(startRes3.status).toBe(200);
484
- expect(startRes3.body.success).toBe(true);
485
- expect(startRes3.body.data.locked_at).not.toBeNull();
486
- });
487
- it('归档后任务的 version_id 仍然保留(不置空)', async () => {
488
- const project = await createProject(user1Cookie, '测试项目');
489
- const version = await createVersion(user1Cookie, project.id, 'v1.0');
490
- const task = await createTask(user1Cookie, project.id, '任务1', version.id);
491
- // 归档版本
492
- await request(app)
493
- .post(`/api/versions/${version.id}/archive`)
494
- .set('Cookie', user1Cookie);
495
- // 任务的 version_id 应该还在(归档不是删除)
496
- const taskRes = await request(app)
497
- .get(`/api/tasks/${task.id}`)
498
- .set('Cookie', user1Cookie);
499
- expect(taskRes.body.data.version_id).toBe(version.id);
500
- });
501
- it('他人版本返回 404', async () => {
502
- const project = await createProject(user1Cookie, '私有项目');
503
- const version = await createVersion(user1Cookie, project.id, 'v1.0');
504
- const res = await request(app)
505
- .post(`/api/versions/${version.id}/archive`)
506
- .set('Cookie', user2Cookie);
507
- expect(res.status).toBe(404);
508
- });
509
- });
510
- describe('DELETE /api/versions/:id', () => {
511
- it('VAL-CORE-018: 删除版本成功', async () => {
512
- const project = await createProject(user1Cookie, '测试项目');
513
- const version = await createVersion(user1Cookie, project.id, 'v1.0');
514
- const res = await request(app)
515
- .delete(`/api/versions/${version.id}`)
516
- .set('Cookie', user1Cookie);
517
- expect(res.status).toBe(200);
518
- expect(res.body.success).toBe(true);
519
- });
520
- it('VAL-CORE-018: 删除版本后任务 version_id 置空', async () => {
521
- const project = await createProject(user1Cookie, '测试项目');
522
- const version = await createVersion(user1Cookie, project.id, 'v1.0');
523
- const task = await createTask(user1Cookie, project.id, '任务1', version.id);
524
- // 确认任务有关联
525
- expect(task.version_id).toBe(version.id);
526
- // 删除版本
527
- await request(app)
528
- .delete(`/api/versions/${version.id}`)
529
- .set('Cookie', user1Cookie);
530
- // 任务的 version_id 应为 null
531
- const taskRes = await request(app)
532
- .get(`/api/tasks/${task.id}`)
533
- .set('Cookie', user1Cookie);
534
- expect(taskRes.body.data.version_id).toBeNull();
535
- });
536
- it('删除后列表不再包含该版本', async () => {
537
- const project = await createProject(user1Cookie, '测试项目');
538
- const version = await createVersion(user1Cookie, project.id, 'v1.0');
539
- await request(app)
540
- .delete(`/api/versions/${version.id}`)
541
- .set('Cookie', user1Cookie);
542
- const res = await request(app)
543
- .get(`/api/versions?project_id=${project.id}`)
544
- .set('Cookie', user1Cookie);
545
- const ids = res.body.data.map((v) => v.id);
546
- expect(ids).not.toContain(version.id);
547
- });
548
- it('他人版本返回 404', async () => {
549
- const project = await createProject(user1Cookie, '私有项目');
550
- const version = await createVersion(user1Cookie, project.id, 'v1.0');
551
- const res = await request(app)
552
- .delete(`/api/versions/${version.id}`)
553
- .set('Cookie', user2Cookie);
554
- expect(res.status).toBe(404);
555
- });
556
- it('不存在的版本返回 404', async () => {
557
- const res = await request(app)
558
- .delete('/api/versions/nonexistent')
559
- .set('Cookie', user1Cookie);
560
- expect(res.status).toBe(404);
561
- });
562
- });
563
- describe('GET /api/versions/:id/stats', () => {
564
- it('VAL-CORE-019: 返回版本统计信息', async () => {
565
- const project = await createProject(user1Cookie, '测试项目');
566
- const version = await createVersion(user1Cookie, project.id, 'v1.0');
567
- // 创建任务
568
- const task1 = await createTask(user1Cookie, project.id, '任务1', version.id);
569
- const task2 = await createTask(user1Cookie, project.id, '任务2', version.id);
570
- // 完成一个任务
571
- await request(app)
572
- .post(`/api/tasks/${task1.id}/complete`)
573
- .set('Cookie', user1Cookie);
574
- const res = await request(app)
575
- .get(`/api/versions/${version.id}/stats`)
576
- .set('Cookie', user1Cookie);
577
- expect(res.status).toBe(200);
578
- expect(res.body.success).toBe(true);
579
- expect(res.body.data).toHaveProperty('totalTasks');
580
- expect(res.body.data).toHaveProperty('doneTasks');
581
- expect(res.body.data).toHaveProperty('progress');
582
- expect(res.body.data).toHaveProperty('insertedTasks');
583
- expect(res.body.data).toHaveProperty('delayDays');
584
- expect(res.body.data.totalTasks).toBe(2);
585
- expect(res.body.data.doneTasks).toBe(1);
586
- expect(res.body.data.progress).toBe(50);
587
- });
588
- it('空版本的统计 totalTasks=0', async () => {
589
- const project = await createProject(user1Cookie, '测试项目');
590
- const version = await createVersion(user1Cookie, project.id, 'v1.0');
591
- const res = await request(app)
592
- .get(`/api/versions/${version.id}/stats`)
593
- .set('Cookie', user1Cookie);
594
- expect(res.status).toBe(200);
595
- expect(res.body.data.totalTasks).toBe(0);
596
- expect(res.body.data.doneTasks).toBe(0);
597
- expect(res.body.data.progress).toBe(0);
598
- });
599
- it('他人版本返回 404', async () => {
600
- const project = await createProject(user1Cookie, '私有项目');
601
- const version = await createVersion(user1Cookie, project.id, 'v1.0');
602
- const res = await request(app)
603
- .get(`/api/versions/${version.id}/stats`)
604
- .set('Cookie', user2Cookie);
605
- expect(res.status).toBe(404);
606
- });
607
- it('不存在的版本返回 404', async () => {
608
- const res = await request(app)
609
- .get('/api/versions/nonexistent/stats')
610
- .set('Cookie', user1Cookie);
611
- expect(res.status).toBe(404);
612
- });
613
- });
614
- describe('GET /api/versions/:id', () => {
615
- it('获取版本详情成功', async () => {
616
- const project = await createProject(user1Cookie, '测试项目');
617
- const version = await createVersion(user1Cookie, project.id, 'v1.0');
618
- const res = await request(app)
619
- .get(`/api/versions/${version.id}`)
620
- .set('Cookie', user1Cookie);
621
- expect(res.status).toBe(200);
622
- expect(res.body.success).toBe(true);
623
- expect(res.body.data.id).toBe(version.id);
624
- expect(res.body.data.name).toBe('v1.0');
625
- });
626
- it('他人版本返回 404', async () => {
627
- const project = await createProject(user1Cookie, '私有项目');
628
- const version = await createVersion(user1Cookie, project.id, 'v1.0');
629
- const res = await request(app)
630
- .get(`/api/versions/${version.id}`)
631
- .set('Cookie', user2Cookie);
632
- expect(res.status).toBe(404);
633
- });
634
- it('不存在的版本返回 404', async () => {
635
- const res = await request(app)
636
- .get('/api/versions/nonexistent')
637
- .set('Cookie', user1Cookie);
638
- expect(res.status).toBe(404);
639
- });
640
- });
641
- //# sourceMappingURL=versions.test.js.map