@vibescope/mcp-server 0.2.9 → 0.3.1
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/CHANGELOG.md +84 -84
- package/README.md +194 -194
- package/dist/api-client.d.ts +36 -0
- package/dist/api-client.js +34 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +30 -38
- package/dist/handlers/discovery.js +2 -0
- package/dist/handlers/session.d.ts +11 -0
- package/dist/handlers/session.js +101 -0
- package/dist/handlers/tasks.d.ts +8 -0
- package/dist/handlers/tasks.js +163 -3
- package/dist/handlers/tool-docs.js +840 -828
- package/dist/handlers/validation.js +49 -2
- package/dist/index.js +73 -73
- package/dist/setup.js +6 -6
- package/dist/templates/agent-guidelines.js +185 -185
- package/dist/templates/help-content.js +1622 -1544
- package/dist/tools.js +130 -74
- package/dist/utils.d.ts +15 -11
- package/dist/utils.js +53 -28
- package/docs/TOOLS.md +2407 -2053
- package/package.json +51 -51
- package/scripts/generate-docs.ts +212 -212
- package/scripts/version-bump.ts +203 -203
- package/src/api-client.test.ts +723 -723
- package/src/api-client.ts +2561 -2499
- package/src/cli.test.ts +24 -8
- package/src/cli.ts +204 -212
- package/src/handlers/__test-setup__.ts +236 -236
- package/src/handlers/__test-utils__.ts +87 -87
- package/src/handlers/blockers.test.ts +468 -468
- package/src/handlers/blockers.ts +163 -163
- package/src/handlers/bodies-of-work.test.ts +704 -704
- package/src/handlers/bodies-of-work.ts +526 -526
- package/src/handlers/connectors.test.ts +834 -834
- package/src/handlers/connectors.ts +229 -229
- package/src/handlers/cost.test.ts +462 -462
- package/src/handlers/cost.ts +285 -285
- package/src/handlers/decisions.test.ts +382 -382
- package/src/handlers/decisions.ts +153 -153
- package/src/handlers/deployment.test.ts +551 -551
- package/src/handlers/deployment.ts +541 -541
- package/src/handlers/discovery.test.ts +206 -206
- package/src/handlers/discovery.ts +392 -390
- package/src/handlers/fallback.test.ts +537 -537
- package/src/handlers/fallback.ts +194 -194
- package/src/handlers/file-checkouts.test.ts +750 -750
- package/src/handlers/file-checkouts.ts +185 -185
- package/src/handlers/findings.test.ts +633 -633
- package/src/handlers/findings.ts +239 -239
- package/src/handlers/git-issues.test.ts +631 -631
- package/src/handlers/git-issues.ts +136 -136
- package/src/handlers/ideas.test.ts +644 -644
- package/src/handlers/ideas.ts +207 -207
- package/src/handlers/index.ts +84 -84
- package/src/handlers/milestones.test.ts +475 -475
- package/src/handlers/milestones.ts +180 -180
- package/src/handlers/organizations.test.ts +826 -826
- package/src/handlers/organizations.ts +315 -315
- package/src/handlers/progress.test.ts +269 -269
- package/src/handlers/progress.ts +77 -77
- package/src/handlers/project.test.ts +546 -546
- package/src/handlers/project.ts +239 -239
- package/src/handlers/requests.test.ts +303 -303
- package/src/handlers/requests.ts +99 -99
- package/src/handlers/roles.test.ts +305 -305
- package/src/handlers/roles.ts +219 -219
- package/src/handlers/session.test.ts +998 -875
- package/src/handlers/session.ts +839 -730
- package/src/handlers/sprints.test.ts +732 -732
- package/src/handlers/sprints.ts +537 -537
- package/src/handlers/tasks.test.ts +931 -907
- package/src/handlers/tasks.ts +1121 -945
- package/src/handlers/tool-categories.test.ts +66 -66
- package/src/handlers/tool-docs.ts +1109 -1096
- package/src/handlers/types.test.ts +259 -259
- package/src/handlers/types.ts +175 -175
- package/src/handlers/validation.test.ts +582 -582
- package/src/handlers/validation.ts +164 -113
- package/src/index.test.ts +674 -0
- package/src/index.ts +792 -792
- package/src/setup.test.ts +233 -233
- package/src/setup.ts +404 -403
- package/src/templates/agent-guidelines.ts +210 -210
- package/src/templates/help-content.ts +1751 -1673
- package/src/token-tracking.test.ts +463 -463
- package/src/token-tracking.ts +166 -166
- package/src/tools.test.ts +416 -0
- package/src/tools.ts +3611 -3555
- package/src/utils.test.ts +785 -683
- package/src/utils.ts +469 -436
- package/src/validators.test.ts +223 -223
- package/src/validators.ts +249 -249
- package/tsconfig.json +16 -16
- package/vitest.config.ts +14 -14
|
@@ -1,475 +1,475 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
-
import {
|
|
3
|
-
addMilestone,
|
|
4
|
-
updateMilestone,
|
|
5
|
-
completeMilestone,
|
|
6
|
-
deleteMilestone,
|
|
7
|
-
getMilestones,
|
|
8
|
-
} from './milestones.js';
|
|
9
|
-
import { ValidationError } from '../validators.js';
|
|
10
|
-
import { createMockContext } from './__test-utils__.js';
|
|
11
|
-
import { mockApiClient } from './__test-setup__.js';
|
|
12
|
-
|
|
13
|
-
// ============================================================================
|
|
14
|
-
// addMilestone Tests
|
|
15
|
-
// ============================================================================
|
|
16
|
-
|
|
17
|
-
describe('addMilestone', () => {
|
|
18
|
-
beforeEach(() => mockApiClient.addMilestone.mockReset());
|
|
19
|
-
|
|
20
|
-
it('should throw error for missing task_id', async () => {
|
|
21
|
-
const ctx = createMockContext();
|
|
22
|
-
|
|
23
|
-
await expect(
|
|
24
|
-
addMilestone({ title: 'Milestone 1' }, ctx)
|
|
25
|
-
).rejects.toThrow(ValidationError);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it('should throw error for invalid task_id UUID', async () => {
|
|
29
|
-
const ctx = createMockContext();
|
|
30
|
-
|
|
31
|
-
await expect(
|
|
32
|
-
addMilestone({ task_id: 'invalid', title: 'Milestone 1' }, ctx)
|
|
33
|
-
).rejects.toThrow(ValidationError);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it('should throw error for missing title', async () => {
|
|
37
|
-
const ctx = createMockContext();
|
|
38
|
-
|
|
39
|
-
await expect(
|
|
40
|
-
addMilestone({ task_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx)
|
|
41
|
-
).rejects.toThrow(ValidationError);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('should add milestone successfully', async () => {
|
|
45
|
-
mockApiClient.addMilestone.mockResolvedValue({
|
|
46
|
-
ok: true,
|
|
47
|
-
data: { milestone_id: 'milestone-1' },
|
|
48
|
-
});
|
|
49
|
-
const ctx = createMockContext();
|
|
50
|
-
|
|
51
|
-
const result = await addMilestone(
|
|
52
|
-
{
|
|
53
|
-
task_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
54
|
-
title: 'First Milestone',
|
|
55
|
-
},
|
|
56
|
-
ctx
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
expect(result.result).toMatchObject({
|
|
60
|
-
success: true,
|
|
61
|
-
milestone_id: 'milestone-1',
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it('should call API client with correct parameters', async () => {
|
|
66
|
-
mockApiClient.addMilestone.mockResolvedValue({
|
|
67
|
-
ok: true,
|
|
68
|
-
data: { milestone_id: 'milestone-1' },
|
|
69
|
-
});
|
|
70
|
-
const ctx = createMockContext();
|
|
71
|
-
|
|
72
|
-
await addMilestone(
|
|
73
|
-
{
|
|
74
|
-
task_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
75
|
-
title: 'Test Milestone',
|
|
76
|
-
description: 'A test milestone',
|
|
77
|
-
order_index: 5,
|
|
78
|
-
},
|
|
79
|
-
ctx
|
|
80
|
-
);
|
|
81
|
-
|
|
82
|
-
expect(mockApiClient.addMilestone).toHaveBeenCalledWith(
|
|
83
|
-
'123e4567-e89b-12d3-a456-426614174000',
|
|
84
|
-
{
|
|
85
|
-
title: 'Test Milestone',
|
|
86
|
-
description: 'A test milestone',
|
|
87
|
-
order_index: 5,
|
|
88
|
-
},
|
|
89
|
-
'session-123'
|
|
90
|
-
);
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it('should return error when API call fails', async () => {
|
|
94
|
-
mockApiClient.addMilestone.mockResolvedValue({
|
|
95
|
-
ok: false,
|
|
96
|
-
error: 'Task not found',
|
|
97
|
-
});
|
|
98
|
-
const ctx = createMockContext();
|
|
99
|
-
|
|
100
|
-
const result = await addMilestone({
|
|
101
|
-
task_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
102
|
-
title: 'Milestone 1',
|
|
103
|
-
}, ctx);
|
|
104
|
-
|
|
105
|
-
expect(result.isError).toBe(true);
|
|
106
|
-
expect(result.result).toMatchObject({ error: 'Task not found' });
|
|
107
|
-
});
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
// ============================================================================
|
|
111
|
-
// updateMilestone Tests
|
|
112
|
-
// ============================================================================
|
|
113
|
-
|
|
114
|
-
describe('updateMilestone', () => {
|
|
115
|
-
beforeEach(() => mockApiClient.updateMilestone.mockReset());
|
|
116
|
-
|
|
117
|
-
it('should throw error for missing milestone_id', async () => {
|
|
118
|
-
const ctx = createMockContext();
|
|
119
|
-
|
|
120
|
-
await expect(updateMilestone({}, ctx)).rejects.toThrow(ValidationError);
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
it('should throw error for invalid milestone_id UUID', async () => {
|
|
124
|
-
const ctx = createMockContext();
|
|
125
|
-
|
|
126
|
-
await expect(
|
|
127
|
-
updateMilestone({ milestone_id: 'invalid', title: 'New Title' }, ctx)
|
|
128
|
-
).rejects.toThrow(ValidationError);
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
it('should throw error when no fields to update', async () => {
|
|
132
|
-
const ctx = createMockContext();
|
|
133
|
-
|
|
134
|
-
await expect(
|
|
135
|
-
updateMilestone({ milestone_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx)
|
|
136
|
-
).rejects.toThrow(ValidationError);
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
it('should throw error for invalid status', async () => {
|
|
140
|
-
const ctx = createMockContext();
|
|
141
|
-
|
|
142
|
-
await expect(
|
|
143
|
-
updateMilestone({
|
|
144
|
-
milestone_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
145
|
-
status: 'invalid_status',
|
|
146
|
-
}, ctx)
|
|
147
|
-
).rejects.toThrow(ValidationError);
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
it('should update title successfully', async () => {
|
|
151
|
-
mockApiClient.updateMilestone.mockResolvedValue({
|
|
152
|
-
ok: true,
|
|
153
|
-
data: { milestone: { id: 'milestone-1', title: 'Updated Title' } },
|
|
154
|
-
});
|
|
155
|
-
const ctx = createMockContext();
|
|
156
|
-
|
|
157
|
-
const result = await updateMilestone(
|
|
158
|
-
{
|
|
159
|
-
milestone_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
160
|
-
title: 'Updated Title',
|
|
161
|
-
},
|
|
162
|
-
ctx
|
|
163
|
-
);
|
|
164
|
-
|
|
165
|
-
expect(result.result).toMatchObject({
|
|
166
|
-
success: true,
|
|
167
|
-
});
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
it('should call API client with correct parameters', async () => {
|
|
171
|
-
mockApiClient.updateMilestone.mockResolvedValue({
|
|
172
|
-
ok: true,
|
|
173
|
-
data: { milestone: { id: 'milestone-1', status: 'completed' } },
|
|
174
|
-
});
|
|
175
|
-
const ctx = createMockContext();
|
|
176
|
-
|
|
177
|
-
await updateMilestone(
|
|
178
|
-
{
|
|
179
|
-
milestone_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
180
|
-
status: 'completed',
|
|
181
|
-
},
|
|
182
|
-
ctx
|
|
183
|
-
);
|
|
184
|
-
|
|
185
|
-
expect(mockApiClient.updateMilestone).toHaveBeenCalledWith(
|
|
186
|
-
'123e4567-e89b-12d3-a456-426614174000',
|
|
187
|
-
{
|
|
188
|
-
title: undefined,
|
|
189
|
-
description: undefined,
|
|
190
|
-
status: 'completed',
|
|
191
|
-
order_index: undefined,
|
|
192
|
-
}
|
|
193
|
-
);
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
it('should return error when API call fails', async () => {
|
|
197
|
-
mockApiClient.updateMilestone.mockResolvedValue({
|
|
198
|
-
ok: false,
|
|
199
|
-
error: 'Milestone not found',
|
|
200
|
-
});
|
|
201
|
-
const ctx = createMockContext();
|
|
202
|
-
|
|
203
|
-
const result = await updateMilestone({
|
|
204
|
-
milestone_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
205
|
-
title: 'New Title',
|
|
206
|
-
}, ctx);
|
|
207
|
-
|
|
208
|
-
expect(result.isError).toBe(true);
|
|
209
|
-
expect(result.result).toMatchObject({ error: 'Milestone not found' });
|
|
210
|
-
});
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
// ============================================================================
|
|
214
|
-
// completeMilestone Tests
|
|
215
|
-
// ============================================================================
|
|
216
|
-
|
|
217
|
-
describe('completeMilestone', () => {
|
|
218
|
-
beforeEach(() => mockApiClient.completeMilestone.mockReset());
|
|
219
|
-
|
|
220
|
-
it('should throw error for missing milestone_id', async () => {
|
|
221
|
-
const ctx = createMockContext();
|
|
222
|
-
|
|
223
|
-
await expect(completeMilestone({}, ctx)).rejects.toThrow(ValidationError);
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
it('should throw error for invalid milestone_id UUID', async () => {
|
|
227
|
-
const ctx = createMockContext();
|
|
228
|
-
|
|
229
|
-
await expect(
|
|
230
|
-
completeMilestone({ milestone_id: 'invalid' }, ctx)
|
|
231
|
-
).rejects.toThrow(ValidationError);
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
it('should complete milestone successfully', async () => {
|
|
235
|
-
mockApiClient.completeMilestone.mockResolvedValue({
|
|
236
|
-
ok: true,
|
|
237
|
-
data: { milestone: { id: 'milestone-1', status: 'completed' } },
|
|
238
|
-
});
|
|
239
|
-
const ctx = createMockContext();
|
|
240
|
-
|
|
241
|
-
const result = await completeMilestone(
|
|
242
|
-
{ milestone_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
243
|
-
ctx
|
|
244
|
-
);
|
|
245
|
-
|
|
246
|
-
expect(result.result).toMatchObject({
|
|
247
|
-
success: true,
|
|
248
|
-
});
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
it('should call API client completeMilestone', async () => {
|
|
252
|
-
mockApiClient.completeMilestone.mockResolvedValue({
|
|
253
|
-
ok: true,
|
|
254
|
-
data: { milestone: { id: 'milestone-1' } },
|
|
255
|
-
});
|
|
256
|
-
const ctx = createMockContext();
|
|
257
|
-
|
|
258
|
-
await completeMilestone(
|
|
259
|
-
{ milestone_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
260
|
-
ctx
|
|
261
|
-
);
|
|
262
|
-
|
|
263
|
-
expect(mockApiClient.completeMilestone).toHaveBeenCalledWith(
|
|
264
|
-
'123e4567-e89b-12d3-a456-426614174000'
|
|
265
|
-
);
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
it('should return error when API call fails', async () => {
|
|
269
|
-
mockApiClient.completeMilestone.mockResolvedValue({
|
|
270
|
-
ok: false,
|
|
271
|
-
error: 'Milestone not found',
|
|
272
|
-
});
|
|
273
|
-
const ctx = createMockContext();
|
|
274
|
-
|
|
275
|
-
const result = await completeMilestone({ milestone_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx);
|
|
276
|
-
|
|
277
|
-
expect(result.isError).toBe(true);
|
|
278
|
-
expect(result.result).toMatchObject({ error: 'Milestone not found' });
|
|
279
|
-
});
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
// ============================================================================
|
|
283
|
-
// deleteMilestone Tests
|
|
284
|
-
// ============================================================================
|
|
285
|
-
|
|
286
|
-
describe('deleteMilestone', () => {
|
|
287
|
-
beforeEach(() => mockApiClient.deleteMilestone.mockReset());
|
|
288
|
-
|
|
289
|
-
it('should throw error for missing milestone_id', async () => {
|
|
290
|
-
const ctx = createMockContext();
|
|
291
|
-
|
|
292
|
-
await expect(deleteMilestone({}, ctx)).rejects.toThrow(ValidationError);
|
|
293
|
-
});
|
|
294
|
-
|
|
295
|
-
it('should throw error for invalid milestone_id UUID', async () => {
|
|
296
|
-
const ctx = createMockContext();
|
|
297
|
-
|
|
298
|
-
await expect(
|
|
299
|
-
deleteMilestone({ milestone_id: 'invalid' }, ctx)
|
|
300
|
-
).rejects.toThrow(ValidationError);
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
it('should delete milestone successfully', async () => {
|
|
304
|
-
mockApiClient.deleteMilestone.mockResolvedValue({
|
|
305
|
-
ok: true,
|
|
306
|
-
data: { success: true },
|
|
307
|
-
});
|
|
308
|
-
const ctx = createMockContext();
|
|
309
|
-
|
|
310
|
-
const result = await deleteMilestone(
|
|
311
|
-
{ milestone_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
312
|
-
ctx
|
|
313
|
-
);
|
|
314
|
-
|
|
315
|
-
expect(result.result).toMatchObject({
|
|
316
|
-
success: true,
|
|
317
|
-
message: 'Milestone deleted',
|
|
318
|
-
});
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
it('should call API client deleteMilestone', async () => {
|
|
322
|
-
mockApiClient.deleteMilestone.mockResolvedValue({
|
|
323
|
-
ok: true,
|
|
324
|
-
data: { success: true },
|
|
325
|
-
});
|
|
326
|
-
const ctx = createMockContext();
|
|
327
|
-
|
|
328
|
-
await deleteMilestone(
|
|
329
|
-
{ milestone_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
330
|
-
ctx
|
|
331
|
-
);
|
|
332
|
-
|
|
333
|
-
expect(mockApiClient.deleteMilestone).toHaveBeenCalledWith(
|
|
334
|
-
'123e4567-e89b-12d3-a456-426614174000'
|
|
335
|
-
);
|
|
336
|
-
});
|
|
337
|
-
|
|
338
|
-
it('should return error when API call fails', async () => {
|
|
339
|
-
mockApiClient.deleteMilestone.mockResolvedValue({
|
|
340
|
-
ok: false,
|
|
341
|
-
error: 'Milestone not found',
|
|
342
|
-
});
|
|
343
|
-
const ctx = createMockContext();
|
|
344
|
-
|
|
345
|
-
const result = await deleteMilestone({ milestone_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx);
|
|
346
|
-
|
|
347
|
-
expect(result.isError).toBe(true);
|
|
348
|
-
expect(result.result).toMatchObject({ error: 'Milestone not found' });
|
|
349
|
-
});
|
|
350
|
-
});
|
|
351
|
-
|
|
352
|
-
// ============================================================================
|
|
353
|
-
// getMilestones Tests
|
|
354
|
-
// ============================================================================
|
|
355
|
-
|
|
356
|
-
describe('getMilestones', () => {
|
|
357
|
-
beforeEach(() => mockApiClient.getMilestones.mockReset());
|
|
358
|
-
|
|
359
|
-
it('should throw error for missing task_id', async () => {
|
|
360
|
-
const ctx = createMockContext();
|
|
361
|
-
|
|
362
|
-
await expect(getMilestones({}, ctx)).rejects.toThrow(ValidationError);
|
|
363
|
-
});
|
|
364
|
-
|
|
365
|
-
it('should throw error for invalid task_id UUID', async () => {
|
|
366
|
-
const ctx = createMockContext();
|
|
367
|
-
|
|
368
|
-
await expect(
|
|
369
|
-
getMilestones({ task_id: 'invalid' }, ctx)
|
|
370
|
-
).rejects.toThrow(ValidationError);
|
|
371
|
-
});
|
|
372
|
-
|
|
373
|
-
it('should return empty list with zero stats', async () => {
|
|
374
|
-
mockApiClient.getMilestones.mockResolvedValue({
|
|
375
|
-
ok: true,
|
|
376
|
-
data: {
|
|
377
|
-
milestones: [],
|
|
378
|
-
stats: {
|
|
379
|
-
total: 0,
|
|
380
|
-
completed: 0,
|
|
381
|
-
in_progress: 0,
|
|
382
|
-
pending: 0,
|
|
383
|
-
progress_percentage: 0,
|
|
384
|
-
},
|
|
385
|
-
},
|
|
386
|
-
});
|
|
387
|
-
const ctx = createMockContext();
|
|
388
|
-
|
|
389
|
-
const result = await getMilestones(
|
|
390
|
-
{ task_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
391
|
-
ctx
|
|
392
|
-
);
|
|
393
|
-
|
|
394
|
-
expect(result.result).toMatchObject({
|
|
395
|
-
milestones: [],
|
|
396
|
-
stats: {
|
|
397
|
-
total: 0,
|
|
398
|
-
completed: 0,
|
|
399
|
-
in_progress: 0,
|
|
400
|
-
pending: 0,
|
|
401
|
-
progress_percentage: 0,
|
|
402
|
-
},
|
|
403
|
-
});
|
|
404
|
-
});
|
|
405
|
-
|
|
406
|
-
it('should return milestones with stats from API', async () => {
|
|
407
|
-
const mockMilestones = [
|
|
408
|
-
{ id: 'm1', title: 'Step 1', status: 'completed', order_index: 0 },
|
|
409
|
-
{ id: 'm2', title: 'Step 2', status: 'in_progress', order_index: 1 },
|
|
410
|
-
{ id: 'm3', title: 'Step 3', status: 'pending', order_index: 2 },
|
|
411
|
-
{ id: 'm4', title: 'Step 4', status: 'pending', order_index: 3 },
|
|
412
|
-
];
|
|
413
|
-
|
|
414
|
-
mockApiClient.getMilestones.mockResolvedValue({
|
|
415
|
-
ok: true,
|
|
416
|
-
data: {
|
|
417
|
-
milestones: mockMilestones,
|
|
418
|
-
stats: {
|
|
419
|
-
total: 4,
|
|
420
|
-
completed: 1,
|
|
421
|
-
in_progress: 1,
|
|
422
|
-
pending: 2,
|
|
423
|
-
progress_percentage: 25,
|
|
424
|
-
},
|
|
425
|
-
},
|
|
426
|
-
});
|
|
427
|
-
const ctx = createMockContext();
|
|
428
|
-
|
|
429
|
-
const result = await getMilestones(
|
|
430
|
-
{ task_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
431
|
-
ctx
|
|
432
|
-
);
|
|
433
|
-
|
|
434
|
-
expect(result.result).toMatchObject({
|
|
435
|
-
milestones: mockMilestones,
|
|
436
|
-
stats: {
|
|
437
|
-
total: 4,
|
|
438
|
-
completed: 1,
|
|
439
|
-
in_progress: 1,
|
|
440
|
-
pending: 2,
|
|
441
|
-
progress_percentage: 25,
|
|
442
|
-
},
|
|
443
|
-
});
|
|
444
|
-
});
|
|
445
|
-
|
|
446
|
-
it('should call API client getMilestones', async () => {
|
|
447
|
-
mockApiClient.getMilestones.mockResolvedValue({
|
|
448
|
-
ok: true,
|
|
449
|
-
data: { milestones: [], stats: {} },
|
|
450
|
-
});
|
|
451
|
-
const ctx = createMockContext();
|
|
452
|
-
|
|
453
|
-
await getMilestones(
|
|
454
|
-
{ task_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
455
|
-
ctx
|
|
456
|
-
);
|
|
457
|
-
|
|
458
|
-
expect(mockApiClient.getMilestones).toHaveBeenCalledWith(
|
|
459
|
-
'123e4567-e89b-12d3-a456-426614174000'
|
|
460
|
-
);
|
|
461
|
-
});
|
|
462
|
-
|
|
463
|
-
it('should return error when API call fails', async () => {
|
|
464
|
-
mockApiClient.getMilestones.mockResolvedValue({
|
|
465
|
-
ok: false,
|
|
466
|
-
error: 'Task not found',
|
|
467
|
-
});
|
|
468
|
-
const ctx = createMockContext();
|
|
469
|
-
|
|
470
|
-
const result = await getMilestones({ task_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx);
|
|
471
|
-
|
|
472
|
-
expect(result.isError).toBe(true);
|
|
473
|
-
expect(result.result).toMatchObject({ error: 'Task not found' });
|
|
474
|
-
});
|
|
475
|
-
});
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
addMilestone,
|
|
4
|
+
updateMilestone,
|
|
5
|
+
completeMilestone,
|
|
6
|
+
deleteMilestone,
|
|
7
|
+
getMilestones,
|
|
8
|
+
} from './milestones.js';
|
|
9
|
+
import { ValidationError } from '../validators.js';
|
|
10
|
+
import { createMockContext } from './__test-utils__.js';
|
|
11
|
+
import { mockApiClient } from './__test-setup__.js';
|
|
12
|
+
|
|
13
|
+
// ============================================================================
|
|
14
|
+
// addMilestone Tests
|
|
15
|
+
// ============================================================================
|
|
16
|
+
|
|
17
|
+
describe('addMilestone', () => {
|
|
18
|
+
beforeEach(() => mockApiClient.addMilestone.mockReset());
|
|
19
|
+
|
|
20
|
+
it('should throw error for missing task_id', async () => {
|
|
21
|
+
const ctx = createMockContext();
|
|
22
|
+
|
|
23
|
+
await expect(
|
|
24
|
+
addMilestone({ title: 'Milestone 1' }, ctx)
|
|
25
|
+
).rejects.toThrow(ValidationError);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should throw error for invalid task_id UUID', async () => {
|
|
29
|
+
const ctx = createMockContext();
|
|
30
|
+
|
|
31
|
+
await expect(
|
|
32
|
+
addMilestone({ task_id: 'invalid', title: 'Milestone 1' }, ctx)
|
|
33
|
+
).rejects.toThrow(ValidationError);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should throw error for missing title', async () => {
|
|
37
|
+
const ctx = createMockContext();
|
|
38
|
+
|
|
39
|
+
await expect(
|
|
40
|
+
addMilestone({ task_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx)
|
|
41
|
+
).rejects.toThrow(ValidationError);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should add milestone successfully', async () => {
|
|
45
|
+
mockApiClient.addMilestone.mockResolvedValue({
|
|
46
|
+
ok: true,
|
|
47
|
+
data: { milestone_id: 'milestone-1' },
|
|
48
|
+
});
|
|
49
|
+
const ctx = createMockContext();
|
|
50
|
+
|
|
51
|
+
const result = await addMilestone(
|
|
52
|
+
{
|
|
53
|
+
task_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
54
|
+
title: 'First Milestone',
|
|
55
|
+
},
|
|
56
|
+
ctx
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
expect(result.result).toMatchObject({
|
|
60
|
+
success: true,
|
|
61
|
+
milestone_id: 'milestone-1',
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should call API client with correct parameters', async () => {
|
|
66
|
+
mockApiClient.addMilestone.mockResolvedValue({
|
|
67
|
+
ok: true,
|
|
68
|
+
data: { milestone_id: 'milestone-1' },
|
|
69
|
+
});
|
|
70
|
+
const ctx = createMockContext();
|
|
71
|
+
|
|
72
|
+
await addMilestone(
|
|
73
|
+
{
|
|
74
|
+
task_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
75
|
+
title: 'Test Milestone',
|
|
76
|
+
description: 'A test milestone',
|
|
77
|
+
order_index: 5,
|
|
78
|
+
},
|
|
79
|
+
ctx
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
expect(mockApiClient.addMilestone).toHaveBeenCalledWith(
|
|
83
|
+
'123e4567-e89b-12d3-a456-426614174000',
|
|
84
|
+
{
|
|
85
|
+
title: 'Test Milestone',
|
|
86
|
+
description: 'A test milestone',
|
|
87
|
+
order_index: 5,
|
|
88
|
+
},
|
|
89
|
+
'session-123'
|
|
90
|
+
);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should return error when API call fails', async () => {
|
|
94
|
+
mockApiClient.addMilestone.mockResolvedValue({
|
|
95
|
+
ok: false,
|
|
96
|
+
error: 'Task not found',
|
|
97
|
+
});
|
|
98
|
+
const ctx = createMockContext();
|
|
99
|
+
|
|
100
|
+
const result = await addMilestone({
|
|
101
|
+
task_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
102
|
+
title: 'Milestone 1',
|
|
103
|
+
}, ctx);
|
|
104
|
+
|
|
105
|
+
expect(result.isError).toBe(true);
|
|
106
|
+
expect(result.result).toMatchObject({ error: 'Task not found' });
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// ============================================================================
|
|
111
|
+
// updateMilestone Tests
|
|
112
|
+
// ============================================================================
|
|
113
|
+
|
|
114
|
+
describe('updateMilestone', () => {
|
|
115
|
+
beforeEach(() => mockApiClient.updateMilestone.mockReset());
|
|
116
|
+
|
|
117
|
+
it('should throw error for missing milestone_id', async () => {
|
|
118
|
+
const ctx = createMockContext();
|
|
119
|
+
|
|
120
|
+
await expect(updateMilestone({}, ctx)).rejects.toThrow(ValidationError);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should throw error for invalid milestone_id UUID', async () => {
|
|
124
|
+
const ctx = createMockContext();
|
|
125
|
+
|
|
126
|
+
await expect(
|
|
127
|
+
updateMilestone({ milestone_id: 'invalid', title: 'New Title' }, ctx)
|
|
128
|
+
).rejects.toThrow(ValidationError);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('should throw error when no fields to update', async () => {
|
|
132
|
+
const ctx = createMockContext();
|
|
133
|
+
|
|
134
|
+
await expect(
|
|
135
|
+
updateMilestone({ milestone_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx)
|
|
136
|
+
).rejects.toThrow(ValidationError);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('should throw error for invalid status', async () => {
|
|
140
|
+
const ctx = createMockContext();
|
|
141
|
+
|
|
142
|
+
await expect(
|
|
143
|
+
updateMilestone({
|
|
144
|
+
milestone_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
145
|
+
status: 'invalid_status',
|
|
146
|
+
}, ctx)
|
|
147
|
+
).rejects.toThrow(ValidationError);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('should update title successfully', async () => {
|
|
151
|
+
mockApiClient.updateMilestone.mockResolvedValue({
|
|
152
|
+
ok: true,
|
|
153
|
+
data: { milestone: { id: 'milestone-1', title: 'Updated Title' } },
|
|
154
|
+
});
|
|
155
|
+
const ctx = createMockContext();
|
|
156
|
+
|
|
157
|
+
const result = await updateMilestone(
|
|
158
|
+
{
|
|
159
|
+
milestone_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
160
|
+
title: 'Updated Title',
|
|
161
|
+
},
|
|
162
|
+
ctx
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
expect(result.result).toMatchObject({
|
|
166
|
+
success: true,
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('should call API client with correct parameters', async () => {
|
|
171
|
+
mockApiClient.updateMilestone.mockResolvedValue({
|
|
172
|
+
ok: true,
|
|
173
|
+
data: { milestone: { id: 'milestone-1', status: 'completed' } },
|
|
174
|
+
});
|
|
175
|
+
const ctx = createMockContext();
|
|
176
|
+
|
|
177
|
+
await updateMilestone(
|
|
178
|
+
{
|
|
179
|
+
milestone_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
180
|
+
status: 'completed',
|
|
181
|
+
},
|
|
182
|
+
ctx
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
expect(mockApiClient.updateMilestone).toHaveBeenCalledWith(
|
|
186
|
+
'123e4567-e89b-12d3-a456-426614174000',
|
|
187
|
+
{
|
|
188
|
+
title: undefined,
|
|
189
|
+
description: undefined,
|
|
190
|
+
status: 'completed',
|
|
191
|
+
order_index: undefined,
|
|
192
|
+
}
|
|
193
|
+
);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('should return error when API call fails', async () => {
|
|
197
|
+
mockApiClient.updateMilestone.mockResolvedValue({
|
|
198
|
+
ok: false,
|
|
199
|
+
error: 'Milestone not found',
|
|
200
|
+
});
|
|
201
|
+
const ctx = createMockContext();
|
|
202
|
+
|
|
203
|
+
const result = await updateMilestone({
|
|
204
|
+
milestone_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
205
|
+
title: 'New Title',
|
|
206
|
+
}, ctx);
|
|
207
|
+
|
|
208
|
+
expect(result.isError).toBe(true);
|
|
209
|
+
expect(result.result).toMatchObject({ error: 'Milestone not found' });
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// ============================================================================
|
|
214
|
+
// completeMilestone Tests
|
|
215
|
+
// ============================================================================
|
|
216
|
+
|
|
217
|
+
describe('completeMilestone', () => {
|
|
218
|
+
beforeEach(() => mockApiClient.completeMilestone.mockReset());
|
|
219
|
+
|
|
220
|
+
it('should throw error for missing milestone_id', async () => {
|
|
221
|
+
const ctx = createMockContext();
|
|
222
|
+
|
|
223
|
+
await expect(completeMilestone({}, ctx)).rejects.toThrow(ValidationError);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('should throw error for invalid milestone_id UUID', async () => {
|
|
227
|
+
const ctx = createMockContext();
|
|
228
|
+
|
|
229
|
+
await expect(
|
|
230
|
+
completeMilestone({ milestone_id: 'invalid' }, ctx)
|
|
231
|
+
).rejects.toThrow(ValidationError);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('should complete milestone successfully', async () => {
|
|
235
|
+
mockApiClient.completeMilestone.mockResolvedValue({
|
|
236
|
+
ok: true,
|
|
237
|
+
data: { milestone: { id: 'milestone-1', status: 'completed' } },
|
|
238
|
+
});
|
|
239
|
+
const ctx = createMockContext();
|
|
240
|
+
|
|
241
|
+
const result = await completeMilestone(
|
|
242
|
+
{ milestone_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
243
|
+
ctx
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
expect(result.result).toMatchObject({
|
|
247
|
+
success: true,
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('should call API client completeMilestone', async () => {
|
|
252
|
+
mockApiClient.completeMilestone.mockResolvedValue({
|
|
253
|
+
ok: true,
|
|
254
|
+
data: { milestone: { id: 'milestone-1' } },
|
|
255
|
+
});
|
|
256
|
+
const ctx = createMockContext();
|
|
257
|
+
|
|
258
|
+
await completeMilestone(
|
|
259
|
+
{ milestone_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
260
|
+
ctx
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
expect(mockApiClient.completeMilestone).toHaveBeenCalledWith(
|
|
264
|
+
'123e4567-e89b-12d3-a456-426614174000'
|
|
265
|
+
);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it('should return error when API call fails', async () => {
|
|
269
|
+
mockApiClient.completeMilestone.mockResolvedValue({
|
|
270
|
+
ok: false,
|
|
271
|
+
error: 'Milestone not found',
|
|
272
|
+
});
|
|
273
|
+
const ctx = createMockContext();
|
|
274
|
+
|
|
275
|
+
const result = await completeMilestone({ milestone_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx);
|
|
276
|
+
|
|
277
|
+
expect(result.isError).toBe(true);
|
|
278
|
+
expect(result.result).toMatchObject({ error: 'Milestone not found' });
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
// ============================================================================
|
|
283
|
+
// deleteMilestone Tests
|
|
284
|
+
// ============================================================================
|
|
285
|
+
|
|
286
|
+
describe('deleteMilestone', () => {
|
|
287
|
+
beforeEach(() => mockApiClient.deleteMilestone.mockReset());
|
|
288
|
+
|
|
289
|
+
it('should throw error for missing milestone_id', async () => {
|
|
290
|
+
const ctx = createMockContext();
|
|
291
|
+
|
|
292
|
+
await expect(deleteMilestone({}, ctx)).rejects.toThrow(ValidationError);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it('should throw error for invalid milestone_id UUID', async () => {
|
|
296
|
+
const ctx = createMockContext();
|
|
297
|
+
|
|
298
|
+
await expect(
|
|
299
|
+
deleteMilestone({ milestone_id: 'invalid' }, ctx)
|
|
300
|
+
).rejects.toThrow(ValidationError);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it('should delete milestone successfully', async () => {
|
|
304
|
+
mockApiClient.deleteMilestone.mockResolvedValue({
|
|
305
|
+
ok: true,
|
|
306
|
+
data: { success: true },
|
|
307
|
+
});
|
|
308
|
+
const ctx = createMockContext();
|
|
309
|
+
|
|
310
|
+
const result = await deleteMilestone(
|
|
311
|
+
{ milestone_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
312
|
+
ctx
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
expect(result.result).toMatchObject({
|
|
316
|
+
success: true,
|
|
317
|
+
message: 'Milestone deleted',
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it('should call API client deleteMilestone', async () => {
|
|
322
|
+
mockApiClient.deleteMilestone.mockResolvedValue({
|
|
323
|
+
ok: true,
|
|
324
|
+
data: { success: true },
|
|
325
|
+
});
|
|
326
|
+
const ctx = createMockContext();
|
|
327
|
+
|
|
328
|
+
await deleteMilestone(
|
|
329
|
+
{ milestone_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
330
|
+
ctx
|
|
331
|
+
);
|
|
332
|
+
|
|
333
|
+
expect(mockApiClient.deleteMilestone).toHaveBeenCalledWith(
|
|
334
|
+
'123e4567-e89b-12d3-a456-426614174000'
|
|
335
|
+
);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
it('should return error when API call fails', async () => {
|
|
339
|
+
mockApiClient.deleteMilestone.mockResolvedValue({
|
|
340
|
+
ok: false,
|
|
341
|
+
error: 'Milestone not found',
|
|
342
|
+
});
|
|
343
|
+
const ctx = createMockContext();
|
|
344
|
+
|
|
345
|
+
const result = await deleteMilestone({ milestone_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx);
|
|
346
|
+
|
|
347
|
+
expect(result.isError).toBe(true);
|
|
348
|
+
expect(result.result).toMatchObject({ error: 'Milestone not found' });
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
// ============================================================================
|
|
353
|
+
// getMilestones Tests
|
|
354
|
+
// ============================================================================
|
|
355
|
+
|
|
356
|
+
describe('getMilestones', () => {
|
|
357
|
+
beforeEach(() => mockApiClient.getMilestones.mockReset());
|
|
358
|
+
|
|
359
|
+
it('should throw error for missing task_id', async () => {
|
|
360
|
+
const ctx = createMockContext();
|
|
361
|
+
|
|
362
|
+
await expect(getMilestones({}, ctx)).rejects.toThrow(ValidationError);
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
it('should throw error for invalid task_id UUID', async () => {
|
|
366
|
+
const ctx = createMockContext();
|
|
367
|
+
|
|
368
|
+
await expect(
|
|
369
|
+
getMilestones({ task_id: 'invalid' }, ctx)
|
|
370
|
+
).rejects.toThrow(ValidationError);
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
it('should return empty list with zero stats', async () => {
|
|
374
|
+
mockApiClient.getMilestones.mockResolvedValue({
|
|
375
|
+
ok: true,
|
|
376
|
+
data: {
|
|
377
|
+
milestones: [],
|
|
378
|
+
stats: {
|
|
379
|
+
total: 0,
|
|
380
|
+
completed: 0,
|
|
381
|
+
in_progress: 0,
|
|
382
|
+
pending: 0,
|
|
383
|
+
progress_percentage: 0,
|
|
384
|
+
},
|
|
385
|
+
},
|
|
386
|
+
});
|
|
387
|
+
const ctx = createMockContext();
|
|
388
|
+
|
|
389
|
+
const result = await getMilestones(
|
|
390
|
+
{ task_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
391
|
+
ctx
|
|
392
|
+
);
|
|
393
|
+
|
|
394
|
+
expect(result.result).toMatchObject({
|
|
395
|
+
milestones: [],
|
|
396
|
+
stats: {
|
|
397
|
+
total: 0,
|
|
398
|
+
completed: 0,
|
|
399
|
+
in_progress: 0,
|
|
400
|
+
pending: 0,
|
|
401
|
+
progress_percentage: 0,
|
|
402
|
+
},
|
|
403
|
+
});
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
it('should return milestones with stats from API', async () => {
|
|
407
|
+
const mockMilestones = [
|
|
408
|
+
{ id: 'm1', title: 'Step 1', status: 'completed', order_index: 0 },
|
|
409
|
+
{ id: 'm2', title: 'Step 2', status: 'in_progress', order_index: 1 },
|
|
410
|
+
{ id: 'm3', title: 'Step 3', status: 'pending', order_index: 2 },
|
|
411
|
+
{ id: 'm4', title: 'Step 4', status: 'pending', order_index: 3 },
|
|
412
|
+
];
|
|
413
|
+
|
|
414
|
+
mockApiClient.getMilestones.mockResolvedValue({
|
|
415
|
+
ok: true,
|
|
416
|
+
data: {
|
|
417
|
+
milestones: mockMilestones,
|
|
418
|
+
stats: {
|
|
419
|
+
total: 4,
|
|
420
|
+
completed: 1,
|
|
421
|
+
in_progress: 1,
|
|
422
|
+
pending: 2,
|
|
423
|
+
progress_percentage: 25,
|
|
424
|
+
},
|
|
425
|
+
},
|
|
426
|
+
});
|
|
427
|
+
const ctx = createMockContext();
|
|
428
|
+
|
|
429
|
+
const result = await getMilestones(
|
|
430
|
+
{ task_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
431
|
+
ctx
|
|
432
|
+
);
|
|
433
|
+
|
|
434
|
+
expect(result.result).toMatchObject({
|
|
435
|
+
milestones: mockMilestones,
|
|
436
|
+
stats: {
|
|
437
|
+
total: 4,
|
|
438
|
+
completed: 1,
|
|
439
|
+
in_progress: 1,
|
|
440
|
+
pending: 2,
|
|
441
|
+
progress_percentage: 25,
|
|
442
|
+
},
|
|
443
|
+
});
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
it('should call API client getMilestones', async () => {
|
|
447
|
+
mockApiClient.getMilestones.mockResolvedValue({
|
|
448
|
+
ok: true,
|
|
449
|
+
data: { milestones: [], stats: {} },
|
|
450
|
+
});
|
|
451
|
+
const ctx = createMockContext();
|
|
452
|
+
|
|
453
|
+
await getMilestones(
|
|
454
|
+
{ task_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
455
|
+
ctx
|
|
456
|
+
);
|
|
457
|
+
|
|
458
|
+
expect(mockApiClient.getMilestones).toHaveBeenCalledWith(
|
|
459
|
+
'123e4567-e89b-12d3-a456-426614174000'
|
|
460
|
+
);
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
it('should return error when API call fails', async () => {
|
|
464
|
+
mockApiClient.getMilestones.mockResolvedValue({
|
|
465
|
+
ok: false,
|
|
466
|
+
error: 'Task not found',
|
|
467
|
+
});
|
|
468
|
+
const ctx = createMockContext();
|
|
469
|
+
|
|
470
|
+
const result = await getMilestones({ task_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx);
|
|
471
|
+
|
|
472
|
+
expect(result.isError).toBe(true);
|
|
473
|
+
expect(result.result).toMatchObject({ error: 'Task not found' });
|
|
474
|
+
});
|
|
475
|
+
});
|