@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.
- 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,694 +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, writeFileSync } from 'fs';
|
|
5
|
-
import { join } from 'path';
|
|
6
|
-
import { tmpdir } from 'os';
|
|
7
|
-
import { handleGetCurrentTask } from '../../mcp/tools/get-current-task.js';
|
|
8
|
-
import { handleGetTask } from '../../mcp/tools/get-task.js';
|
|
9
|
-
import { handleListTasks } from '../../mcp/tools/list-tasks.js';
|
|
10
|
-
// 每个测试用唯一的临时目录,避免并行测试冲突
|
|
11
|
-
let TEST_DIR;
|
|
12
|
-
let TEST_DB_PATH;
|
|
13
|
-
let app;
|
|
14
|
-
// 测试用户 cookie
|
|
15
|
-
let user1Cookie;
|
|
16
|
-
let user1Id;
|
|
17
|
-
let user2Cookie;
|
|
18
|
-
let user2Id;
|
|
19
|
-
let projectId;
|
|
20
|
-
// 每个测试独立的 token,通过 session 创建
|
|
21
|
-
let user1Token;
|
|
22
|
-
let user2Token;
|
|
23
|
-
beforeAll(async () => {
|
|
24
|
-
TEST_DIR = join(tmpdir(), `omt-mcp-test-${Date.now()}`);
|
|
25
|
-
TEST_DB_PATH = join(TEST_DIR, 'data', 'data.db');
|
|
26
|
-
mkdirSync(join(TEST_DIR, 'data'), { recursive: true });
|
|
27
|
-
process.env.DB_PATH = TEST_DB_PATH;
|
|
28
|
-
// 初始化数据库
|
|
29
|
-
const db = new Database(TEST_DB_PATH);
|
|
30
|
-
db.pragma('journal_mode = WAL');
|
|
31
|
-
db.pragma('foreign_keys = ON');
|
|
32
|
-
const schemaSql = readFileSync(join(process.cwd(), 'src', 'db', 'schema.sql'), 'utf-8');
|
|
33
|
-
db.exec(schemaSql);
|
|
34
|
-
db.close();
|
|
35
|
-
// 动态导入 app
|
|
36
|
-
const serverModule = await import('../../api/server.js');
|
|
37
|
-
app = serverModule.default;
|
|
38
|
-
});
|
|
39
|
-
afterAll(() => {
|
|
40
|
-
try {
|
|
41
|
-
if (existsSync(TEST_DIR)) {
|
|
42
|
-
rmSync(TEST_DIR, { recursive: true, force: true });
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
catch {
|
|
46
|
-
// Windows 可能因文件锁定无法立即删除
|
|
47
|
-
}
|
|
48
|
-
delete process.env.DB_PATH;
|
|
49
|
-
});
|
|
50
|
-
beforeEach(async () => {
|
|
51
|
-
// 注册用户1(admin)
|
|
52
|
-
const res1 = await request(app)
|
|
53
|
-
.post('/api/auth/register')
|
|
54
|
-
.send({
|
|
55
|
-
name: 'User1',
|
|
56
|
-
email: `user1-mcp-${Date.now()}@test.com`,
|
|
57
|
-
password: 'UserPass123'
|
|
58
|
-
});
|
|
59
|
-
user1Cookie = res1.headers['set-cookie']?.[0] || '';
|
|
60
|
-
user1Id = res1.body.data?.user?.id || '';
|
|
61
|
-
// 注册用户2(member)
|
|
62
|
-
const res2 = await request(app)
|
|
63
|
-
.post('/api/auth/register')
|
|
64
|
-
.send({
|
|
65
|
-
name: 'User2',
|
|
66
|
-
email: `user2-mcp-${Date.now()}@test.com`,
|
|
67
|
-
password: 'UserPass123'
|
|
68
|
-
});
|
|
69
|
-
user2Cookie = res2.headers['set-cookie']?.[0] || '';
|
|
70
|
-
user2Id = res2.body.data?.user?.id || '';
|
|
71
|
-
// 创建项目
|
|
72
|
-
const projRes = await request(app)
|
|
73
|
-
.post('/api/projects')
|
|
74
|
-
.set('Cookie', user1Cookie)
|
|
75
|
-
.send({ name: 'MCP测试项目' });
|
|
76
|
-
projectId = projRes.body.data?.id || '';
|
|
77
|
-
// 通过 session 创建 Token 用于 Bearer 认证
|
|
78
|
-
const tokenRes1 = await request(app)
|
|
79
|
-
.post('/api/tokens')
|
|
80
|
-
.set('Cookie', user1Cookie)
|
|
81
|
-
.send({ name: `test-token-u1-${Date.now()}` });
|
|
82
|
-
user1Token = tokenRes1.body.data?.token?.plain_token || '';
|
|
83
|
-
const tokenRes2 = await request(app)
|
|
84
|
-
.post('/api/tokens')
|
|
85
|
-
.set('Cookie', user2Cookie)
|
|
86
|
-
.send({ name: `test-token-u2-${Date.now()}` });
|
|
87
|
-
user2Token = tokenRes2.body.data?.token?.plain_token || '';
|
|
88
|
-
});
|
|
89
|
-
describe('MCP Tools — 通过 HTTP API 模拟', () => {
|
|
90
|
-
// ========================
|
|
91
|
-
// VAL-MCP-001: init_project 自动创建项目
|
|
92
|
-
// ========================
|
|
93
|
-
describe('VAL-MCP-001: init_project 自动创建项目', () => {
|
|
94
|
-
it('项目不存在时创建新项目', async () => {
|
|
95
|
-
const res = await request(app)
|
|
96
|
-
.post('/api/projects')
|
|
97
|
-
.set('Authorization', `Bearer ${user1Token}`)
|
|
98
|
-
.send({ name: '新MCP项目', description: '测试描述' });
|
|
99
|
-
expect(res.status).toBe(201);
|
|
100
|
-
expect(res.body.success).toBe(true);
|
|
101
|
-
expect(res.body.data).toHaveProperty('id');
|
|
102
|
-
expect(res.body.data.name).toBe('新MCP项目');
|
|
103
|
-
expect(res.body.data.owner_id).toBe(user1Id);
|
|
104
|
-
});
|
|
105
|
-
it('同名项目已存在时返回 409', async () => {
|
|
106
|
-
// 先创建
|
|
107
|
-
await request(app)
|
|
108
|
-
.post('/api/projects')
|
|
109
|
-
.set('Authorization', `Bearer ${user1Token}`)
|
|
110
|
-
.send({ name: '重复项目' });
|
|
111
|
-
// 再创建同名
|
|
112
|
-
const res = await request(app)
|
|
113
|
-
.post('/api/projects')
|
|
114
|
-
.set('Authorization', `Bearer ${user1Token}`)
|
|
115
|
-
.send({ name: '重复项目' });
|
|
116
|
-
expect(res.status).toBe(409);
|
|
117
|
-
expect(res.body.success).toBe(false);
|
|
118
|
-
});
|
|
119
|
-
});
|
|
120
|
-
// ========================
|
|
121
|
-
// VAL-MCP-002: init_project 已有项目返回现有
|
|
122
|
-
// ========================
|
|
123
|
-
describe('VAL-MCP-002: init_project 已有项目返回现有', () => {
|
|
124
|
-
it('通过 GET /api/projects 获取现有项目列表', async () => {
|
|
125
|
-
const res = await request(app)
|
|
126
|
-
.get('/api/projects')
|
|
127
|
-
.set('Authorization', `Bearer ${user1Token}`);
|
|
128
|
-
expect(res.status).toBe(200);
|
|
129
|
-
expect(res.body.success).toBe(true);
|
|
130
|
-
expect(Array.isArray(res.body.data)).toBe(true);
|
|
131
|
-
// 至少包含 beforeEach 中创建的项目
|
|
132
|
-
expect(res.body.data.length).toBeGreaterThanOrEqual(1);
|
|
133
|
-
const project = res.body.data.find((p) => p.name === 'MCP测试项目');
|
|
134
|
-
expect(project).toBeDefined();
|
|
135
|
-
expect(project.id).toBe(projectId);
|
|
136
|
-
});
|
|
137
|
-
});
|
|
138
|
-
// ========================
|
|
139
|
-
// VAL-MCP-003: create_version 创建版本
|
|
140
|
-
// ========================
|
|
141
|
-
describe('VAL-MCP-003: create_version 创建版本', () => {
|
|
142
|
-
it('创建版本成功', async () => {
|
|
143
|
-
const res = await request(app)
|
|
144
|
-
.post('/api/versions')
|
|
145
|
-
.set('Authorization', `Bearer ${user1Token}`)
|
|
146
|
-
.send({ project_id: projectId, name: 'v1.0', due_date: '2026-05-30' });
|
|
147
|
-
expect(res.status).toBe(201);
|
|
148
|
-
expect(res.body.success).toBe(true);
|
|
149
|
-
expect(res.body.data).toHaveProperty('id');
|
|
150
|
-
expect(res.body.data.name).toBe('v1.0');
|
|
151
|
-
expect(res.body.data.project_id).toBe(projectId);
|
|
152
|
-
});
|
|
153
|
-
it('缺少 name 时返回 400', async () => {
|
|
154
|
-
const res = await request(app)
|
|
155
|
-
.post('/api/versions')
|
|
156
|
-
.set('Authorization', `Bearer ${user1Token}`)
|
|
157
|
-
.send({ project_id: projectId });
|
|
158
|
-
expect(res.status).toBe(400);
|
|
159
|
-
expect(res.body.success).toBe(false);
|
|
160
|
-
});
|
|
161
|
-
});
|
|
162
|
-
// ========================
|
|
163
|
-
// VAL-MCP-004: list_versions 列出版本
|
|
164
|
-
// ========================
|
|
165
|
-
describe('VAL-MCP-004: list_versions 列出版本并标记活跃', () => {
|
|
166
|
-
it('返回项目的版本列表', async () => {
|
|
167
|
-
// 创建第一个版本并结束
|
|
168
|
-
const v1 = await request(app)
|
|
169
|
-
.post('/api/versions')
|
|
170
|
-
.set('Authorization', `Bearer ${user1Token}`)
|
|
171
|
-
.send({ project_id: projectId, name: 'v1.0', due_date: '2026-05-30' });
|
|
172
|
-
await request(app)
|
|
173
|
-
.post(`/api/versions/${v1.body.data.id}/start`)
|
|
174
|
-
.set('Authorization', `Bearer ${user1Token}`);
|
|
175
|
-
const task = await request(app)
|
|
176
|
-
.post('/api/tasks')
|
|
177
|
-
.set('Authorization', `Bearer ${user1Token}`)
|
|
178
|
-
.send({ project_id: projectId, version_id: v1.body.data.id, title: 'v1任务' });
|
|
179
|
-
await request(app)
|
|
180
|
-
.post(`/api/tasks/${task.body.data.id}/complete`)
|
|
181
|
-
.set('Authorization', `Bearer ${user1Token}`);
|
|
182
|
-
await request(app)
|
|
183
|
-
.post(`/api/versions/${v1.body.data.id}/complete`)
|
|
184
|
-
.set('Authorization', `Bearer ${user1Token}`);
|
|
185
|
-
await request(app)
|
|
186
|
-
.post('/api/versions')
|
|
187
|
-
.set('Authorization', `Bearer ${user1Token}`)
|
|
188
|
-
.send({ project_id: projectId, name: 'v2.0', due_date: '2026-05-30' });
|
|
189
|
-
const res = await request(app)
|
|
190
|
-
.get(`/api/versions?project_id=${projectId}`)
|
|
191
|
-
.set('Authorization', `Bearer ${user1Token}`);
|
|
192
|
-
expect(res.status).toBe(200);
|
|
193
|
-
expect(res.body.success).toBe(true);
|
|
194
|
-
expect(res.body.data.length).toBe(2);
|
|
195
|
-
// 标记活跃版本
|
|
196
|
-
const activeVersion = res.body.data.find((v) => v.locked_at !== null && v.completed_at === null);
|
|
197
|
-
// 此时没有活跃版本
|
|
198
|
-
expect(activeVersion).toBeUndefined();
|
|
199
|
-
});
|
|
200
|
-
it('开始版本后标记为活跃', async () => {
|
|
201
|
-
// 创建版本
|
|
202
|
-
const vRes = await request(app)
|
|
203
|
-
.post('/api/versions')
|
|
204
|
-
.set('Authorization', `Bearer ${user1Token}`)
|
|
205
|
-
.send({ project_id: projectId, name: 'v1.0', due_date: '2026-05-30' });
|
|
206
|
-
const versionId = vRes.body.data.id;
|
|
207
|
-
// 开始版本
|
|
208
|
-
await request(app)
|
|
209
|
-
.post(`/api/versions/${versionId}/start`)
|
|
210
|
-
.set('Authorization', `Bearer ${user1Token}`);
|
|
211
|
-
const res = await request(app)
|
|
212
|
-
.get(`/api/versions?project_id=${projectId}`)
|
|
213
|
-
.set('Authorization', `Bearer ${user1Token}`);
|
|
214
|
-
expect(res.status).toBe(200);
|
|
215
|
-
const activeVersion = res.body.data.find((v) => v.id === versionId && v.locked_at !== null);
|
|
216
|
-
expect(activeVersion).toBeDefined();
|
|
217
|
-
});
|
|
218
|
-
});
|
|
219
|
-
// ========================
|
|
220
|
-
// VAL-MCP-005: create_task 创建任务
|
|
221
|
-
// ========================
|
|
222
|
-
describe('VAL-MCP-005: create_task 创建任务', () => {
|
|
223
|
-
let versionId;
|
|
224
|
-
beforeEach(async () => {
|
|
225
|
-
const vRes = await request(app)
|
|
226
|
-
.post('/api/versions')
|
|
227
|
-
.set('Authorization', `Bearer ${user1Token}`)
|
|
228
|
-
.send({ project_id: projectId, name: 'v1.0', due_date: '2026-05-30' });
|
|
229
|
-
versionId = vRes.body.data.id;
|
|
230
|
-
await request(app)
|
|
231
|
-
.post(`/api/versions/${versionId}/start`)
|
|
232
|
-
.set('Authorization', `Bearer ${user1Token}`);
|
|
233
|
-
});
|
|
234
|
-
it('创建任务成功,自动关联活跃版本', async () => {
|
|
235
|
-
const res = await request(app)
|
|
236
|
-
.post('/api/tasks')
|
|
237
|
-
.set('Authorization', `Bearer ${user1Token}`)
|
|
238
|
-
.send({
|
|
239
|
-
project_id: projectId,
|
|
240
|
-
title: '测试任务',
|
|
241
|
-
});
|
|
242
|
-
expect(res.status).toBe(201);
|
|
243
|
-
expect(res.body.success).toBe(true);
|
|
244
|
-
expect(res.body.data.title).toBe('测试任务');
|
|
245
|
-
expect(res.body.data.status).toBe('planned');
|
|
246
|
-
});
|
|
247
|
-
it('指定 version_id 创建任务', async () => {
|
|
248
|
-
const res = await request(app)
|
|
249
|
-
.post('/api/tasks')
|
|
250
|
-
.set('Authorization', `Bearer ${user1Token}`)
|
|
251
|
-
.send({
|
|
252
|
-
project_id: projectId,
|
|
253
|
-
version_id: versionId,
|
|
254
|
-
title: '指定版本任务',
|
|
255
|
-
});
|
|
256
|
-
expect(res.status).toBe(201);
|
|
257
|
-
expect(res.body.data.version_id).toBe(versionId);
|
|
258
|
-
});
|
|
259
|
-
});
|
|
260
|
-
// ========================
|
|
261
|
-
// VAL-MCP-006: create_task parent_title 匹配
|
|
262
|
-
// ========================
|
|
263
|
-
describe('VAL-MCP-006: create_task parent_title 匹配', () => {
|
|
264
|
-
it('通过 parent_title 找到父任务并创建子任务', async () => {
|
|
265
|
-
// 先创建父任务
|
|
266
|
-
const parentRes = await request(app)
|
|
267
|
-
.post('/api/tasks')
|
|
268
|
-
.set('Authorization', `Bearer ${user1Token}`)
|
|
269
|
-
.send({ project_id: projectId, title: '父任务标题' });
|
|
270
|
-
const parentId = parentRes.body.data.id;
|
|
271
|
-
// 查找父任务(模拟 parent_title 匹配逻辑)
|
|
272
|
-
const listRes = await request(app)
|
|
273
|
-
.get(`/api/tasks?project_id=${projectId}`)
|
|
274
|
-
.set('Authorization', `Bearer ${user1Token}`);
|
|
275
|
-
const parentTask = listRes.body.data.find((t) => t.title === '父任务标题');
|
|
276
|
-
expect(parentTask).toBeDefined();
|
|
277
|
-
expect(parentTask.id).toBe(parentId);
|
|
278
|
-
// 使用找到的 parent_id 创建子任务
|
|
279
|
-
const childRes = await request(app)
|
|
280
|
-
.post('/api/tasks')
|
|
281
|
-
.set('Authorization', `Bearer ${user1Token}`)
|
|
282
|
-
.send({
|
|
283
|
-
project_id: projectId,
|
|
284
|
-
parent_id: parentId,
|
|
285
|
-
title: '子任务',
|
|
286
|
-
});
|
|
287
|
-
expect(childRes.status).toBe(201);
|
|
288
|
-
expect(childRes.body.data.parent_id).toBe(parentId);
|
|
289
|
-
});
|
|
290
|
-
it('parent_title 找不到时返回错误', async () => {
|
|
291
|
-
// 尝试使用不存在的 parent_id
|
|
292
|
-
const res = await request(app)
|
|
293
|
-
.post('/api/tasks')
|
|
294
|
-
.set('Authorization', `Bearer ${user1Token}`)
|
|
295
|
-
.send({
|
|
296
|
-
project_id: projectId,
|
|
297
|
-
parent_id: 'non-existent-id',
|
|
298
|
-
title: '应该失败的任务',
|
|
299
|
-
});
|
|
300
|
-
expect(res.status).toBe(400);
|
|
301
|
-
expect(res.body.success).toBe(false);
|
|
302
|
-
});
|
|
303
|
-
});
|
|
304
|
-
// ========================
|
|
305
|
-
// VAL-MCP-007: list_tasks 查询任务
|
|
306
|
-
// ========================
|
|
307
|
-
describe('VAL-MCP-007: list_tasks 按状态过滤', () => {
|
|
308
|
-
it('按状态过滤任务列表', async () => {
|
|
309
|
-
// 创建多个任务
|
|
310
|
-
await request(app)
|
|
311
|
-
.post('/api/tasks')
|
|
312
|
-
.set('Authorization', `Bearer ${user1Token}`)
|
|
313
|
-
.send({ project_id: projectId, title: '计划任务' });
|
|
314
|
-
await request(app)
|
|
315
|
-
.post('/api/tasks')
|
|
316
|
-
.set('Authorization', `Bearer ${user1Token}`)
|
|
317
|
-
.send({ project_id: projectId, title: '另一个计划任务' });
|
|
318
|
-
// 查询 planned 状态的任务
|
|
319
|
-
const res = await request(app)
|
|
320
|
-
.get(`/api/tasks?project_id=${projectId}&status=planned`)
|
|
321
|
-
.set('Authorization', `Bearer ${user1Token}`);
|
|
322
|
-
expect(res.status).toBe(200);
|
|
323
|
-
expect(res.body.success).toBe(true);
|
|
324
|
-
expect(res.body.data.length).toBeGreaterThanOrEqual(2);
|
|
325
|
-
res.body.data.forEach((task) => {
|
|
326
|
-
expect(task.status).toBe('planned');
|
|
327
|
-
});
|
|
328
|
-
});
|
|
329
|
-
it('返回空列表当没有匹配任务时', async () => {
|
|
330
|
-
const res = await request(app)
|
|
331
|
-
.get(`/api/tasks?project_id=${projectId}&status=in_progress`)
|
|
332
|
-
.set('Authorization', `Bearer ${user1Token}`);
|
|
333
|
-
expect(res.status).toBe(200);
|
|
334
|
-
expect(res.body.data.length).toBe(0);
|
|
335
|
-
});
|
|
336
|
-
});
|
|
337
|
-
// ========================
|
|
338
|
-
// VAL-MCP-008: get_task 获取任务详情
|
|
339
|
-
// ========================
|
|
340
|
-
describe('VAL-MCP-008: get_task 返回任务树', () => {
|
|
341
|
-
it('返回任务详情含子任务树', async () => {
|
|
342
|
-
// 创建父任务
|
|
343
|
-
const parentRes = await request(app)
|
|
344
|
-
.post('/api/tasks')
|
|
345
|
-
.set('Authorization', `Bearer ${user1Token}`)
|
|
346
|
-
.send({ project_id: projectId, title: '父任务' });
|
|
347
|
-
const parentId = parentRes.body.data.id;
|
|
348
|
-
// 创建子任务
|
|
349
|
-
await request(app)
|
|
350
|
-
.post('/api/tasks')
|
|
351
|
-
.set('Authorization', `Bearer ${user1Token}`)
|
|
352
|
-
.send({ project_id: projectId, parent_id: parentId, title: '子任务1' });
|
|
353
|
-
await request(app)
|
|
354
|
-
.post('/api/tasks')
|
|
355
|
-
.set('Authorization', `Bearer ${user1Token}`)
|
|
356
|
-
.send({ project_id: projectId, parent_id: parentId, title: '子任务2' });
|
|
357
|
-
const res = await request(app)
|
|
358
|
-
.get(`/api/tasks/${parentId}`)
|
|
359
|
-
.set('Authorization', `Bearer ${user1Token}`);
|
|
360
|
-
expect(res.status).toBe(200);
|
|
361
|
-
expect(res.body.success).toBe(true);
|
|
362
|
-
expect(res.body.data.title).toBe('父任务');
|
|
363
|
-
expect(res.body.data.children).toBeDefined();
|
|
364
|
-
expect(res.body.data.children.length).toBe(2);
|
|
365
|
-
expect(res.body.data.children[0].title).toBe('子任务1');
|
|
366
|
-
expect(res.body.data.children[1].title).toBe('子任务2');
|
|
367
|
-
});
|
|
368
|
-
});
|
|
369
|
-
describe('MCP 任务查询瘦身', () => {
|
|
370
|
-
async function createActiveVersion() {
|
|
371
|
-
const versionRes = await request(app)
|
|
372
|
-
.post('/api/versions')
|
|
373
|
-
.set('Authorization', `Bearer ${user1Token}`)
|
|
374
|
-
.send({ project_id: projectId, name: `v-query-${Date.now()}`, due_date: '2026-05-30' });
|
|
375
|
-
const versionId = versionRes.body.data.id;
|
|
376
|
-
await request(app)
|
|
377
|
-
.post(`/api/versions/${versionId}/start`)
|
|
378
|
-
.set('Authorization', `Bearer ${user1Token}`);
|
|
379
|
-
return versionId;
|
|
380
|
-
}
|
|
381
|
-
function createMcpProjectDir() {
|
|
382
|
-
const projectDir = join(TEST_DIR, `mcp-project-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
383
|
-
mkdirSync(projectDir, { recursive: true });
|
|
384
|
-
writeFileSync(join(projectDir, '.omt.json'), JSON.stringify({
|
|
385
|
-
project_id: projectId,
|
|
386
|
-
project_path: projectDir,
|
|
387
|
-
server_url: '',
|
|
388
|
-
created_at: new Date().toISOString(),
|
|
389
|
-
}));
|
|
390
|
-
return projectDir;
|
|
391
|
-
}
|
|
392
|
-
async function withMcpServer(run) {
|
|
393
|
-
const server = app.listen(0);
|
|
394
|
-
try {
|
|
395
|
-
const address = server.address();
|
|
396
|
-
if (!address || typeof address === 'string') {
|
|
397
|
-
throw new Error('Failed to start test server');
|
|
398
|
-
}
|
|
399
|
-
return await run({
|
|
400
|
-
serverUrl: `http://127.0.0.1:${address.port}`,
|
|
401
|
-
token: user1Token,
|
|
402
|
-
});
|
|
403
|
-
}
|
|
404
|
-
finally {
|
|
405
|
-
await new Promise((resolve) => server.close(() => resolve()));
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
it('list_tasks 默认只返回当前任务摘要,不返回完整任务列表', async () => {
|
|
409
|
-
const versionId = await createActiveVersion();
|
|
410
|
-
const projectDir = createMcpProjectDir();
|
|
411
|
-
const currentTask = await request(app)
|
|
412
|
-
.post('/api/tasks')
|
|
413
|
-
.set('Authorization', `Bearer ${user1Token}`)
|
|
414
|
-
.send({ project_id: projectId, version_id: versionId, title: '当前主任务', description: '当前详细描述' });
|
|
415
|
-
await request(app)
|
|
416
|
-
.post(`/api/tasks/${currentTask.body.data.id}/activate`)
|
|
417
|
-
.set('Authorization', `Bearer ${user1Token}`);
|
|
418
|
-
await request(app)
|
|
419
|
-
.post('/api/tasks')
|
|
420
|
-
.set('Authorization', `Bearer ${user1Token}`)
|
|
421
|
-
.send({ project_id: projectId, version_id: versionId, title: '未激活任务', description: '不应出现在默认输出' });
|
|
422
|
-
await withMcpServer(async (context) => {
|
|
423
|
-
const result = await handleListTasks({ path: projectDir }, context);
|
|
424
|
-
const text = result.content[0].text;
|
|
425
|
-
expect(text).toContain('当前主任务');
|
|
426
|
-
expect(text).not.toContain('未激活任务');
|
|
427
|
-
expect(text).not.toContain('当前详细描述');
|
|
428
|
-
});
|
|
429
|
-
});
|
|
430
|
-
it('list_tasks outline 只返回任务关系摘要', async () => {
|
|
431
|
-
const versionId = await createActiveVersion();
|
|
432
|
-
const projectDir = createMcpProjectDir();
|
|
433
|
-
const parent = await request(app)
|
|
434
|
-
.post('/api/tasks')
|
|
435
|
-
.set('Authorization', `Bearer ${user1Token}`)
|
|
436
|
-
.send({ project_id: projectId, version_id: versionId, title: '父级概要任务', description: '父级详细描述' });
|
|
437
|
-
await request(app)
|
|
438
|
-
.post('/api/tasks')
|
|
439
|
-
.set('Authorization', `Bearer ${user1Token}`)
|
|
440
|
-
.send({ project_id: projectId, version_id: versionId, parent_id: parent.body.data.id, title: '子级概要任务', description: '子级详细描述' });
|
|
441
|
-
await withMcpServer(async (context) => {
|
|
442
|
-
const result = await handleListTasks({ path: projectDir, view: 'outline' }, context);
|
|
443
|
-
const text = result.content[0].text;
|
|
444
|
-
expect(text).toContain('父级概要任务');
|
|
445
|
-
expect(text).toContain('子级概要任务');
|
|
446
|
-
expect(text).not.toContain('父级详细描述');
|
|
447
|
-
expect(text).not.toContain('子级详细描述');
|
|
448
|
-
});
|
|
449
|
-
});
|
|
450
|
-
it('get_task 默认返回摘要,detail=full 才返回完整字段', async () => {
|
|
451
|
-
const versionId = await createActiveVersion();
|
|
452
|
-
const projectDir = createMcpProjectDir();
|
|
453
|
-
const parent = await request(app)
|
|
454
|
-
.post('/api/tasks')
|
|
455
|
-
.set('Authorization', `Bearer ${user1Token}`)
|
|
456
|
-
.send({ project_id: projectId, version_id: versionId, title: '详情父任务', description: '完整描述字段' });
|
|
457
|
-
await withMcpServer(async (context) => {
|
|
458
|
-
const summary = await handleGetTask({ path: projectDir, task_id: parent.body.data.id }, context);
|
|
459
|
-
expect(summary.content[0].text).toContain('详情父任务');
|
|
460
|
-
expect(summary.content[0].text).not.toContain('完整描述字段');
|
|
461
|
-
const full = await handleGetTask({ path: projectDir, task_id: parent.body.data.id, detail: 'full' }, context);
|
|
462
|
-
expect(full.content[0].text).toContain('完整描述字段');
|
|
463
|
-
});
|
|
464
|
-
});
|
|
465
|
-
it('get_current_task 返回子任务进度并默认隐藏已完成子任务', async () => {
|
|
466
|
-
const versionId = await createActiveVersion();
|
|
467
|
-
const projectDir = createMcpProjectDir();
|
|
468
|
-
const parent = await request(app)
|
|
469
|
-
.post('/api/tasks')
|
|
470
|
-
.set('Authorization', `Bearer ${user1Token}`)
|
|
471
|
-
.send({ project_id: projectId, version_id: versionId, title: '进行中主任务' });
|
|
472
|
-
await request(app)
|
|
473
|
-
.post(`/api/tasks/${parent.body.data.id}/activate`)
|
|
474
|
-
.set('Authorization', `Bearer ${user1Token}`);
|
|
475
|
-
await request(app)
|
|
476
|
-
.post('/api/tasks')
|
|
477
|
-
.set('Authorization', `Bearer ${user1Token}`)
|
|
478
|
-
.send({ project_id: projectId, version_id: versionId, parent_id: parent.body.data.id, title: '待办子任务' });
|
|
479
|
-
const doneChild = await request(app)
|
|
480
|
-
.post('/api/tasks')
|
|
481
|
-
.set('Authorization', `Bearer ${user1Token}`)
|
|
482
|
-
.send({ project_id: projectId, version_id: versionId, parent_id: parent.body.data.id, title: '已完成子任务' });
|
|
483
|
-
await request(app)
|
|
484
|
-
.post(`/api/tasks/${doneChild.body.data.id}/complete`)
|
|
485
|
-
.set('Authorization', `Bearer ${user1Token}`);
|
|
486
|
-
await withMcpServer(async (context) => {
|
|
487
|
-
const result = await handleGetCurrentTask({ path: projectDir }, context);
|
|
488
|
-
const text = result.content[0].text;
|
|
489
|
-
expect(text).toContain('进行中主任务');
|
|
490
|
-
expect(text).toContain('总数: 2');
|
|
491
|
-
expect(text).toContain('完成: 1');
|
|
492
|
-
expect(text).toContain('待办子任务');
|
|
493
|
-
expect(text).not.toContain('已完成子任务');
|
|
494
|
-
});
|
|
495
|
-
});
|
|
496
|
-
});
|
|
497
|
-
// ========================
|
|
498
|
-
// VAL-MCP-009: activate_task 激活任务
|
|
499
|
-
// ========================
|
|
500
|
-
describe('VAL-MCP-009: activate_task 激活任务', () => {
|
|
501
|
-
it('将任务状态设为 in_progress', async () => {
|
|
502
|
-
const taskRes = await request(app)
|
|
503
|
-
.post('/api/tasks')
|
|
504
|
-
.set('Authorization', `Bearer ${user1Token}`)
|
|
505
|
-
.send({ project_id: projectId, title: '待激活任务' });
|
|
506
|
-
const taskId = taskRes.body.data.id;
|
|
507
|
-
const res = await request(app)
|
|
508
|
-
.post(`/api/tasks/${taskId}/activate`)
|
|
509
|
-
.set('Authorization', `Bearer ${user1Token}`);
|
|
510
|
-
expect(res.status).toBe(200);
|
|
511
|
-
expect(res.body.success).toBe(true);
|
|
512
|
-
expect(res.body.data.status).toBe('in_progress');
|
|
513
|
-
expect(res.body.data.actual_start).toBeDefined();
|
|
514
|
-
});
|
|
515
|
-
});
|
|
516
|
-
// ========================
|
|
517
|
-
// VAL-MCP-010: complete_task 完成任务
|
|
518
|
-
// ========================
|
|
519
|
-
describe('VAL-MCP-010: complete_task 完成任务', () => {
|
|
520
|
-
it('将任务状态设为 done', async () => {
|
|
521
|
-
const taskRes = await request(app)
|
|
522
|
-
.post('/api/tasks')
|
|
523
|
-
.set('Authorization', `Bearer ${user1Token}`)
|
|
524
|
-
.send({ project_id: projectId, title: '待完成任务' });
|
|
525
|
-
const taskId = taskRes.body.data.id;
|
|
526
|
-
const res = await request(app)
|
|
527
|
-
.post(`/api/tasks/${taskId}/complete`)
|
|
528
|
-
.set('Authorization', `Bearer ${user1Token}`);
|
|
529
|
-
expect(res.status).toBe(200);
|
|
530
|
-
expect(res.body.success).toBe(true);
|
|
531
|
-
expect(res.body.data.status).toBe('done');
|
|
532
|
-
});
|
|
533
|
-
it('完成父任务级联完成子任务', async () => {
|
|
534
|
-
const parentRes = await request(app)
|
|
535
|
-
.post('/api/tasks')
|
|
536
|
-
.set('Authorization', `Bearer ${user1Token}`)
|
|
537
|
-
.send({ project_id: projectId, title: '父任务' });
|
|
538
|
-
const parentId = parentRes.body.data.id;
|
|
539
|
-
const childRes = await request(app)
|
|
540
|
-
.post('/api/tasks')
|
|
541
|
-
.set('Authorization', `Bearer ${user1Token}`)
|
|
542
|
-
.send({ project_id: projectId, parent_id: parentId, title: '子任务' });
|
|
543
|
-
const childId = childRes.body.data.id;
|
|
544
|
-
// 完成父任务
|
|
545
|
-
const res = await request(app)
|
|
546
|
-
.post(`/api/tasks/${parentId}/complete`)
|
|
547
|
-
.set('Authorization', `Bearer ${user1Token}`);
|
|
548
|
-
expect(res.status).toBe(200);
|
|
549
|
-
expect(res.body.data.status).toBe('done');
|
|
550
|
-
// 验证子任务也被完成
|
|
551
|
-
const childGet = await request(app)
|
|
552
|
-
.get(`/api/tasks/${childId}`)
|
|
553
|
-
.set('Authorization', `Bearer ${user1Token}`);
|
|
554
|
-
expect(childGet.body.data.status).toBe('done');
|
|
555
|
-
});
|
|
556
|
-
});
|
|
557
|
-
// ========================
|
|
558
|
-
// VAL-MCP-011: delete_task 删除任务
|
|
559
|
-
// ========================
|
|
560
|
-
describe('VAL-MCP-011: delete_task 删除任务', () => {
|
|
561
|
-
it('软删除任务及其子任务', async () => {
|
|
562
|
-
const parentRes = await request(app)
|
|
563
|
-
.post('/api/tasks')
|
|
564
|
-
.set('Authorization', `Bearer ${user1Token}`)
|
|
565
|
-
.send({ project_id: projectId, title: '待删除父任务' });
|
|
566
|
-
const parentId = parentRes.body.data.id;
|
|
567
|
-
await request(app)
|
|
568
|
-
.post('/api/tasks')
|
|
569
|
-
.set('Authorization', `Bearer ${user1Token}`)
|
|
570
|
-
.send({ project_id: projectId, parent_id: parentId, title: '待删除子任务' });
|
|
571
|
-
const res = await request(app)
|
|
572
|
-
.delete(`/api/tasks/${parentId}`)
|
|
573
|
-
.set('Authorization', `Bearer ${user1Token}`);
|
|
574
|
-
expect(res.status).toBe(200);
|
|
575
|
-
expect(res.body.success).toBe(true);
|
|
576
|
-
// 验证父任务不再出现
|
|
577
|
-
const listRes = await request(app)
|
|
578
|
-
.get(`/api/tasks?project_id=${projectId}`)
|
|
579
|
-
.set('Authorization', `Bearer ${user1Token}`);
|
|
580
|
-
const found = listRes.body.data.find((t) => t.id === parentId);
|
|
581
|
-
expect(found).toBeUndefined();
|
|
582
|
-
});
|
|
583
|
-
});
|
|
584
|
-
// ========================
|
|
585
|
-
// VAL-MCP-012: auto_schedule 自动排期
|
|
586
|
-
// ========================
|
|
587
|
-
describe('VAL-MCP-012: auto_schedule 自动排期', () => {
|
|
588
|
-
it('按 sort_order 顺序自动排期所有任务', async () => {
|
|
589
|
-
// 创建版本并开始
|
|
590
|
-
const vRes = await request(app)
|
|
591
|
-
.post('/api/versions')
|
|
592
|
-
.set('Authorization', `Bearer ${user1Token}`)
|
|
593
|
-
.send({ project_id: projectId, name: '排期版本', due_date: '2026-05-30' });
|
|
594
|
-
const versionId = vRes.body.data.id;
|
|
595
|
-
await request(app)
|
|
596
|
-
.post(`/api/versions/${versionId}/start`)
|
|
597
|
-
.set('Authorization', `Bearer ${user1Token}`);
|
|
598
|
-
// 创建任务(带 estimated_days)
|
|
599
|
-
await request(app)
|
|
600
|
-
.post('/api/tasks')
|
|
601
|
-
.set('Authorization', `Bearer ${user1Token}`)
|
|
602
|
-
.send({ project_id: projectId, version_id: versionId, title: '任务1', estimated_days: 2 });
|
|
603
|
-
await request(app)
|
|
604
|
-
.post('/api/tasks')
|
|
605
|
-
.set('Authorization', `Bearer ${user1Token}`)
|
|
606
|
-
.send({ project_id: projectId, version_id: versionId, title: '任务2', estimated_days: 3 });
|
|
607
|
-
// 自动排期
|
|
608
|
-
const res = await request(app)
|
|
609
|
-
.post('/api/schedule/auto')
|
|
610
|
-
.set('Authorization', `Bearer ${user1Token}`)
|
|
611
|
-
.send({ project_id: projectId, start_date: '2026-04-13' });
|
|
612
|
-
expect(res.status).toBe(200);
|
|
613
|
-
expect(res.body.success).toBe(true);
|
|
614
|
-
// 验证任务有日期
|
|
615
|
-
const listRes = await request(app)
|
|
616
|
-
.get(`/api/tasks?project_id=${projectId}&version_id=${versionId}`)
|
|
617
|
-
.set('Authorization', `Bearer ${user1Token}`);
|
|
618
|
-
const tasks = listRes.body.data;
|
|
619
|
-
expect(tasks.length).toBe(2);
|
|
620
|
-
// 任务应该有 start_date 和 due_date
|
|
621
|
-
tasks.forEach((task) => {
|
|
622
|
-
expect(task.start_date).not.toBeNull();
|
|
623
|
-
expect(task.due_date).not.toBeNull();
|
|
624
|
-
});
|
|
625
|
-
});
|
|
626
|
-
});
|
|
627
|
-
// ========================
|
|
628
|
-
// VAL-MCP-013: MCP Token 认证
|
|
629
|
-
// ========================
|
|
630
|
-
describe('VAL-MCP-013: MCP Token 认证', () => {
|
|
631
|
-
it('有效 Token 授权访问受保护 API', async () => {
|
|
632
|
-
const res = await request(app)
|
|
633
|
-
.get('/api/projects')
|
|
634
|
-
.set('Authorization', `Bearer ${user1Token}`);
|
|
635
|
-
expect(res.status).toBe(200);
|
|
636
|
-
expect(res.body.success).toBe(true);
|
|
637
|
-
});
|
|
638
|
-
it('无效 Token 返回 401', async () => {
|
|
639
|
-
const res = await request(app)
|
|
640
|
-
.get('/api/projects')
|
|
641
|
-
.set('Authorization', 'Bearer invalid-token-xxx');
|
|
642
|
-
expect(res.status).toBe(401);
|
|
643
|
-
expect(res.body.success).toBe(false);
|
|
644
|
-
});
|
|
645
|
-
it('无 Token 返回 401', async () => {
|
|
646
|
-
const res = await request(app)
|
|
647
|
-
.get('/api/projects');
|
|
648
|
-
expect(res.status).toBe(401);
|
|
649
|
-
expect(res.body.success).toBe(false);
|
|
650
|
-
});
|
|
651
|
-
it('其他用户的 Token 无法访问本用户的项目', async () => {
|
|
652
|
-
// user2 的 token 不能查看 user1 的项目(但 user2 自己没有这个项目)
|
|
653
|
-
const res = await request(app)
|
|
654
|
-
.get(`/api/projects`)
|
|
655
|
-
.set('Authorization', `Bearer ${user2Token}`);
|
|
656
|
-
expect(res.status).toBe(200);
|
|
657
|
-
// user2 看不到 user1 的项目
|
|
658
|
-
const found = res.body.data.find((p) => p.id === projectId);
|
|
659
|
-
expect(found).toBeUndefined();
|
|
660
|
-
});
|
|
661
|
-
});
|
|
662
|
-
// ========================
|
|
663
|
-
// VAL-MCP-014: 版本开始后添加的任务为插队任务
|
|
664
|
-
// ========================
|
|
665
|
-
describe('VAL-MCP-014: 版本开始后添加的任务为插队任务', () => {
|
|
666
|
-
it('版本开始后创建的任务 inserted=true', async () => {
|
|
667
|
-
// 创建版本
|
|
668
|
-
const vRes = await request(app)
|
|
669
|
-
.post('/api/versions')
|
|
670
|
-
.set('Authorization', `Bearer ${user1Token}`)
|
|
671
|
-
.send({ project_id: projectId, name: '插队测试版本', due_date: '2026-05-30' });
|
|
672
|
-
const versionId = vRes.body.data.id;
|
|
673
|
-
// 在版本开始前创建任务
|
|
674
|
-
const beforeRes = await request(app)
|
|
675
|
-
.post('/api/tasks')
|
|
676
|
-
.set('Authorization', `Bearer ${user1Token}`)
|
|
677
|
-
.send({ project_id: projectId, version_id: versionId, title: '版本前任务' });
|
|
678
|
-
// 开始版本
|
|
679
|
-
await request(app)
|
|
680
|
-
.post(`/api/versions/${versionId}/start`)
|
|
681
|
-
.set('Authorization', `Bearer ${user1Token}`);
|
|
682
|
-
// 版本开始后创建任务
|
|
683
|
-
const afterRes = await request(app)
|
|
684
|
-
.post('/api/tasks')
|
|
685
|
-
.set('Authorization', `Bearer ${user1Token}`)
|
|
686
|
-
.send({ project_id: projectId, version_id: versionId, title: '插队任务' });
|
|
687
|
-
expect(afterRes.status).toBe(201);
|
|
688
|
-
expect(afterRes.body.data.inserted).toBe(1); // inserted = true
|
|
689
|
-
// 版本前的任务 inserted = false
|
|
690
|
-
expect(beforeRes.body.data.inserted).toBe(0);
|
|
691
|
-
});
|
|
692
|
-
});
|
|
693
|
-
});
|
|
694
|
-
//# sourceMappingURL=mcp-tools.test.js.map
|