@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,390 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
addBlocker,
|
|
4
|
+
resolveBlocker,
|
|
5
|
+
getBlockers,
|
|
6
|
+
deleteBlocker,
|
|
7
|
+
} from './blockers.js';
|
|
8
|
+
import { ValidationError } from '../validators.js';
|
|
9
|
+
import { createMockSupabase, createMockContext } from './__test-utils__.js';
|
|
10
|
+
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// addBlocker Tests
|
|
13
|
+
// ============================================================================
|
|
14
|
+
|
|
15
|
+
describe('addBlocker', () => {
|
|
16
|
+
beforeEach(() => vi.clearAllMocks());
|
|
17
|
+
|
|
18
|
+
it('should throw error for missing project_id', async () => {
|
|
19
|
+
const supabase = createMockSupabase();
|
|
20
|
+
const ctx = createMockContext(supabase);
|
|
21
|
+
|
|
22
|
+
await expect(
|
|
23
|
+
addBlocker({ description: 'Some blocker' }, ctx)
|
|
24
|
+
).rejects.toThrow(ValidationError);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should throw error for invalid project_id UUID', async () => {
|
|
28
|
+
const supabase = createMockSupabase();
|
|
29
|
+
const ctx = createMockContext(supabase);
|
|
30
|
+
|
|
31
|
+
await expect(
|
|
32
|
+
addBlocker({ project_id: 'invalid', description: 'Some blocker' }, ctx)
|
|
33
|
+
).rejects.toThrow(ValidationError);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should throw error for missing description', async () => {
|
|
37
|
+
const supabase = createMockSupabase();
|
|
38
|
+
const ctx = createMockContext(supabase);
|
|
39
|
+
|
|
40
|
+
await expect(
|
|
41
|
+
addBlocker({ project_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx)
|
|
42
|
+
).rejects.toThrow(ValidationError);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should add blocker successfully', async () => {
|
|
46
|
+
const supabase = createMockSupabase({
|
|
47
|
+
insertResult: { data: { id: 'blocker-1' }, error: null },
|
|
48
|
+
});
|
|
49
|
+
const ctx = createMockContext(supabase);
|
|
50
|
+
|
|
51
|
+
const result = await addBlocker(
|
|
52
|
+
{
|
|
53
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
54
|
+
description: 'API is down',
|
|
55
|
+
},
|
|
56
|
+
ctx
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
expect(result.result).toMatchObject({
|
|
60
|
+
success: true,
|
|
61
|
+
blocker_id: 'blocker-1',
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should include session_id in insert', async () => {
|
|
66
|
+
const supabase = createMockSupabase({
|
|
67
|
+
insertResult: { data: { id: 'blocker-1' }, error: null },
|
|
68
|
+
});
|
|
69
|
+
const ctx = createMockContext(supabase, { sessionId: 'my-session' });
|
|
70
|
+
|
|
71
|
+
await addBlocker(
|
|
72
|
+
{
|
|
73
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
74
|
+
description: 'Blocked by dependency',
|
|
75
|
+
},
|
|
76
|
+
ctx
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
expect(supabase.insert).toHaveBeenCalledWith(
|
|
80
|
+
expect.objectContaining({
|
|
81
|
+
created_by: 'agent',
|
|
82
|
+
created_by_session_id: 'my-session',
|
|
83
|
+
})
|
|
84
|
+
);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should throw error when insert fails', async () => {
|
|
88
|
+
const supabase = createMockSupabase({
|
|
89
|
+
insertResult: { data: null, error: { message: 'Insert failed' } },
|
|
90
|
+
});
|
|
91
|
+
const ctx = createMockContext(supabase);
|
|
92
|
+
|
|
93
|
+
await expect(
|
|
94
|
+
addBlocker({
|
|
95
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
96
|
+
description: 'Test blocker',
|
|
97
|
+
}, ctx)
|
|
98
|
+
).rejects.toThrow('Failed to add blocker: Insert failed');
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// ============================================================================
|
|
103
|
+
// resolveBlocker Tests
|
|
104
|
+
// ============================================================================
|
|
105
|
+
|
|
106
|
+
describe('resolveBlocker', () => {
|
|
107
|
+
beforeEach(() => vi.clearAllMocks());
|
|
108
|
+
|
|
109
|
+
it('should throw error for missing blocker_id', async () => {
|
|
110
|
+
const supabase = createMockSupabase();
|
|
111
|
+
const ctx = createMockContext(supabase);
|
|
112
|
+
|
|
113
|
+
await expect(resolveBlocker({}, ctx)).rejects.toThrow(ValidationError);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('should throw error for invalid blocker_id UUID', async () => {
|
|
117
|
+
const supabase = createMockSupabase();
|
|
118
|
+
const ctx = createMockContext(supabase);
|
|
119
|
+
|
|
120
|
+
await expect(
|
|
121
|
+
resolveBlocker({ blocker_id: 'invalid' }, ctx)
|
|
122
|
+
).rejects.toThrow(ValidationError);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('should resolve blocker successfully', async () => {
|
|
126
|
+
const supabase = createMockSupabase({
|
|
127
|
+
updateResult: { data: null, error: null },
|
|
128
|
+
});
|
|
129
|
+
const ctx = createMockContext(supabase);
|
|
130
|
+
|
|
131
|
+
const result = await resolveBlocker(
|
|
132
|
+
{ blocker_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
133
|
+
ctx
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
expect(result.result).toMatchObject({
|
|
137
|
+
success: true,
|
|
138
|
+
blocker_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('should resolve blocker with resolution note', async () => {
|
|
143
|
+
const supabase = createMockSupabase({
|
|
144
|
+
updateResult: { data: null, error: null },
|
|
145
|
+
});
|
|
146
|
+
const ctx = createMockContext(supabase);
|
|
147
|
+
|
|
148
|
+
await resolveBlocker(
|
|
149
|
+
{
|
|
150
|
+
blocker_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
151
|
+
resolution_note: 'Fixed by upgrading dependency',
|
|
152
|
+
},
|
|
153
|
+
ctx
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
expect(supabase.update).toHaveBeenCalledWith(
|
|
157
|
+
expect.objectContaining({
|
|
158
|
+
status: 'resolved',
|
|
159
|
+
resolution_note: 'Fixed by upgrading dependency',
|
|
160
|
+
})
|
|
161
|
+
);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('should set resolved_at timestamp', async () => {
|
|
165
|
+
const supabase = createMockSupabase({
|
|
166
|
+
updateResult: { data: null, error: null },
|
|
167
|
+
});
|
|
168
|
+
const ctx = createMockContext(supabase);
|
|
169
|
+
|
|
170
|
+
await resolveBlocker(
|
|
171
|
+
{ blocker_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
172
|
+
ctx
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
expect(supabase.update).toHaveBeenCalledWith(
|
|
176
|
+
expect.objectContaining({
|
|
177
|
+
resolved_at: expect.any(String),
|
|
178
|
+
})
|
|
179
|
+
);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('should throw error when update fails', async () => {
|
|
183
|
+
const supabase = createMockSupabase();
|
|
184
|
+
const ctx = createMockContext(supabase);
|
|
185
|
+
|
|
186
|
+
// Override update to return error
|
|
187
|
+
vi.mocked(supabase.from('').update).mockReturnValue({
|
|
188
|
+
...supabase,
|
|
189
|
+
eq: vi.fn().mockReturnValue({
|
|
190
|
+
then: (resolve: (val: unknown) => void) =>
|
|
191
|
+
Promise.resolve({ data: null, error: { message: 'Update failed' } }).then(resolve),
|
|
192
|
+
}),
|
|
193
|
+
} as unknown as ReturnType<SupabaseClient['from']>);
|
|
194
|
+
|
|
195
|
+
await expect(
|
|
196
|
+
resolveBlocker({ blocker_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx)
|
|
197
|
+
).rejects.toThrow('Failed to resolve blocker');
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// ============================================================================
|
|
202
|
+
// getBlockers Tests
|
|
203
|
+
// ============================================================================
|
|
204
|
+
|
|
205
|
+
describe('getBlockers', () => {
|
|
206
|
+
beforeEach(() => vi.clearAllMocks());
|
|
207
|
+
|
|
208
|
+
it('should throw error for missing project_id', async () => {
|
|
209
|
+
const supabase = createMockSupabase();
|
|
210
|
+
const ctx = createMockContext(supabase);
|
|
211
|
+
|
|
212
|
+
await expect(getBlockers({}, ctx)).rejects.toThrow(ValidationError);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('should throw error for invalid project_id UUID', async () => {
|
|
216
|
+
const supabase = createMockSupabase();
|
|
217
|
+
const ctx = createMockContext(supabase);
|
|
218
|
+
|
|
219
|
+
await expect(
|
|
220
|
+
getBlockers({ project_id: 'invalid' }, ctx)
|
|
221
|
+
).rejects.toThrow(ValidationError);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('should return empty list when no blockers', async () => {
|
|
225
|
+
const supabase = createMockSupabase({
|
|
226
|
+
selectResult: { data: [], error: null },
|
|
227
|
+
});
|
|
228
|
+
const ctx = createMockContext(supabase);
|
|
229
|
+
|
|
230
|
+
const result = await getBlockers(
|
|
231
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
232
|
+
ctx
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
expect(result.result).toMatchObject({
|
|
236
|
+
blockers: [],
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('should return blockers list', async () => {
|
|
241
|
+
const mockBlockers = [
|
|
242
|
+
{ id: 'b1', description: 'Blocker 1', status: 'open', created_at: '2025-01-14T10:00:00Z' },
|
|
243
|
+
{ id: 'b2', description: 'Blocker 2', status: 'open', created_at: '2025-01-14T11:00:00Z' },
|
|
244
|
+
];
|
|
245
|
+
|
|
246
|
+
const supabase = createMockSupabase({
|
|
247
|
+
selectResult: { data: mockBlockers, error: null },
|
|
248
|
+
});
|
|
249
|
+
const ctx = createMockContext(supabase);
|
|
250
|
+
|
|
251
|
+
const result = await getBlockers(
|
|
252
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
253
|
+
ctx
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
expect((result.result as { blockers: unknown[] }).blockers).toHaveLength(2);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('should filter by status (default: open)', async () => {
|
|
260
|
+
const supabase = createMockSupabase({
|
|
261
|
+
selectResult: { data: [], error: null },
|
|
262
|
+
});
|
|
263
|
+
const ctx = createMockContext(supabase);
|
|
264
|
+
|
|
265
|
+
await getBlockers(
|
|
266
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
267
|
+
ctx
|
|
268
|
+
);
|
|
269
|
+
|
|
270
|
+
expect(supabase.eq).toHaveBeenCalledWith('status', 'open');
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it('should filter by custom status', async () => {
|
|
274
|
+
const supabase = createMockSupabase({
|
|
275
|
+
selectResult: { data: [], error: null },
|
|
276
|
+
});
|
|
277
|
+
const ctx = createMockContext(supabase);
|
|
278
|
+
|
|
279
|
+
await getBlockers(
|
|
280
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000', status: 'resolved' },
|
|
281
|
+
ctx
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
expect(supabase.eq).toHaveBeenCalledWith('status', 'resolved');
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it('should query blockers table', async () => {
|
|
288
|
+
const supabase = createMockSupabase({
|
|
289
|
+
selectResult: { data: [], error: null },
|
|
290
|
+
});
|
|
291
|
+
const ctx = createMockContext(supabase);
|
|
292
|
+
|
|
293
|
+
await getBlockers(
|
|
294
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
295
|
+
ctx
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
expect(supabase.from).toHaveBeenCalledWith('blockers');
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
it('should throw error when query fails', async () => {
|
|
302
|
+
const supabase = createMockSupabase();
|
|
303
|
+
const ctx = createMockContext(supabase);
|
|
304
|
+
|
|
305
|
+
// Override to return error
|
|
306
|
+
vi.mocked(supabase.from('').select).mockReturnValue({
|
|
307
|
+
...supabase,
|
|
308
|
+
then: (resolve: (val: unknown) => void) =>
|
|
309
|
+
Promise.resolve({ data: null, error: { message: 'Query failed' } }).then(resolve),
|
|
310
|
+
} as unknown as ReturnType<SupabaseClient['from']>);
|
|
311
|
+
|
|
312
|
+
await expect(
|
|
313
|
+
getBlockers({ project_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx)
|
|
314
|
+
).rejects.toThrow('Failed to fetch blockers');
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
// ============================================================================
|
|
319
|
+
// deleteBlocker Tests
|
|
320
|
+
// ============================================================================
|
|
321
|
+
|
|
322
|
+
describe('deleteBlocker', () => {
|
|
323
|
+
beforeEach(() => vi.clearAllMocks());
|
|
324
|
+
|
|
325
|
+
it('should throw error for missing blocker_id', async () => {
|
|
326
|
+
const supabase = createMockSupabase();
|
|
327
|
+
const ctx = createMockContext(supabase);
|
|
328
|
+
|
|
329
|
+
await expect(deleteBlocker({}, ctx)).rejects.toThrow(ValidationError);
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
it('should throw error for invalid blocker_id UUID', async () => {
|
|
333
|
+
const supabase = createMockSupabase();
|
|
334
|
+
const ctx = createMockContext(supabase);
|
|
335
|
+
|
|
336
|
+
await expect(
|
|
337
|
+
deleteBlocker({ blocker_id: 'invalid' }, ctx)
|
|
338
|
+
).rejects.toThrow(ValidationError);
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
it('should delete blocker successfully', async () => {
|
|
342
|
+
const supabase = createMockSupabase({
|
|
343
|
+
deleteResult: { data: null, error: null },
|
|
344
|
+
});
|
|
345
|
+
const ctx = createMockContext(supabase);
|
|
346
|
+
|
|
347
|
+
const result = await deleteBlocker(
|
|
348
|
+
{ blocker_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
349
|
+
ctx
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
expect(result.result).toMatchObject({
|
|
353
|
+
success: true,
|
|
354
|
+
});
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
it('should call delete on blockers table', async () => {
|
|
358
|
+
const supabase = createMockSupabase({
|
|
359
|
+
deleteResult: { data: null, error: null },
|
|
360
|
+
});
|
|
361
|
+
const ctx = createMockContext(supabase);
|
|
362
|
+
|
|
363
|
+
await deleteBlocker(
|
|
364
|
+
{ blocker_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
365
|
+
ctx
|
|
366
|
+
);
|
|
367
|
+
|
|
368
|
+
expect(supabase.from).toHaveBeenCalledWith('blockers');
|
|
369
|
+
expect(supabase.delete).toHaveBeenCalled();
|
|
370
|
+
expect(supabase.eq).toHaveBeenCalledWith('id', '123e4567-e89b-12d3-a456-426614174000');
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
it('should throw error when delete fails', async () => {
|
|
374
|
+
const supabase = createMockSupabase();
|
|
375
|
+
const ctx = createMockContext(supabase);
|
|
376
|
+
|
|
377
|
+
// Override delete to return error
|
|
378
|
+
vi.mocked(supabase.from('').delete).mockReturnValue({
|
|
379
|
+
...supabase,
|
|
380
|
+
eq: vi.fn().mockReturnValue({
|
|
381
|
+
then: (resolve: (val: unknown) => void) =>
|
|
382
|
+
Promise.resolve({ data: null, error: { message: 'Delete failed' } }).then(resolve),
|
|
383
|
+
}),
|
|
384
|
+
} as unknown as ReturnType<SupabaseClient['from']>);
|
|
385
|
+
|
|
386
|
+
await expect(
|
|
387
|
+
deleteBlocker({ blocker_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx)
|
|
388
|
+
).rejects.toThrow('Failed to delete blocker');
|
|
389
|
+
});
|
|
390
|
+
});
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Blockers Handlers
|
|
3
|
+
*
|
|
4
|
+
* Handles blocker management:
|
|
5
|
+
* - add_blocker
|
|
6
|
+
* - resolve_blocker
|
|
7
|
+
* - get_blockers
|
|
8
|
+
* - delete_blocker
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { Handler, HandlerRegistry } from './types.js';
|
|
12
|
+
import { validateRequired, validateUUID } from '../validators.js';
|
|
13
|
+
|
|
14
|
+
export const addBlocker: Handler = async (args, ctx) => {
|
|
15
|
+
const { project_id, description } = args as {
|
|
16
|
+
project_id: string;
|
|
17
|
+
description: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
validateRequired(project_id, 'project_id');
|
|
21
|
+
validateUUID(project_id, 'project_id');
|
|
22
|
+
validateRequired(description, 'description');
|
|
23
|
+
|
|
24
|
+
const { supabase, session } = ctx;
|
|
25
|
+
|
|
26
|
+
const { data, error } = await supabase
|
|
27
|
+
.from('blockers')
|
|
28
|
+
.insert({
|
|
29
|
+
project_id,
|
|
30
|
+
description,
|
|
31
|
+
created_by: 'agent',
|
|
32
|
+
created_by_session_id: session.currentSessionId,
|
|
33
|
+
})
|
|
34
|
+
.select('id')
|
|
35
|
+
.single();
|
|
36
|
+
|
|
37
|
+
if (error) throw new Error(`Failed to add blocker: ${error.message}`);
|
|
38
|
+
|
|
39
|
+
return { result: { success: true, blocker_id: data.id } };
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const resolveBlocker: Handler = async (args, ctx) => {
|
|
43
|
+
const { blocker_id, resolution_note } = args as {
|
|
44
|
+
blocker_id: string;
|
|
45
|
+
resolution_note?: string;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
validateRequired(blocker_id, 'blocker_id');
|
|
49
|
+
validateUUID(blocker_id, 'blocker_id');
|
|
50
|
+
|
|
51
|
+
const { error } = await ctx.supabase
|
|
52
|
+
.from('blockers')
|
|
53
|
+
.update({
|
|
54
|
+
status: 'resolved',
|
|
55
|
+
resolved_at: new Date().toISOString(),
|
|
56
|
+
resolution_note: resolution_note || null,
|
|
57
|
+
})
|
|
58
|
+
.eq('id', blocker_id);
|
|
59
|
+
|
|
60
|
+
if (error) throw new Error(`Failed to resolve blocker: ${error.message}`);
|
|
61
|
+
|
|
62
|
+
return { result: { success: true, blocker_id } };
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export const getBlockers: Handler = async (args, ctx) => {
|
|
66
|
+
const { project_id, status = 'open' } = args as {
|
|
67
|
+
project_id: string;
|
|
68
|
+
status?: string;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
validateRequired(project_id, 'project_id');
|
|
72
|
+
validateUUID(project_id, 'project_id');
|
|
73
|
+
|
|
74
|
+
const { data, error } = await ctx.supabase
|
|
75
|
+
.from('blockers')
|
|
76
|
+
.select('id, description, status, created_at, resolved_at, resolution_note')
|
|
77
|
+
.eq('project_id', project_id)
|
|
78
|
+
.eq('status', status)
|
|
79
|
+
.order('created_at', { ascending: false });
|
|
80
|
+
|
|
81
|
+
if (error) throw new Error(`Failed to fetch blockers: ${error.message}`);
|
|
82
|
+
|
|
83
|
+
return { result: { blockers: data || [] } };
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export const deleteBlocker: Handler = async (args, ctx) => {
|
|
87
|
+
const { blocker_id } = args as { blocker_id: string };
|
|
88
|
+
|
|
89
|
+
validateRequired(blocker_id, 'blocker_id');
|
|
90
|
+
validateUUID(blocker_id, 'blocker_id');
|
|
91
|
+
|
|
92
|
+
const { error } = await ctx.supabase
|
|
93
|
+
.from('blockers')
|
|
94
|
+
.delete()
|
|
95
|
+
.eq('id', blocker_id);
|
|
96
|
+
|
|
97
|
+
if (error) throw new Error(`Failed to delete blocker: ${error.message}`);
|
|
98
|
+
|
|
99
|
+
return { result: { success: true } };
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Blockers handlers registry
|
|
104
|
+
*/
|
|
105
|
+
export const blockerHandlers: HandlerRegistry = {
|
|
106
|
+
add_blocker: addBlocker,
|
|
107
|
+
resolve_blocker: resolveBlocker,
|
|
108
|
+
get_blockers: getBlockers,
|
|
109
|
+
delete_blocker: deleteBlocker,
|
|
110
|
+
};
|