@qq33357486/oh-my-task 1.4.3 → 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.
- package/dist/db/schema.sql +205 -0
- package/package.json +2 -2
- package/dist/__tests__/auth-admin.test.d.ts +0 -2
- package/dist/__tests__/auth-admin.test.d.ts.map +0 -1
- package/dist/__tests__/auth-admin.test.js +0 -440
- package/dist/__tests__/auth-admin.test.js.map +0 -1
- package/dist/__tests__/auth-login-logout.test.d.ts +0 -2
- package/dist/__tests__/auth-login-logout.test.d.ts.map +0 -1
- package/dist/__tests__/auth-login-logout.test.js +0 -400
- package/dist/__tests__/auth-login-logout.test.js.map +0 -1
- package/dist/__tests__/auth-password.test.d.ts +0 -2
- package/dist/__tests__/auth-password.test.d.ts.map +0 -1
- package/dist/__tests__/auth-password.test.js +0 -419
- package/dist/__tests__/auth-password.test.js.map +0 -1
- package/dist/__tests__/auth-register.test.d.ts +0 -2
- package/dist/__tests__/auth-register.test.d.ts.map +0 -1
- package/dist/__tests__/auth-register.test.js +0 -342
- package/dist/__tests__/auth-register.test.js.map +0 -1
- package/dist/__tests__/auth-tokens.test.d.ts +0 -2
- package/dist/__tests__/auth-tokens.test.d.ts.map +0 -1
- package/dist/__tests__/auth-tokens.test.js +0 -392
- package/dist/__tests__/auth-tokens.test.js.map +0 -1
- package/dist/__tests__/db-schema.test.d.ts +0 -2
- package/dist/__tests__/db-schema.test.d.ts.map +0 -1
- package/dist/__tests__/db-schema.test.js +0 -245
- package/dist/__tests__/db-schema.test.js.map +0 -1
- package/dist/__tests__/express-server.test.d.ts +0 -2
- package/dist/__tests__/express-server.test.d.ts.map +0 -1
- package/dist/__tests__/express-server.test.js +0 -119
- package/dist/__tests__/express-server.test.js.map +0 -1
- package/dist/__tests__/fix-hcaptcha-dev-bypass.test.d.ts +0 -2
- package/dist/__tests__/fix-hcaptcha-dev-bypass.test.d.ts.map +0 -1
- package/dist/__tests__/fix-hcaptcha-dev-bypass.test.js +0 -85
- package/dist/__tests__/fix-hcaptcha-dev-bypass.test.js.map +0 -1
- package/dist/__tests__/mcp/mcp-tools.test.d.ts +0 -2
- package/dist/__tests__/mcp/mcp-tools.test.d.ts.map +0 -1
- package/dist/__tests__/mcp/mcp-tools.test.js +0 -694
- package/dist/__tests__/mcp/mcp-tools.test.js.map +0 -1
- package/dist/__tests__/projects.test.d.ts +0 -2
- package/dist/__tests__/projects.test.d.ts.map +0 -1
- package/dist/__tests__/projects.test.js +0 -406
- package/dist/__tests__/projects.test.js.map +0 -1
- package/dist/__tests__/schedule.test.d.ts +0 -2
- package/dist/__tests__/schedule.test.d.ts.map +0 -1
- package/dist/__tests__/schedule.test.js +0 -587
- package/dist/__tests__/schedule.test.js.map +0 -1
- package/dist/__tests__/tasks-crud.test.d.ts +0 -2
- package/dist/__tests__/tasks-crud.test.d.ts.map +0 -1
- package/dist/__tests__/tasks-crud.test.js +0 -617
- package/dist/__tests__/tasks-crud.test.js.map +0 -1
- package/dist/__tests__/tasks-lifecycle.test.d.ts +0 -2
- package/dist/__tests__/tasks-lifecycle.test.d.ts.map +0 -1
- package/dist/__tests__/tasks-lifecycle.test.js +0 -712
- package/dist/__tests__/tasks-lifecycle.test.js.map +0 -1
- package/dist/__tests__/versions.test.d.ts +0 -2
- package/dist/__tests__/versions.test.d.ts.map +0 -1
- package/dist/__tests__/versions.test.js +0 -641
- package/dist/__tests__/versions.test.js.map +0 -1
|
@@ -1,712 +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
|
-
beforeAll(async () => {
|
|
16
|
-
TEST_DIR = join(tmpdir(), `omt-tasks-lifecycle-test-${Date.now()}`);
|
|
17
|
-
TEST_DB_PATH = join(TEST_DIR, 'data', 'data.db');
|
|
18
|
-
mkdirSync(join(TEST_DIR, 'data'), { recursive: true });
|
|
19
|
-
process.env.DB_PATH = TEST_DB_PATH;
|
|
20
|
-
// 初始化数据库
|
|
21
|
-
const db = new Database(TEST_DB_PATH);
|
|
22
|
-
db.pragma('journal_mode = WAL');
|
|
23
|
-
db.pragma('foreign_keys = ON');
|
|
24
|
-
const schemaSql = readFileSync(join(process.cwd(), 'src', 'db', 'schema.sql'), 'utf-8');
|
|
25
|
-
db.exec(schemaSql);
|
|
26
|
-
db.close();
|
|
27
|
-
// 强制重新加载模块
|
|
28
|
-
const modulePaths = Object.keys(require.cache || {}).filter(k => k.includes('oh-my-task'));
|
|
29
|
-
for (const p of modulePaths) {
|
|
30
|
-
delete require.cache[p];
|
|
31
|
-
}
|
|
32
|
-
// 动态导入 app(需要在数据库初始化后)
|
|
33
|
-
const serverModule = await import('../api/server.js');
|
|
34
|
-
app = serverModule.default;
|
|
35
|
-
});
|
|
36
|
-
afterAll(() => {
|
|
37
|
-
try {
|
|
38
|
-
if (existsSync(TEST_DIR)) {
|
|
39
|
-
rmSync(TEST_DIR, { recursive: true, force: true });
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
catch {
|
|
43
|
-
// Windows 可能因文件锁定无法立即删除
|
|
44
|
-
}
|
|
45
|
-
delete process.env.DB_PATH;
|
|
46
|
-
});
|
|
47
|
-
beforeEach(async () => {
|
|
48
|
-
// 注册用户1(admin)
|
|
49
|
-
const res1 = await request(app)
|
|
50
|
-
.post('/api/auth/register')
|
|
51
|
-
.send({
|
|
52
|
-
name: 'User1',
|
|
53
|
-
email: `user1-lifecycle-${Date.now()}@test.com`,
|
|
54
|
-
password: 'UserPass123'
|
|
55
|
-
});
|
|
56
|
-
user1Cookie = res1.headers['set-cookie']?.[0] || '';
|
|
57
|
-
user1Id = res1.body.data?.user?.id || '';
|
|
58
|
-
// 注册用户2(member)
|
|
59
|
-
const res2 = await request(app)
|
|
60
|
-
.post('/api/auth/register')
|
|
61
|
-
.send({
|
|
62
|
-
name: 'User2',
|
|
63
|
-
email: `user2-lifecycle-${Date.now()}@test.com`,
|
|
64
|
-
password: 'UserPass123'
|
|
65
|
-
});
|
|
66
|
-
user2Cookie = res2.headers['set-cookie']?.[0] || '';
|
|
67
|
-
});
|
|
68
|
-
// ==================== 辅助函数 ====================
|
|
69
|
-
async function createProject(cookie, name) {
|
|
70
|
-
const res = await request(app)
|
|
71
|
-
.post('/api/projects')
|
|
72
|
-
.set('Cookie', cookie)
|
|
73
|
-
.send({ name });
|
|
74
|
-
return res.body.data;
|
|
75
|
-
}
|
|
76
|
-
async function createVersion(cookie, projectId, name) {
|
|
77
|
-
const res = await request(app)
|
|
78
|
-
.post('/api/versions')
|
|
79
|
-
.set('Cookie', cookie)
|
|
80
|
-
.send({ project_id: projectId, name, due_date: '2026-05-30' });
|
|
81
|
-
return res.body.data;
|
|
82
|
-
}
|
|
83
|
-
async function createTask(cookie, projectId, title, extra) {
|
|
84
|
-
const body = { project_id: projectId, title };
|
|
85
|
-
if (extra)
|
|
86
|
-
Object.assign(body, extra);
|
|
87
|
-
const res = await request(app)
|
|
88
|
-
.post('/api/tasks')
|
|
89
|
-
.set('Cookie', cookie)
|
|
90
|
-
.send(body);
|
|
91
|
-
return { status: res.status, data: res.body.data, body: res.body };
|
|
92
|
-
}
|
|
93
|
-
async function startVersion(cookie, versionId) {
|
|
94
|
-
const res = await request(app)
|
|
95
|
-
.post(`/api/versions/${versionId}/start`)
|
|
96
|
-
.set('Cookie', cookie);
|
|
97
|
-
return res;
|
|
98
|
-
}
|
|
99
|
-
// ==================== VAL-CORE-026: 激活任务 ====================
|
|
100
|
-
describe('POST /api/tasks/:id/activate — 激活任务', () => {
|
|
101
|
-
it('VAL-CORE-026: 激活任务设置 status=in_progress + actual_start', async () => {
|
|
102
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
103
|
-
const { data: task } = await createTask(user1Cookie, project.id, '待激活任务');
|
|
104
|
-
expect(task.status).toBe('planned');
|
|
105
|
-
expect(task.actual_start).toBeNull();
|
|
106
|
-
const res = await request(app)
|
|
107
|
-
.post(`/api/tasks/${task.id}/activate`)
|
|
108
|
-
.set('Cookie', user1Cookie);
|
|
109
|
-
expect(res.status).toBe(200);
|
|
110
|
-
expect(res.body.success).toBe(true);
|
|
111
|
-
expect(res.body.data.status).toBe('in_progress');
|
|
112
|
-
expect(res.body.data.actual_start).not.toBeNull();
|
|
113
|
-
});
|
|
114
|
-
it('重复激活幂等,不重置 actual_start', async () => {
|
|
115
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
116
|
-
const { data: task } = await createTask(user1Cookie, project.id, '任务');
|
|
117
|
-
// 第一次激活
|
|
118
|
-
const res1 = await request(app)
|
|
119
|
-
.post(`/api/tasks/${task.id}/activate`)
|
|
120
|
-
.set('Cookie', user1Cookie);
|
|
121
|
-
const firstActualStart = res1.body.data.actual_start;
|
|
122
|
-
// 第二次激活
|
|
123
|
-
const res2 = await request(app)
|
|
124
|
-
.post(`/api/tasks/${task.id}/activate`)
|
|
125
|
-
.set('Cookie', user1Cookie);
|
|
126
|
-
expect(res2.status).toBe(200);
|
|
127
|
-
expect(res2.body.data.status).toBe('in_progress');
|
|
128
|
-
expect(res2.body.data.actual_start).toBe(firstActualStart);
|
|
129
|
-
});
|
|
130
|
-
it('激活不存在的任务返回 404', async () => {
|
|
131
|
-
const res = await request(app)
|
|
132
|
-
.post('/api/tasks/nonexistent/activate')
|
|
133
|
-
.set('Cookie', user1Cookie);
|
|
134
|
-
expect(res.status).toBe(404);
|
|
135
|
-
expect(res.body.success).toBe(false);
|
|
136
|
-
});
|
|
137
|
-
it('他人任务激活返回 404', async () => {
|
|
138
|
-
const project = await createProject(user1Cookie, '私有项目');
|
|
139
|
-
const { data: task } = await createTask(user1Cookie, project.id, '私有任务');
|
|
140
|
-
const res = await request(app)
|
|
141
|
-
.post(`/api/tasks/${task.id}/activate`)
|
|
142
|
-
.set('Cookie', user2Cookie);
|
|
143
|
-
expect(res.status).toBe(404);
|
|
144
|
-
});
|
|
145
|
-
it('未认证时返回 401', async () => {
|
|
146
|
-
const res = await request(app)
|
|
147
|
-
.post('/api/tasks/some-id/activate');
|
|
148
|
-
expect(res.status).toBe(401);
|
|
149
|
-
});
|
|
150
|
-
it('激活任务记录状态变更历史', async () => {
|
|
151
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
152
|
-
const { data: task } = await createTask(user1Cookie, project.id, '任务');
|
|
153
|
-
await request(app)
|
|
154
|
-
.post(`/api/tasks/${task.id}/activate`)
|
|
155
|
-
.set('Cookie', user1Cookie);
|
|
156
|
-
const historyRes = await request(app)
|
|
157
|
-
.get(`/api/tasks/${task.id}/history`)
|
|
158
|
-
.set('Cookie', user1Cookie);
|
|
159
|
-
expect(historyRes.status).toBe(200);
|
|
160
|
-
const history = historyRes.body.data;
|
|
161
|
-
// 应有 created + status_changed(planned -> in_progress)
|
|
162
|
-
const statusChanges = history.filter((h) => h.action === 'status_changed');
|
|
163
|
-
expect(statusChanges.length).toBeGreaterThanOrEqual(1);
|
|
164
|
-
const activateRecord = statusChanges.find((h) => h.new_value === 'in_progress' && h.old_value === 'planned');
|
|
165
|
-
expect(activateRecord).toBeDefined();
|
|
166
|
-
});
|
|
167
|
-
});
|
|
168
|
-
// ==================== VAL-CORE-027: 完成任务 ====================
|
|
169
|
-
describe('POST /api/tasks/:id/complete — 完成任务', () => {
|
|
170
|
-
it('VAL-CORE-027: 完成任务设置 status=done', async () => {
|
|
171
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
172
|
-
const { data: task } = await createTask(user1Cookie, project.id, '待完成任务');
|
|
173
|
-
// 先激活
|
|
174
|
-
await request(app)
|
|
175
|
-
.post(`/api/tasks/${task.id}/activate`)
|
|
176
|
-
.set('Cookie', user1Cookie);
|
|
177
|
-
// 完成
|
|
178
|
-
const res = await request(app)
|
|
179
|
-
.post(`/api/tasks/${task.id}/complete`)
|
|
180
|
-
.set('Cookie', user1Cookie);
|
|
181
|
-
expect(res.status).toBe(200);
|
|
182
|
-
expect(res.body.success).toBe(true);
|
|
183
|
-
expect(res.body.data.status).toBe('done');
|
|
184
|
-
expect(res.body.data.actual_end).not.toBeNull();
|
|
185
|
-
});
|
|
186
|
-
it('planned 状态也可以直接完成', async () => {
|
|
187
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
188
|
-
const { data: task } = await createTask(user1Cookie, project.id, '任务');
|
|
189
|
-
// 直接完成(不先激活)
|
|
190
|
-
const res = await request(app)
|
|
191
|
-
.post(`/api/tasks/${task.id}/complete`)
|
|
192
|
-
.set('Cookie', user1Cookie);
|
|
193
|
-
expect(res.status).toBe(200);
|
|
194
|
-
expect(res.body.data.status).toBe('done');
|
|
195
|
-
expect(res.body.data.actual_end).not.toBeNull();
|
|
196
|
-
});
|
|
197
|
-
it('完成任务记录状态变更历史', async () => {
|
|
198
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
199
|
-
const { data: task } = await createTask(user1Cookie, project.id, '任务');
|
|
200
|
-
await request(app)
|
|
201
|
-
.post(`/api/tasks/${task.id}/complete`)
|
|
202
|
-
.set('Cookie', user1Cookie);
|
|
203
|
-
const historyRes = await request(app)
|
|
204
|
-
.get(`/api/tasks/${task.id}/history`)
|
|
205
|
-
.set('Cookie', user1Cookie);
|
|
206
|
-
const history = historyRes.body.data;
|
|
207
|
-
const statusChanges = history.filter((h) => h.action === 'status_changed');
|
|
208
|
-
const completeRecord = statusChanges.find((h) => h.new_value === 'done');
|
|
209
|
-
expect(completeRecord).toBeDefined();
|
|
210
|
-
});
|
|
211
|
-
it('完成不存在的任务返回 404', async () => {
|
|
212
|
-
const res = await request(app)
|
|
213
|
-
.post('/api/tasks/nonexistent/complete')
|
|
214
|
-
.set('Cookie', user1Cookie);
|
|
215
|
-
expect(res.status).toBe(404);
|
|
216
|
-
});
|
|
217
|
-
it('他人任务完成返回 404', async () => {
|
|
218
|
-
const project = await createProject(user1Cookie, '私有项目');
|
|
219
|
-
const { data: task } = await createTask(user1Cookie, project.id, '私有任务');
|
|
220
|
-
const res = await request(app)
|
|
221
|
-
.post(`/api/tasks/${task.id}/complete`)
|
|
222
|
-
.set('Cookie', user2Cookie);
|
|
223
|
-
expect(res.status).toBe(404);
|
|
224
|
-
});
|
|
225
|
-
it('未认证时返回 401', async () => {
|
|
226
|
-
const res = await request(app)
|
|
227
|
-
.post('/api/tasks/some-id/complete');
|
|
228
|
-
expect(res.status).toBe(401);
|
|
229
|
-
});
|
|
230
|
-
});
|
|
231
|
-
// ==================== VAL-CORE-028: 完成父任务级联完成子任务 ====================
|
|
232
|
-
describe('POST /api/tasks/:id/complete — 级联完成子任务', () => {
|
|
233
|
-
it('VAL-CORE-028: 完成父任务时所有子任务自动标记为 done', async () => {
|
|
234
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
235
|
-
const { data: parent } = await createTask(user1Cookie, project.id, '父任务');
|
|
236
|
-
const { data: child1 } = await createTask(user1Cookie, project.id, '子任务1', { parent_id: parent.id });
|
|
237
|
-
const { data: child2 } = await createTask(user1Cookie, project.id, '子任务2', { parent_id: parent.id });
|
|
238
|
-
// 子任务状态应为 planned
|
|
239
|
-
expect(child1.status).toBe('planned');
|
|
240
|
-
expect(child2.status).toBe('planned');
|
|
241
|
-
// 完成父任务
|
|
242
|
-
const res = await request(app)
|
|
243
|
-
.post(`/api/tasks/${parent.id}/complete`)
|
|
244
|
-
.set('Cookie', user1Cookie);
|
|
245
|
-
expect(res.status).toBe(200);
|
|
246
|
-
expect(res.body.data.status).toBe('done');
|
|
247
|
-
// 验证子任务也变为 done
|
|
248
|
-
const child1Res = await request(app)
|
|
249
|
-
.get(`/api/tasks/${child1.id}`)
|
|
250
|
-
.set('Cookie', user1Cookie);
|
|
251
|
-
expect(child1Res.body.data.status).toBe('done');
|
|
252
|
-
const child2Res = await request(app)
|
|
253
|
-
.get(`/api/tasks/${child2.id}`)
|
|
254
|
-
.set('Cookie', user1Cookie);
|
|
255
|
-
expect(child2Res.body.data.status).toBe('done');
|
|
256
|
-
});
|
|
257
|
-
it('级联完成也记录子任务的历史', async () => {
|
|
258
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
259
|
-
const { data: parent } = await createTask(user1Cookie, project.id, '父任务');
|
|
260
|
-
const { data: child } = await createTask(user1Cookie, project.id, '子任务', { parent_id: parent.id });
|
|
261
|
-
await request(app)
|
|
262
|
-
.post(`/api/tasks/${parent.id}/complete`)
|
|
263
|
-
.set('Cookie', user1Cookie);
|
|
264
|
-
const childHistory = await request(app)
|
|
265
|
-
.get(`/api/tasks/${child.id}/history`)
|
|
266
|
-
.set('Cookie', user1Cookie);
|
|
267
|
-
const statusChanges = childHistory.body.data.filter((h) => h.action === 'status_changed' && h.new_value === 'done');
|
|
268
|
-
expect(statusChanges.length).toBeGreaterThanOrEqual(1);
|
|
269
|
-
expect(statusChanges[0].reason).toBe('父任务状态级联');
|
|
270
|
-
});
|
|
271
|
-
it('多层子任务也级联完成', async () => {
|
|
272
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
273
|
-
const { data: l1 } = await createTask(user1Cookie, project.id, 'L1');
|
|
274
|
-
const { data: l2 } = await createTask(user1Cookie, project.id, 'L2', { parent_id: l1.id });
|
|
275
|
-
const { data: l3 } = await createTask(user1Cookie, project.id, 'L3', { parent_id: l2.id });
|
|
276
|
-
await request(app)
|
|
277
|
-
.post(`/api/tasks/${l1.id}/complete`)
|
|
278
|
-
.set('Cookie', user1Cookie);
|
|
279
|
-
const l3Res = await request(app)
|
|
280
|
-
.get(`/api/tasks/${l3.id}`)
|
|
281
|
-
.set('Cookie', user1Cookie);
|
|
282
|
-
expect(l3Res.body.data.status).toBe('done');
|
|
283
|
-
});
|
|
284
|
-
});
|
|
285
|
-
// ==================== VAL-CORE-029: 所有子任务完成自动完成父任务 ====================
|
|
286
|
-
describe('子任务全部完成 → 自动完成父任务', () => {
|
|
287
|
-
it('VAL-CORE-029: 最后一个子任务完成后父任务自动标记为 done', async () => {
|
|
288
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
289
|
-
const { data: parent } = await createTask(user1Cookie, project.id, '父任务');
|
|
290
|
-
const { data: child1 } = await createTask(user1Cookie, project.id, '子任务1', { parent_id: parent.id });
|
|
291
|
-
const { data: child2 } = await createTask(user1Cookie, project.id, '子任务2', { parent_id: parent.id });
|
|
292
|
-
// 完成第一个子任务
|
|
293
|
-
await request(app)
|
|
294
|
-
.post(`/api/tasks/${child1.id}/complete`)
|
|
295
|
-
.set('Cookie', user1Cookie);
|
|
296
|
-
// 父任务不应被完成
|
|
297
|
-
const parentRes1 = await request(app)
|
|
298
|
-
.get(`/api/tasks/${parent.id}`)
|
|
299
|
-
.set('Cookie', user1Cookie);
|
|
300
|
-
expect(parentRes1.body.data.status).toBe('planned');
|
|
301
|
-
// 完成第二个子任务
|
|
302
|
-
await request(app)
|
|
303
|
-
.post(`/api/tasks/${child2.id}/complete`)
|
|
304
|
-
.set('Cookie', user1Cookie);
|
|
305
|
-
// 父任务应自动完成
|
|
306
|
-
const parentRes2 = await request(app)
|
|
307
|
-
.get(`/api/tasks/${parent.id}`)
|
|
308
|
-
.set('Cookie', user1Cookie);
|
|
309
|
-
expect(parentRes2.body.data.status).toBe('done');
|
|
310
|
-
});
|
|
311
|
-
it('自动完成父任务也记录历史', async () => {
|
|
312
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
313
|
-
const { data: parent } = await createTask(user1Cookie, project.id, '父任务');
|
|
314
|
-
const { data: child } = await createTask(user1Cookie, project.id, '子任务', { parent_id: parent.id });
|
|
315
|
-
await request(app)
|
|
316
|
-
.post(`/api/tasks/${child.id}/complete`)
|
|
317
|
-
.set('Cookie', user1Cookie);
|
|
318
|
-
const parentHistory = await request(app)
|
|
319
|
-
.get(`/api/tasks/${parent.id}/history`)
|
|
320
|
-
.set('Cookie', user1Cookie);
|
|
321
|
-
const statusChanges = parentHistory.body.data.filter((h) => h.action === 'status_changed' && h.new_value === 'done');
|
|
322
|
-
expect(statusChanges.length).toBeGreaterThanOrEqual(1);
|
|
323
|
-
expect(statusChanges[0].reason).toBe('子任务状态级联');
|
|
324
|
-
});
|
|
325
|
-
it('多层嵌套:底层子任务完成后逐层完成父任务', async () => {
|
|
326
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
327
|
-
const { data: l1 } = await createTask(user1Cookie, project.id, 'L1');
|
|
328
|
-
const { data: l2 } = await createTask(user1Cookie, project.id, 'L2', { parent_id: l1.id });
|
|
329
|
-
const { data: l3 } = await createTask(user1Cookie, project.id, 'L3', { parent_id: l2.id });
|
|
330
|
-
// 完成最底层
|
|
331
|
-
await request(app)
|
|
332
|
-
.post(`/api/tasks/${l3.id}/complete`)
|
|
333
|
-
.set('Cookie', user1Cookie);
|
|
334
|
-
// L2 应自动完成(只有一个子任务)
|
|
335
|
-
const l2Res = await request(app)
|
|
336
|
-
.get(`/api/tasks/${l2.id}`)
|
|
337
|
-
.set('Cookie', user1Cookie);
|
|
338
|
-
expect(l2Res.body.data.status).toBe('done');
|
|
339
|
-
// L1 也应自动完成
|
|
340
|
-
const l1Res = await request(app)
|
|
341
|
-
.get(`/api/tasks/${l1.id}`)
|
|
342
|
-
.set('Cookie', user1Cookie);
|
|
343
|
-
expect(l1Res.body.data.status).toBe('done');
|
|
344
|
-
});
|
|
345
|
-
});
|
|
346
|
-
// ==================== VAL-CORE-038: 已完成任务状态不可回退 ====================
|
|
347
|
-
describe('VAL-CORE-038: 已完成任务状态不可回退', () => {
|
|
348
|
-
it('将 done 状态的任务改为 planned 返回 400', async () => {
|
|
349
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
350
|
-
const { data: task } = await createTask(user1Cookie, project.id, '已完成任务');
|
|
351
|
-
// 先完成任务
|
|
352
|
-
await request(app)
|
|
353
|
-
.post(`/api/tasks/${task.id}/complete`)
|
|
354
|
-
.set('Cookie', user1Cookie);
|
|
355
|
-
// 尝试通过 PUT 改回 planned
|
|
356
|
-
const res = await request(app)
|
|
357
|
-
.put(`/api/tasks/${task.id}`)
|
|
358
|
-
.set('Cookie', user1Cookie)
|
|
359
|
-
.send({ status: 'planned' });
|
|
360
|
-
expect(res.status).toBe(400);
|
|
361
|
-
expect(res.body.success).toBe(false);
|
|
362
|
-
expect(res.body.error).toMatch(/已完成|不可回退|不可逆/i);
|
|
363
|
-
});
|
|
364
|
-
it('将 done 状态的任务改为 in_progress 返回 400', async () => {
|
|
365
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
366
|
-
const { data: task } = await createTask(user1Cookie, project.id, '已完成任务');
|
|
367
|
-
await request(app)
|
|
368
|
-
.post(`/api/tasks/${task.id}/complete`)
|
|
369
|
-
.set('Cookie', user1Cookie);
|
|
370
|
-
const res = await request(app)
|
|
371
|
-
.put(`/api/tasks/${task.id}`)
|
|
372
|
-
.set('Cookie', user1Cookie)
|
|
373
|
-
.send({ status: 'in_progress' });
|
|
374
|
-
expect(res.status).toBe(400);
|
|
375
|
-
expect(res.body.success).toBe(false);
|
|
376
|
-
});
|
|
377
|
-
it('已完成任务可以通过 PUT 更新其他字段(title, description)', async () => {
|
|
378
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
379
|
-
const { data: task } = await createTask(user1Cookie, project.id, '任务');
|
|
380
|
-
await request(app)
|
|
381
|
-
.post(`/api/tasks/${task.id}/complete`)
|
|
382
|
-
.set('Cookie', user1Cookie);
|
|
383
|
-
// 更新 title(不是状态)应成功
|
|
384
|
-
const res = await request(app)
|
|
385
|
-
.put(`/api/tasks/${task.id}`)
|
|
386
|
-
.set('Cookie', user1Cookie)
|
|
387
|
-
.send({ title: '更新后的标题' });
|
|
388
|
-
expect(res.status).toBe(200);
|
|
389
|
-
expect(res.body.data.title).toBe('更新后的标题');
|
|
390
|
-
expect(res.body.data.status).toBe('done'); // 状态不变
|
|
391
|
-
});
|
|
392
|
-
it('已完成任务不能通过 activate 激活', async () => {
|
|
393
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
394
|
-
const { data: task } = await createTask(user1Cookie, project.id, '已完成任务');
|
|
395
|
-
await request(app)
|
|
396
|
-
.post(`/api/tasks/${task.id}/complete`)
|
|
397
|
-
.set('Cookie', user1Cookie);
|
|
398
|
-
// 尝试 activate 已完成的任务 — 应该拒绝或保持 done
|
|
399
|
-
const res = await request(app)
|
|
400
|
-
.post(`/api/tasks/${task.id}/activate`)
|
|
401
|
-
.set('Cookie', user1Cookie);
|
|
402
|
-
// 确保状态仍然为 done
|
|
403
|
-
expect(res.body.data.status).toBe('done');
|
|
404
|
-
});
|
|
405
|
-
it('非 done 状态可以正常修改状态', async () => {
|
|
406
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
407
|
-
const { data: task } = await createTask(user1Cookie, project.id, '任务');
|
|
408
|
-
// planned -> in_progress 应该成功
|
|
409
|
-
const res = await request(app)
|
|
410
|
-
.put(`/api/tasks/${task.id}`)
|
|
411
|
-
.set('Cookie', user1Cookie)
|
|
412
|
-
.send({ status: 'in_progress' });
|
|
413
|
-
expect(res.status).toBe(200);
|
|
414
|
-
expect(res.body.data.status).toBe('in_progress');
|
|
415
|
-
// in_progress -> planned 也应该成功
|
|
416
|
-
const res2 = await request(app)
|
|
417
|
-
.put(`/api/tasks/${task.id}`)
|
|
418
|
-
.set('Cookie', user1Cookie)
|
|
419
|
-
.send({ status: 'planned' });
|
|
420
|
-
expect(res2.status).toBe(200);
|
|
421
|
-
expect(res2.body.data.status).toBe('planned');
|
|
422
|
-
});
|
|
423
|
-
});
|
|
424
|
-
// ==================== VAL-CORE-031: 插队任务标记 ====================
|
|
425
|
-
describe('VAL-CORE-031: 版本开始后创建的任务 inserted=true', () => {
|
|
426
|
-
it('版本开始后创建的任务 inserted=true', async () => {
|
|
427
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
428
|
-
const version = await createVersion(user1Cookie, project.id, 'v1.0');
|
|
429
|
-
// 版本开始前创建任务
|
|
430
|
-
const { data: taskBefore } = await createTask(user1Cookie, project.id, '版本前任务', {
|
|
431
|
-
version_id: version.id,
|
|
432
|
-
});
|
|
433
|
-
expect(taskBefore.inserted).toBe(0);
|
|
434
|
-
// 启动版本
|
|
435
|
-
await startVersion(user1Cookie, version.id);
|
|
436
|
-
// 版本开始后创建任务
|
|
437
|
-
const { data: taskAfter } = await createTask(user1Cookie, project.id, '插队任务');
|
|
438
|
-
expect(taskAfter.inserted).toBe(1);
|
|
439
|
-
expect(taskAfter.version_id).toBe(version.id);
|
|
440
|
-
});
|
|
441
|
-
it('版本未开始时创建的任务 inserted=false', async () => {
|
|
442
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
443
|
-
const { data: task } = await createTask(user1Cookie, project.id, '普通任务');
|
|
444
|
-
expect(task.inserted).toBe(0);
|
|
445
|
-
});
|
|
446
|
-
});
|
|
447
|
-
// ==================== VAL-CORE-033: 任务历史记录 ====================
|
|
448
|
-
describe('GET /api/tasks/:id/history — 任务历史记录', () => {
|
|
449
|
-
it('VAL-CORE-033: 返回任务的状态变更历史', async () => {
|
|
450
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
451
|
-
const { data: task } = await createTask(user1Cookie, project.id, '任务');
|
|
452
|
-
const historyRes = await request(app)
|
|
453
|
-
.get(`/api/tasks/${task.id}/history`)
|
|
454
|
-
.set('Cookie', user1Cookie);
|
|
455
|
-
expect(historyRes.status).toBe(200);
|
|
456
|
-
expect(historyRes.body.success).toBe(true);
|
|
457
|
-
expect(Array.isArray(historyRes.body.data)).toBe(true);
|
|
458
|
-
// 创建时应有一条 created 记录
|
|
459
|
-
expect(historyRes.body.data.length).toBeGreaterThanOrEqual(1);
|
|
460
|
-
expect(historyRes.body.data[0].action).toBe('created');
|
|
461
|
-
});
|
|
462
|
-
it('历史记录包含 action, old_value, new_value, changed_at', async () => {
|
|
463
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
464
|
-
const { data: task } = await createTask(user1Cookie, project.id, '任务');
|
|
465
|
-
// 激活任务产生状态变更
|
|
466
|
-
await request(app)
|
|
467
|
-
.post(`/api/tasks/${task.id}/activate`)
|
|
468
|
-
.set('Cookie', user1Cookie);
|
|
469
|
-
const historyRes = await request(app)
|
|
470
|
-
.get(`/api/tasks/${task.id}/history`)
|
|
471
|
-
.set('Cookie', user1Cookie);
|
|
472
|
-
const statusChange = historyRes.body.data.find((h) => h.action === 'status_changed');
|
|
473
|
-
expect(statusChange).toBeDefined();
|
|
474
|
-
expect(statusChange.field).toBe('status');
|
|
475
|
-
expect(statusChange.old_value).toBe('planned');
|
|
476
|
-
expect(statusChange.new_value).toBe('in_progress');
|
|
477
|
-
expect(statusChange.changed_at).toBeDefined();
|
|
478
|
-
});
|
|
479
|
-
it('历史记录按 changed_at 降序排列', async () => {
|
|
480
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
481
|
-
const { data: task } = await createTask(user1Cookie, project.id, '任务');
|
|
482
|
-
// 激活
|
|
483
|
-
await request(app)
|
|
484
|
-
.post(`/api/tasks/${task.id}/activate`)
|
|
485
|
-
.set('Cookie', user1Cookie);
|
|
486
|
-
// 完成
|
|
487
|
-
await request(app)
|
|
488
|
-
.post(`/api/tasks/${task.id}/complete`)
|
|
489
|
-
.set('Cookie', user1Cookie);
|
|
490
|
-
const historyRes = await request(app)
|
|
491
|
-
.get(`/api/tasks/${task.id}/history`)
|
|
492
|
-
.set('Cookie', user1Cookie);
|
|
493
|
-
const history = historyRes.body.data;
|
|
494
|
-
// 最新记录在前
|
|
495
|
-
const doneIdx = history.findIndex((h) => h.new_value === 'done');
|
|
496
|
-
const inProgressIdx = history.findIndex((h) => h.new_value === 'in_progress');
|
|
497
|
-
expect(doneIdx).toBeLessThan(inProgressIdx);
|
|
498
|
-
});
|
|
499
|
-
it('他人任务历史返回 404', async () => {
|
|
500
|
-
const project = await createProject(user1Cookie, '私有项目');
|
|
501
|
-
const { data: task } = await createTask(user1Cookie, project.id, '私有任务');
|
|
502
|
-
const res = await request(app)
|
|
503
|
-
.get(`/api/tasks/${task.id}/history`)
|
|
504
|
-
.set('Cookie', user2Cookie);
|
|
505
|
-
expect(res.status).toBe(404);
|
|
506
|
-
});
|
|
507
|
-
it('不存在的任务返回 404', async () => {
|
|
508
|
-
const res = await request(app)
|
|
509
|
-
.get('/api/tasks/nonexistent/history')
|
|
510
|
-
.set('Cookie', user1Cookie);
|
|
511
|
-
expect(res.status).toBe(404);
|
|
512
|
-
});
|
|
513
|
-
it('未认证时返回 401', async () => {
|
|
514
|
-
const res = await request(app)
|
|
515
|
-
.get('/api/tasks/some-id/history');
|
|
516
|
-
expect(res.status).toBe(401);
|
|
517
|
-
});
|
|
518
|
-
});
|
|
519
|
-
// ==================== VAL-CORE-034: 添加任务备注 ====================
|
|
520
|
-
describe('POST /api/tasks/:id/history — 添加任务备注', () => {
|
|
521
|
-
it('VAL-CORE-034: 添加备注成功', async () => {
|
|
522
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
523
|
-
const { data: task } = await createTask(user1Cookie, project.id, '任务');
|
|
524
|
-
const res = await request(app)
|
|
525
|
-
.post(`/api/tasks/${task.id}/history`)
|
|
526
|
-
.set('Cookie', user1Cookie)
|
|
527
|
-
.send({ note: '这是一个备注' });
|
|
528
|
-
expect(res.status).toBe(201);
|
|
529
|
-
expect(res.body.success).toBe(true);
|
|
530
|
-
expect(res.body.data.action).toBe('noted');
|
|
531
|
-
expect(res.body.data.reason).toBe('这是一个备注');
|
|
532
|
-
});
|
|
533
|
-
it('添加备注后 GET history 包含该备注', async () => {
|
|
534
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
535
|
-
const { data: task } = await createTask(user1Cookie, project.id, '任务');
|
|
536
|
-
await request(app)
|
|
537
|
-
.post(`/api/tasks/${task.id}/history`)
|
|
538
|
-
.set('Cookie', user1Cookie)
|
|
539
|
-
.send({ note: '测试备注' });
|
|
540
|
-
const historyRes = await request(app)
|
|
541
|
-
.get(`/api/tasks/${task.id}/history`)
|
|
542
|
-
.set('Cookie', user1Cookie);
|
|
543
|
-
const notes = historyRes.body.data.filter((h) => h.action === 'noted');
|
|
544
|
-
expect(notes.length).toBe(1);
|
|
545
|
-
expect(notes[0].reason).toBe('测试备注');
|
|
546
|
-
});
|
|
547
|
-
it('note 为空返回 400', async () => {
|
|
548
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
549
|
-
const { data: task } = await createTask(user1Cookie, project.id, '任务');
|
|
550
|
-
const res = await request(app)
|
|
551
|
-
.post(`/api/tasks/${task.id}/history`)
|
|
552
|
-
.set('Cookie', user1Cookie)
|
|
553
|
-
.send({ note: '' });
|
|
554
|
-
expect(res.status).toBe(400);
|
|
555
|
-
expect(res.body.success).toBe(false);
|
|
556
|
-
});
|
|
557
|
-
it('缺少 note 返回 400', async () => {
|
|
558
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
559
|
-
const { data: task } = await createTask(user1Cookie, project.id, '任务');
|
|
560
|
-
const res = await request(app)
|
|
561
|
-
.post(`/api/tasks/${task.id}/history`)
|
|
562
|
-
.set('Cookie', user1Cookie)
|
|
563
|
-
.send({});
|
|
564
|
-
expect(res.status).toBe(400);
|
|
565
|
-
});
|
|
566
|
-
it('他人任务添加备注返回 404', async () => {
|
|
567
|
-
const project = await createProject(user1Cookie, '私有项目');
|
|
568
|
-
const { data: task } = await createTask(user1Cookie, project.id, '私有任务');
|
|
569
|
-
const res = await request(app)
|
|
570
|
-
.post(`/api/tasks/${task.id}/history`)
|
|
571
|
-
.set('Cookie', user2Cookie)
|
|
572
|
-
.send({ note: '试图添加备注' });
|
|
573
|
-
expect(res.status).toBe(404);
|
|
574
|
-
});
|
|
575
|
-
it('未认证时返回 401', async () => {
|
|
576
|
-
const res = await request(app)
|
|
577
|
-
.post('/api/tasks/some-id/history')
|
|
578
|
-
.send({ note: '备注' });
|
|
579
|
-
expect(res.status).toBe(401);
|
|
580
|
-
});
|
|
581
|
-
});
|
|
582
|
-
// ==================== VAL-CORE-035: 任务排期 ====================
|
|
583
|
-
describe('VAL-CORE-035: 任务排期 — estimated_days 自动计算日期', () => {
|
|
584
|
-
it('设置 estimated_days 后自动计算 start_date 和 due_date', async () => {
|
|
585
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
586
|
-
const version = await createVersion(user1Cookie, project.id, 'v1.0');
|
|
587
|
-
await startVersion(user1Cookie, version.id);
|
|
588
|
-
const { data: task } = await createTask(user1Cookie, project.id, '任务', {
|
|
589
|
-
estimated_days: 3,
|
|
590
|
-
});
|
|
591
|
-
// 由于版本已开始,任务应该自动排期
|
|
592
|
-
// start_date 应为今天或下一个工作日
|
|
593
|
-
// due_date 应为 start_date + 3 个工作日
|
|
594
|
-
expect(task.start_date).not.toBeNull();
|
|
595
|
-
expect(task.due_date).not.toBeNull();
|
|
596
|
-
expect(task.estimated_days).toBe(3);
|
|
597
|
-
});
|
|
598
|
-
it('更新 estimated_days 后自动重新计算 due_date', async () => {
|
|
599
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
600
|
-
const version = await createVersion(user1Cookie, project.id, 'v1.0');
|
|
601
|
-
await startVersion(user1Cookie, version.id);
|
|
602
|
-
const { data: task } = await createTask(user1Cookie, project.id, '任务', {
|
|
603
|
-
estimated_days: 2,
|
|
604
|
-
});
|
|
605
|
-
const originalDueDate = task.due_date;
|
|
606
|
-
// 更新 estimated_days
|
|
607
|
-
const res = await request(app)
|
|
608
|
-
.put(`/api/tasks/${task.id}`)
|
|
609
|
-
.set('Cookie', user1Cookie)
|
|
610
|
-
.send({ estimated_days: 5 });
|
|
611
|
-
expect(res.status).toBe(200);
|
|
612
|
-
// due_date 应该更新
|
|
613
|
-
expect(res.body.data.estimated_days).toBe(5);
|
|
614
|
-
if (originalDueDate) {
|
|
615
|
-
// 新的 due_date 应该比原来的晚(多了 3 个工作日)
|
|
616
|
-
expect(res.body.data.due_date).not.toBe(originalDueDate);
|
|
617
|
-
}
|
|
618
|
-
});
|
|
619
|
-
it('排期跳过周末', async () => {
|
|
620
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
621
|
-
const version = await createVersion(user1Cookie, project.id, 'v1.0');
|
|
622
|
-
await startVersion(user1Cookie, version.id);
|
|
623
|
-
// 创建一个 estimated_days=1 的任务
|
|
624
|
-
const { data: task } = await createTask(user1Cookie, project.id, '任务', {
|
|
625
|
-
estimated_days: 1,
|
|
626
|
-
});
|
|
627
|
-
expect(task.start_date).not.toBeNull();
|
|
628
|
-
// 检查 start_date 是否为工作日(周一到周五)
|
|
629
|
-
const startDate = new Date(task.start_date);
|
|
630
|
-
const day = startDate.getDay();
|
|
631
|
-
expect(day).not.toBe(0); // 不是周日
|
|
632
|
-
expect(day).not.toBe(6); // 不是周六
|
|
633
|
-
});
|
|
634
|
-
it('estimated_days=0 时 due_date 等于 start_date', async () => {
|
|
635
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
636
|
-
const version = await createVersion(user1Cookie, project.id, 'v1.0');
|
|
637
|
-
await startVersion(user1Cookie, version.id);
|
|
638
|
-
const { data: task } = await createTask(user1Cookie, project.id, '任务', {
|
|
639
|
-
estimated_days: 0,
|
|
640
|
-
});
|
|
641
|
-
expect(task.start_date).not.toBeNull();
|
|
642
|
-
// estimated_days=0 意味着当天完成
|
|
643
|
-
expect(task.due_date).toBe(task.start_date);
|
|
644
|
-
});
|
|
645
|
-
it('无版本时创建任务不自动排期', async () => {
|
|
646
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
647
|
-
const { data: task } = await createTask(user1Cookie, project.id, '任务', {
|
|
648
|
-
estimated_days: 3,
|
|
649
|
-
});
|
|
650
|
-
// 无活跃版本,不应自动排期
|
|
651
|
-
expect(task.start_date).toBeNull();
|
|
652
|
-
expect(task.due_date).toBeNull();
|
|
653
|
-
});
|
|
654
|
-
});
|
|
655
|
-
// ==================== 综合场景测试 ====================
|
|
656
|
-
describe('综合场景:完整任务生命周期', () => {
|
|
657
|
-
it('创建 → 激活 → 完成 全流程', async () => {
|
|
658
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
659
|
-
const { data: task } = await createTask(user1Cookie, project.id, '完整生命周期');
|
|
660
|
-
// 1. 创建状态
|
|
661
|
-
expect(task.status).toBe('planned');
|
|
662
|
-
expect(task.actual_start).toBeNull();
|
|
663
|
-
expect(task.actual_end).toBeNull();
|
|
664
|
-
// 2. 激活
|
|
665
|
-
const activateRes = await request(app)
|
|
666
|
-
.post(`/api/tasks/${task.id}/activate`)
|
|
667
|
-
.set('Cookie', user1Cookie);
|
|
668
|
-
expect(activateRes.body.data.status).toBe('in_progress');
|
|
669
|
-
expect(activateRes.body.data.actual_start).not.toBeNull();
|
|
670
|
-
// 3. 完成
|
|
671
|
-
const completeRes = await request(app)
|
|
672
|
-
.post(`/api/tasks/${task.id}/complete`)
|
|
673
|
-
.set('Cookie', user1Cookie);
|
|
674
|
-
expect(completeRes.body.data.status).toBe('done');
|
|
675
|
-
expect(completeRes.body.data.actual_end).not.toBeNull();
|
|
676
|
-
// 4. 历史记录完整
|
|
677
|
-
const historyRes = await request(app)
|
|
678
|
-
.get(`/api/tasks/${task.id}/history`)
|
|
679
|
-
.set('Cookie', user1Cookie);
|
|
680
|
-
const history = historyRes.body.data;
|
|
681
|
-
expect(history.some((h) => h.action === 'created')).toBe(true);
|
|
682
|
-
expect(history.some((h) => h.action === 'status_changed')).toBe(true);
|
|
683
|
-
});
|
|
684
|
-
it('版本生命周期:创建版本 → 添加任务 → 启动 → 完成', async () => {
|
|
685
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
686
|
-
const version = await createVersion(user1Cookie, project.id, 'v1.0');
|
|
687
|
-
// 启动前创建任务
|
|
688
|
-
const { data: taskBefore } = await createTask(user1Cookie, project.id, '版本前任务', {
|
|
689
|
-
version_id: version.id,
|
|
690
|
-
});
|
|
691
|
-
expect(taskBefore.inserted).toBe(0);
|
|
692
|
-
// 启动版本
|
|
693
|
-
await startVersion(user1Cookie, version.id);
|
|
694
|
-
// 启动后创建任务
|
|
695
|
-
const { data: taskAfter } = await createTask(user1Cookie, project.id, '插队任务');
|
|
696
|
-
expect(taskAfter.inserted).toBe(1);
|
|
697
|
-
// 完成所有任务
|
|
698
|
-
await request(app)
|
|
699
|
-
.post(`/api/tasks/${taskBefore.id}/complete`)
|
|
700
|
-
.set('Cookie', user1Cookie);
|
|
701
|
-
await request(app)
|
|
702
|
-
.post(`/api/tasks/${taskAfter.id}/complete`)
|
|
703
|
-
.set('Cookie', user1Cookie);
|
|
704
|
-
// 版本完成
|
|
705
|
-
const completeVersionRes = await request(app)
|
|
706
|
-
.post(`/api/versions/${version.id}/complete`)
|
|
707
|
-
.set('Cookie', user1Cookie);
|
|
708
|
-
expect(completeVersionRes.status).toBe(200);
|
|
709
|
-
expect(completeVersionRes.body.data.completed_at).not.toBeNull();
|
|
710
|
-
});
|
|
711
|
-
});
|
|
712
|
-
//# sourceMappingURL=tasks-lifecycle.test.js.map
|