@vibescope/mcp-server 0.0.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/README.md +98 -0
- package/dist/cli.d.ts +34 -0
- package/dist/cli.js +356 -0
- package/dist/cli.test.d.ts +1 -0
- package/dist/cli.test.js +367 -0
- package/dist/handlers/__test-utils__.d.ts +72 -0
- package/dist/handlers/__test-utils__.js +176 -0
- package/dist/handlers/blockers.d.ts +18 -0
- package/dist/handlers/blockers.js +81 -0
- package/dist/handlers/bodies-of-work.d.ts +34 -0
- package/dist/handlers/bodies-of-work.js +614 -0
- package/dist/handlers/checkouts.d.ts +37 -0
- package/dist/handlers/checkouts.js +377 -0
- package/dist/handlers/cost.d.ts +39 -0
- package/dist/handlers/cost.js +247 -0
- package/dist/handlers/decisions.d.ts +16 -0
- package/dist/handlers/decisions.js +64 -0
- package/dist/handlers/deployment.d.ts +36 -0
- package/dist/handlers/deployment.js +1062 -0
- package/dist/handlers/discovery.d.ts +14 -0
- package/dist/handlers/discovery.js +870 -0
- package/dist/handlers/fallback.d.ts +18 -0
- package/dist/handlers/fallback.js +216 -0
- package/dist/handlers/findings.d.ts +18 -0
- package/dist/handlers/findings.js +110 -0
- package/dist/handlers/git-issues.d.ts +22 -0
- package/dist/handlers/git-issues.js +247 -0
- package/dist/handlers/ideas.d.ts +19 -0
- package/dist/handlers/ideas.js +188 -0
- package/dist/handlers/index.d.ts +29 -0
- package/dist/handlers/index.js +65 -0
- package/dist/handlers/knowledge-query.d.ts +22 -0
- package/dist/handlers/knowledge-query.js +253 -0
- package/dist/handlers/knowledge.d.ts +12 -0
- package/dist/handlers/knowledge.js +108 -0
- package/dist/handlers/milestones.d.ts +20 -0
- package/dist/handlers/milestones.js +179 -0
- package/dist/handlers/organizations.d.ts +36 -0
- package/dist/handlers/organizations.js +428 -0
- package/dist/handlers/progress.d.ts +14 -0
- package/dist/handlers/progress.js +149 -0
- package/dist/handlers/project.d.ts +20 -0
- package/dist/handlers/project.js +278 -0
- package/dist/handlers/requests.d.ts +16 -0
- package/dist/handlers/requests.js +131 -0
- package/dist/handlers/roles.d.ts +30 -0
- package/dist/handlers/roles.js +281 -0
- package/dist/handlers/session.d.ts +20 -0
- package/dist/handlers/session.js +791 -0
- package/dist/handlers/tasks.d.ts +52 -0
- package/dist/handlers/tasks.js +1111 -0
- package/dist/handlers/tasks.test.d.ts +1 -0
- package/dist/handlers/tasks.test.js +431 -0
- package/dist/handlers/types.d.ts +94 -0
- package/dist/handlers/types.js +1 -0
- package/dist/handlers/validation.d.ts +16 -0
- package/dist/handlers/validation.js +188 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2707 -0
- package/dist/knowledge.d.ts +6 -0
- package/dist/knowledge.js +121 -0
- package/dist/tools.d.ts +2 -0
- package/dist/tools.js +2498 -0
- package/dist/utils.d.ts +149 -0
- package/dist/utils.js +317 -0
- package/dist/utils.test.d.ts +1 -0
- package/dist/utils.test.js +532 -0
- package/dist/validators.d.ts +35 -0
- package/dist/validators.js +111 -0
- package/dist/validators.test.d.ts +1 -0
- package/dist/validators.test.js +176 -0
- package/package.json +44 -0
- package/src/cli.test.ts +442 -0
- package/src/cli.ts +439 -0
- package/src/handlers/__test-utils__.ts +217 -0
- package/src/handlers/blockers.test.ts +390 -0
- package/src/handlers/blockers.ts +110 -0
- package/src/handlers/bodies-of-work.test.ts +1276 -0
- package/src/handlers/bodies-of-work.ts +783 -0
- package/src/handlers/cost.test.ts +436 -0
- package/src/handlers/cost.ts +322 -0
- package/src/handlers/decisions.test.ts +401 -0
- package/src/handlers/decisions.ts +86 -0
- package/src/handlers/deployment.test.ts +516 -0
- package/src/handlers/deployment.ts +1289 -0
- package/src/handlers/discovery.test.ts +254 -0
- package/src/handlers/discovery.ts +969 -0
- package/src/handlers/fallback.test.ts +687 -0
- package/src/handlers/fallback.ts +260 -0
- package/src/handlers/findings.test.ts +565 -0
- package/src/handlers/findings.ts +153 -0
- package/src/handlers/ideas.test.ts +753 -0
- package/src/handlers/ideas.ts +247 -0
- package/src/handlers/index.ts +69 -0
- package/src/handlers/milestones.test.ts +584 -0
- package/src/handlers/milestones.ts +217 -0
- package/src/handlers/organizations.test.ts +997 -0
- package/src/handlers/organizations.ts +550 -0
- package/src/handlers/progress.test.ts +369 -0
- package/src/handlers/progress.ts +188 -0
- package/src/handlers/project.test.ts +562 -0
- package/src/handlers/project.ts +352 -0
- package/src/handlers/requests.test.ts +531 -0
- package/src/handlers/requests.ts +150 -0
- package/src/handlers/session.test.ts +459 -0
- package/src/handlers/session.ts +912 -0
- package/src/handlers/tasks.test.ts +602 -0
- package/src/handlers/tasks.ts +1393 -0
- package/src/handlers/types.ts +88 -0
- package/src/handlers/validation.test.ts +880 -0
- package/src/handlers/validation.ts +223 -0
- package/src/index.ts +3205 -0
- package/src/knowledge.ts +132 -0
- package/src/tmpclaude-0078-cwd +1 -0
- package/src/tmpclaude-0ee1-cwd +1 -0
- package/src/tmpclaude-2dd5-cwd +1 -0
- package/src/tmpclaude-344c-cwd +1 -0
- package/src/tmpclaude-3860-cwd +1 -0
- package/src/tmpclaude-4b63-cwd +1 -0
- package/src/tmpclaude-5c73-cwd +1 -0
- package/src/tmpclaude-5ee3-cwd +1 -0
- package/src/tmpclaude-6795-cwd +1 -0
- package/src/tmpclaude-709e-cwd +1 -0
- package/src/tmpclaude-9839-cwd +1 -0
- package/src/tmpclaude-d829-cwd +1 -0
- package/src/tmpclaude-e072-cwd +1 -0
- package/src/tmpclaude-f6ee-cwd +1 -0
- package/src/utils.test.ts +681 -0
- package/src/utils.ts +375 -0
- package/src/validators.test.ts +223 -0
- package/src/validators.ts +122 -0
- package/tmpclaude-0439-cwd +1 -0
- package/tmpclaude-132f-cwd +1 -0
- package/tmpclaude-15bb-cwd +1 -0
- package/tmpclaude-165a-cwd +1 -0
- package/tmpclaude-1ba9-cwd +1 -0
- package/tmpclaude-21a3-cwd +1 -0
- package/tmpclaude-2a38-cwd +1 -0
- package/tmpclaude-2adf-cwd +1 -0
- package/tmpclaude-2f56-cwd +1 -0
- package/tmpclaude-3626-cwd +1 -0
- package/tmpclaude-3727-cwd +1 -0
- package/tmpclaude-40bc-cwd +1 -0
- package/tmpclaude-436f-cwd +1 -0
- package/tmpclaude-4783-cwd +1 -0
- package/tmpclaude-4b6d-cwd +1 -0
- package/tmpclaude-4ba4-cwd +1 -0
- package/tmpclaude-51e6-cwd +1 -0
- package/tmpclaude-5ecf-cwd +1 -0
- package/tmpclaude-6f97-cwd +1 -0
- package/tmpclaude-7fb2-cwd +1 -0
- package/tmpclaude-825c-cwd +1 -0
- package/tmpclaude-8baf-cwd +1 -0
- package/tmpclaude-8d9f-cwd +1 -0
- package/tmpclaude-975c-cwd +1 -0
- package/tmpclaude-9983-cwd +1 -0
- package/tmpclaude-a045-cwd +1 -0
- package/tmpclaude-ac4a-cwd +1 -0
- package/tmpclaude-b593-cwd +1 -0
- package/tmpclaude-b891-cwd +1 -0
- package/tmpclaude-c032-cwd +1 -0
- package/tmpclaude-cf43-cwd +1 -0
- package/tmpclaude-d040-cwd +1 -0
- package/tmpclaude-dcdd-cwd +1 -0
- package/tmpclaude-dcee-cwd +1 -0
- package/tmpclaude-e16b-cwd +1 -0
- package/tmpclaude-ecd2-cwd +1 -0
- package/tmpclaude-f48d-cwd +1 -0
- package/tsconfig.json +16 -0
- package/vitest.config.ts +13 -0
|
@@ -0,0 +1,753 @@
|
|
|
1
|
+
import { describe, it, expect, vi, 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 { createMockSupabase, createMockContext } from './__test-utils__.js';
|
|
11
|
+
|
|
12
|
+
// ============================================================================
|
|
13
|
+
// addIdea Tests
|
|
14
|
+
// ============================================================================
|
|
15
|
+
|
|
16
|
+
describe('addIdea', () => {
|
|
17
|
+
beforeEach(() => vi.clearAllMocks());
|
|
18
|
+
|
|
19
|
+
it('should throw error for missing project_id', async () => {
|
|
20
|
+
const supabase = createMockSupabase();
|
|
21
|
+
const ctx = createMockContext(supabase);
|
|
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 supabase = createMockSupabase();
|
|
30
|
+
const ctx = createMockContext(supabase);
|
|
31
|
+
|
|
32
|
+
await expect(
|
|
33
|
+
addIdea({ project_id: 'invalid', title: 'New Feature' }, ctx)
|
|
34
|
+
).rejects.toThrow(ValidationError);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should throw error for missing title', async () => {
|
|
38
|
+
const supabase = createMockSupabase();
|
|
39
|
+
const ctx = createMockContext(supabase);
|
|
40
|
+
|
|
41
|
+
await expect(
|
|
42
|
+
addIdea({ project_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx)
|
|
43
|
+
).rejects.toThrow(ValidationError);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should add idea successfully', async () => {
|
|
47
|
+
const supabase = createMockSupabase({
|
|
48
|
+
insertResult: { data: { id: 'idea-1' }, error: null },
|
|
49
|
+
});
|
|
50
|
+
const ctx = createMockContext(supabase);
|
|
51
|
+
|
|
52
|
+
const result = await addIdea(
|
|
53
|
+
{
|
|
54
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
55
|
+
title: 'Dark Mode Support',
|
|
56
|
+
},
|
|
57
|
+
ctx
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
expect(result.result).toMatchObject({
|
|
61
|
+
success: true,
|
|
62
|
+
idea_id: 'idea-1',
|
|
63
|
+
title: 'Dark Mode Support',
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should insert with default status "raw"', async () => {
|
|
68
|
+
const supabase = createMockSupabase({
|
|
69
|
+
insertResult: { data: { id: 'idea-1' }, error: null },
|
|
70
|
+
});
|
|
71
|
+
const ctx = createMockContext(supabase);
|
|
72
|
+
|
|
73
|
+
await addIdea(
|
|
74
|
+
{
|
|
75
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
76
|
+
title: 'New Feature',
|
|
77
|
+
},
|
|
78
|
+
ctx
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
expect(supabase.insert).toHaveBeenCalledWith(
|
|
82
|
+
expect.objectContaining({
|
|
83
|
+
status: 'raw',
|
|
84
|
+
})
|
|
85
|
+
);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('should insert with custom status', async () => {
|
|
89
|
+
const supabase = createMockSupabase({
|
|
90
|
+
insertResult: { data: { id: 'idea-1' }, error: null },
|
|
91
|
+
});
|
|
92
|
+
const ctx = createMockContext(supabase);
|
|
93
|
+
|
|
94
|
+
await addIdea(
|
|
95
|
+
{
|
|
96
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
97
|
+
title: 'New Feature',
|
|
98
|
+
status: 'exploring',
|
|
99
|
+
},
|
|
100
|
+
ctx
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
expect(supabase.insert).toHaveBeenCalledWith(
|
|
104
|
+
expect.objectContaining({
|
|
105
|
+
status: 'exploring',
|
|
106
|
+
})
|
|
107
|
+
);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('should include session_id in insert', async () => {
|
|
111
|
+
const supabase = createMockSupabase({
|
|
112
|
+
insertResult: { data: { id: 'idea-1' }, error: null },
|
|
113
|
+
});
|
|
114
|
+
const ctx = createMockContext(supabase, { sessionId: 'my-session' });
|
|
115
|
+
|
|
116
|
+
await addIdea(
|
|
117
|
+
{
|
|
118
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
119
|
+
title: 'New Feature',
|
|
120
|
+
},
|
|
121
|
+
ctx
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
expect(supabase.insert).toHaveBeenCalledWith(
|
|
125
|
+
expect.objectContaining({
|
|
126
|
+
created_by: 'agent',
|
|
127
|
+
created_by_session_id: 'my-session',
|
|
128
|
+
})
|
|
129
|
+
);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('should throw error when insert fails', async () => {
|
|
133
|
+
const supabase = createMockSupabase({
|
|
134
|
+
insertResult: { data: null, error: { message: 'Insert failed' } },
|
|
135
|
+
});
|
|
136
|
+
const ctx = createMockContext(supabase);
|
|
137
|
+
|
|
138
|
+
await expect(
|
|
139
|
+
addIdea({
|
|
140
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
141
|
+
title: 'New Feature',
|
|
142
|
+
}, ctx)
|
|
143
|
+
).rejects.toThrow('Failed to add idea');
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// ============================================================================
|
|
148
|
+
// updateIdea Tests
|
|
149
|
+
// ============================================================================
|
|
150
|
+
|
|
151
|
+
describe('updateIdea', () => {
|
|
152
|
+
beforeEach(() => vi.clearAllMocks());
|
|
153
|
+
|
|
154
|
+
it('should throw error for missing idea_id', async () => {
|
|
155
|
+
const supabase = createMockSupabase();
|
|
156
|
+
const ctx = createMockContext(supabase);
|
|
157
|
+
|
|
158
|
+
await expect(updateIdea({}, ctx)).rejects.toThrow(ValidationError);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('should throw error for invalid idea_id UUID', async () => {
|
|
162
|
+
const supabase = createMockSupabase();
|
|
163
|
+
const ctx = createMockContext(supabase);
|
|
164
|
+
|
|
165
|
+
await expect(
|
|
166
|
+
updateIdea({ idea_id: 'invalid' }, ctx)
|
|
167
|
+
).rejects.toThrow(ValidationError);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('should throw error when idea not found', async () => {
|
|
171
|
+
const supabase = createMockSupabase({
|
|
172
|
+
selectResult: { data: null, error: null },
|
|
173
|
+
});
|
|
174
|
+
const ctx = createMockContext(supabase);
|
|
175
|
+
|
|
176
|
+
await expect(
|
|
177
|
+
updateIdea({ idea_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx)
|
|
178
|
+
).rejects.toThrow('Idea not found');
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('should update idea title', async () => {
|
|
182
|
+
const supabase = createMockSupabase({
|
|
183
|
+
selectResult: { data: { status: 'raw' }, error: null },
|
|
184
|
+
updateResult: { data: null, error: null },
|
|
185
|
+
});
|
|
186
|
+
const ctx = createMockContext(supabase);
|
|
187
|
+
|
|
188
|
+
const result = await updateIdea(
|
|
189
|
+
{
|
|
190
|
+
idea_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
191
|
+
title: 'Updated Title',
|
|
192
|
+
},
|
|
193
|
+
ctx
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
expect(result.result).toMatchObject({
|
|
197
|
+
success: true,
|
|
198
|
+
idea_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
199
|
+
});
|
|
200
|
+
expect(supabase.update).toHaveBeenCalledWith(
|
|
201
|
+
expect.objectContaining({
|
|
202
|
+
title: 'Updated Title',
|
|
203
|
+
updated_at: expect.any(String),
|
|
204
|
+
})
|
|
205
|
+
);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('should set planned_at when transitioning to planned status', async () => {
|
|
209
|
+
const supabase = createMockSupabase({
|
|
210
|
+
selectResult: { data: { status: 'exploring' }, error: null },
|
|
211
|
+
updateResult: { data: null, error: null },
|
|
212
|
+
});
|
|
213
|
+
const ctx = createMockContext(supabase);
|
|
214
|
+
|
|
215
|
+
await updateIdea(
|
|
216
|
+
{
|
|
217
|
+
idea_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
218
|
+
status: 'planned',
|
|
219
|
+
},
|
|
220
|
+
ctx
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
expect(supabase.update).toHaveBeenCalledWith(
|
|
224
|
+
expect.objectContaining({
|
|
225
|
+
status: 'planned',
|
|
226
|
+
planned_at: expect.any(String),
|
|
227
|
+
})
|
|
228
|
+
);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('should not set planned_at when already in planned status', async () => {
|
|
232
|
+
const supabase = createMockSupabase({
|
|
233
|
+
selectResult: { data: { status: 'planned' }, error: null },
|
|
234
|
+
updateResult: { data: null, error: null },
|
|
235
|
+
});
|
|
236
|
+
const ctx = createMockContext(supabase);
|
|
237
|
+
|
|
238
|
+
await updateIdea(
|
|
239
|
+
{
|
|
240
|
+
idea_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
241
|
+
status: 'planned',
|
|
242
|
+
},
|
|
243
|
+
ctx
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
// Should NOT have planned_at since it's already planned
|
|
247
|
+
expect(supabase.update).toHaveBeenCalledWith(
|
|
248
|
+
expect.not.objectContaining({
|
|
249
|
+
planned_at: expect.any(String),
|
|
250
|
+
})
|
|
251
|
+
);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it('should update doc_url', async () => {
|
|
255
|
+
const supabase = createMockSupabase({
|
|
256
|
+
selectResult: { data: { status: 'exploring' }, error: null },
|
|
257
|
+
updateResult: { data: null, error: null },
|
|
258
|
+
});
|
|
259
|
+
const ctx = createMockContext(supabase);
|
|
260
|
+
|
|
261
|
+
await updateIdea(
|
|
262
|
+
{
|
|
263
|
+
idea_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
264
|
+
doc_url: 'https://docs.example.com/feature',
|
|
265
|
+
},
|
|
266
|
+
ctx
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
expect(supabase.update).toHaveBeenCalledWith(
|
|
270
|
+
expect.objectContaining({
|
|
271
|
+
doc_url: 'https://docs.example.com/feature',
|
|
272
|
+
})
|
|
273
|
+
);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it('should throw error when update fails', async () => {
|
|
277
|
+
const supabase = createMockSupabase({
|
|
278
|
+
selectResult: { data: { status: 'raw' }, error: null },
|
|
279
|
+
});
|
|
280
|
+
const ctx = createMockContext(supabase);
|
|
281
|
+
|
|
282
|
+
// Override update to return error
|
|
283
|
+
vi.mocked(supabase.from('').update).mockReturnValue({
|
|
284
|
+
...supabase,
|
|
285
|
+
eq: vi.fn().mockReturnValue({
|
|
286
|
+
then: (resolve: (val: unknown) => void) =>
|
|
287
|
+
Promise.resolve({ data: null, error: { message: 'Update failed' } }).then(resolve),
|
|
288
|
+
}),
|
|
289
|
+
} as unknown as ReturnType<SupabaseClient['from']>);
|
|
290
|
+
|
|
291
|
+
await expect(
|
|
292
|
+
updateIdea({ idea_id: '123e4567-e89b-12d3-a456-426614174000', title: 'New' }, ctx)
|
|
293
|
+
).rejects.toThrow('Failed to update idea');
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
// ============================================================================
|
|
298
|
+
// getIdeas Tests
|
|
299
|
+
// ============================================================================
|
|
300
|
+
|
|
301
|
+
describe('getIdeas', () => {
|
|
302
|
+
beforeEach(() => vi.clearAllMocks());
|
|
303
|
+
|
|
304
|
+
it('should throw error for missing project_id', async () => {
|
|
305
|
+
const supabase = createMockSupabase();
|
|
306
|
+
const ctx = createMockContext(supabase);
|
|
307
|
+
|
|
308
|
+
await expect(getIdeas({}, ctx)).rejects.toThrow(ValidationError);
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it('should throw error for invalid project_id UUID', async () => {
|
|
312
|
+
const supabase = createMockSupabase();
|
|
313
|
+
const ctx = createMockContext(supabase);
|
|
314
|
+
|
|
315
|
+
await expect(
|
|
316
|
+
getIdeas({ project_id: 'invalid' }, ctx)
|
|
317
|
+
).rejects.toThrow(ValidationError);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it('should return empty list when no ideas', async () => {
|
|
321
|
+
const supabase = createMockSupabase({
|
|
322
|
+
selectResult: { data: [], error: null },
|
|
323
|
+
});
|
|
324
|
+
const ctx = createMockContext(supabase);
|
|
325
|
+
|
|
326
|
+
// Override to return array result
|
|
327
|
+
vi.mocked(supabase.from('').select).mockReturnValue({
|
|
328
|
+
...supabase,
|
|
329
|
+
then: (resolve: (val: unknown) => void) =>
|
|
330
|
+
Promise.resolve({ data: [], error: null }).then(resolve),
|
|
331
|
+
} as unknown as ReturnType<SupabaseClient['from']>);
|
|
332
|
+
|
|
333
|
+
const result = await getIdeas(
|
|
334
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
335
|
+
ctx
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
expect(result.result).toMatchObject({ ideas: [] });
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
it('should return ideas list', async () => {
|
|
342
|
+
const mockIdeas = [
|
|
343
|
+
{ id: 'i1', title: 'Idea 1', description: null, status: 'raw', doc_url: null },
|
|
344
|
+
{ id: 'i2', title: 'Idea 2', description: 'Some desc', status: 'exploring', doc_url: null },
|
|
345
|
+
];
|
|
346
|
+
|
|
347
|
+
const supabase = createMockSupabase({
|
|
348
|
+
selectResult: { data: mockIdeas, error: null },
|
|
349
|
+
});
|
|
350
|
+
const ctx = createMockContext(supabase);
|
|
351
|
+
|
|
352
|
+
// Override to return array result
|
|
353
|
+
vi.mocked(supabase.from('').select).mockReturnValue({
|
|
354
|
+
...supabase,
|
|
355
|
+
then: (resolve: (val: unknown) => void) =>
|
|
356
|
+
Promise.resolve({ data: mockIdeas, error: null }).then(resolve),
|
|
357
|
+
} as unknown as ReturnType<SupabaseClient['from']>);
|
|
358
|
+
|
|
359
|
+
const result = await getIdeas(
|
|
360
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
361
|
+
ctx
|
|
362
|
+
);
|
|
363
|
+
|
|
364
|
+
expect((result.result as { ideas: unknown[] }).ideas).toHaveLength(2);
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
it('should filter by status when provided', async () => {
|
|
368
|
+
const supabase = createMockSupabase({
|
|
369
|
+
selectResult: { data: [], error: null },
|
|
370
|
+
});
|
|
371
|
+
const ctx = createMockContext(supabase);
|
|
372
|
+
|
|
373
|
+
await getIdeas(
|
|
374
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000', status: 'planned' },
|
|
375
|
+
ctx
|
|
376
|
+
);
|
|
377
|
+
|
|
378
|
+
expect(supabase.eq).toHaveBeenCalledWith('status', 'planned');
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
it('should query ideas table', async () => {
|
|
382
|
+
const supabase = createMockSupabase({
|
|
383
|
+
selectResult: { data: [], error: null },
|
|
384
|
+
});
|
|
385
|
+
const ctx = createMockContext(supabase);
|
|
386
|
+
|
|
387
|
+
await getIdeas(
|
|
388
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
389
|
+
ctx
|
|
390
|
+
);
|
|
391
|
+
|
|
392
|
+
expect(supabase.from).toHaveBeenCalledWith('ideas');
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
it('should order by created_at descending', async () => {
|
|
396
|
+
const supabase = createMockSupabase({
|
|
397
|
+
selectResult: { data: [], error: null },
|
|
398
|
+
});
|
|
399
|
+
const ctx = createMockContext(supabase);
|
|
400
|
+
|
|
401
|
+
await getIdeas(
|
|
402
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
403
|
+
ctx
|
|
404
|
+
);
|
|
405
|
+
|
|
406
|
+
expect(supabase.order).toHaveBeenCalledWith('created_at', { ascending: false });
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
it('should throw error when query fails', async () => {
|
|
410
|
+
const supabase = createMockSupabase();
|
|
411
|
+
const ctx = createMockContext(supabase);
|
|
412
|
+
|
|
413
|
+
// Override to return error
|
|
414
|
+
vi.mocked(supabase.from('').select).mockReturnValue({
|
|
415
|
+
...supabase,
|
|
416
|
+
then: (resolve: (val: unknown) => void) =>
|
|
417
|
+
Promise.resolve({ data: null, error: { message: 'Query failed' } }).then(resolve),
|
|
418
|
+
} as unknown as ReturnType<SupabaseClient['from']>);
|
|
419
|
+
|
|
420
|
+
await expect(
|
|
421
|
+
getIdeas({ project_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx)
|
|
422
|
+
).rejects.toThrow('Failed to fetch ideas');
|
|
423
|
+
});
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
// ============================================================================
|
|
427
|
+
// deleteIdea Tests
|
|
428
|
+
// ============================================================================
|
|
429
|
+
|
|
430
|
+
describe('deleteIdea', () => {
|
|
431
|
+
beforeEach(() => vi.clearAllMocks());
|
|
432
|
+
|
|
433
|
+
it('should throw error for missing idea_id', async () => {
|
|
434
|
+
const supabase = createMockSupabase();
|
|
435
|
+
const ctx = createMockContext(supabase);
|
|
436
|
+
|
|
437
|
+
await expect(deleteIdea({}, ctx)).rejects.toThrow(ValidationError);
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
it('should throw error for invalid idea_id UUID', async () => {
|
|
441
|
+
const supabase = createMockSupabase();
|
|
442
|
+
const ctx = createMockContext(supabase);
|
|
443
|
+
|
|
444
|
+
await expect(
|
|
445
|
+
deleteIdea({ idea_id: 'invalid' }, ctx)
|
|
446
|
+
).rejects.toThrow(ValidationError);
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
it('should delete idea successfully', async () => {
|
|
450
|
+
const supabase = createMockSupabase({
|
|
451
|
+
deleteResult: { data: null, error: null },
|
|
452
|
+
});
|
|
453
|
+
const ctx = createMockContext(supabase);
|
|
454
|
+
|
|
455
|
+
// Override delete to return success
|
|
456
|
+
vi.mocked(supabase.from('').delete).mockReturnValue({
|
|
457
|
+
...supabase,
|
|
458
|
+
eq: vi.fn().mockReturnValue({
|
|
459
|
+
then: (resolve: (val: unknown) => void) =>
|
|
460
|
+
Promise.resolve({ data: null, error: null }).then(resolve),
|
|
461
|
+
}),
|
|
462
|
+
} as unknown as ReturnType<SupabaseClient['from']>);
|
|
463
|
+
|
|
464
|
+
const result = await deleteIdea(
|
|
465
|
+
{ idea_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
466
|
+
ctx
|
|
467
|
+
);
|
|
468
|
+
|
|
469
|
+
expect(result.result).toMatchObject({ success: true });
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
it('should call delete on ideas table', async () => {
|
|
473
|
+
const supabase = createMockSupabase();
|
|
474
|
+
const ctx = createMockContext(supabase);
|
|
475
|
+
|
|
476
|
+
const mockEq = vi.fn().mockReturnValue({
|
|
477
|
+
then: (resolve: (val: unknown) => void) =>
|
|
478
|
+
Promise.resolve({ data: null, error: null }).then(resolve),
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
vi.mocked(supabase.from('').delete).mockReturnValue({
|
|
482
|
+
...supabase,
|
|
483
|
+
eq: mockEq,
|
|
484
|
+
} as unknown as ReturnType<SupabaseClient['from']>);
|
|
485
|
+
|
|
486
|
+
await deleteIdea(
|
|
487
|
+
{ idea_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
488
|
+
ctx
|
|
489
|
+
);
|
|
490
|
+
|
|
491
|
+
expect(supabase.from).toHaveBeenCalledWith('ideas');
|
|
492
|
+
expect(supabase.delete).toHaveBeenCalled();
|
|
493
|
+
expect(mockEq).toHaveBeenCalledWith('id', '123e4567-e89b-12d3-a456-426614174000');
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
it('should throw error when delete fails', async () => {
|
|
497
|
+
const supabase = createMockSupabase();
|
|
498
|
+
const ctx = createMockContext(supabase);
|
|
499
|
+
|
|
500
|
+
// Override delete to return error
|
|
501
|
+
vi.mocked(supabase.from('').delete).mockReturnValue({
|
|
502
|
+
...supabase,
|
|
503
|
+
eq: vi.fn().mockReturnValue({
|
|
504
|
+
then: (resolve: (val: unknown) => void) =>
|
|
505
|
+
Promise.resolve({ data: null, error: { message: 'Delete failed' } }).then(resolve),
|
|
506
|
+
}),
|
|
507
|
+
} as unknown as ReturnType<SupabaseClient['from']>);
|
|
508
|
+
|
|
509
|
+
await expect(
|
|
510
|
+
deleteIdea({ idea_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx)
|
|
511
|
+
).rejects.toThrow('Failed to delete idea');
|
|
512
|
+
});
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
// ============================================================================
|
|
516
|
+
// convertIdeaToTask Tests
|
|
517
|
+
// ============================================================================
|
|
518
|
+
|
|
519
|
+
describe('convertIdeaToTask', () => {
|
|
520
|
+
beforeEach(() => vi.clearAllMocks());
|
|
521
|
+
|
|
522
|
+
it('should throw error for missing idea_id', async () => {
|
|
523
|
+
const supabase = createMockSupabase();
|
|
524
|
+
const ctx = createMockContext(supabase);
|
|
525
|
+
|
|
526
|
+
await expect(convertIdeaToTask({}, ctx)).rejects.toThrow(ValidationError);
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
it('should throw error for invalid idea_id UUID', async () => {
|
|
530
|
+
const supabase = createMockSupabase();
|
|
531
|
+
const ctx = createMockContext(supabase);
|
|
532
|
+
|
|
533
|
+
await expect(
|
|
534
|
+
convertIdeaToTask({ idea_id: 'invalid' }, ctx)
|
|
535
|
+
).rejects.toThrow(ValidationError);
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
it('should throw error for invalid priority', async () => {
|
|
539
|
+
const supabase = createMockSupabase();
|
|
540
|
+
const ctx = createMockContext(supabase);
|
|
541
|
+
|
|
542
|
+
await expect(
|
|
543
|
+
convertIdeaToTask({
|
|
544
|
+
idea_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
545
|
+
priority: 10, // Invalid
|
|
546
|
+
}, ctx)
|
|
547
|
+
).rejects.toThrow(ValidationError);
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
it('should throw error when idea not found', async () => {
|
|
551
|
+
const supabase = createMockSupabase({
|
|
552
|
+
selectResult: { data: null, error: { message: 'Not found' } },
|
|
553
|
+
});
|
|
554
|
+
const ctx = createMockContext(supabase);
|
|
555
|
+
|
|
556
|
+
await expect(
|
|
557
|
+
convertIdeaToTask({ idea_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx)
|
|
558
|
+
).rejects.toThrow('Idea not found');
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
it('should return error when idea already converted', async () => {
|
|
562
|
+
const supabase = createMockSupabase({
|
|
563
|
+
selectResult: {
|
|
564
|
+
data: {
|
|
565
|
+
id: 'idea-1',
|
|
566
|
+
project_id: 'proj-1',
|
|
567
|
+
title: 'My Idea',
|
|
568
|
+
description: null,
|
|
569
|
+
status: 'in_development',
|
|
570
|
+
converted_to_task_id: 'existing-task-id',
|
|
571
|
+
},
|
|
572
|
+
error: null,
|
|
573
|
+
},
|
|
574
|
+
});
|
|
575
|
+
const ctx = createMockContext(supabase);
|
|
576
|
+
|
|
577
|
+
const result = await convertIdeaToTask(
|
|
578
|
+
{ idea_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
579
|
+
ctx
|
|
580
|
+
);
|
|
581
|
+
|
|
582
|
+
expect(result.result).toMatchObject({
|
|
583
|
+
success: false,
|
|
584
|
+
error: 'Idea has already been converted to a task',
|
|
585
|
+
existing_task_id: 'existing-task-id',
|
|
586
|
+
});
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
it('should convert idea to task successfully', async () => {
|
|
590
|
+
const mockIdea = {
|
|
591
|
+
id: 'idea-1',
|
|
592
|
+
project_id: 'proj-1',
|
|
593
|
+
title: 'Feature Request',
|
|
594
|
+
description: 'Add dark mode',
|
|
595
|
+
status: 'planned',
|
|
596
|
+
converted_to_task_id: null,
|
|
597
|
+
};
|
|
598
|
+
|
|
599
|
+
const supabase = createMockSupabase({
|
|
600
|
+
selectResult: { data: mockIdea, error: null },
|
|
601
|
+
insertResult: { data: { id: 'task-1', title: 'Feature Request' }, error: null },
|
|
602
|
+
updateResult: { data: null, error: null },
|
|
603
|
+
});
|
|
604
|
+
const ctx = createMockContext(supabase);
|
|
605
|
+
|
|
606
|
+
const result = await convertIdeaToTask(
|
|
607
|
+
{ idea_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
608
|
+
ctx
|
|
609
|
+
);
|
|
610
|
+
|
|
611
|
+
expect(result.result).toMatchObject({
|
|
612
|
+
success: true,
|
|
613
|
+
task_id: 'task-1',
|
|
614
|
+
task_title: 'Feature Request',
|
|
615
|
+
idea_id: 'idea-1',
|
|
616
|
+
});
|
|
617
|
+
});
|
|
618
|
+
|
|
619
|
+
it('should use default priority of 3', async () => {
|
|
620
|
+
const mockIdea = {
|
|
621
|
+
id: 'idea-1',
|
|
622
|
+
project_id: 'proj-1',
|
|
623
|
+
title: 'Feature Request',
|
|
624
|
+
description: null,
|
|
625
|
+
status: 'planned',
|
|
626
|
+
converted_to_task_id: null,
|
|
627
|
+
};
|
|
628
|
+
|
|
629
|
+
const supabase = createMockSupabase({
|
|
630
|
+
selectResult: { data: mockIdea, error: null },
|
|
631
|
+
insertResult: { data: { id: 'task-1', title: 'Feature Request' }, error: null },
|
|
632
|
+
updateResult: { data: null, error: null },
|
|
633
|
+
});
|
|
634
|
+
const ctx = createMockContext(supabase);
|
|
635
|
+
|
|
636
|
+
await convertIdeaToTask(
|
|
637
|
+
{ idea_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
638
|
+
ctx
|
|
639
|
+
);
|
|
640
|
+
|
|
641
|
+
expect(supabase.insert).toHaveBeenCalledWith(
|
|
642
|
+
expect.objectContaining({
|
|
643
|
+
priority: 3,
|
|
644
|
+
})
|
|
645
|
+
);
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
it('should use custom priority when provided', async () => {
|
|
649
|
+
const mockIdea = {
|
|
650
|
+
id: 'idea-1',
|
|
651
|
+
project_id: 'proj-1',
|
|
652
|
+
title: 'Feature Request',
|
|
653
|
+
description: null,
|
|
654
|
+
status: 'planned',
|
|
655
|
+
converted_to_task_id: null,
|
|
656
|
+
};
|
|
657
|
+
|
|
658
|
+
const supabase = createMockSupabase({
|
|
659
|
+
selectResult: { data: mockIdea, error: null },
|
|
660
|
+
insertResult: { data: { id: 'task-1', title: 'Feature Request' }, error: null },
|
|
661
|
+
updateResult: { data: null, error: null },
|
|
662
|
+
});
|
|
663
|
+
const ctx = createMockContext(supabase);
|
|
664
|
+
|
|
665
|
+
await convertIdeaToTask(
|
|
666
|
+
{
|
|
667
|
+
idea_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
668
|
+
priority: 1,
|
|
669
|
+
},
|
|
670
|
+
ctx
|
|
671
|
+
);
|
|
672
|
+
|
|
673
|
+
expect(supabase.insert).toHaveBeenCalledWith(
|
|
674
|
+
expect.objectContaining({
|
|
675
|
+
priority: 1,
|
|
676
|
+
})
|
|
677
|
+
);
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
it('should update idea status to in_development by default', async () => {
|
|
681
|
+
const mockIdea = {
|
|
682
|
+
id: 'idea-1',
|
|
683
|
+
project_id: 'proj-1',
|
|
684
|
+
title: 'Feature Request',
|
|
685
|
+
description: null,
|
|
686
|
+
status: 'planned',
|
|
687
|
+
converted_to_task_id: null,
|
|
688
|
+
};
|
|
689
|
+
|
|
690
|
+
const supabase = createMockSupabase({
|
|
691
|
+
selectResult: { data: mockIdea, error: null },
|
|
692
|
+
insertResult: { data: { id: 'task-1', title: 'Feature Request' }, error: null },
|
|
693
|
+
updateResult: { data: null, error: null },
|
|
694
|
+
});
|
|
695
|
+
const ctx = createMockContext(supabase);
|
|
696
|
+
|
|
697
|
+
const result = await convertIdeaToTask(
|
|
698
|
+
{ idea_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
699
|
+
ctx
|
|
700
|
+
);
|
|
701
|
+
|
|
702
|
+
expect((result.result as { idea_status: string }).idea_status).toBe('in_development');
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
it('should not update status when update_status is false', async () => {
|
|
706
|
+
const mockIdea = {
|
|
707
|
+
id: 'idea-1',
|
|
708
|
+
project_id: 'proj-1',
|
|
709
|
+
title: 'Feature Request',
|
|
710
|
+
description: null,
|
|
711
|
+
status: 'planned',
|
|
712
|
+
converted_to_task_id: null,
|
|
713
|
+
};
|
|
714
|
+
|
|
715
|
+
const supabase = createMockSupabase({
|
|
716
|
+
selectResult: { data: mockIdea, error: null },
|
|
717
|
+
insertResult: { data: { id: 'task-1', title: 'Feature Request' }, error: null },
|
|
718
|
+
updateResult: { data: null, error: null },
|
|
719
|
+
});
|
|
720
|
+
const ctx = createMockContext(supabase);
|
|
721
|
+
|
|
722
|
+
const result = await convertIdeaToTask(
|
|
723
|
+
{
|
|
724
|
+
idea_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
725
|
+
update_status: false,
|
|
726
|
+
},
|
|
727
|
+
ctx
|
|
728
|
+
);
|
|
729
|
+
|
|
730
|
+
expect((result.result as { idea_status: string }).idea_status).toBe('planned');
|
|
731
|
+
});
|
|
732
|
+
|
|
733
|
+
it('should throw error when task creation fails', async () => {
|
|
734
|
+
const mockIdea = {
|
|
735
|
+
id: 'idea-1',
|
|
736
|
+
project_id: 'proj-1',
|
|
737
|
+
title: 'Feature Request',
|
|
738
|
+
description: null,
|
|
739
|
+
status: 'planned',
|
|
740
|
+
converted_to_task_id: null,
|
|
741
|
+
};
|
|
742
|
+
|
|
743
|
+
const supabase = createMockSupabase({
|
|
744
|
+
selectResult: { data: mockIdea, error: null },
|
|
745
|
+
insertResult: { data: null, error: { message: 'Insert failed' } },
|
|
746
|
+
});
|
|
747
|
+
const ctx = createMockContext(supabase);
|
|
748
|
+
|
|
749
|
+
await expect(
|
|
750
|
+
convertIdeaToTask({ idea_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx)
|
|
751
|
+
).rejects.toThrow('Failed to create task');
|
|
752
|
+
});
|
|
753
|
+
});
|