@vibescope/mcp-server 0.2.5 → 0.2.6
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/cli.js +26 -26
- package/dist/handlers/tool-docs.js +828 -828
- package/dist/index.js +73 -73
- package/dist/templates/agent-guidelines.js +185 -185
- package/dist/token-tracking.js +4 -2
- package/dist/tools.js +65 -65
- package/dist/utils.js +11 -11
- package/docs/TOOLS.md +2053 -2053
- package/package.json +1 -1
- 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 +2499 -2499
- package/src/cli.ts +212 -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 +390 -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 +303 -303
- package/src/handlers/roles.ts +226 -226
- package/src/handlers/session.test.ts +875 -875
- package/src/handlers/session.ts +738 -738
- package/src/handlers/sprints.test.ts +732 -732
- package/src/handlers/sprints.ts +537 -537
- package/src/handlers/tasks.test.ts +907 -907
- package/src/handlers/tasks.ts +945 -945
- package/src/handlers/tool-categories.test.ts +66 -66
- package/src/handlers/tool-docs.ts +1096 -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 +97 -97
- package/src/index.ts +792 -792
- package/src/setup.test.ts +233 -231
- package/src/setup.ts +370 -370
- package/src/templates/agent-guidelines.ts +210 -210
- package/src/token-tracking.test.ts +463 -453
- package/src/token-tracking.ts +166 -164
- package/src/tools.ts +3562 -3562
- package/src/utils.test.ts +683 -683
- package/src/utils.ts +436 -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
- package/dist/knowledge.d.ts +0 -6
- package/dist/knowledge.js +0 -218
|
@@ -1,644 +1,644 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
-
import {
|
|
3
|
-
addIdea,
|
|
4
|
-
updateIdea,
|
|
5
|
-
getIdeas,
|
|
6
|
-
deleteIdea,
|
|
7
|
-
convertIdeaToTask,
|
|
8
|
-
} from './ideas.js';
|
|
9
|
-
import { ValidationError } from '../validators.js';
|
|
10
|
-
import { createMockContext } from './__test-utils__.js';
|
|
11
|
-
import { mockApiClient } from './__test-setup__.js';
|
|
12
|
-
|
|
13
|
-
// ============================================================================
|
|
14
|
-
// addIdea Tests
|
|
15
|
-
// ============================================================================
|
|
16
|
-
|
|
17
|
-
describe('addIdea', () => {
|
|
18
|
-
beforeEach(() => {});
|
|
19
|
-
|
|
20
|
-
it('should throw error for missing project_id', async () => {
|
|
21
|
-
const ctx = createMockContext();
|
|
22
|
-
|
|
23
|
-
await expect(
|
|
24
|
-
addIdea({ title: 'New Feature' }, ctx)
|
|
25
|
-
).rejects.toThrow(ValidationError);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it('should throw error for invalid project_id UUID', async () => {
|
|
29
|
-
const ctx = createMockContext();
|
|
30
|
-
|
|
31
|
-
await expect(
|
|
32
|
-
addIdea({ project_id: 'invalid', title: 'New Feature' }, 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
|
-
addIdea({ project_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx)
|
|
41
|
-
).rejects.toThrow(ValidationError);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('should add idea successfully', async () => {
|
|
45
|
-
mockApiClient.addIdea.mockResolvedValue({
|
|
46
|
-
ok: true,
|
|
47
|
-
data: { idea_id: 'idea-1' },
|
|
48
|
-
});
|
|
49
|
-
const ctx = createMockContext();
|
|
50
|
-
|
|
51
|
-
const result = await addIdea(
|
|
52
|
-
{
|
|
53
|
-
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
54
|
-
title: 'Dark Mode Support',
|
|
55
|
-
},
|
|
56
|
-
ctx
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
expect(result.result).toMatchObject({
|
|
60
|
-
success: true,
|
|
61
|
-
idea_id: 'idea-1',
|
|
62
|
-
title: 'Dark Mode Support',
|
|
63
|
-
});
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it('should call API client with default status "raw"', async () => {
|
|
67
|
-
mockApiClient.addIdea.mockResolvedValue({
|
|
68
|
-
ok: true,
|
|
69
|
-
data: { idea_id: 'idea-1' },
|
|
70
|
-
});
|
|
71
|
-
const ctx = createMockContext();
|
|
72
|
-
|
|
73
|
-
await addIdea(
|
|
74
|
-
{
|
|
75
|
-
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
76
|
-
title: 'New Feature',
|
|
77
|
-
},
|
|
78
|
-
ctx
|
|
79
|
-
);
|
|
80
|
-
|
|
81
|
-
expect(mockApiClient.addIdea).toHaveBeenCalledWith(
|
|
82
|
-
'123e4567-e89b-12d3-a456-426614174000',
|
|
83
|
-
{
|
|
84
|
-
title: 'New Feature',
|
|
85
|
-
description: undefined,
|
|
86
|
-
status: undefined, // Default status applied server-side
|
|
87
|
-
},
|
|
88
|
-
'session-123'
|
|
89
|
-
);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it('should call API client with custom status', async () => {
|
|
93
|
-
mockApiClient.addIdea.mockResolvedValue({
|
|
94
|
-
ok: true,
|
|
95
|
-
data: { idea_id: 'idea-1' },
|
|
96
|
-
});
|
|
97
|
-
const ctx = createMockContext();
|
|
98
|
-
|
|
99
|
-
await addIdea(
|
|
100
|
-
{
|
|
101
|
-
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
102
|
-
title: 'New Feature',
|
|
103
|
-
status: 'exploring',
|
|
104
|
-
},
|
|
105
|
-
ctx
|
|
106
|
-
);
|
|
107
|
-
|
|
108
|
-
expect(mockApiClient.addIdea).toHaveBeenCalledWith(
|
|
109
|
-
'123e4567-e89b-12d3-a456-426614174000',
|
|
110
|
-
{
|
|
111
|
-
title: 'New Feature',
|
|
112
|
-
description: undefined,
|
|
113
|
-
status: 'exploring',
|
|
114
|
-
},
|
|
115
|
-
'session-123'
|
|
116
|
-
);
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
it('should include session_id in API call', async () => {
|
|
120
|
-
mockApiClient.addIdea.mockResolvedValue({
|
|
121
|
-
ok: true,
|
|
122
|
-
data: { idea_id: 'idea-1' },
|
|
123
|
-
});
|
|
124
|
-
const ctx = createMockContext({ sessionId: 'my-session' });
|
|
125
|
-
|
|
126
|
-
await addIdea(
|
|
127
|
-
{
|
|
128
|
-
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
129
|
-
title: 'New Feature',
|
|
130
|
-
},
|
|
131
|
-
ctx
|
|
132
|
-
);
|
|
133
|
-
|
|
134
|
-
expect(mockApiClient.addIdea).toHaveBeenCalledWith(
|
|
135
|
-
'123e4567-e89b-12d3-a456-426614174000',
|
|
136
|
-
expect.any(Object),
|
|
137
|
-
'my-session'
|
|
138
|
-
);
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
it('should return error when API call fails', async () => {
|
|
142
|
-
mockApiClient.addIdea.mockResolvedValue({
|
|
143
|
-
ok: false,
|
|
144
|
-
error: 'Insert failed',
|
|
145
|
-
});
|
|
146
|
-
const ctx = createMockContext();
|
|
147
|
-
|
|
148
|
-
const result = await addIdea({
|
|
149
|
-
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
150
|
-
title: 'New Feature',
|
|
151
|
-
}, ctx);
|
|
152
|
-
|
|
153
|
-
expect(result.isError).toBe(true);
|
|
154
|
-
expect(result.result).toMatchObject({ error: 'Insert failed' });
|
|
155
|
-
});
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
// ============================================================================
|
|
159
|
-
// updateIdea Tests
|
|
160
|
-
// ============================================================================
|
|
161
|
-
|
|
162
|
-
describe('updateIdea', () => {
|
|
163
|
-
beforeEach(() => {});
|
|
164
|
-
|
|
165
|
-
it('should throw error for missing idea_id', async () => {
|
|
166
|
-
const ctx = createMockContext();
|
|
167
|
-
|
|
168
|
-
await expect(updateIdea({}, ctx)).rejects.toThrow(ValidationError);
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
it('should throw error for invalid idea_id UUID', async () => {
|
|
172
|
-
const ctx = createMockContext();
|
|
173
|
-
|
|
174
|
-
await expect(
|
|
175
|
-
updateIdea({ idea_id: 'invalid' }, ctx)
|
|
176
|
-
).rejects.toThrow(ValidationError);
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
it('should update idea title', async () => {
|
|
180
|
-
mockApiClient.updateIdea.mockResolvedValue({
|
|
181
|
-
ok: true,
|
|
182
|
-
data: { idea: { id: 'idea-1', title: 'Updated Title' } },
|
|
183
|
-
});
|
|
184
|
-
const ctx = createMockContext();
|
|
185
|
-
|
|
186
|
-
const result = await updateIdea(
|
|
187
|
-
{
|
|
188
|
-
idea_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
189
|
-
title: 'Updated Title',
|
|
190
|
-
},
|
|
191
|
-
ctx
|
|
192
|
-
);
|
|
193
|
-
|
|
194
|
-
expect(result.result).toMatchObject({
|
|
195
|
-
success: true,
|
|
196
|
-
idea_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
197
|
-
});
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
it('should call API client with status update', async () => {
|
|
201
|
-
mockApiClient.updateIdea.mockResolvedValue({
|
|
202
|
-
ok: true,
|
|
203
|
-
data: { idea: { id: 'idea-1', status: 'planned' } },
|
|
204
|
-
});
|
|
205
|
-
const ctx = createMockContext();
|
|
206
|
-
|
|
207
|
-
await updateIdea(
|
|
208
|
-
{
|
|
209
|
-
idea_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
210
|
-
status: 'planned',
|
|
211
|
-
},
|
|
212
|
-
ctx
|
|
213
|
-
);
|
|
214
|
-
|
|
215
|
-
expect(mockApiClient.updateIdea).toHaveBeenCalledWith(
|
|
216
|
-
'123e4567-e89b-12d3-a456-426614174000',
|
|
217
|
-
{
|
|
218
|
-
title: undefined,
|
|
219
|
-
description: undefined,
|
|
220
|
-
status: 'planned',
|
|
221
|
-
doc_url: undefined,
|
|
222
|
-
}
|
|
223
|
-
);
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
it('should update doc_url', async () => {
|
|
227
|
-
mockApiClient.updateIdea.mockResolvedValue({
|
|
228
|
-
ok: true,
|
|
229
|
-
data: { idea: { id: 'idea-1' } },
|
|
230
|
-
});
|
|
231
|
-
const ctx = createMockContext();
|
|
232
|
-
|
|
233
|
-
await updateIdea(
|
|
234
|
-
{
|
|
235
|
-
idea_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
236
|
-
doc_url: 'https://docs.example.com/feature',
|
|
237
|
-
},
|
|
238
|
-
ctx
|
|
239
|
-
);
|
|
240
|
-
|
|
241
|
-
expect(mockApiClient.updateIdea).toHaveBeenCalledWith(
|
|
242
|
-
'123e4567-e89b-12d3-a456-426614174000',
|
|
243
|
-
{
|
|
244
|
-
title: undefined,
|
|
245
|
-
description: undefined,
|
|
246
|
-
status: undefined,
|
|
247
|
-
doc_url: 'https://docs.example.com/feature',
|
|
248
|
-
}
|
|
249
|
-
);
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
it('should return error when API call fails', async () => {
|
|
253
|
-
mockApiClient.updateIdea.mockResolvedValue({
|
|
254
|
-
ok: false,
|
|
255
|
-
error: 'Idea not found',
|
|
256
|
-
});
|
|
257
|
-
const ctx = createMockContext();
|
|
258
|
-
|
|
259
|
-
const result = await updateIdea({ idea_id: '123e4567-e89b-12d3-a456-426614174000', title: 'New' }, ctx);
|
|
260
|
-
|
|
261
|
-
expect(result.isError).toBe(true);
|
|
262
|
-
expect(result.result).toMatchObject({ error: 'Idea not found' });
|
|
263
|
-
});
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
// ============================================================================
|
|
267
|
-
// getIdeas Tests
|
|
268
|
-
// ============================================================================
|
|
269
|
-
|
|
270
|
-
describe('getIdeas', () => {
|
|
271
|
-
beforeEach(() => {});
|
|
272
|
-
|
|
273
|
-
it('should throw error for missing project_id', async () => {
|
|
274
|
-
const ctx = createMockContext();
|
|
275
|
-
|
|
276
|
-
await expect(getIdeas({}, ctx)).rejects.toThrow(ValidationError);
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
it('should throw error for invalid project_id UUID', async () => {
|
|
280
|
-
const ctx = createMockContext();
|
|
281
|
-
|
|
282
|
-
await expect(
|
|
283
|
-
getIdeas({ project_id: 'invalid' }, ctx)
|
|
284
|
-
).rejects.toThrow(ValidationError);
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
it('should return empty list when no ideas', async () => {
|
|
288
|
-
mockApiClient.getIdeas.mockResolvedValue({
|
|
289
|
-
ok: true,
|
|
290
|
-
data: { ideas: [] },
|
|
291
|
-
});
|
|
292
|
-
const ctx = createMockContext();
|
|
293
|
-
|
|
294
|
-
const result = await getIdeas(
|
|
295
|
-
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
296
|
-
ctx
|
|
297
|
-
);
|
|
298
|
-
|
|
299
|
-
expect(result.result).toMatchObject({ ideas: [] });
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
it('should return ideas list', async () => {
|
|
303
|
-
const mockIdeas = [
|
|
304
|
-
{ id: 'i1', title: 'Idea 1', description: null, status: 'raw', doc_url: null },
|
|
305
|
-
{ id: 'i2', title: 'Idea 2', description: 'Some desc', status: 'exploring', doc_url: null },
|
|
306
|
-
];
|
|
307
|
-
|
|
308
|
-
mockApiClient.getIdeas.mockResolvedValue({
|
|
309
|
-
ok: true,
|
|
310
|
-
data: { ideas: mockIdeas },
|
|
311
|
-
});
|
|
312
|
-
const ctx = createMockContext();
|
|
313
|
-
|
|
314
|
-
const result = await getIdeas(
|
|
315
|
-
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
316
|
-
ctx
|
|
317
|
-
);
|
|
318
|
-
|
|
319
|
-
expect((result.result as { ideas: unknown[] }).ideas).toHaveLength(2);
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
it('should pass status filter to API', async () => {
|
|
323
|
-
mockApiClient.getIdeas.mockResolvedValue({
|
|
324
|
-
ok: true,
|
|
325
|
-
data: { ideas: [] },
|
|
326
|
-
});
|
|
327
|
-
const ctx = createMockContext();
|
|
328
|
-
|
|
329
|
-
await getIdeas(
|
|
330
|
-
{ project_id: '123e4567-e89b-12d3-a456-426614174000', status: 'planned' },
|
|
331
|
-
ctx
|
|
332
|
-
);
|
|
333
|
-
|
|
334
|
-
expect(mockApiClient.getIdeas).toHaveBeenCalledWith(
|
|
335
|
-
'123e4567-e89b-12d3-a456-426614174000',
|
|
336
|
-
{
|
|
337
|
-
status: 'planned',
|
|
338
|
-
limit: 10,
|
|
339
|
-
offset: 0,
|
|
340
|
-
search_query: undefined,
|
|
341
|
-
}
|
|
342
|
-
);
|
|
343
|
-
});
|
|
344
|
-
|
|
345
|
-
it('should pass limit and offset to API', async () => {
|
|
346
|
-
mockApiClient.getIdeas.mockResolvedValue({
|
|
347
|
-
ok: true,
|
|
348
|
-
data: { ideas: [] },
|
|
349
|
-
});
|
|
350
|
-
const ctx = createMockContext();
|
|
351
|
-
|
|
352
|
-
await getIdeas(
|
|
353
|
-
{ project_id: '123e4567-e89b-12d3-a456-426614174000', limit: 10, offset: 5 },
|
|
354
|
-
ctx
|
|
355
|
-
);
|
|
356
|
-
|
|
357
|
-
expect(mockApiClient.getIdeas).toHaveBeenCalledWith(
|
|
358
|
-
'123e4567-e89b-12d3-a456-426614174000',
|
|
359
|
-
{
|
|
360
|
-
status: undefined,
|
|
361
|
-
limit: 10,
|
|
362
|
-
offset: 5,
|
|
363
|
-
search_query: undefined,
|
|
364
|
-
}
|
|
365
|
-
);
|
|
366
|
-
});
|
|
367
|
-
|
|
368
|
-
it('should return error when API call fails', async () => {
|
|
369
|
-
mockApiClient.getIdeas.mockResolvedValue({
|
|
370
|
-
ok: false,
|
|
371
|
-
error: 'Query failed',
|
|
372
|
-
});
|
|
373
|
-
const ctx = createMockContext();
|
|
374
|
-
|
|
375
|
-
const result = await getIdeas({ project_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx);
|
|
376
|
-
|
|
377
|
-
expect(result.isError).toBe(true);
|
|
378
|
-
expect(result.result).toMatchObject({ error: 'Query failed' });
|
|
379
|
-
});
|
|
380
|
-
});
|
|
381
|
-
|
|
382
|
-
// ============================================================================
|
|
383
|
-
// deleteIdea Tests
|
|
384
|
-
// ============================================================================
|
|
385
|
-
|
|
386
|
-
describe('deleteIdea', () => {
|
|
387
|
-
beforeEach(() => {});
|
|
388
|
-
|
|
389
|
-
it('should throw error for missing idea_id', async () => {
|
|
390
|
-
const ctx = createMockContext();
|
|
391
|
-
|
|
392
|
-
await expect(deleteIdea({}, ctx)).rejects.toThrow(ValidationError);
|
|
393
|
-
});
|
|
394
|
-
|
|
395
|
-
it('should throw error for invalid idea_id UUID', async () => {
|
|
396
|
-
const ctx = createMockContext();
|
|
397
|
-
|
|
398
|
-
await expect(
|
|
399
|
-
deleteIdea({ idea_id: 'invalid' }, ctx)
|
|
400
|
-
).rejects.toThrow(ValidationError);
|
|
401
|
-
});
|
|
402
|
-
|
|
403
|
-
it('should delete idea successfully', async () => {
|
|
404
|
-
mockApiClient.deleteIdea.mockResolvedValue({
|
|
405
|
-
ok: true,
|
|
406
|
-
data: { success: true },
|
|
407
|
-
});
|
|
408
|
-
const ctx = createMockContext();
|
|
409
|
-
|
|
410
|
-
const result = await deleteIdea(
|
|
411
|
-
{ idea_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
412
|
-
ctx
|
|
413
|
-
);
|
|
414
|
-
|
|
415
|
-
expect(result.result).toMatchObject({ success: true });
|
|
416
|
-
});
|
|
417
|
-
|
|
418
|
-
it('should call API client deleteIdea', async () => {
|
|
419
|
-
mockApiClient.deleteIdea.mockResolvedValue({
|
|
420
|
-
ok: true,
|
|
421
|
-
data: { success: true },
|
|
422
|
-
});
|
|
423
|
-
const ctx = createMockContext();
|
|
424
|
-
|
|
425
|
-
await deleteIdea(
|
|
426
|
-
{ idea_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
427
|
-
ctx
|
|
428
|
-
);
|
|
429
|
-
|
|
430
|
-
expect(mockApiClient.deleteIdea).toHaveBeenCalledWith(
|
|
431
|
-
'123e4567-e89b-12d3-a456-426614174000'
|
|
432
|
-
);
|
|
433
|
-
});
|
|
434
|
-
|
|
435
|
-
it('should return error when API call fails', async () => {
|
|
436
|
-
mockApiClient.deleteIdea.mockResolvedValue({
|
|
437
|
-
ok: false,
|
|
438
|
-
error: 'Delete failed',
|
|
439
|
-
});
|
|
440
|
-
const ctx = createMockContext();
|
|
441
|
-
|
|
442
|
-
const result = await deleteIdea({ idea_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx);
|
|
443
|
-
|
|
444
|
-
expect(result.isError).toBe(true);
|
|
445
|
-
expect(result.result).toMatchObject({ error: 'Delete failed' });
|
|
446
|
-
});
|
|
447
|
-
});
|
|
448
|
-
|
|
449
|
-
// ============================================================================
|
|
450
|
-
// convertIdeaToTask Tests
|
|
451
|
-
// ============================================================================
|
|
452
|
-
|
|
453
|
-
describe('convertIdeaToTask', () => {
|
|
454
|
-
beforeEach(() => {});
|
|
455
|
-
|
|
456
|
-
it('should throw error for missing idea_id', async () => {
|
|
457
|
-
const ctx = createMockContext();
|
|
458
|
-
|
|
459
|
-
await expect(convertIdeaToTask({}, ctx)).rejects.toThrow(ValidationError);
|
|
460
|
-
});
|
|
461
|
-
|
|
462
|
-
it('should throw error for invalid idea_id UUID', async () => {
|
|
463
|
-
const ctx = createMockContext();
|
|
464
|
-
|
|
465
|
-
await expect(
|
|
466
|
-
convertIdeaToTask({ idea_id: 'invalid' }, ctx)
|
|
467
|
-
).rejects.toThrow(ValidationError);
|
|
468
|
-
});
|
|
469
|
-
|
|
470
|
-
it('should throw error for invalid priority', async () => {
|
|
471
|
-
const ctx = createMockContext();
|
|
472
|
-
|
|
473
|
-
await expect(
|
|
474
|
-
convertIdeaToTask({
|
|
475
|
-
idea_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
476
|
-
priority: 10, // Invalid
|
|
477
|
-
}, ctx)
|
|
478
|
-
).rejects.toThrow(ValidationError);
|
|
479
|
-
});
|
|
480
|
-
|
|
481
|
-
it('should return error when idea already converted', async () => {
|
|
482
|
-
mockApiClient.proxy.mockResolvedValue({
|
|
483
|
-
ok: true,
|
|
484
|
-
data: {
|
|
485
|
-
success: false,
|
|
486
|
-
error: 'Idea has already been converted to a task',
|
|
487
|
-
existing_task_id: 'existing-task-id',
|
|
488
|
-
},
|
|
489
|
-
});
|
|
490
|
-
const ctx = createMockContext();
|
|
491
|
-
|
|
492
|
-
const result = await convertIdeaToTask(
|
|
493
|
-
{ idea_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
494
|
-
ctx
|
|
495
|
-
);
|
|
496
|
-
|
|
497
|
-
expect(result.result).toMatchObject({
|
|
498
|
-
success: false,
|
|
499
|
-
error: 'Idea has already been converted to a task',
|
|
500
|
-
existing_task_id: 'existing-task-id',
|
|
501
|
-
});
|
|
502
|
-
});
|
|
503
|
-
|
|
504
|
-
it('should convert idea to task successfully', async () => {
|
|
505
|
-
mockApiClient.proxy.mockResolvedValue({
|
|
506
|
-
ok: true,
|
|
507
|
-
data: {
|
|
508
|
-
success: true,
|
|
509
|
-
task_id: 'task-1',
|
|
510
|
-
task_title: 'Feature Request',
|
|
511
|
-
idea_id: 'idea-1',
|
|
512
|
-
idea_status: 'in_development',
|
|
513
|
-
},
|
|
514
|
-
});
|
|
515
|
-
const ctx = createMockContext();
|
|
516
|
-
|
|
517
|
-
const result = await convertIdeaToTask(
|
|
518
|
-
{ idea_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
519
|
-
ctx
|
|
520
|
-
);
|
|
521
|
-
|
|
522
|
-
expect(result.result).toMatchObject({
|
|
523
|
-
success: true,
|
|
524
|
-
task_id: 'task-1',
|
|
525
|
-
task_title: 'Feature Request',
|
|
526
|
-
idea_id: 'idea-1',
|
|
527
|
-
});
|
|
528
|
-
});
|
|
529
|
-
|
|
530
|
-
it('should use default priority of 3', async () => {
|
|
531
|
-
mockApiClient.proxy.mockResolvedValue({
|
|
532
|
-
ok: true,
|
|
533
|
-
data: { success: true, task_id: 'task-1' },
|
|
534
|
-
});
|
|
535
|
-
const ctx = createMockContext();
|
|
536
|
-
|
|
537
|
-
await convertIdeaToTask(
|
|
538
|
-
{ idea_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
539
|
-
ctx
|
|
540
|
-
);
|
|
541
|
-
|
|
542
|
-
expect(mockApiClient.proxy).toHaveBeenCalledWith(
|
|
543
|
-
'convert_idea_to_task',
|
|
544
|
-
{
|
|
545
|
-
idea_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
546
|
-
priority: 3,
|
|
547
|
-
estimated_minutes: undefined,
|
|
548
|
-
update_status: true,
|
|
549
|
-
}
|
|
550
|
-
);
|
|
551
|
-
});
|
|
552
|
-
|
|
553
|
-
it('should use custom priority when provided', async () => {
|
|
554
|
-
mockApiClient.proxy.mockResolvedValue({
|
|
555
|
-
ok: true,
|
|
556
|
-
data: { success: true, task_id: 'task-1' },
|
|
557
|
-
});
|
|
558
|
-
const ctx = createMockContext();
|
|
559
|
-
|
|
560
|
-
await convertIdeaToTask(
|
|
561
|
-
{
|
|
562
|
-
idea_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
563
|
-
priority: 1,
|
|
564
|
-
},
|
|
565
|
-
ctx
|
|
566
|
-
);
|
|
567
|
-
|
|
568
|
-
expect(mockApiClient.proxy).toHaveBeenCalledWith(
|
|
569
|
-
'convert_idea_to_task',
|
|
570
|
-
{
|
|
571
|
-
idea_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
572
|
-
priority: 1,
|
|
573
|
-
estimated_minutes: undefined,
|
|
574
|
-
update_status: true,
|
|
575
|
-
}
|
|
576
|
-
);
|
|
577
|
-
});
|
|
578
|
-
|
|
579
|
-
it('should update idea status to in_development by default', async () => {
|
|
580
|
-
mockApiClient.proxy.mockResolvedValue({
|
|
581
|
-
ok: true,
|
|
582
|
-
data: {
|
|
583
|
-
success: true,
|
|
584
|
-
task_id: 'task-1',
|
|
585
|
-
task_title: 'Feature Request',
|
|
586
|
-
idea_id: 'idea-1',
|
|
587
|
-
idea_status: 'in_development',
|
|
588
|
-
},
|
|
589
|
-
});
|
|
590
|
-
const ctx = createMockContext();
|
|
591
|
-
|
|
592
|
-
const result = await convertIdeaToTask(
|
|
593
|
-
{ idea_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
594
|
-
ctx
|
|
595
|
-
);
|
|
596
|
-
|
|
597
|
-
expect((result.result as { idea_status: string }).idea_status).toBe('in_development');
|
|
598
|
-
});
|
|
599
|
-
|
|
600
|
-
it('should not update status when update_status is false', async () => {
|
|
601
|
-
mockApiClient.proxy.mockResolvedValue({
|
|
602
|
-
ok: true,
|
|
603
|
-
data: {
|
|
604
|
-
success: true,
|
|
605
|
-
task_id: 'task-1',
|
|
606
|
-
task_title: 'Feature Request',
|
|
607
|
-
idea_id: 'idea-1',
|
|
608
|
-
idea_status: 'planned',
|
|
609
|
-
},
|
|
610
|
-
});
|
|
611
|
-
const ctx = createMockContext();
|
|
612
|
-
|
|
613
|
-
await convertIdeaToTask(
|
|
614
|
-
{
|
|
615
|
-
idea_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
616
|
-
update_status: false,
|
|
617
|
-
},
|
|
618
|
-
ctx
|
|
619
|
-
);
|
|
620
|
-
|
|
621
|
-
expect(mockApiClient.proxy).toHaveBeenCalledWith(
|
|
622
|
-
'convert_idea_to_task',
|
|
623
|
-
{
|
|
624
|
-
idea_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
625
|
-
priority: 3,
|
|
626
|
-
estimated_minutes: undefined,
|
|
627
|
-
update_status: false,
|
|
628
|
-
}
|
|
629
|
-
);
|
|
630
|
-
});
|
|
631
|
-
|
|
632
|
-
it('should return error when API call fails', async () => {
|
|
633
|
-
mockApiClient.proxy.mockResolvedValue({
|
|
634
|
-
ok: false,
|
|
635
|
-
error: 'Idea not found',
|
|
636
|
-
});
|
|
637
|
-
const ctx = createMockContext();
|
|
638
|
-
|
|
639
|
-
const result = await convertIdeaToTask({ idea_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx);
|
|
640
|
-
|
|
641
|
-
expect(result.isError).toBe(true);
|
|
642
|
-
expect(result.result).toMatchObject({ error: 'Idea not found' });
|
|
643
|
-
});
|
|
644
|
-
});
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
addIdea,
|
|
4
|
+
updateIdea,
|
|
5
|
+
getIdeas,
|
|
6
|
+
deleteIdea,
|
|
7
|
+
convertIdeaToTask,
|
|
8
|
+
} from './ideas.js';
|
|
9
|
+
import { ValidationError } from '../validators.js';
|
|
10
|
+
import { createMockContext } from './__test-utils__.js';
|
|
11
|
+
import { mockApiClient } from './__test-setup__.js';
|
|
12
|
+
|
|
13
|
+
// ============================================================================
|
|
14
|
+
// addIdea Tests
|
|
15
|
+
// ============================================================================
|
|
16
|
+
|
|
17
|
+
describe('addIdea', () => {
|
|
18
|
+
beforeEach(() => {});
|
|
19
|
+
|
|
20
|
+
it('should throw error for missing project_id', async () => {
|
|
21
|
+
const ctx = createMockContext();
|
|
22
|
+
|
|
23
|
+
await expect(
|
|
24
|
+
addIdea({ title: 'New Feature' }, ctx)
|
|
25
|
+
).rejects.toThrow(ValidationError);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should throw error for invalid project_id UUID', async () => {
|
|
29
|
+
const ctx = createMockContext();
|
|
30
|
+
|
|
31
|
+
await expect(
|
|
32
|
+
addIdea({ project_id: 'invalid', title: 'New Feature' }, 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
|
+
addIdea({ project_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx)
|
|
41
|
+
).rejects.toThrow(ValidationError);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should add idea successfully', async () => {
|
|
45
|
+
mockApiClient.addIdea.mockResolvedValue({
|
|
46
|
+
ok: true,
|
|
47
|
+
data: { idea_id: 'idea-1' },
|
|
48
|
+
});
|
|
49
|
+
const ctx = createMockContext();
|
|
50
|
+
|
|
51
|
+
const result = await addIdea(
|
|
52
|
+
{
|
|
53
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
54
|
+
title: 'Dark Mode Support',
|
|
55
|
+
},
|
|
56
|
+
ctx
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
expect(result.result).toMatchObject({
|
|
60
|
+
success: true,
|
|
61
|
+
idea_id: 'idea-1',
|
|
62
|
+
title: 'Dark Mode Support',
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should call API client with default status "raw"', async () => {
|
|
67
|
+
mockApiClient.addIdea.mockResolvedValue({
|
|
68
|
+
ok: true,
|
|
69
|
+
data: { idea_id: 'idea-1' },
|
|
70
|
+
});
|
|
71
|
+
const ctx = createMockContext();
|
|
72
|
+
|
|
73
|
+
await addIdea(
|
|
74
|
+
{
|
|
75
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
76
|
+
title: 'New Feature',
|
|
77
|
+
},
|
|
78
|
+
ctx
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
expect(mockApiClient.addIdea).toHaveBeenCalledWith(
|
|
82
|
+
'123e4567-e89b-12d3-a456-426614174000',
|
|
83
|
+
{
|
|
84
|
+
title: 'New Feature',
|
|
85
|
+
description: undefined,
|
|
86
|
+
status: undefined, // Default status applied server-side
|
|
87
|
+
},
|
|
88
|
+
'session-123'
|
|
89
|
+
);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('should call API client with custom status', async () => {
|
|
93
|
+
mockApiClient.addIdea.mockResolvedValue({
|
|
94
|
+
ok: true,
|
|
95
|
+
data: { idea_id: 'idea-1' },
|
|
96
|
+
});
|
|
97
|
+
const ctx = createMockContext();
|
|
98
|
+
|
|
99
|
+
await addIdea(
|
|
100
|
+
{
|
|
101
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
102
|
+
title: 'New Feature',
|
|
103
|
+
status: 'exploring',
|
|
104
|
+
},
|
|
105
|
+
ctx
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
expect(mockApiClient.addIdea).toHaveBeenCalledWith(
|
|
109
|
+
'123e4567-e89b-12d3-a456-426614174000',
|
|
110
|
+
{
|
|
111
|
+
title: 'New Feature',
|
|
112
|
+
description: undefined,
|
|
113
|
+
status: 'exploring',
|
|
114
|
+
},
|
|
115
|
+
'session-123'
|
|
116
|
+
);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should include session_id in API call', async () => {
|
|
120
|
+
mockApiClient.addIdea.mockResolvedValue({
|
|
121
|
+
ok: true,
|
|
122
|
+
data: { idea_id: 'idea-1' },
|
|
123
|
+
});
|
|
124
|
+
const ctx = createMockContext({ sessionId: 'my-session' });
|
|
125
|
+
|
|
126
|
+
await addIdea(
|
|
127
|
+
{
|
|
128
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
129
|
+
title: 'New Feature',
|
|
130
|
+
},
|
|
131
|
+
ctx
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
expect(mockApiClient.addIdea).toHaveBeenCalledWith(
|
|
135
|
+
'123e4567-e89b-12d3-a456-426614174000',
|
|
136
|
+
expect.any(Object),
|
|
137
|
+
'my-session'
|
|
138
|
+
);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('should return error when API call fails', async () => {
|
|
142
|
+
mockApiClient.addIdea.mockResolvedValue({
|
|
143
|
+
ok: false,
|
|
144
|
+
error: 'Insert failed',
|
|
145
|
+
});
|
|
146
|
+
const ctx = createMockContext();
|
|
147
|
+
|
|
148
|
+
const result = await addIdea({
|
|
149
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
150
|
+
title: 'New Feature',
|
|
151
|
+
}, ctx);
|
|
152
|
+
|
|
153
|
+
expect(result.isError).toBe(true);
|
|
154
|
+
expect(result.result).toMatchObject({ error: 'Insert failed' });
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// ============================================================================
|
|
159
|
+
// updateIdea Tests
|
|
160
|
+
// ============================================================================
|
|
161
|
+
|
|
162
|
+
describe('updateIdea', () => {
|
|
163
|
+
beforeEach(() => {});
|
|
164
|
+
|
|
165
|
+
it('should throw error for missing idea_id', async () => {
|
|
166
|
+
const ctx = createMockContext();
|
|
167
|
+
|
|
168
|
+
await expect(updateIdea({}, ctx)).rejects.toThrow(ValidationError);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('should throw error for invalid idea_id UUID', async () => {
|
|
172
|
+
const ctx = createMockContext();
|
|
173
|
+
|
|
174
|
+
await expect(
|
|
175
|
+
updateIdea({ idea_id: 'invalid' }, ctx)
|
|
176
|
+
).rejects.toThrow(ValidationError);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('should update idea title', async () => {
|
|
180
|
+
mockApiClient.updateIdea.mockResolvedValue({
|
|
181
|
+
ok: true,
|
|
182
|
+
data: { idea: { id: 'idea-1', title: 'Updated Title' } },
|
|
183
|
+
});
|
|
184
|
+
const ctx = createMockContext();
|
|
185
|
+
|
|
186
|
+
const result = await updateIdea(
|
|
187
|
+
{
|
|
188
|
+
idea_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
189
|
+
title: 'Updated Title',
|
|
190
|
+
},
|
|
191
|
+
ctx
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
expect(result.result).toMatchObject({
|
|
195
|
+
success: true,
|
|
196
|
+
idea_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('should call API client with status update', async () => {
|
|
201
|
+
mockApiClient.updateIdea.mockResolvedValue({
|
|
202
|
+
ok: true,
|
|
203
|
+
data: { idea: { id: 'idea-1', status: 'planned' } },
|
|
204
|
+
});
|
|
205
|
+
const ctx = createMockContext();
|
|
206
|
+
|
|
207
|
+
await updateIdea(
|
|
208
|
+
{
|
|
209
|
+
idea_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
210
|
+
status: 'planned',
|
|
211
|
+
},
|
|
212
|
+
ctx
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
expect(mockApiClient.updateIdea).toHaveBeenCalledWith(
|
|
216
|
+
'123e4567-e89b-12d3-a456-426614174000',
|
|
217
|
+
{
|
|
218
|
+
title: undefined,
|
|
219
|
+
description: undefined,
|
|
220
|
+
status: 'planned',
|
|
221
|
+
doc_url: undefined,
|
|
222
|
+
}
|
|
223
|
+
);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('should update doc_url', async () => {
|
|
227
|
+
mockApiClient.updateIdea.mockResolvedValue({
|
|
228
|
+
ok: true,
|
|
229
|
+
data: { idea: { id: 'idea-1' } },
|
|
230
|
+
});
|
|
231
|
+
const ctx = createMockContext();
|
|
232
|
+
|
|
233
|
+
await updateIdea(
|
|
234
|
+
{
|
|
235
|
+
idea_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
236
|
+
doc_url: 'https://docs.example.com/feature',
|
|
237
|
+
},
|
|
238
|
+
ctx
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
expect(mockApiClient.updateIdea).toHaveBeenCalledWith(
|
|
242
|
+
'123e4567-e89b-12d3-a456-426614174000',
|
|
243
|
+
{
|
|
244
|
+
title: undefined,
|
|
245
|
+
description: undefined,
|
|
246
|
+
status: undefined,
|
|
247
|
+
doc_url: 'https://docs.example.com/feature',
|
|
248
|
+
}
|
|
249
|
+
);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it('should return error when API call fails', async () => {
|
|
253
|
+
mockApiClient.updateIdea.mockResolvedValue({
|
|
254
|
+
ok: false,
|
|
255
|
+
error: 'Idea not found',
|
|
256
|
+
});
|
|
257
|
+
const ctx = createMockContext();
|
|
258
|
+
|
|
259
|
+
const result = await updateIdea({ idea_id: '123e4567-e89b-12d3-a456-426614174000', title: 'New' }, ctx);
|
|
260
|
+
|
|
261
|
+
expect(result.isError).toBe(true);
|
|
262
|
+
expect(result.result).toMatchObject({ error: 'Idea not found' });
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
// ============================================================================
|
|
267
|
+
// getIdeas Tests
|
|
268
|
+
// ============================================================================
|
|
269
|
+
|
|
270
|
+
describe('getIdeas', () => {
|
|
271
|
+
beforeEach(() => {});
|
|
272
|
+
|
|
273
|
+
it('should throw error for missing project_id', async () => {
|
|
274
|
+
const ctx = createMockContext();
|
|
275
|
+
|
|
276
|
+
await expect(getIdeas({}, ctx)).rejects.toThrow(ValidationError);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('should throw error for invalid project_id UUID', async () => {
|
|
280
|
+
const ctx = createMockContext();
|
|
281
|
+
|
|
282
|
+
await expect(
|
|
283
|
+
getIdeas({ project_id: 'invalid' }, ctx)
|
|
284
|
+
).rejects.toThrow(ValidationError);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it('should return empty list when no ideas', async () => {
|
|
288
|
+
mockApiClient.getIdeas.mockResolvedValue({
|
|
289
|
+
ok: true,
|
|
290
|
+
data: { ideas: [] },
|
|
291
|
+
});
|
|
292
|
+
const ctx = createMockContext();
|
|
293
|
+
|
|
294
|
+
const result = await getIdeas(
|
|
295
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
296
|
+
ctx
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
expect(result.result).toMatchObject({ ideas: [] });
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it('should return ideas list', async () => {
|
|
303
|
+
const mockIdeas = [
|
|
304
|
+
{ id: 'i1', title: 'Idea 1', description: null, status: 'raw', doc_url: null },
|
|
305
|
+
{ id: 'i2', title: 'Idea 2', description: 'Some desc', status: 'exploring', doc_url: null },
|
|
306
|
+
];
|
|
307
|
+
|
|
308
|
+
mockApiClient.getIdeas.mockResolvedValue({
|
|
309
|
+
ok: true,
|
|
310
|
+
data: { ideas: mockIdeas },
|
|
311
|
+
});
|
|
312
|
+
const ctx = createMockContext();
|
|
313
|
+
|
|
314
|
+
const result = await getIdeas(
|
|
315
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
316
|
+
ctx
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
expect((result.result as { ideas: unknown[] }).ideas).toHaveLength(2);
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
it('should pass status filter to API', async () => {
|
|
323
|
+
mockApiClient.getIdeas.mockResolvedValue({
|
|
324
|
+
ok: true,
|
|
325
|
+
data: { ideas: [] },
|
|
326
|
+
});
|
|
327
|
+
const ctx = createMockContext();
|
|
328
|
+
|
|
329
|
+
await getIdeas(
|
|
330
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000', status: 'planned' },
|
|
331
|
+
ctx
|
|
332
|
+
);
|
|
333
|
+
|
|
334
|
+
expect(mockApiClient.getIdeas).toHaveBeenCalledWith(
|
|
335
|
+
'123e4567-e89b-12d3-a456-426614174000',
|
|
336
|
+
{
|
|
337
|
+
status: 'planned',
|
|
338
|
+
limit: 10,
|
|
339
|
+
offset: 0,
|
|
340
|
+
search_query: undefined,
|
|
341
|
+
}
|
|
342
|
+
);
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
it('should pass limit and offset to API', async () => {
|
|
346
|
+
mockApiClient.getIdeas.mockResolvedValue({
|
|
347
|
+
ok: true,
|
|
348
|
+
data: { ideas: [] },
|
|
349
|
+
});
|
|
350
|
+
const ctx = createMockContext();
|
|
351
|
+
|
|
352
|
+
await getIdeas(
|
|
353
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000', limit: 10, offset: 5 },
|
|
354
|
+
ctx
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
expect(mockApiClient.getIdeas).toHaveBeenCalledWith(
|
|
358
|
+
'123e4567-e89b-12d3-a456-426614174000',
|
|
359
|
+
{
|
|
360
|
+
status: undefined,
|
|
361
|
+
limit: 10,
|
|
362
|
+
offset: 5,
|
|
363
|
+
search_query: undefined,
|
|
364
|
+
}
|
|
365
|
+
);
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
it('should return error when API call fails', async () => {
|
|
369
|
+
mockApiClient.getIdeas.mockResolvedValue({
|
|
370
|
+
ok: false,
|
|
371
|
+
error: 'Query failed',
|
|
372
|
+
});
|
|
373
|
+
const ctx = createMockContext();
|
|
374
|
+
|
|
375
|
+
const result = await getIdeas({ project_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx);
|
|
376
|
+
|
|
377
|
+
expect(result.isError).toBe(true);
|
|
378
|
+
expect(result.result).toMatchObject({ error: 'Query failed' });
|
|
379
|
+
});
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
// ============================================================================
|
|
383
|
+
// deleteIdea Tests
|
|
384
|
+
// ============================================================================
|
|
385
|
+
|
|
386
|
+
describe('deleteIdea', () => {
|
|
387
|
+
beforeEach(() => {});
|
|
388
|
+
|
|
389
|
+
it('should throw error for missing idea_id', async () => {
|
|
390
|
+
const ctx = createMockContext();
|
|
391
|
+
|
|
392
|
+
await expect(deleteIdea({}, ctx)).rejects.toThrow(ValidationError);
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
it('should throw error for invalid idea_id UUID', async () => {
|
|
396
|
+
const ctx = createMockContext();
|
|
397
|
+
|
|
398
|
+
await expect(
|
|
399
|
+
deleteIdea({ idea_id: 'invalid' }, ctx)
|
|
400
|
+
).rejects.toThrow(ValidationError);
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
it('should delete idea successfully', async () => {
|
|
404
|
+
mockApiClient.deleteIdea.mockResolvedValue({
|
|
405
|
+
ok: true,
|
|
406
|
+
data: { success: true },
|
|
407
|
+
});
|
|
408
|
+
const ctx = createMockContext();
|
|
409
|
+
|
|
410
|
+
const result = await deleteIdea(
|
|
411
|
+
{ idea_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
412
|
+
ctx
|
|
413
|
+
);
|
|
414
|
+
|
|
415
|
+
expect(result.result).toMatchObject({ success: true });
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
it('should call API client deleteIdea', async () => {
|
|
419
|
+
mockApiClient.deleteIdea.mockResolvedValue({
|
|
420
|
+
ok: true,
|
|
421
|
+
data: { success: true },
|
|
422
|
+
});
|
|
423
|
+
const ctx = createMockContext();
|
|
424
|
+
|
|
425
|
+
await deleteIdea(
|
|
426
|
+
{ idea_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
427
|
+
ctx
|
|
428
|
+
);
|
|
429
|
+
|
|
430
|
+
expect(mockApiClient.deleteIdea).toHaveBeenCalledWith(
|
|
431
|
+
'123e4567-e89b-12d3-a456-426614174000'
|
|
432
|
+
);
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
it('should return error when API call fails', async () => {
|
|
436
|
+
mockApiClient.deleteIdea.mockResolvedValue({
|
|
437
|
+
ok: false,
|
|
438
|
+
error: 'Delete failed',
|
|
439
|
+
});
|
|
440
|
+
const ctx = createMockContext();
|
|
441
|
+
|
|
442
|
+
const result = await deleteIdea({ idea_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx);
|
|
443
|
+
|
|
444
|
+
expect(result.isError).toBe(true);
|
|
445
|
+
expect(result.result).toMatchObject({ error: 'Delete failed' });
|
|
446
|
+
});
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
// ============================================================================
|
|
450
|
+
// convertIdeaToTask Tests
|
|
451
|
+
// ============================================================================
|
|
452
|
+
|
|
453
|
+
describe('convertIdeaToTask', () => {
|
|
454
|
+
beforeEach(() => {});
|
|
455
|
+
|
|
456
|
+
it('should throw error for missing idea_id', async () => {
|
|
457
|
+
const ctx = createMockContext();
|
|
458
|
+
|
|
459
|
+
await expect(convertIdeaToTask({}, ctx)).rejects.toThrow(ValidationError);
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
it('should throw error for invalid idea_id UUID', async () => {
|
|
463
|
+
const ctx = createMockContext();
|
|
464
|
+
|
|
465
|
+
await expect(
|
|
466
|
+
convertIdeaToTask({ idea_id: 'invalid' }, ctx)
|
|
467
|
+
).rejects.toThrow(ValidationError);
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
it('should throw error for invalid priority', async () => {
|
|
471
|
+
const ctx = createMockContext();
|
|
472
|
+
|
|
473
|
+
await expect(
|
|
474
|
+
convertIdeaToTask({
|
|
475
|
+
idea_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
476
|
+
priority: 10, // Invalid
|
|
477
|
+
}, ctx)
|
|
478
|
+
).rejects.toThrow(ValidationError);
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
it('should return error when idea already converted', async () => {
|
|
482
|
+
mockApiClient.proxy.mockResolvedValue({
|
|
483
|
+
ok: true,
|
|
484
|
+
data: {
|
|
485
|
+
success: false,
|
|
486
|
+
error: 'Idea has already been converted to a task',
|
|
487
|
+
existing_task_id: 'existing-task-id',
|
|
488
|
+
},
|
|
489
|
+
});
|
|
490
|
+
const ctx = createMockContext();
|
|
491
|
+
|
|
492
|
+
const result = await convertIdeaToTask(
|
|
493
|
+
{ idea_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
494
|
+
ctx
|
|
495
|
+
);
|
|
496
|
+
|
|
497
|
+
expect(result.result).toMatchObject({
|
|
498
|
+
success: false,
|
|
499
|
+
error: 'Idea has already been converted to a task',
|
|
500
|
+
existing_task_id: 'existing-task-id',
|
|
501
|
+
});
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
it('should convert idea to task successfully', async () => {
|
|
505
|
+
mockApiClient.proxy.mockResolvedValue({
|
|
506
|
+
ok: true,
|
|
507
|
+
data: {
|
|
508
|
+
success: true,
|
|
509
|
+
task_id: 'task-1',
|
|
510
|
+
task_title: 'Feature Request',
|
|
511
|
+
idea_id: 'idea-1',
|
|
512
|
+
idea_status: 'in_development',
|
|
513
|
+
},
|
|
514
|
+
});
|
|
515
|
+
const ctx = createMockContext();
|
|
516
|
+
|
|
517
|
+
const result = await convertIdeaToTask(
|
|
518
|
+
{ idea_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
519
|
+
ctx
|
|
520
|
+
);
|
|
521
|
+
|
|
522
|
+
expect(result.result).toMatchObject({
|
|
523
|
+
success: true,
|
|
524
|
+
task_id: 'task-1',
|
|
525
|
+
task_title: 'Feature Request',
|
|
526
|
+
idea_id: 'idea-1',
|
|
527
|
+
});
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
it('should use default priority of 3', async () => {
|
|
531
|
+
mockApiClient.proxy.mockResolvedValue({
|
|
532
|
+
ok: true,
|
|
533
|
+
data: { success: true, task_id: 'task-1' },
|
|
534
|
+
});
|
|
535
|
+
const ctx = createMockContext();
|
|
536
|
+
|
|
537
|
+
await convertIdeaToTask(
|
|
538
|
+
{ idea_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
539
|
+
ctx
|
|
540
|
+
);
|
|
541
|
+
|
|
542
|
+
expect(mockApiClient.proxy).toHaveBeenCalledWith(
|
|
543
|
+
'convert_idea_to_task',
|
|
544
|
+
{
|
|
545
|
+
idea_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
546
|
+
priority: 3,
|
|
547
|
+
estimated_minutes: undefined,
|
|
548
|
+
update_status: true,
|
|
549
|
+
}
|
|
550
|
+
);
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
it('should use custom priority when provided', async () => {
|
|
554
|
+
mockApiClient.proxy.mockResolvedValue({
|
|
555
|
+
ok: true,
|
|
556
|
+
data: { success: true, task_id: 'task-1' },
|
|
557
|
+
});
|
|
558
|
+
const ctx = createMockContext();
|
|
559
|
+
|
|
560
|
+
await convertIdeaToTask(
|
|
561
|
+
{
|
|
562
|
+
idea_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
563
|
+
priority: 1,
|
|
564
|
+
},
|
|
565
|
+
ctx
|
|
566
|
+
);
|
|
567
|
+
|
|
568
|
+
expect(mockApiClient.proxy).toHaveBeenCalledWith(
|
|
569
|
+
'convert_idea_to_task',
|
|
570
|
+
{
|
|
571
|
+
idea_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
572
|
+
priority: 1,
|
|
573
|
+
estimated_minutes: undefined,
|
|
574
|
+
update_status: true,
|
|
575
|
+
}
|
|
576
|
+
);
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
it('should update idea status to in_development by default', async () => {
|
|
580
|
+
mockApiClient.proxy.mockResolvedValue({
|
|
581
|
+
ok: true,
|
|
582
|
+
data: {
|
|
583
|
+
success: true,
|
|
584
|
+
task_id: 'task-1',
|
|
585
|
+
task_title: 'Feature Request',
|
|
586
|
+
idea_id: 'idea-1',
|
|
587
|
+
idea_status: 'in_development',
|
|
588
|
+
},
|
|
589
|
+
});
|
|
590
|
+
const ctx = createMockContext();
|
|
591
|
+
|
|
592
|
+
const result = await convertIdeaToTask(
|
|
593
|
+
{ idea_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
594
|
+
ctx
|
|
595
|
+
);
|
|
596
|
+
|
|
597
|
+
expect((result.result as { idea_status: string }).idea_status).toBe('in_development');
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
it('should not update status when update_status is false', async () => {
|
|
601
|
+
mockApiClient.proxy.mockResolvedValue({
|
|
602
|
+
ok: true,
|
|
603
|
+
data: {
|
|
604
|
+
success: true,
|
|
605
|
+
task_id: 'task-1',
|
|
606
|
+
task_title: 'Feature Request',
|
|
607
|
+
idea_id: 'idea-1',
|
|
608
|
+
idea_status: 'planned',
|
|
609
|
+
},
|
|
610
|
+
});
|
|
611
|
+
const ctx = createMockContext();
|
|
612
|
+
|
|
613
|
+
await convertIdeaToTask(
|
|
614
|
+
{
|
|
615
|
+
idea_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
616
|
+
update_status: false,
|
|
617
|
+
},
|
|
618
|
+
ctx
|
|
619
|
+
);
|
|
620
|
+
|
|
621
|
+
expect(mockApiClient.proxy).toHaveBeenCalledWith(
|
|
622
|
+
'convert_idea_to_task',
|
|
623
|
+
{
|
|
624
|
+
idea_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
625
|
+
priority: 3,
|
|
626
|
+
estimated_minutes: undefined,
|
|
627
|
+
update_status: false,
|
|
628
|
+
}
|
|
629
|
+
);
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
it('should return error when API call fails', async () => {
|
|
633
|
+
mockApiClient.proxy.mockResolvedValue({
|
|
634
|
+
ok: false,
|
|
635
|
+
error: 'Idea not found',
|
|
636
|
+
});
|
|
637
|
+
const ctx = createMockContext();
|
|
638
|
+
|
|
639
|
+
const result = await convertIdeaToTask({ idea_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx);
|
|
640
|
+
|
|
641
|
+
expect(result.isError).toBe(true);
|
|
642
|
+
expect(result.result).toMatchObject({ error: 'Idea not found' });
|
|
643
|
+
});
|
|
644
|
+
});
|