@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,617 +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
|
-
beforeAll(async () => {
|
|
17
|
-
TEST_DIR = join(tmpdir(), `omt-tasks-crud-test-${Date.now()}`);
|
|
18
|
-
TEST_DB_PATH = join(TEST_DIR, 'data', 'data.db');
|
|
19
|
-
mkdirSync(join(TEST_DIR, 'data'), { recursive: true });
|
|
20
|
-
// 写一个标记文件供 vitest 识别这是新环境
|
|
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
|
-
// 强制重新加载模块
|
|
30
|
-
const modulePaths = Object.keys(require.cache || {}).filter(k => k.includes('oh-my-task'));
|
|
31
|
-
for (const p of modulePaths) {
|
|
32
|
-
delete require.cache[p];
|
|
33
|
-
}
|
|
34
|
-
// 动态导入 app(需要在数据库初始化后)
|
|
35
|
-
const serverModule = await import('../api/server.js');
|
|
36
|
-
app = serverModule.default;
|
|
37
|
-
});
|
|
38
|
-
afterAll(() => {
|
|
39
|
-
try {
|
|
40
|
-
if (existsSync(TEST_DIR)) {
|
|
41
|
-
rmSync(TEST_DIR, { recursive: true, force: true });
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
catch {
|
|
45
|
-
// Windows 可能因文件锁定无法立即删除
|
|
46
|
-
}
|
|
47
|
-
delete process.env.DB_PATH;
|
|
48
|
-
});
|
|
49
|
-
beforeEach(async () => {
|
|
50
|
-
// 注册用户1(admin)
|
|
51
|
-
const res1 = await request(app)
|
|
52
|
-
.post('/api/auth/register')
|
|
53
|
-
.send({
|
|
54
|
-
name: 'User1',
|
|
55
|
-
email: `user1-tasks-${Date.now()}@test.com`,
|
|
56
|
-
password: 'UserPass123'
|
|
57
|
-
});
|
|
58
|
-
user1Cookie = res1.headers['set-cookie']?.[0] || '';
|
|
59
|
-
user1Id = res1.body.data?.user?.id || '';
|
|
60
|
-
// 注册用户2(member)
|
|
61
|
-
const res2 = await request(app)
|
|
62
|
-
.post('/api/auth/register')
|
|
63
|
-
.send({
|
|
64
|
-
name: 'User2',
|
|
65
|
-
email: `user2-tasks-${Date.now()}@test.com`,
|
|
66
|
-
password: 'UserPass123'
|
|
67
|
-
});
|
|
68
|
-
user2Cookie = res2.headers['set-cookie']?.[0] || '';
|
|
69
|
-
user2Id = res2.body.data?.user?.id || '';
|
|
70
|
-
});
|
|
71
|
-
// 辅助函数:创建项目
|
|
72
|
-
async function createProject(cookie, name) {
|
|
73
|
-
const res = await request(app)
|
|
74
|
-
.post('/api/projects')
|
|
75
|
-
.set('Cookie', cookie)
|
|
76
|
-
.send({ name });
|
|
77
|
-
return res.body.data;
|
|
78
|
-
}
|
|
79
|
-
// 辅助函数:创建版本
|
|
80
|
-
async function createVersion(cookie, projectId, name) {
|
|
81
|
-
const res = await request(app)
|
|
82
|
-
.post('/api/versions')
|
|
83
|
-
.set('Cookie', cookie)
|
|
84
|
-
.send({ project_id: projectId, name, due_date: '2026-05-30' });
|
|
85
|
-
return res.body.data;
|
|
86
|
-
}
|
|
87
|
-
// 辅助函数:创建任务
|
|
88
|
-
async function createTask(cookie, projectId, title, extra) {
|
|
89
|
-
const body = { project_id: projectId, title };
|
|
90
|
-
if (extra)
|
|
91
|
-
Object.assign(body, extra);
|
|
92
|
-
const res = await request(app)
|
|
93
|
-
.post('/api/tasks')
|
|
94
|
-
.set('Cookie', cookie)
|
|
95
|
-
.send(body);
|
|
96
|
-
return { status: res.status, data: res.body.data, body: res.body };
|
|
97
|
-
}
|
|
98
|
-
// 辅助函数:启动版本
|
|
99
|
-
async function startVersion(cookie, versionId) {
|
|
100
|
-
const res = await request(app)
|
|
101
|
-
.post(`/api/versions/${versionId}/start`)
|
|
102
|
-
.set('Cookie', cookie);
|
|
103
|
-
return res;
|
|
104
|
-
}
|
|
105
|
-
describe('POST /api/tasks — 创建任务', () => {
|
|
106
|
-
it('VAL-CORE-020: 创建任务成功,status=planned,自动关联活跃版本', async () => {
|
|
107
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
108
|
-
const version = await createVersion(user1Cookie, project.id, 'v1.0');
|
|
109
|
-
// 启动版本使其成为活跃版本
|
|
110
|
-
await startVersion(user1Cookie, version.id);
|
|
111
|
-
const { status, data } = await createTask(user1Cookie, project.id, '新任务');
|
|
112
|
-
expect(status).toBe(201);
|
|
113
|
-
expect(data.id).toBeDefined();
|
|
114
|
-
expect(data.title).toBe('新任务');
|
|
115
|
-
expect(data.status).toBe('planned');
|
|
116
|
-
expect(data.version_id).toBe(version.id);
|
|
117
|
-
});
|
|
118
|
-
it('没有活跃版本时,version_id 为 null', async () => {
|
|
119
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
120
|
-
const { status, data } = await createTask(user1Cookie, project.id, '无版本任务');
|
|
121
|
-
expect(status).toBe(201);
|
|
122
|
-
expect(data.version_id).toBeNull();
|
|
123
|
-
});
|
|
124
|
-
it('VAL-CORE-021: 指定 parent_id 创建子任务', async () => {
|
|
125
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
126
|
-
const parent = await createTask(user1Cookie, project.id, '父任务');
|
|
127
|
-
const { status, data } = await createTask(user1Cookie, project.id, '子任务', { parent_id: parent.data.id });
|
|
128
|
-
expect(status).toBe(201);
|
|
129
|
-
// SQLite stores NULL as null in JSON; parent_id should be the parent's id
|
|
130
|
-
expect(data.parent_id).toBe(parent.data.id);
|
|
131
|
-
});
|
|
132
|
-
it('VAL-CORE-022: 超过3级层级限制返回 400', async () => {
|
|
133
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
134
|
-
// Level 1
|
|
135
|
-
const l1 = await createTask(user1Cookie, project.id, 'L1');
|
|
136
|
-
// Level 2
|
|
137
|
-
const l2 = await createTask(user1Cookie, project.id, 'L2', { parent_id: l1.data.id });
|
|
138
|
-
// Level 3
|
|
139
|
-
const l3 = await createTask(user1Cookie, project.id, 'L3', { parent_id: l2.data.id });
|
|
140
|
-
// Level 4 — 应该失败
|
|
141
|
-
const { status, body } = await createTask(user1Cookie, project.id, 'L4', { parent_id: l3.data.id });
|
|
142
|
-
expect(status).toBe(400);
|
|
143
|
-
expect(body.success).toBe(false);
|
|
144
|
-
expect(body.error).toMatch(/层级|level/i);
|
|
145
|
-
});
|
|
146
|
-
it('VAL-CORE-036: title 为空字符串返回 400', async () => {
|
|
147
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
148
|
-
const res = await request(app)
|
|
149
|
-
.post('/api/tasks')
|
|
150
|
-
.set('Cookie', user1Cookie)
|
|
151
|
-
.send({ project_id: project.id, title: '' });
|
|
152
|
-
expect(res.status).toBe(400);
|
|
153
|
-
expect(res.body.success).toBe(false);
|
|
154
|
-
expect(res.body.error).toContain('title');
|
|
155
|
-
});
|
|
156
|
-
it('缺少 title 返回 400', async () => {
|
|
157
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
158
|
-
const res = await request(app)
|
|
159
|
-
.post('/api/tasks')
|
|
160
|
-
.set('Cookie', user1Cookie)
|
|
161
|
-
.send({ project_id: project.id });
|
|
162
|
-
expect(res.status).toBe(400);
|
|
163
|
-
expect(res.body.success).toBe(false);
|
|
164
|
-
});
|
|
165
|
-
it('缺少 project_id 返回 400', async () => {
|
|
166
|
-
const res = await request(app)
|
|
167
|
-
.post('/api/tasks')
|
|
168
|
-
.set('Cookie', user1Cookie)
|
|
169
|
-
.send({ title: '任务' });
|
|
170
|
-
expect(res.status).toBe(400);
|
|
171
|
-
expect(res.body.success).toBe(false);
|
|
172
|
-
});
|
|
173
|
-
it('VAL-CORE-037: parent_id 指向不存在的任务返回 400', async () => {
|
|
174
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
175
|
-
const res = await request(app)
|
|
176
|
-
.post('/api/tasks')
|
|
177
|
-
.set('Cookie', user1Cookie)
|
|
178
|
-
.send({ project_id: project.id, title: '子任务', parent_id: 'nonexistent-id' });
|
|
179
|
-
expect(res.status).toBe(400);
|
|
180
|
-
expect(res.body.success).toBe(false);
|
|
181
|
-
});
|
|
182
|
-
it('parent_id 指向其他项目的任务返回 400', async () => {
|
|
183
|
-
const project1 = await createProject(user1Cookie, '项目1');
|
|
184
|
-
const project2 = await createProject(user1Cookie, '项目2');
|
|
185
|
-
const parent = await createTask(user1Cookie, project1.id, '父任务');
|
|
186
|
-
const res = await request(app)
|
|
187
|
-
.post('/api/tasks')
|
|
188
|
-
.set('Cookie', user1Cookie)
|
|
189
|
-
.send({ project_id: project2.id, title: '子任务', parent_id: parent.data.id });
|
|
190
|
-
expect(res.status).toBe(400);
|
|
191
|
-
expect(res.body.success).toBe(false);
|
|
192
|
-
});
|
|
193
|
-
it('项目不存在时返回 404', async () => {
|
|
194
|
-
const res = await request(app)
|
|
195
|
-
.post('/api/tasks')
|
|
196
|
-
.set('Cookie', user1Cookie)
|
|
197
|
-
.send({ project_id: 'nonexistent', title: '任务' });
|
|
198
|
-
expect(res.status).toBe(404);
|
|
199
|
-
expect(res.body.success).toBe(false);
|
|
200
|
-
});
|
|
201
|
-
it('他人项目返回 404', async () => {
|
|
202
|
-
const project = await createProject(user1Cookie, '私有项目');
|
|
203
|
-
const res = await request(app)
|
|
204
|
-
.post('/api/tasks')
|
|
205
|
-
.set('Cookie', user2Cookie)
|
|
206
|
-
.send({ project_id: project.id, title: '任务' });
|
|
207
|
-
expect(res.status).toBe(404);
|
|
208
|
-
expect(res.body.success).toBe(false);
|
|
209
|
-
});
|
|
210
|
-
it('未认证时返回 401', async () => {
|
|
211
|
-
const res = await request(app)
|
|
212
|
-
.post('/api/tasks')
|
|
213
|
-
.send({ project_id: 'xxx', title: '任务' });
|
|
214
|
-
expect(res.status).toBe(401);
|
|
215
|
-
});
|
|
216
|
-
it('创建任务时可附带 description 和 estimated_days', async () => {
|
|
217
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
218
|
-
const { status, data } = await createTask(user1Cookie, project.id, '有详情的任务', {
|
|
219
|
-
description: '任务描述',
|
|
220
|
-
estimated_days: 5,
|
|
221
|
-
});
|
|
222
|
-
expect(status).toBe(201);
|
|
223
|
-
expect(data.description).toBe('任务描述');
|
|
224
|
-
expect(data.estimated_days).toBe(5);
|
|
225
|
-
});
|
|
226
|
-
it('创建任务时可附带 notes', async () => {
|
|
227
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
228
|
-
const { status, data } = await createTask(user1Cookie, project.id, '有备注的任务', {
|
|
229
|
-
notes: '备注内容',
|
|
230
|
-
});
|
|
231
|
-
expect(status).toBe(201);
|
|
232
|
-
expect(data.notes).toBe('备注内容');
|
|
233
|
-
});
|
|
234
|
-
it('版本开始后创建的任务 inserted=true', async () => {
|
|
235
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
236
|
-
const version = await createVersion(user1Cookie, project.id, 'v1.0');
|
|
237
|
-
// 启动版本
|
|
238
|
-
await startVersion(user1Cookie, version.id);
|
|
239
|
-
const { status, data } = await createTask(user1Cookie, project.id, '插队任务');
|
|
240
|
-
expect(status).toBe(201);
|
|
241
|
-
expect(data.inserted).toBe(1);
|
|
242
|
-
});
|
|
243
|
-
it('版本未开始时创建的任务 inserted=false', async () => {
|
|
244
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
245
|
-
const version = await createVersion(user1Cookie, project.id, 'v1.0');
|
|
246
|
-
const { status, data } = await createTask(user1Cookie, project.id, '普通任务', {
|
|
247
|
-
version_id: version.id,
|
|
248
|
-
});
|
|
249
|
-
expect(status).toBe(201);
|
|
250
|
-
expect(data.inserted).toBe(0);
|
|
251
|
-
});
|
|
252
|
-
it('显式指定 version_id 时使用指定值', async () => {
|
|
253
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
254
|
-
const version = await createVersion(user1Cookie, project.id, 'v1.0');
|
|
255
|
-
const { status, data } = await createTask(user1Cookie, project.id, '指定版本任务', {
|
|
256
|
-
version_id: version.id,
|
|
257
|
-
});
|
|
258
|
-
expect(status).toBe(201);
|
|
259
|
-
expect(data.version_id).toBe(version.id);
|
|
260
|
-
});
|
|
261
|
-
});
|
|
262
|
-
describe('GET /api/tasks — 任务列表', () => {
|
|
263
|
-
it('VAL-CORE-023: 返回项目任务列表', async () => {
|
|
264
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
265
|
-
await createTask(user1Cookie, project.id, '任务1');
|
|
266
|
-
await createTask(user1Cookie, project.id, '任务2');
|
|
267
|
-
const res = await request(app)
|
|
268
|
-
.get(`/api/tasks?project_id=${project.id}`)
|
|
269
|
-
.set('Cookie', user1Cookie);
|
|
270
|
-
expect(res.status).toBe(200);
|
|
271
|
-
expect(res.body.success).toBe(true);
|
|
272
|
-
expect(Array.isArray(res.body.data)).toBe(true);
|
|
273
|
-
expect(res.body.data.length).toBe(2);
|
|
274
|
-
});
|
|
275
|
-
it('按 status 过滤', async () => {
|
|
276
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
277
|
-
const t1 = await createTask(user1Cookie, project.id, '任务1');
|
|
278
|
-
// 激活一个任务
|
|
279
|
-
await request(app)
|
|
280
|
-
.post(`/api/tasks/${t1.data.id}/activate`)
|
|
281
|
-
.set('Cookie', user1Cookie);
|
|
282
|
-
const res = await request(app)
|
|
283
|
-
.get(`/api/tasks?project_id=${project.id}&status=in_progress`)
|
|
284
|
-
.set('Cookie', user1Cookie);
|
|
285
|
-
expect(res.status).toBe(200);
|
|
286
|
-
expect(res.body.data.length).toBe(1);
|
|
287
|
-
expect(res.body.data[0].status).toBe('in_progress');
|
|
288
|
-
});
|
|
289
|
-
it('按 version_id 过滤', async () => {
|
|
290
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
291
|
-
const v1 = await createVersion(user1Cookie, project.id, 'v1.0');
|
|
292
|
-
await startVersion(user1Cookie, v1.id);
|
|
293
|
-
const t1 = await createTask(user1Cookie, project.id, '完成版1任务', { version_id: v1.id });
|
|
294
|
-
await request(app)
|
|
295
|
-
.post(`/api/tasks/${t1.data.id}/complete`)
|
|
296
|
-
.set('Cookie', user1Cookie);
|
|
297
|
-
await request(app)
|
|
298
|
-
.post(`/api/versions/${v1.id}/complete`)
|
|
299
|
-
.set('Cookie', user1Cookie);
|
|
300
|
-
await request(app)
|
|
301
|
-
.delete(`/api/tasks/${t1.data.id}`)
|
|
302
|
-
.set('Cookie', user1Cookie);
|
|
303
|
-
const v2 = await createVersion(user1Cookie, project.id, 'v2.0');
|
|
304
|
-
await createTask(user1Cookie, project.id, 'V1任务', { version_id: v1.id });
|
|
305
|
-
await createTask(user1Cookie, project.id, 'V2任务', { version_id: v2.id });
|
|
306
|
-
const res = await request(app)
|
|
307
|
-
.get(`/api/tasks?project_id=${project.id}&version_id=${v1.id}`)
|
|
308
|
-
.set('Cookie', user1Cookie);
|
|
309
|
-
expect(res.status).toBe(200);
|
|
310
|
-
expect(res.body.data.length).toBe(1);
|
|
311
|
-
expect(res.body.data[0].title).toBe('V1任务');
|
|
312
|
-
});
|
|
313
|
-
it('按 parent_id 过滤', async () => {
|
|
314
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
315
|
-
const parent = await createTask(user1Cookie, project.id, '父任务');
|
|
316
|
-
await createTask(user1Cookie, project.id, '子任务1', { parent_id: parent.data.id });
|
|
317
|
-
await createTask(user1Cookie, project.id, '子任务2', { parent_id: parent.data.id });
|
|
318
|
-
const res = await request(app)
|
|
319
|
-
.get(`/api/tasks?project_id=${project.id}&parent_id=${parent.data.id}`)
|
|
320
|
-
.set('Cookie', user1Cookie);
|
|
321
|
-
expect(res.status).toBe(200);
|
|
322
|
-
expect(res.body.data.length).toBe(2);
|
|
323
|
-
});
|
|
324
|
-
it('parent_id=null 返回顶层任务', async () => {
|
|
325
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
326
|
-
const parent = await createTask(user1Cookie, project.id, '父任务');
|
|
327
|
-
await createTask(user1Cookie, project.id, '子任务', { parent_id: parent.data.id });
|
|
328
|
-
const res = await request(app)
|
|
329
|
-
.get(`/api/tasks?project_id=${project.id}&parent_id=null`)
|
|
330
|
-
.set('Cookie', user1Cookie);
|
|
331
|
-
expect(res.status).toBe(200);
|
|
332
|
-
expect(res.body.data.length).toBe(1);
|
|
333
|
-
expect(res.body.data[0].title).toBe('父任务');
|
|
334
|
-
});
|
|
335
|
-
it('VAL-CORE-039: 他人任务不可见', async () => {
|
|
336
|
-
const project = await createProject(user1Cookie, '私有项目');
|
|
337
|
-
await createTask(user1Cookie, project.id, '私有任务');
|
|
338
|
-
const res = await request(app)
|
|
339
|
-
.get(`/api/tasks?project_id=${project.id}`)
|
|
340
|
-
.set('Cookie', user2Cookie);
|
|
341
|
-
expect(res.status).toBe(404);
|
|
342
|
-
});
|
|
343
|
-
it('未认证时返回 401', async () => {
|
|
344
|
-
const res = await request(app)
|
|
345
|
-
.get('/api/tasks?project_id=xxx');
|
|
346
|
-
expect(res.status).toBe(401);
|
|
347
|
-
});
|
|
348
|
-
it('不显示已删除的任务', async () => {
|
|
349
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
350
|
-
const task = await createTask(user1Cookie, project.id, '要删除的任务');
|
|
351
|
-
// 删除任务
|
|
352
|
-
await request(app)
|
|
353
|
-
.delete(`/api/tasks/${task.data.id}`)
|
|
354
|
-
.set('Cookie', user1Cookie);
|
|
355
|
-
const res = await request(app)
|
|
356
|
-
.get(`/api/tasks?project_id=${project.id}`)
|
|
357
|
-
.set('Cookie', user1Cookie);
|
|
358
|
-
expect(res.status).toBe(200);
|
|
359
|
-
expect(res.body.data.length).toBe(0);
|
|
360
|
-
});
|
|
361
|
-
});
|
|
362
|
-
describe('GET /api/tasks/:id — 任务详情(含子任务树)', () => {
|
|
363
|
-
it('VAL-CORE-024: 返回任务及其子任务树', async () => {
|
|
364
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
365
|
-
const parent = await createTask(user1Cookie, project.id, '父任务');
|
|
366
|
-
const child1 = await createTask(user1Cookie, project.id, '子任务1', { parent_id: parent.data.id });
|
|
367
|
-
const child2 = await createTask(user1Cookie, project.id, '子任务2', { parent_id: parent.data.id });
|
|
368
|
-
const res = await request(app)
|
|
369
|
-
.get(`/api/tasks/${parent.data.id}`)
|
|
370
|
-
.set('Cookie', user1Cookie);
|
|
371
|
-
expect(res.status).toBe(200);
|
|
372
|
-
expect(res.body.success).toBe(true);
|
|
373
|
-
expect(res.body.data.id).toBe(parent.data.id);
|
|
374
|
-
expect(res.body.data.title).toBe('父任务');
|
|
375
|
-
expect(res.body.data.children).toBeDefined();
|
|
376
|
-
expect(res.body.data.children.length).toBe(2);
|
|
377
|
-
expect(res.body.data.children[0].title).toBe('子任务1');
|
|
378
|
-
expect(res.body.data.children[1].title).toBe('子任务2');
|
|
379
|
-
});
|
|
380
|
-
it('子任务也有自己的子任务(多层树)', async () => {
|
|
381
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
382
|
-
const l1 = await createTask(user1Cookie, project.id, 'L1');
|
|
383
|
-
const l2 = await createTask(user1Cookie, project.id, 'L2', { parent_id: l1.data.id });
|
|
384
|
-
await createTask(user1Cookie, project.id, 'L3', { parent_id: l2.data.id });
|
|
385
|
-
const res = await request(app)
|
|
386
|
-
.get(`/api/tasks/${l1.data.id}`)
|
|
387
|
-
.set('Cookie', user1Cookie);
|
|
388
|
-
expect(res.status).toBe(200);
|
|
389
|
-
expect(res.body.data.children.length).toBe(1);
|
|
390
|
-
expect(res.body.data.children[0].children.length).toBe(1);
|
|
391
|
-
expect(res.body.data.children[0].children[0].title).toBe('L3');
|
|
392
|
-
});
|
|
393
|
-
it('VAL-CORE-039: 他人任务返回 404', async () => {
|
|
394
|
-
const project = await createProject(user1Cookie, '私有项目');
|
|
395
|
-
const task = await createTask(user1Cookie, project.id, '私有任务');
|
|
396
|
-
const res = await request(app)
|
|
397
|
-
.get(`/api/tasks/${task.data.id}`)
|
|
398
|
-
.set('Cookie', user2Cookie);
|
|
399
|
-
expect(res.status).toBe(404);
|
|
400
|
-
});
|
|
401
|
-
it('不存在的任务返回 404', async () => {
|
|
402
|
-
const res = await request(app)
|
|
403
|
-
.get('/api/tasks/nonexistent')
|
|
404
|
-
.set('Cookie', user1Cookie);
|
|
405
|
-
expect(res.status).toBe(404);
|
|
406
|
-
});
|
|
407
|
-
it('已删除的任务返回 404', async () => {
|
|
408
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
409
|
-
const task = await createTask(user1Cookie, project.id, '要删除的任务');
|
|
410
|
-
await request(app)
|
|
411
|
-
.delete(`/api/tasks/${task.data.id}`)
|
|
412
|
-
.set('Cookie', user1Cookie);
|
|
413
|
-
const res = await request(app)
|
|
414
|
-
.get(`/api/tasks/${task.data.id}`)
|
|
415
|
-
.set('Cookie', user1Cookie);
|
|
416
|
-
expect(res.status).toBe(404);
|
|
417
|
-
});
|
|
418
|
-
});
|
|
419
|
-
describe('PUT /api/tasks/:id — 更新任务', () => {
|
|
420
|
-
it('VAL-CORE-025: 更新任务 title 成功', async () => {
|
|
421
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
422
|
-
const task = await createTask(user1Cookie, project.id, '旧标题');
|
|
423
|
-
const res = await request(app)
|
|
424
|
-
.put(`/api/tasks/${task.data.id}`)
|
|
425
|
-
.set('Cookie', user1Cookie)
|
|
426
|
-
.send({ title: '新标题' });
|
|
427
|
-
expect(res.status).toBe(200);
|
|
428
|
-
expect(res.body.success).toBe(true);
|
|
429
|
-
expect(res.body.data.title).toBe('新标题');
|
|
430
|
-
});
|
|
431
|
-
it('更新 description 成功', async () => {
|
|
432
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
433
|
-
const task = await createTask(user1Cookie, project.id, '任务');
|
|
434
|
-
const res = await request(app)
|
|
435
|
-
.put(`/api/tasks/${task.data.id}`)
|
|
436
|
-
.set('Cookie', user1Cookie)
|
|
437
|
-
.send({ description: '新描述' });
|
|
438
|
-
expect(res.status).toBe(200);
|
|
439
|
-
expect(res.body.data.description).toBe('新描述');
|
|
440
|
-
});
|
|
441
|
-
it('更新 notes 成功', async () => {
|
|
442
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
443
|
-
const task = await createTask(user1Cookie, project.id, '任务');
|
|
444
|
-
const res = await request(app)
|
|
445
|
-
.put(`/api/tasks/${task.data.id}`)
|
|
446
|
-
.set('Cookie', user1Cookie)
|
|
447
|
-
.send({ notes: '新备注' });
|
|
448
|
-
expect(res.status).toBe(200);
|
|
449
|
-
expect(res.body.data.notes).toBe('新备注');
|
|
450
|
-
});
|
|
451
|
-
it('更新 estimated_days 成功', async () => {
|
|
452
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
453
|
-
const task = await createTask(user1Cookie, project.id, '任务');
|
|
454
|
-
const res = await request(app)
|
|
455
|
-
.put(`/api/tasks/${task.data.id}`)
|
|
456
|
-
.set('Cookie', user1Cookie)
|
|
457
|
-
.send({ estimated_days: 10 });
|
|
458
|
-
expect(res.status).toBe(200);
|
|
459
|
-
expect(res.body.data.estimated_days).toBe(10);
|
|
460
|
-
});
|
|
461
|
-
it('更新后 GET 确认值已变更', async () => {
|
|
462
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
463
|
-
const task = await createTask(user1Cookie, project.id, '旧标题');
|
|
464
|
-
await request(app)
|
|
465
|
-
.put(`/api/tasks/${task.data.id}`)
|
|
466
|
-
.set('Cookie', user1Cookie)
|
|
467
|
-
.send({ title: '新标题', description: '新描述' });
|
|
468
|
-
const getRes = await request(app)
|
|
469
|
-
.get(`/api/tasks/${task.data.id}`)
|
|
470
|
-
.set('Cookie', user1Cookie);
|
|
471
|
-
expect(getRes.body.data.title).toBe('新标题');
|
|
472
|
-
expect(getRes.body.data.description).toBe('新描述');
|
|
473
|
-
});
|
|
474
|
-
it('VAL-CORE-039: 更新他人任务返回 404', async () => {
|
|
475
|
-
const project = await createProject(user1Cookie, '私有项目');
|
|
476
|
-
const task = await createTask(user1Cookie, project.id, '私有任务');
|
|
477
|
-
const res = await request(app)
|
|
478
|
-
.put(`/api/tasks/${task.data.id}`)
|
|
479
|
-
.set('Cookie', user2Cookie)
|
|
480
|
-
.send({ title: '试图修改' });
|
|
481
|
-
expect(res.status).toBe(404);
|
|
482
|
-
});
|
|
483
|
-
it('更新不存在的任务返回 404', async () => {
|
|
484
|
-
const res = await request(app)
|
|
485
|
-
.put('/api/tasks/nonexistent')
|
|
486
|
-
.set('Cookie', user1Cookie)
|
|
487
|
-
.send({ title: '不存在' });
|
|
488
|
-
expect(res.status).toBe(404);
|
|
489
|
-
});
|
|
490
|
-
it('更新 title 为空字符串返回 400', async () => {
|
|
491
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
492
|
-
const task = await createTask(user1Cookie, project.id, '任务');
|
|
493
|
-
const res = await request(app)
|
|
494
|
-
.put(`/api/tasks/${task.data.id}`)
|
|
495
|
-
.set('Cookie', user1Cookie)
|
|
496
|
-
.send({ title: '' });
|
|
497
|
-
expect(res.status).toBe(400);
|
|
498
|
-
expect(res.body.success).toBe(false);
|
|
499
|
-
});
|
|
500
|
-
});
|
|
501
|
-
describe('DELETE /api/tasks/:id — 删除任务(级联软删除)', () => {
|
|
502
|
-
it('VAL-CORE-030: 删除任务成功', async () => {
|
|
503
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
504
|
-
const task = await createTask(user1Cookie, project.id, '要删除的任务');
|
|
505
|
-
const res = await request(app)
|
|
506
|
-
.delete(`/api/tasks/${task.data.id}`)
|
|
507
|
-
.set('Cookie', user1Cookie);
|
|
508
|
-
expect(res.status).toBe(200);
|
|
509
|
-
expect(res.body.success).toBe(true);
|
|
510
|
-
});
|
|
511
|
-
it('VAL-CORE-030: 级联软删除子任务', async () => {
|
|
512
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
513
|
-
const parent = await createTask(user1Cookie, project.id, '父任务');
|
|
514
|
-
const child1 = await createTask(user1Cookie, project.id, '子任务1', { parent_id: parent.data.id });
|
|
515
|
-
const child2 = await createTask(user1Cookie, project.id, '子任务2', { parent_id: parent.data.id });
|
|
516
|
-
// 孙子任务
|
|
517
|
-
await createTask(user1Cookie, project.id, '孙子任务', { parent_id: child1.data.id });
|
|
518
|
-
// 删除父任务
|
|
519
|
-
await request(app)
|
|
520
|
-
.delete(`/api/tasks/${parent.data.id}`)
|
|
521
|
-
.set('Cookie', user1Cookie);
|
|
522
|
-
// 父任务不可见
|
|
523
|
-
const parentRes = await request(app)
|
|
524
|
-
.get(`/api/tasks/${parent.data.id}`)
|
|
525
|
-
.set('Cookie', user1Cookie);
|
|
526
|
-
expect(parentRes.status).toBe(404);
|
|
527
|
-
// 子任务也不可见
|
|
528
|
-
const childRes = await request(app)
|
|
529
|
-
.get(`/api/tasks/${child1.data.id}`)
|
|
530
|
-
.set('Cookie', user1Cookie);
|
|
531
|
-
expect(childRes.status).toBe(404);
|
|
532
|
-
const child2Res = await request(app)
|
|
533
|
-
.get(`/api/tasks/${child2.data.id}`)
|
|
534
|
-
.set('Cookie', user1Cookie);
|
|
535
|
-
expect(child2Res.status).toBe(404);
|
|
536
|
-
});
|
|
537
|
-
it('删除后列表不再包含该任务', async () => {
|
|
538
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
539
|
-
const task = await createTask(user1Cookie, project.id, '要删除的任务');
|
|
540
|
-
await request(app)
|
|
541
|
-
.delete(`/api/tasks/${task.data.id}`)
|
|
542
|
-
.set('Cookie', user1Cookie);
|
|
543
|
-
const res = await request(app)
|
|
544
|
-
.get(`/api/tasks?project_id=${project.id}`)
|
|
545
|
-
.set('Cookie', user1Cookie);
|
|
546
|
-
const ids = res.body.data.map((t) => t.id);
|
|
547
|
-
expect(ids).not.toContain(task.data.id);
|
|
548
|
-
});
|
|
549
|
-
it('VAL-CORE-039: 删除他人任务返回 404', async () => {
|
|
550
|
-
const project = await createProject(user1Cookie, '私有项目');
|
|
551
|
-
const task = await createTask(user1Cookie, project.id, '私有任务');
|
|
552
|
-
const res = await request(app)
|
|
553
|
-
.delete(`/api/tasks/${task.data.id}`)
|
|
554
|
-
.set('Cookie', user2Cookie);
|
|
555
|
-
expect(res.status).toBe(404);
|
|
556
|
-
});
|
|
557
|
-
it('删除不存在的任务返回 404', async () => {
|
|
558
|
-
const res = await request(app)
|
|
559
|
-
.delete('/api/tasks/nonexistent')
|
|
560
|
-
.set('Cookie', user1Cookie);
|
|
561
|
-
expect(res.status).toBe(404);
|
|
562
|
-
});
|
|
563
|
-
});
|
|
564
|
-
describe('PUT /api/tasks/reorder — 任务排序', () => {
|
|
565
|
-
it('VAL-CORE-032: 按 ID 数组重新排序任务', async () => {
|
|
566
|
-
const project = await createProject(user1Cookie, '测试项目');
|
|
567
|
-
const t1 = await createTask(user1Cookie, project.id, '任务1');
|
|
568
|
-
const t2 = await createTask(user1Cookie, project.id, '任务2');
|
|
569
|
-
const t3 = await createTask(user1Cookie, project.id, '任务3');
|
|
570
|
-
// 反转顺序:t3, t2, t1
|
|
571
|
-
const res = await request(app)
|
|
572
|
-
.put('/api/tasks/reorder')
|
|
573
|
-
.set('Cookie', user1Cookie)
|
|
574
|
-
.send({ task_ids: [t3.data.id, t2.data.id, t1.data.id] });
|
|
575
|
-
expect(res.status).toBe(200);
|
|
576
|
-
expect(res.body.success).toBe(true);
|
|
577
|
-
// 确认顺序
|
|
578
|
-
const listRes = await request(app)
|
|
579
|
-
.get(`/api/tasks?project_id=${project.id}`)
|
|
580
|
-
.set('Cookie', user1Cookie);
|
|
581
|
-
expect(listRes.body.data[0].id).toBe(t3.data.id);
|
|
582
|
-
expect(listRes.body.data[1].id).toBe(t2.data.id);
|
|
583
|
-
expect(listRes.body.data[2].id).toBe(t1.data.id);
|
|
584
|
-
});
|
|
585
|
-
it('task_ids 不是数组时返回 400', async () => {
|
|
586
|
-
const res = await request(app)
|
|
587
|
-
.put('/api/tasks/reorder')
|
|
588
|
-
.set('Cookie', user1Cookie)
|
|
589
|
-
.send({ task_ids: 'not-an-array' });
|
|
590
|
-
expect(res.status).toBe(400);
|
|
591
|
-
expect(res.body.success).toBe(false);
|
|
592
|
-
});
|
|
593
|
-
it('缺少 task_ids 时返回 400', async () => {
|
|
594
|
-
const res = await request(app)
|
|
595
|
-
.put('/api/tasks/reorder')
|
|
596
|
-
.set('Cookie', user1Cookie)
|
|
597
|
-
.send({});
|
|
598
|
-
expect(res.status).toBe(400);
|
|
599
|
-
expect(res.body.success).toBe(false);
|
|
600
|
-
});
|
|
601
|
-
it('他人任务排序返回 403', async () => {
|
|
602
|
-
const project = await createProject(user1Cookie, '私有项目');
|
|
603
|
-
const task = await createTask(user1Cookie, project.id, '私有任务');
|
|
604
|
-
const res = await request(app)
|
|
605
|
-
.put('/api/tasks/reorder')
|
|
606
|
-
.set('Cookie', user2Cookie)
|
|
607
|
-
.send({ task_ids: [task.data.id] });
|
|
608
|
-
expect(res.status).toBe(403);
|
|
609
|
-
});
|
|
610
|
-
it('未认证时返回 401', async () => {
|
|
611
|
-
const res = await request(app)
|
|
612
|
-
.put('/api/tasks/reorder')
|
|
613
|
-
.send({ task_ids: ['xxx'] });
|
|
614
|
-
expect(res.status).toBe(401);
|
|
615
|
-
});
|
|
616
|
-
});
|
|
617
|
-
//# sourceMappingURL=tasks-crud.test.js.map
|