Skip to content

Commit 433387e

Browse files
Passing tests for all tools
1 parent 1cb339d commit 433387e

13 files changed

+1135
-51
lines changed

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "taskqueue-mcp",
3-
"version": "1.3.4",
3+
"version": "1.4.0",
44
"description": "Task Queue MCP Server",
55
"author": "Christopher C. Smith ([email protected])",
66
"main": "dist/src/server/index.js",

src/client/cli.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const program = new Command();
1414
program
1515
.name("taskqueue")
1616
.description("CLI for the Task Manager MCP Server")
17-
.version("1.3.4")
17+
.version("1.4.0")
1818
.option(
1919
'-f, --file-path <path>',
2020
'Specify the path to the tasks JSON file. Overrides TASK_MANAGER_FILE_PATH env var.'
@@ -235,7 +235,7 @@ program
235235
// Filter tasks based on state if provided
236236
const tasksToList = filterState
237237
? project.tasks.filter((task: Task) => {
238-
if (filterState === 'open') return task.status !== 'done';
238+
if (filterState === 'open') return !task.approved;
239239
if (filterState === 'pending_approval') return task.status === 'done' && !task.approved;
240240
if (filterState === 'completed') return task.status === 'done' && task.approved;
241241
return true; // Should not happen

src/server/TaskManager.ts

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -364,20 +364,24 @@ export class TaskManager {
364364
};
365365
}
366366

367-
public async openTaskDetails(taskId: string): Promise<OpenTaskSuccessData> {
367+
public async openTaskDetails(projectId: string, taskId: string): Promise<OpenTaskSuccessData> {
368368
await this.ensureInitialized();
369369
await this.reloadFromDisk();
370370

371-
for (const proj of this.data.projects) {
372-
const target = proj.tasks.find((t) => t.id === taskId);
373-
if (target) {
374-
return {
375-
projectId: proj.projectId,
376-
task: { ...target },
377-
};
378-
}
371+
const project = this.data.projects.find((p) => p.projectId === projectId);
372+
if (!project) {
373+
throw new AppError(`Project ${projectId} not found`, AppErrorCode.ProjectNotFound);
379374
}
380-
throw new AppError(`Task ${taskId} not found`, AppErrorCode.TaskNotFound);
375+
376+
const target = project.tasks.find((t) => t.id === taskId);
377+
if (!target) {
378+
throw new AppError(`Task ${taskId} not found`, AppErrorCode.TaskNotFound);
379+
}
380+
381+
return {
382+
projectId: project.projectId,
383+
task: { ...target },
384+
};
381385
}
382386

383387
public async listProjects(state?: TaskState): Promise<ListProjectsSuccessData> {
@@ -442,7 +446,7 @@ export class TaskManager {
442446
allTasks = allTasks.filter((task) => {
443447
switch (state) {
444448
case "open":
445-
return task.status !== "done";
449+
return !task.approved;
446450
case "completed":
447451
return task.status === "done" && task.approved;
448452
case "pending_approval":

src/server/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { ListToolsRequestSchema, CallToolRequestSchema } from "@modelcontextprot
1010
const server = new Server(
1111
{
1212
name: "task-manager-server",
13-
version: "1.3.4"
13+
version: "1.4.0"
1414
},
1515
{
1616
capabilities: {

src/server/toolExecutors.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -418,10 +418,11 @@ const readTaskToolExecutor: ToolExecutor = {
418418
name: "read_task",
419419
async execute(taskManager, args) {
420420
// 1. Argument Validation
421+
const projectId = validateProjectId(args.projectId);
421422
const taskId = validateTaskId(args.taskId);
422423

423424
// 2. Core Logic Execution
424-
const resultData = await taskManager.openTaskDetails(taskId);
425+
const resultData = await taskManager.openTaskDetails(projectId, taskId);
425426

426427
// 3. Return raw success data
427428
return resultData;

src/server/tools.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,12 +261,16 @@ const readTaskTool: Tool = {
261261
inputSchema: {
262262
type: "object",
263263
properties: {
264+
projectId: {
265+
type: "string",
266+
description: "The ID of the project containing the task (e.g., proj-1).",
267+
},
264268
taskId: {
265269
type: "string",
266270
description: "The ID of the task to read (e.g., task-1).",
267271
},
268272
},
269-
required: ["taskId"],
273+
required: ["projectId", "taskId"],
270274
},
271275
};
272276

Lines changed: 160 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,179 @@
11
import { describe, it, expect, beforeEach } from '@jest/globals';
2-
import { setupTestContext, teardownTestContext, TestContext, createTestProject } from '../test-helpers.js';
2+
import { setupTestContext, teardownTestContext, TestContext, createTestProject, verifyCallToolResult, verifyTaskInFile, verifyToolExecutionError, verifyProtocolError } from '../test-helpers.js';
3+
import { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
34

45
describe('add_tasks_to_project Tool', () => {
56
let context: TestContext;
7+
let projectId: string;
68

79
beforeEach(async () => {
810
context = await setupTestContext();
11+
// Create a test project for each test case
12+
projectId = await createTestProject(context.client);
913
});
1014

1115
afterEach(async () => {
1216
await teardownTestContext(context);
1317
});
1418

1519
describe('Success Cases', () => {
16-
// TODO: Add success test cases
20+
it('should add a single task to project', async () => {
21+
const result = await context.client.callTool({
22+
name: "add_tasks_to_project",
23+
arguments: {
24+
projectId,
25+
tasks: [
26+
{ title: "New Task", description: "A task to add" }
27+
]
28+
}
29+
}) as CallToolResult;
30+
31+
verifyCallToolResult(result);
32+
expect(result.isError).toBeFalsy();
33+
34+
// Parse and verify response
35+
const responseData = JSON.parse((result.content[0] as { text: string }).text);
36+
expect(responseData).toHaveProperty('message');
37+
expect(responseData).toHaveProperty('newTasks');
38+
expect(responseData.newTasks).toHaveLength(1);
39+
const newTask = responseData.newTasks[0];
40+
41+
// Verify task was added to file
42+
await verifyTaskInFile(context.testFilePath, projectId, newTask.id, {
43+
title: "New Task",
44+
description: "A task to add",
45+
status: "not started",
46+
approved: false
47+
});
48+
});
49+
50+
it('should add multiple tasks to project', async () => {
51+
const tasks = [
52+
{ title: "Task 1", description: "First task to add" },
53+
{ title: "Task 2", description: "Second task to add" },
54+
{ title: "Task 3", description: "Third task to add" }
55+
];
56+
57+
const result = await context.client.callTool({
58+
name: "add_tasks_to_project",
59+
arguments: {
60+
projectId,
61+
tasks
62+
}
63+
}) as CallToolResult;
64+
65+
verifyCallToolResult(result);
66+
const responseData = JSON.parse((result.content[0] as { text: string }).text);
67+
expect(responseData.newTasks).toHaveLength(3);
68+
69+
// Verify all tasks were added
70+
for (let i = 0; i < tasks.length; i++) {
71+
await verifyTaskInFile(context.testFilePath, projectId, responseData.newTasks[i].id, {
72+
title: tasks[i].title,
73+
description: tasks[i].description,
74+
status: "not started"
75+
});
76+
}
77+
});
78+
79+
it('should add tasks with tool and rule recommendations', async () => {
80+
const result = await context.client.callTool({
81+
name: "add_tasks_to_project",
82+
arguments: {
83+
projectId,
84+
tasks: [{
85+
title: "Task with Recommendations",
86+
description: "Task with specific recommendations",
87+
toolRecommendations: "Use tool A and B",
88+
ruleRecommendations: "Follow rules X and Y"
89+
}]
90+
}
91+
}) as CallToolResult;
92+
93+
verifyCallToolResult(result);
94+
const responseData = JSON.parse((result.content[0] as { text: string }).text);
95+
const newTask = responseData.newTasks[0];
96+
97+
await verifyTaskInFile(context.testFilePath, projectId, newTask.id, {
98+
title: "Task with Recommendations",
99+
description: "Task with specific recommendations",
100+
toolRecommendations: "Use tool A and B",
101+
ruleRecommendations: "Follow rules X and Y"
102+
});
103+
});
104+
105+
it('should handle empty tasks array', async () => {
106+
const result = await context.client.callTool({
107+
name: "add_tasks_to_project",
108+
arguments: {
109+
projectId,
110+
tasks: []
111+
}
112+
}) as CallToolResult;
113+
114+
verifyCallToolResult(result);
115+
expect(result.isError).toBeFalsy();
116+
const responseData = JSON.parse((result.content[0] as { text: string }).text);
117+
expect(responseData.newTasks).toHaveLength(0);
118+
});
17119
});
18120

19121
describe('Error Cases', () => {
20-
// TODO: Add error test cases
122+
it('should return error for missing required parameters', async () => {
123+
try {
124+
await context.client.callTool({
125+
name: "add_tasks_to_project",
126+
arguments: {
127+
projectId
128+
// Missing tasks array
129+
}
130+
});
131+
expect(true).toBe(false); // This line should never be reached
132+
} catch (error) {
133+
verifyProtocolError(error, -32602, 'Invalid or missing required parameter');
134+
}
135+
});
136+
137+
it('should return error for invalid project ID', async () => {
138+
const result = await context.client.callTool({
139+
name: "add_tasks_to_project",
140+
arguments: {
141+
projectId: "non-existent-project",
142+
tasks: [{ title: "Test Task", description: "Test Description" }]
143+
}
144+
}) as CallToolResult;
145+
146+
verifyToolExecutionError(result, /Project non-existent-project not found/);
147+
});
148+
149+
it('should return error for task with empty title', async () => {
150+
try {
151+
await context.client.callTool({
152+
name: "add_tasks_to_project",
153+
arguments: {
154+
projectId,
155+
tasks: [{ title: "", description: "Test Description" }]
156+
}
157+
});
158+
expect(true).toBe(false); // This line should never be reached
159+
} catch (error) {
160+
verifyProtocolError(error, -32602, 'Invalid or missing required parameter: title');
161+
}
162+
});
163+
164+
it('should return error for task with empty description', async () => {
165+
try {
166+
await context.client.callTool({
167+
name: "add_tasks_to_project",
168+
arguments: {
169+
projectId,
170+
tasks: [{ title: "Test Task", description: "" }]
171+
}
172+
});
173+
expect(true).toBe(false); // This line should never be reached
174+
} catch (error) {
175+
verifyProtocolError(error, -32602, 'Invalid or missing required parameter: description');
176+
}
177+
});
21178
});
22179
});

0 commit comments

Comments
 (0)