@vibescope/mcp-server 0.0.1 → 0.2.0
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 +113 -98
- package/dist/api-client.d.ts +1169 -0
- package/dist/api-client.js +713 -0
- package/dist/cli.d.ts +1 -6
- package/dist/cli.js +39 -240
- package/dist/config/tool-categories.d.ts +31 -0
- package/dist/config/tool-categories.js +253 -0
- package/dist/handlers/blockers.js +57 -58
- package/dist/handlers/bodies-of-work.d.ts +2 -0
- package/dist/handlers/bodies-of-work.js +108 -477
- package/dist/handlers/cost.d.ts +1 -0
- package/dist/handlers/cost.js +35 -113
- package/dist/handlers/decisions.d.ts +2 -0
- package/dist/handlers/decisions.js +28 -27
- package/dist/handlers/deployment.js +113 -828
- package/dist/handlers/discovery.d.ts +3 -0
- package/dist/handlers/discovery.js +26 -627
- package/dist/handlers/fallback.d.ts +2 -0
- package/dist/handlers/fallback.js +56 -142
- package/dist/handlers/findings.d.ts +8 -1
- package/dist/handlers/findings.js +65 -68
- package/dist/handlers/git-issues.d.ts +9 -13
- package/dist/handlers/git-issues.js +80 -225
- package/dist/handlers/ideas.d.ts +3 -0
- package/dist/handlers/ideas.js +53 -134
- package/dist/handlers/index.d.ts +2 -0
- package/dist/handlers/index.js +6 -0
- package/dist/handlers/milestones.d.ts +2 -0
- package/dist/handlers/milestones.js +51 -98
- package/dist/handlers/organizations.js +79 -275
- package/dist/handlers/progress.d.ts +2 -0
- package/dist/handlers/progress.js +25 -123
- package/dist/handlers/project.js +42 -221
- package/dist/handlers/requests.d.ts +2 -0
- package/dist/handlers/requests.js +23 -83
- package/dist/handlers/session.js +119 -590
- package/dist/handlers/sprints.d.ts +32 -0
- package/dist/handlers/sprints.js +275 -0
- package/dist/handlers/tasks.d.ts +7 -10
- package/dist/handlers/tasks.js +245 -894
- package/dist/handlers/tool-docs.d.ts +9 -0
- package/dist/handlers/tool-docs.js +904 -0
- package/dist/handlers/types.d.ts +11 -3
- package/dist/handlers/validation.d.ts +1 -1
- package/dist/handlers/validation.js +38 -153
- package/dist/index.js +493 -162
- package/dist/knowledge.js +106 -9
- package/dist/tools.js +34 -4
- package/dist/validators.d.ts +21 -0
- package/dist/validators.js +91 -0
- package/package.json +2 -3
- package/src/api-client.ts +1822 -0
- package/src/cli.test.ts +128 -302
- package/src/cli.ts +41 -285
- package/src/handlers/__test-setup__.ts +215 -0
- package/src/handlers/__test-utils__.ts +4 -134
- package/src/handlers/blockers.test.ts +114 -124
- package/src/handlers/blockers.ts +68 -70
- package/src/handlers/bodies-of-work.test.ts +236 -831
- package/src/handlers/bodies-of-work.ts +210 -525
- package/src/handlers/cost.test.ts +149 -113
- package/src/handlers/cost.ts +44 -132
- package/src/handlers/decisions.test.ts +111 -209
- package/src/handlers/decisions.ts +35 -27
- package/src/handlers/deployment.test.ts +193 -239
- package/src/handlers/deployment.ts +143 -896
- package/src/handlers/discovery.test.ts +20 -67
- package/src/handlers/discovery.ts +29 -714
- package/src/handlers/fallback.test.ts +206 -361
- package/src/handlers/fallback.ts +81 -156
- package/src/handlers/findings.test.ts +229 -320
- package/src/handlers/findings.ts +76 -64
- package/src/handlers/git-issues.test.ts +623 -0
- package/src/handlers/git-issues.ts +174 -0
- package/src/handlers/ideas.test.ts +229 -343
- package/src/handlers/ideas.ts +69 -143
- package/src/handlers/index.ts +6 -0
- package/src/handlers/milestones.test.ts +167 -281
- package/src/handlers/milestones.ts +54 -93
- package/src/handlers/organizations.test.ts +275 -467
- package/src/handlers/organizations.ts +84 -294
- package/src/handlers/progress.test.ts +112 -218
- package/src/handlers/progress.ts +29 -142
- package/src/handlers/project.test.ts +203 -226
- package/src/handlers/project.ts +48 -238
- package/src/handlers/requests.test.ts +74 -342
- package/src/handlers/requests.ts +25 -83
- package/src/handlers/session.test.ts +276 -206
- package/src/handlers/session.ts +136 -662
- package/src/handlers/sprints.test.ts +711 -0
- package/src/handlers/sprints.ts +510 -0
- package/src/handlers/tasks.test.ts +669 -353
- package/src/handlers/tasks.ts +263 -1015
- package/src/handlers/tool-docs.ts +1024 -0
- package/src/handlers/types.ts +12 -4
- package/src/handlers/validation.test.ts +237 -568
- package/src/handlers/validation.ts +43 -167
- package/src/index.ts +493 -186
- package/src/tools.ts +2532 -0
- package/src/validators.test.ts +223 -223
- package/src/validators.ts +127 -0
- package/tsconfig.json +1 -1
- package/vitest.config.ts +14 -13
- package/dist/cli.test.d.ts +0 -1
- package/dist/cli.test.js +0 -367
- package/dist/handlers/__test-utils__.d.ts +0 -72
- package/dist/handlers/__test-utils__.js +0 -176
- package/dist/handlers/checkouts.d.ts +0 -37
- package/dist/handlers/checkouts.js +0 -377
- package/dist/handlers/knowledge-query.d.ts +0 -22
- package/dist/handlers/knowledge-query.js +0 -253
- package/dist/handlers/knowledge.d.ts +0 -12
- package/dist/handlers/knowledge.js +0 -108
- package/dist/handlers/roles.d.ts +0 -30
- package/dist/handlers/roles.js +0 -281
- package/dist/handlers/tasks.test.d.ts +0 -1
- package/dist/handlers/tasks.test.js +0 -431
- package/dist/utils.test.d.ts +0 -1
- package/dist/utils.test.js +0 -532
- package/dist/validators.test.d.ts +0 -1
- package/dist/validators.test.js +0 -176
- package/src/knowledge.ts +0 -132
- package/src/tmpclaude-0078-cwd +0 -1
- package/src/tmpclaude-0ee1-cwd +0 -1
- package/src/tmpclaude-2dd5-cwd +0 -1
- package/src/tmpclaude-344c-cwd +0 -1
- package/src/tmpclaude-3860-cwd +0 -1
- package/src/tmpclaude-4b63-cwd +0 -1
- package/src/tmpclaude-5c73-cwd +0 -1
- package/src/tmpclaude-5ee3-cwd +0 -1
- package/src/tmpclaude-6795-cwd +0 -1
- package/src/tmpclaude-709e-cwd +0 -1
- package/src/tmpclaude-9839-cwd +0 -1
- package/src/tmpclaude-d829-cwd +0 -1
- package/src/tmpclaude-e072-cwd +0 -1
- package/src/tmpclaude-f6ee-cwd +0 -1
- package/tmpclaude-0439-cwd +0 -1
- package/tmpclaude-132f-cwd +0 -1
- package/tmpclaude-15bb-cwd +0 -1
- package/tmpclaude-165a-cwd +0 -1
- package/tmpclaude-1ba9-cwd +0 -1
- package/tmpclaude-21a3-cwd +0 -1
- package/tmpclaude-2a38-cwd +0 -1
- package/tmpclaude-2adf-cwd +0 -1
- package/tmpclaude-2f56-cwd +0 -1
- package/tmpclaude-3626-cwd +0 -1
- package/tmpclaude-3727-cwd +0 -1
- package/tmpclaude-40bc-cwd +0 -1
- package/tmpclaude-436f-cwd +0 -1
- package/tmpclaude-4783-cwd +0 -1
- package/tmpclaude-4b6d-cwd +0 -1
- package/tmpclaude-4ba4-cwd +0 -1
- package/tmpclaude-51e6-cwd +0 -1
- package/tmpclaude-5ecf-cwd +0 -1
- package/tmpclaude-6f97-cwd +0 -1
- package/tmpclaude-7fb2-cwd +0 -1
- package/tmpclaude-825c-cwd +0 -1
- package/tmpclaude-8baf-cwd +0 -1
- package/tmpclaude-8d9f-cwd +0 -1
- package/tmpclaude-975c-cwd +0 -1
- package/tmpclaude-9983-cwd +0 -1
- package/tmpclaude-a045-cwd +0 -1
- package/tmpclaude-ac4a-cwd +0 -1
- package/tmpclaude-b593-cwd +0 -1
- package/tmpclaude-b891-cwd +0 -1
- package/tmpclaude-c032-cwd +0 -1
- package/tmpclaude-cf43-cwd +0 -1
- package/tmpclaude-d040-cwd +0 -1
- package/tmpclaude-dcdd-cwd +0 -1
- package/tmpclaude-dcee-cwd +0 -1
- package/tmpclaude-e16b-cwd +0 -1
- package/tmpclaude-ecd2-cwd +0 -1
- package/tmpclaude-f48d-cwd +0 -1
|
@@ -1,119 +1,18 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import type { SupabaseClient } from '@supabase/supabase-js';
|
|
3
|
-
import type { HandlerContext, TokenUsage } from './types.js';
|
|
4
2
|
import {
|
|
5
3
|
getTasksAwaitingValidation,
|
|
6
4
|
claimValidation,
|
|
7
5
|
validateTask,
|
|
8
6
|
} from './validation.js';
|
|
9
7
|
import { ValidationError } from '../validators.js';
|
|
8
|
+
import { createMockContext } from './__test-utils__.js';
|
|
9
|
+
import { mockApiClient } from './__test-setup__.js';
|
|
10
10
|
|
|
11
11
|
// ============================================================================
|
|
12
|
-
// Test
|
|
12
|
+
// Test Constants
|
|
13
13
|
// ============================================================================
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
selectResult?: { data: unknown; error: unknown };
|
|
17
|
-
insertResult?: { data: unknown; error: unknown };
|
|
18
|
-
updateResult?: { data: unknown; error: unknown };
|
|
19
|
-
} = {}) {
|
|
20
|
-
const defaultResult = { data: null, error: null };
|
|
21
|
-
let currentOperation = 'select';
|
|
22
|
-
let insertThenSelect = false;
|
|
23
|
-
|
|
24
|
-
const mock = {
|
|
25
|
-
from: vi.fn().mockReturnThis(),
|
|
26
|
-
select: vi.fn(() => {
|
|
27
|
-
if (currentOperation === 'insert') {
|
|
28
|
-
insertThenSelect = true;
|
|
29
|
-
} else {
|
|
30
|
-
currentOperation = 'select';
|
|
31
|
-
insertThenSelect = false;
|
|
32
|
-
}
|
|
33
|
-
return mock;
|
|
34
|
-
}),
|
|
35
|
-
insert: vi.fn(() => {
|
|
36
|
-
currentOperation = 'insert';
|
|
37
|
-
insertThenSelect = false;
|
|
38
|
-
return mock;
|
|
39
|
-
}),
|
|
40
|
-
update: vi.fn(() => {
|
|
41
|
-
currentOperation = 'update';
|
|
42
|
-
insertThenSelect = false;
|
|
43
|
-
return mock;
|
|
44
|
-
}),
|
|
45
|
-
delete: vi.fn(() => {
|
|
46
|
-
currentOperation = 'delete';
|
|
47
|
-
insertThenSelect = false;
|
|
48
|
-
return mock;
|
|
49
|
-
}),
|
|
50
|
-
eq: vi.fn().mockReturnThis(),
|
|
51
|
-
neq: vi.fn().mockReturnThis(),
|
|
52
|
-
in: vi.fn().mockReturnThis(),
|
|
53
|
-
is: vi.fn().mockReturnThis(),
|
|
54
|
-
not: vi.fn().mockReturnThis(),
|
|
55
|
-
or: vi.fn().mockReturnThis(),
|
|
56
|
-
gte: vi.fn().mockReturnThis(),
|
|
57
|
-
lte: vi.fn().mockReturnThis(),
|
|
58
|
-
lt: vi.fn().mockReturnThis(),
|
|
59
|
-
order: vi.fn().mockReturnThis(),
|
|
60
|
-
limit: vi.fn().mockReturnThis(),
|
|
61
|
-
single: vi.fn(() => {
|
|
62
|
-
if (currentOperation === 'insert' || insertThenSelect) {
|
|
63
|
-
return Promise.resolve(overrides.insertResult ?? defaultResult);
|
|
64
|
-
}
|
|
65
|
-
if (currentOperation === 'select') {
|
|
66
|
-
return Promise.resolve(overrides.selectResult ?? defaultResult);
|
|
67
|
-
}
|
|
68
|
-
if (currentOperation === 'update') {
|
|
69
|
-
return Promise.resolve(overrides.updateResult ?? defaultResult);
|
|
70
|
-
}
|
|
71
|
-
return Promise.resolve(defaultResult);
|
|
72
|
-
}),
|
|
73
|
-
maybeSingle: vi.fn(() => {
|
|
74
|
-
return Promise.resolve(overrides.selectResult ?? defaultResult);
|
|
75
|
-
}),
|
|
76
|
-
then: vi.fn((resolve: (value: unknown) => void) => {
|
|
77
|
-
if (currentOperation === 'insert' || insertThenSelect) {
|
|
78
|
-
return Promise.resolve(overrides.insertResult ?? defaultResult).then(resolve);
|
|
79
|
-
}
|
|
80
|
-
if (currentOperation === 'select') {
|
|
81
|
-
return Promise.resolve(overrides.selectResult ?? defaultResult).then(resolve);
|
|
82
|
-
}
|
|
83
|
-
if (currentOperation === 'update') {
|
|
84
|
-
return Promise.resolve(overrides.updateResult ?? defaultResult).then(resolve);
|
|
85
|
-
}
|
|
86
|
-
return Promise.resolve(defaultResult).then(resolve);
|
|
87
|
-
}),
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
return mock as unknown as SupabaseClient;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function createMockContext(
|
|
94
|
-
supabase: SupabaseClient,
|
|
95
|
-
options: { sessionId?: string | null } = {}
|
|
96
|
-
): HandlerContext {
|
|
97
|
-
const defaultTokenUsage: TokenUsage = {
|
|
98
|
-
callCount: 5,
|
|
99
|
-
totalTokens: 2500,
|
|
100
|
-
byTool: {},
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
const sessionId = 'sessionId' in options ? options.sessionId : 'session-123';
|
|
104
|
-
|
|
105
|
-
return {
|
|
106
|
-
supabase,
|
|
107
|
-
auth: { userId: 'user-123', apiKeyId: 'api-key-123' },
|
|
108
|
-
session: {
|
|
109
|
-
instanceId: 'instance-abc',
|
|
110
|
-
currentSessionId: sessionId,
|
|
111
|
-
currentPersona: 'Wave',
|
|
112
|
-
tokenUsage: defaultTokenUsage,
|
|
113
|
-
},
|
|
114
|
-
updateSession: vi.fn(),
|
|
115
|
-
};
|
|
116
|
-
}
|
|
15
|
+
const VALID_UUID = '123e4567-e89b-12d3-a456-426614174000';
|
|
117
16
|
|
|
118
17
|
// ============================================================================
|
|
119
18
|
// getTasksAwaitingValidation Tests
|
|
@@ -123,15 +22,13 @@ describe('getTasksAwaitingValidation', () => {
|
|
|
123
22
|
beforeEach(() => vi.clearAllMocks());
|
|
124
23
|
|
|
125
24
|
it('should throw error for missing project_id', async () => {
|
|
126
|
-
const
|
|
127
|
-
const ctx = createMockContext(supabase);
|
|
25
|
+
const ctx = createMockContext();
|
|
128
26
|
|
|
129
27
|
await expect(getTasksAwaitingValidation({}, ctx)).rejects.toThrow(ValidationError);
|
|
130
28
|
});
|
|
131
29
|
|
|
132
30
|
it('should throw error for invalid project_id UUID', async () => {
|
|
133
|
-
const
|
|
134
|
-
const ctx = createMockContext(supabase);
|
|
31
|
+
const ctx = createMockContext();
|
|
135
32
|
|
|
136
33
|
await expect(
|
|
137
34
|
getTasksAwaitingValidation({ project_id: 'invalid' }, ctx)
|
|
@@ -139,26 +36,19 @@ describe('getTasksAwaitingValidation', () => {
|
|
|
139
36
|
});
|
|
140
37
|
|
|
141
38
|
it('should return empty list when no tasks awaiting validation', async () => {
|
|
142
|
-
|
|
143
|
-
|
|
39
|
+
mockApiClient.getTasksAwaitingValidation.mockResolvedValue({
|
|
40
|
+
ok: true,
|
|
41
|
+
data: { tasks: [], count: 0 },
|
|
144
42
|
});
|
|
145
|
-
const ctx = createMockContext(
|
|
146
|
-
|
|
147
|
-
// Override to return array result
|
|
148
|
-
vi.mocked(supabase.from('').select).mockReturnValue({
|
|
149
|
-
...supabase,
|
|
150
|
-
then: (resolve: (val: unknown) => void) =>
|
|
151
|
-
Promise.resolve({ data: [], error: null }).then(resolve),
|
|
152
|
-
} as unknown as ReturnType<SupabaseClient['from']>);
|
|
43
|
+
const ctx = createMockContext();
|
|
153
44
|
|
|
154
45
|
const result = await getTasksAwaitingValidation(
|
|
155
|
-
{ project_id:
|
|
46
|
+
{ project_id: VALID_UUID },
|
|
156
47
|
ctx
|
|
157
48
|
);
|
|
158
49
|
|
|
159
50
|
expect(result.result).toHaveProperty('tasks');
|
|
160
51
|
expect((result.result as { tasks: unknown[] }).tasks).toEqual([]);
|
|
161
|
-
expect((result.result as { hint?: string }).hint).toBeUndefined();
|
|
162
52
|
});
|
|
163
53
|
|
|
164
54
|
it('should return tasks with review status info', async () => {
|
|
@@ -168,33 +58,31 @@ describe('getTasksAwaitingValidation', () => {
|
|
|
168
58
|
title: 'Implement feature A',
|
|
169
59
|
completed_at: '2025-01-14T10:00:00Z',
|
|
170
60
|
completed_by_session_id: 'other-session',
|
|
171
|
-
|
|
172
|
-
|
|
61
|
+
being_reviewed: false,
|
|
62
|
+
review_minutes: null,
|
|
173
63
|
},
|
|
174
64
|
{
|
|
175
65
|
id: 'task-2',
|
|
176
66
|
title: 'Implement feature B',
|
|
177
67
|
completed_at: '2025-01-14T11:00:00Z',
|
|
178
68
|
completed_by_session_id: 'other-session',
|
|
179
|
-
|
|
180
|
-
|
|
69
|
+
being_reviewed: true,
|
|
70
|
+
review_minutes: 5,
|
|
181
71
|
},
|
|
182
72
|
];
|
|
183
73
|
|
|
184
|
-
|
|
185
|
-
|
|
74
|
+
mockApiClient.getTasksAwaitingValidation.mockResolvedValue({
|
|
75
|
+
ok: true,
|
|
76
|
+
data: {
|
|
77
|
+
tasks: mockTasks,
|
|
78
|
+
count: 2,
|
|
79
|
+
hint: 'Use claim_validation to claim a task for review',
|
|
80
|
+
},
|
|
186
81
|
});
|
|
187
|
-
const ctx = createMockContext(
|
|
188
|
-
|
|
189
|
-
// Override to return array result
|
|
190
|
-
vi.mocked(supabase.from('').select).mockReturnValue({
|
|
191
|
-
...supabase,
|
|
192
|
-
then: (resolve: (val: unknown) => void) =>
|
|
193
|
-
Promise.resolve({ data: mockTasks, error: null }).then(resolve),
|
|
194
|
-
} as unknown as ReturnType<SupabaseClient['from']>);
|
|
82
|
+
const ctx = createMockContext();
|
|
195
83
|
|
|
196
84
|
const result = await getTasksAwaitingValidation(
|
|
197
|
-
{ project_id:
|
|
85
|
+
{ project_id: VALID_UUID },
|
|
198
86
|
ctx
|
|
199
87
|
);
|
|
200
88
|
|
|
@@ -207,46 +95,37 @@ describe('getTasksAwaitingValidation', () => {
|
|
|
207
95
|
|
|
208
96
|
// Second task being reviewed for ~5 minutes
|
|
209
97
|
expect(tasks[1].being_reviewed).toBe(true);
|
|
210
|
-
expect(tasks[1].review_minutes).
|
|
211
|
-
expect(tasks[1].review_minutes).toBeLessThanOrEqual(6);
|
|
98
|
+
expect(tasks[1].review_minutes).toBe(5);
|
|
212
99
|
|
|
213
100
|
// Should have hint when tasks are present
|
|
214
101
|
expect((result.result as { hint?: string }).hint).toContain('claim_validation');
|
|
215
102
|
});
|
|
216
103
|
|
|
217
|
-
it('should
|
|
218
|
-
|
|
219
|
-
|
|
104
|
+
it('should call API client with correct project_id', async () => {
|
|
105
|
+
mockApiClient.getTasksAwaitingValidation.mockResolvedValue({
|
|
106
|
+
ok: true,
|
|
107
|
+
data: { tasks: [], count: 0 },
|
|
220
108
|
});
|
|
221
|
-
const ctx = createMockContext(
|
|
109
|
+
const ctx = createMockContext();
|
|
222
110
|
|
|
223
111
|
await getTasksAwaitingValidation(
|
|
224
|
-
{ project_id:
|
|
112
|
+
{ project_id: VALID_UUID },
|
|
225
113
|
ctx
|
|
226
114
|
);
|
|
227
115
|
|
|
228
|
-
expect(
|
|
229
|
-
expect(supabase.eq).toHaveBeenCalledWith('project_id', '123e4567-e89b-12d3-a456-426614174000');
|
|
230
|
-
expect(supabase.eq).toHaveBeenCalledWith('status', 'completed');
|
|
231
|
-
expect(supabase.is).toHaveBeenCalledWith('validated_at', null);
|
|
116
|
+
expect(mockApiClient.getTasksAwaitingValidation).toHaveBeenCalledWith(VALID_UUID);
|
|
232
117
|
});
|
|
233
118
|
|
|
234
|
-
it('should throw error when
|
|
235
|
-
|
|
236
|
-
|
|
119
|
+
it('should throw error when API call fails', async () => {
|
|
120
|
+
mockApiClient.getTasksAwaitingValidation.mockResolvedValue({
|
|
121
|
+
ok: false,
|
|
122
|
+
error: 'Failed to fetch tasks awaiting validation',
|
|
237
123
|
});
|
|
238
|
-
const ctx = createMockContext(
|
|
239
|
-
|
|
240
|
-
// Override to return error
|
|
241
|
-
vi.mocked(supabase.from('').select).mockReturnValue({
|
|
242
|
-
...supabase,
|
|
243
|
-
then: (resolve: (val: unknown) => void) =>
|
|
244
|
-
Promise.resolve({ data: null, error: { message: 'Database error' } }).then(resolve),
|
|
245
|
-
} as unknown as ReturnType<SupabaseClient['from']>);
|
|
124
|
+
const ctx = createMockContext();
|
|
246
125
|
|
|
247
126
|
await expect(
|
|
248
|
-
getTasksAwaitingValidation({ project_id:
|
|
249
|
-
).rejects.toThrow('Failed to fetch tasks');
|
|
127
|
+
getTasksAwaitingValidation({ project_id: VALID_UUID }, ctx)
|
|
128
|
+
).rejects.toThrow('Failed to fetch tasks awaiting validation');
|
|
250
129
|
});
|
|
251
130
|
});
|
|
252
131
|
|
|
@@ -258,15 +137,13 @@ describe('claimValidation', () => {
|
|
|
258
137
|
beforeEach(() => vi.clearAllMocks());
|
|
259
138
|
|
|
260
139
|
it('should throw error for missing task_id', async () => {
|
|
261
|
-
const
|
|
262
|
-
const ctx = createMockContext(supabase);
|
|
140
|
+
const ctx = createMockContext();
|
|
263
141
|
|
|
264
142
|
await expect(claimValidation({}, ctx)).rejects.toThrow(ValidationError);
|
|
265
143
|
});
|
|
266
144
|
|
|
267
145
|
it('should throw error for invalid task_id UUID', async () => {
|
|
268
|
-
const
|
|
269
|
-
const ctx = createMockContext(supabase);
|
|
146
|
+
const ctx = createMockContext();
|
|
270
147
|
|
|
271
148
|
await expect(
|
|
272
149
|
claimValidation({ task_id: 'not-a-uuid' }, ctx)
|
|
@@ -274,194 +151,133 @@ describe('claimValidation', () => {
|
|
|
274
151
|
});
|
|
275
152
|
|
|
276
153
|
it('should throw error when task not found', async () => {
|
|
277
|
-
|
|
278
|
-
|
|
154
|
+
mockApiClient.claimValidation.mockResolvedValue({
|
|
155
|
+
ok: false,
|
|
156
|
+
error: 'Task not found',
|
|
279
157
|
});
|
|
280
|
-
const ctx = createMockContext(
|
|
158
|
+
const ctx = createMockContext();
|
|
281
159
|
|
|
282
160
|
await expect(
|
|
283
|
-
claimValidation({ task_id:
|
|
161
|
+
claimValidation({ task_id: VALID_UUID }, ctx)
|
|
284
162
|
).rejects.toThrow('Task not found');
|
|
285
163
|
});
|
|
286
164
|
|
|
287
165
|
it('should throw error when task is not completed', async () => {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
id: 'task-1',
|
|
292
|
-
title: 'Test Task',
|
|
293
|
-
status: 'in_progress',
|
|
294
|
-
validated_at: null,
|
|
295
|
-
completed_by_session_id: 'other-session',
|
|
296
|
-
reviewing_by_session_id: null,
|
|
297
|
-
},
|
|
298
|
-
error: null,
|
|
299
|
-
},
|
|
166
|
+
mockApiClient.claimValidation.mockResolvedValue({
|
|
167
|
+
ok: false,
|
|
168
|
+
error: 'Can only claim completed tasks for review',
|
|
300
169
|
});
|
|
301
|
-
const ctx = createMockContext(
|
|
170
|
+
const ctx = createMockContext();
|
|
302
171
|
|
|
303
172
|
await expect(
|
|
304
|
-
claimValidation({ task_id:
|
|
173
|
+
claimValidation({ task_id: VALID_UUID }, ctx)
|
|
305
174
|
).rejects.toThrow('Can only claim completed tasks for review');
|
|
306
175
|
});
|
|
307
176
|
|
|
308
177
|
it('should throw error when task is already validated', async () => {
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
id: 'task-1',
|
|
313
|
-
title: 'Test Task',
|
|
314
|
-
status: 'completed',
|
|
315
|
-
validated_at: '2025-01-14T12:00:00Z',
|
|
316
|
-
completed_by_session_id: 'other-session',
|
|
317
|
-
reviewing_by_session_id: null,
|
|
318
|
-
},
|
|
319
|
-
error: null,
|
|
320
|
-
},
|
|
178
|
+
mockApiClient.claimValidation.mockResolvedValue({
|
|
179
|
+
ok: false,
|
|
180
|
+
error: 'Task has already been validated',
|
|
321
181
|
});
|
|
322
|
-
const ctx = createMockContext(
|
|
182
|
+
const ctx = createMockContext();
|
|
323
183
|
|
|
324
184
|
await expect(
|
|
325
|
-
claimValidation({ task_id:
|
|
185
|
+
claimValidation({ task_id: VALID_UUID }, ctx)
|
|
326
186
|
).rejects.toThrow('Task has already been validated');
|
|
327
187
|
});
|
|
328
188
|
|
|
329
189
|
it('should throw error when task is being reviewed by another agent', async () => {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
id: 'task-1',
|
|
334
|
-
title: 'Test Task',
|
|
335
|
-
status: 'completed',
|
|
336
|
-
validated_at: null,
|
|
337
|
-
completed_by_session_id: 'other-session',
|
|
338
|
-
reviewing_by_session_id: 'different-reviewer',
|
|
339
|
-
},
|
|
340
|
-
error: null,
|
|
341
|
-
},
|
|
190
|
+
mockApiClient.claimValidation.mockResolvedValue({
|
|
191
|
+
ok: false,
|
|
192
|
+
error: 'Task is already being reviewed by another agent',
|
|
342
193
|
});
|
|
343
|
-
const ctx = createMockContext(
|
|
194
|
+
const ctx = createMockContext({ sessionId: 'session-123' });
|
|
344
195
|
|
|
345
196
|
await expect(
|
|
346
|
-
claimValidation({ task_id:
|
|
197
|
+
claimValidation({ task_id: VALID_UUID }, ctx)
|
|
347
198
|
).rejects.toThrow('Task is already being reviewed by another agent');
|
|
348
199
|
});
|
|
349
200
|
|
|
350
201
|
it('should allow same agent to re-claim their own review', async () => {
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
completed_by_session_id: 'other-session',
|
|
359
|
-
reviewing_by_session_id: 'session-123', // Same as current session
|
|
360
|
-
},
|
|
361
|
-
error: null,
|
|
202
|
+
mockApiClient.claimValidation.mockResolvedValue({
|
|
203
|
+
ok: true,
|
|
204
|
+
data: {
|
|
205
|
+
success: true,
|
|
206
|
+
task_id: VALID_UUID,
|
|
207
|
+
title: 'Test Task',
|
|
208
|
+
message: 'Re-claimed task for review',
|
|
362
209
|
},
|
|
363
|
-
updateResult: { data: null, error: null },
|
|
364
210
|
});
|
|
365
|
-
const ctx = createMockContext(
|
|
211
|
+
const ctx = createMockContext({ sessionId: 'session-123' });
|
|
366
212
|
|
|
367
213
|
const result = await claimValidation(
|
|
368
|
-
{ task_id:
|
|
214
|
+
{ task_id: VALID_UUID },
|
|
369
215
|
ctx
|
|
370
216
|
);
|
|
371
217
|
|
|
372
218
|
expect(result.result).toMatchObject({
|
|
373
219
|
success: true,
|
|
374
|
-
task_id:
|
|
220
|
+
task_id: VALID_UUID,
|
|
375
221
|
title: 'Test Task',
|
|
376
222
|
});
|
|
377
223
|
});
|
|
378
224
|
|
|
379
225
|
it('should claim task successfully', async () => {
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
reviewing_by_session_id: null,
|
|
389
|
-
},
|
|
390
|
-
error: null,
|
|
226
|
+
mockApiClient.claimValidation.mockResolvedValue({
|
|
227
|
+
ok: true,
|
|
228
|
+
data: {
|
|
229
|
+
success: true,
|
|
230
|
+
task_id: VALID_UUID,
|
|
231
|
+
title: 'Test Task',
|
|
232
|
+
message: 'Task claimed for review. Dashboard updated.',
|
|
233
|
+
next_step: 'Run tests and verify implementation. Call validate_task when done.',
|
|
391
234
|
},
|
|
392
|
-
updateResult: { data: null, error: null },
|
|
393
235
|
});
|
|
394
|
-
const ctx = createMockContext(
|
|
236
|
+
const ctx = createMockContext();
|
|
395
237
|
|
|
396
238
|
const result = await claimValidation(
|
|
397
|
-
{ task_id:
|
|
239
|
+
{ task_id: VALID_UUID },
|
|
398
240
|
ctx
|
|
399
241
|
);
|
|
400
242
|
|
|
401
243
|
expect(result.result).toMatchObject({
|
|
402
244
|
success: true,
|
|
403
|
-
task_id:
|
|
245
|
+
task_id: VALID_UUID,
|
|
404
246
|
title: 'Test Task',
|
|
405
247
|
message: expect.stringContaining('Dashboard'),
|
|
406
248
|
});
|
|
407
249
|
expect((result.result as { next_step: string }).next_step).toContain('validate_task');
|
|
408
250
|
});
|
|
409
251
|
|
|
410
|
-
it('should
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
validated_at: null,
|
|
418
|
-
completed_by_session_id: 'other-session',
|
|
419
|
-
reviewing_by_session_id: null,
|
|
420
|
-
},
|
|
421
|
-
error: null,
|
|
252
|
+
it('should call API client with session_id', async () => {
|
|
253
|
+
mockApiClient.claimValidation.mockResolvedValue({
|
|
254
|
+
ok: true,
|
|
255
|
+
data: {
|
|
256
|
+
success: true,
|
|
257
|
+
task_id: VALID_UUID,
|
|
258
|
+
title: 'Test Task',
|
|
422
259
|
},
|
|
423
|
-
updateResult: { data: null, error: null },
|
|
424
260
|
});
|
|
425
|
-
const ctx = createMockContext(
|
|
261
|
+
const ctx = createMockContext({ sessionId: 'reviewer-session' });
|
|
426
262
|
|
|
427
|
-
await claimValidation({ task_id:
|
|
263
|
+
await claimValidation({ task_id: VALID_UUID }, ctx);
|
|
428
264
|
|
|
429
|
-
expect(
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
reviewing_started_at: expect.any(String),
|
|
433
|
-
})
|
|
265
|
+
expect(mockApiClient.claimValidation).toHaveBeenCalledWith(
|
|
266
|
+
VALID_UUID,
|
|
267
|
+
'reviewer-session'
|
|
434
268
|
);
|
|
435
269
|
});
|
|
436
270
|
|
|
437
|
-
it('should throw error when
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
id: 'task-1',
|
|
442
|
-
title: 'Test Task',
|
|
443
|
-
status: 'completed',
|
|
444
|
-
validated_at: null,
|
|
445
|
-
completed_by_session_id: 'other-session',
|
|
446
|
-
reviewing_by_session_id: null,
|
|
447
|
-
},
|
|
448
|
-
error: null,
|
|
449
|
-
},
|
|
271
|
+
it('should throw error when claim fails', async () => {
|
|
272
|
+
mockApiClient.claimValidation.mockResolvedValue({
|
|
273
|
+
ok: false,
|
|
274
|
+
error: 'Failed to claim task for validation',
|
|
450
275
|
});
|
|
451
|
-
const ctx = createMockContext(
|
|
452
|
-
|
|
453
|
-
// Override update to return error
|
|
454
|
-
vi.mocked(supabase.from('').update).mockReturnValue({
|
|
455
|
-
...supabase,
|
|
456
|
-
eq: vi.fn().mockReturnValue({
|
|
457
|
-
then: (resolve: (val: unknown) => void) =>
|
|
458
|
-
Promise.resolve({ data: null, error: { message: 'Update failed' } }).then(resolve),
|
|
459
|
-
}),
|
|
460
|
-
} as unknown as ReturnType<SupabaseClient['from']>);
|
|
276
|
+
const ctx = createMockContext();
|
|
461
277
|
|
|
462
278
|
await expect(
|
|
463
|
-
claimValidation({ task_id:
|
|
464
|
-
).rejects.toThrow('Failed to claim task');
|
|
279
|
+
claimValidation({ task_id: VALID_UUID }, ctx)
|
|
280
|
+
).rejects.toThrow('Failed to claim task for validation');
|
|
465
281
|
});
|
|
466
282
|
});
|
|
467
283
|
|
|
@@ -473,8 +289,7 @@ describe('validateTask', () => {
|
|
|
473
289
|
beforeEach(() => vi.clearAllMocks());
|
|
474
290
|
|
|
475
291
|
it('should throw error for missing task_id', async () => {
|
|
476
|
-
const
|
|
477
|
-
const ctx = createMockContext(supabase);
|
|
292
|
+
const ctx = createMockContext();
|
|
478
293
|
|
|
479
294
|
await expect(
|
|
480
295
|
validateTask({ approved: true }, ctx)
|
|
@@ -482,122 +297,94 @@ describe('validateTask', () => {
|
|
|
482
297
|
});
|
|
483
298
|
|
|
484
299
|
it('should throw error for invalid task_id UUID', async () => {
|
|
485
|
-
const
|
|
486
|
-
const ctx = createMockContext(supabase);
|
|
300
|
+
const ctx = createMockContext();
|
|
487
301
|
|
|
488
302
|
await expect(
|
|
489
303
|
validateTask({ task_id: 'invalid', approved: true }, ctx)
|
|
490
304
|
).rejects.toThrow(ValidationError);
|
|
491
305
|
});
|
|
492
306
|
|
|
307
|
+
it('should throw error when approved is missing', async () => {
|
|
308
|
+
const ctx = createMockContext();
|
|
309
|
+
|
|
310
|
+
await expect(
|
|
311
|
+
validateTask({ task_id: VALID_UUID }, ctx)
|
|
312
|
+
).rejects.toThrow('approved is required');
|
|
313
|
+
});
|
|
314
|
+
|
|
493
315
|
it('should throw error when task not found', async () => {
|
|
494
|
-
|
|
495
|
-
|
|
316
|
+
mockApiClient.validateTask.mockResolvedValue({
|
|
317
|
+
ok: false,
|
|
318
|
+
error: 'Task not found',
|
|
496
319
|
});
|
|
497
|
-
const ctx = createMockContext(
|
|
320
|
+
const ctx = createMockContext();
|
|
498
321
|
|
|
499
322
|
await expect(
|
|
500
|
-
validateTask({ task_id:
|
|
323
|
+
validateTask({ task_id: VALID_UUID, approved: true }, ctx)
|
|
501
324
|
).rejects.toThrow('Task not found');
|
|
502
325
|
});
|
|
503
326
|
|
|
504
327
|
it('should throw error when task is not completed', async () => {
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
id: 'task-1',
|
|
509
|
-
title: 'Test Task',
|
|
510
|
-
status: 'in_progress',
|
|
511
|
-
validated_at: null,
|
|
512
|
-
completed_by_session_id: 'other-session',
|
|
513
|
-
project_id: 'proj-1',
|
|
514
|
-
git_branch: null,
|
|
515
|
-
},
|
|
516
|
-
error: null,
|
|
517
|
-
},
|
|
328
|
+
mockApiClient.validateTask.mockResolvedValue({
|
|
329
|
+
ok: false,
|
|
330
|
+
error: 'Can only validate completed tasks',
|
|
518
331
|
});
|
|
519
|
-
const ctx = createMockContext(
|
|
332
|
+
const ctx = createMockContext();
|
|
520
333
|
|
|
521
334
|
await expect(
|
|
522
|
-
validateTask({ task_id:
|
|
335
|
+
validateTask({ task_id: VALID_UUID, approved: true }, ctx)
|
|
523
336
|
).rejects.toThrow('Can only validate completed tasks');
|
|
524
337
|
});
|
|
525
338
|
|
|
526
339
|
it('should throw error when task already validated', async () => {
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
id: 'task-1',
|
|
531
|
-
title: 'Test Task',
|
|
532
|
-
status: 'completed',
|
|
533
|
-
validated_at: '2025-01-14T12:00:00Z',
|
|
534
|
-
completed_by_session_id: 'other-session',
|
|
535
|
-
project_id: 'proj-1',
|
|
536
|
-
git_branch: null,
|
|
537
|
-
},
|
|
538
|
-
error: null,
|
|
539
|
-
},
|
|
340
|
+
mockApiClient.validateTask.mockResolvedValue({
|
|
341
|
+
ok: false,
|
|
342
|
+
error: 'Task has already been validated',
|
|
540
343
|
});
|
|
541
|
-
const ctx = createMockContext(
|
|
344
|
+
const ctx = createMockContext();
|
|
542
345
|
|
|
543
346
|
await expect(
|
|
544
|
-
validateTask({ task_id:
|
|
347
|
+
validateTask({ task_id: VALID_UUID, approved: true }, ctx)
|
|
545
348
|
).rejects.toThrow('Task has already been validated');
|
|
546
349
|
});
|
|
547
350
|
|
|
548
351
|
describe('when approved', () => {
|
|
549
352
|
it('should mark task as validated', async () => {
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
validated_at: null,
|
|
557
|
-
completed_by_session_id: 'other-session',
|
|
558
|
-
project_id: 'proj-1',
|
|
559
|
-
git_branch: null,
|
|
560
|
-
},
|
|
561
|
-
error: null,
|
|
353
|
+
mockApiClient.validateTask.mockResolvedValue({
|
|
354
|
+
ok: true,
|
|
355
|
+
data: {
|
|
356
|
+
success: true,
|
|
357
|
+
validated_task_id: VALID_UUID,
|
|
358
|
+
self_validated: false,
|
|
562
359
|
},
|
|
563
|
-
updateResult: { data: null, error: null },
|
|
564
|
-
insertResult: { data: null, error: null },
|
|
565
360
|
});
|
|
566
|
-
const ctx = createMockContext(
|
|
361
|
+
const ctx = createMockContext({ sessionId: 'validator-session' });
|
|
567
362
|
|
|
568
363
|
const result = await validateTask(
|
|
569
|
-
{ task_id:
|
|
364
|
+
{ task_id: VALID_UUID, approved: true },
|
|
570
365
|
ctx
|
|
571
366
|
);
|
|
572
367
|
|
|
573
368
|
expect(result.result).toMatchObject({
|
|
574
369
|
success: true,
|
|
575
|
-
validated_task_id:
|
|
370
|
+
validated_task_id: VALID_UUID,
|
|
576
371
|
self_validated: false,
|
|
577
372
|
});
|
|
578
373
|
});
|
|
579
374
|
|
|
580
375
|
it('should detect self-validation', async () => {
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
validated_at: null,
|
|
588
|
-
completed_by_session_id: 'session-123', // Same as validator
|
|
589
|
-
project_id: 'proj-1',
|
|
590
|
-
git_branch: null,
|
|
591
|
-
},
|
|
592
|
-
error: null,
|
|
376
|
+
mockApiClient.validateTask.mockResolvedValue({
|
|
377
|
+
ok: true,
|
|
378
|
+
data: {
|
|
379
|
+
success: true,
|
|
380
|
+
validated_task_id: VALID_UUID,
|
|
381
|
+
self_validated: true,
|
|
593
382
|
},
|
|
594
|
-
updateResult: { data: null, error: null },
|
|
595
|
-
insertResult: { data: null, error: null },
|
|
596
383
|
});
|
|
597
|
-
const ctx = createMockContext(
|
|
384
|
+
const ctx = createMockContext({ sessionId: 'session-123' });
|
|
598
385
|
|
|
599
386
|
const result = await validateTask(
|
|
600
|
-
{ task_id:
|
|
387
|
+
{ task_id: VALID_UUID, approved: true },
|
|
601
388
|
ctx
|
|
602
389
|
);
|
|
603
390
|
|
|
@@ -607,274 +394,156 @@ describe('validateTask', () => {
|
|
|
607
394
|
});
|
|
608
395
|
});
|
|
609
396
|
|
|
610
|
-
it('should
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
validated_at: null,
|
|
618
|
-
completed_by_session_id: 'other-session',
|
|
619
|
-
project_id: 'proj-1',
|
|
620
|
-
git_branch: null,
|
|
621
|
-
},
|
|
622
|
-
error: null,
|
|
623
|
-
},
|
|
624
|
-
updateResult: { data: null, error: null },
|
|
625
|
-
insertResult: { data: null, error: null },
|
|
626
|
-
});
|
|
627
|
-
const ctx = createMockContext(supabase);
|
|
628
|
-
|
|
629
|
-
await validateTask(
|
|
630
|
-
{ task_id: '123e4567-e89b-12d3-a456-426614174000', approved: true },
|
|
631
|
-
ctx
|
|
632
|
-
);
|
|
633
|
-
|
|
634
|
-
expect(supabase.update).toHaveBeenCalledWith(
|
|
635
|
-
expect.objectContaining({
|
|
636
|
-
validated_at: expect.any(String),
|
|
637
|
-
reviewing_by_session_id: null,
|
|
638
|
-
reviewing_started_at: null,
|
|
639
|
-
})
|
|
640
|
-
);
|
|
641
|
-
});
|
|
642
|
-
|
|
643
|
-
it('should log validation in progress_logs', async () => {
|
|
644
|
-
const supabase = createMockSupabase({
|
|
645
|
-
selectResult: {
|
|
646
|
-
data: {
|
|
647
|
-
id: 'task-1',
|
|
648
|
-
title: 'Test Feature',
|
|
649
|
-
status: 'completed',
|
|
650
|
-
validated_at: null,
|
|
651
|
-
completed_by_session_id: 'other-session',
|
|
652
|
-
project_id: 'proj-123',
|
|
653
|
-
git_branch: null,
|
|
654
|
-
},
|
|
655
|
-
error: null,
|
|
397
|
+
it('should pass validation_notes to API', async () => {
|
|
398
|
+
mockApiClient.validateTask.mockResolvedValue({
|
|
399
|
+
ok: true,
|
|
400
|
+
data: {
|
|
401
|
+
success: true,
|
|
402
|
+
validated_task_id: VALID_UUID,
|
|
403
|
+
self_validated: false,
|
|
656
404
|
},
|
|
657
|
-
updateResult: { data: null, error: null },
|
|
658
|
-
insertResult: { data: null, error: null },
|
|
659
405
|
});
|
|
660
|
-
const ctx = createMockContext(
|
|
406
|
+
const ctx = createMockContext({ sessionId: 'validator-session' });
|
|
661
407
|
|
|
662
408
|
await validateTask(
|
|
663
409
|
{
|
|
664
|
-
task_id:
|
|
410
|
+
task_id: VALID_UUID,
|
|
665
411
|
approved: true,
|
|
666
412
|
validation_notes: 'Looks good!',
|
|
667
413
|
},
|
|
668
414
|
ctx
|
|
669
415
|
);
|
|
670
416
|
|
|
671
|
-
expect(
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
task_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
676
|
-
summary: expect.stringContaining('Validated'),
|
|
677
|
-
details: 'Looks good!',
|
|
678
|
-
created_by: 'agent',
|
|
679
|
-
})
|
|
417
|
+
expect(mockApiClient.validateTask).toHaveBeenCalledWith(
|
|
418
|
+
VALID_UUID,
|
|
419
|
+
{ approved: true, validation_notes: 'Looks good!' },
|
|
420
|
+
'validator-session'
|
|
680
421
|
);
|
|
681
422
|
});
|
|
682
423
|
});
|
|
683
424
|
|
|
684
425
|
describe('when rejected', () => {
|
|
685
426
|
it('should reopen task', async () => {
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
status: 'completed',
|
|
692
|
-
validated_at: null,
|
|
693
|
-
completed_by_session_id: 'other-session',
|
|
694
|
-
project_id: 'proj-1',
|
|
695
|
-
git_branch: null,
|
|
696
|
-
},
|
|
697
|
-
error: null,
|
|
427
|
+
mockApiClient.validateTask.mockResolvedValue({
|
|
428
|
+
ok: true,
|
|
429
|
+
data: {
|
|
430
|
+
success: true,
|
|
431
|
+
reopened_task_id: VALID_UUID,
|
|
698
432
|
},
|
|
699
|
-
updateResult: { data: null, error: null },
|
|
700
|
-
insertResult: { data: null, error: null },
|
|
701
433
|
});
|
|
702
|
-
const ctx = createMockContext(
|
|
434
|
+
const ctx = createMockContext();
|
|
703
435
|
|
|
704
436
|
const result = await validateTask(
|
|
705
|
-
{ task_id:
|
|
437
|
+
{ task_id: VALID_UUID, approved: false },
|
|
706
438
|
ctx
|
|
707
439
|
);
|
|
708
440
|
|
|
709
441
|
expect(result.result).toMatchObject({
|
|
710
442
|
success: true,
|
|
711
|
-
reopened_task_id:
|
|
443
|
+
reopened_task_id: VALID_UUID,
|
|
712
444
|
});
|
|
713
445
|
});
|
|
714
446
|
|
|
715
|
-
it('should
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
status: 'completed',
|
|
722
|
-
validated_at: null,
|
|
723
|
-
completed_by_session_id: 'other-session',
|
|
724
|
-
project_id: 'proj-1',
|
|
725
|
-
git_branch: null,
|
|
726
|
-
},
|
|
727
|
-
error: null,
|
|
728
|
-
},
|
|
729
|
-
updateResult: { data: null, error: null },
|
|
730
|
-
insertResult: { data: null, error: null },
|
|
731
|
-
});
|
|
732
|
-
const ctx = createMockContext(supabase);
|
|
733
|
-
|
|
734
|
-
await validateTask(
|
|
735
|
-
{ task_id: '123e4567-e89b-12d3-a456-426614174000', approved: false },
|
|
736
|
-
ctx
|
|
737
|
-
);
|
|
738
|
-
|
|
739
|
-
expect(supabase.update).toHaveBeenCalledWith(
|
|
740
|
-
expect.objectContaining({
|
|
741
|
-
status: 'in_progress',
|
|
742
|
-
completed_at: null,
|
|
743
|
-
progress_percentage: 80,
|
|
744
|
-
reviewing_by_session_id: null,
|
|
745
|
-
reviewing_started_at: null,
|
|
746
|
-
})
|
|
747
|
-
);
|
|
748
|
-
});
|
|
749
|
-
|
|
750
|
-
it('should log rejection in progress_logs', async () => {
|
|
751
|
-
const supabase = createMockSupabase({
|
|
752
|
-
selectResult: {
|
|
753
|
-
data: {
|
|
754
|
-
id: 'task-1',
|
|
755
|
-
title: 'Broken Feature',
|
|
756
|
-
status: 'completed',
|
|
757
|
-
validated_at: null,
|
|
758
|
-
completed_by_session_id: 'other-session',
|
|
759
|
-
project_id: 'proj-123',
|
|
760
|
-
git_branch: null,
|
|
761
|
-
},
|
|
762
|
-
error: null,
|
|
447
|
+
it('should pass rejection notes to API', async () => {
|
|
448
|
+
mockApiClient.validateTask.mockResolvedValue({
|
|
449
|
+
ok: true,
|
|
450
|
+
data: {
|
|
451
|
+
success: true,
|
|
452
|
+
reopened_task_id: VALID_UUID,
|
|
763
453
|
},
|
|
764
|
-
updateResult: { data: null, error: null },
|
|
765
|
-
insertResult: { data: null, error: null },
|
|
766
454
|
});
|
|
767
|
-
const ctx = createMockContext(
|
|
455
|
+
const ctx = createMockContext();
|
|
768
456
|
|
|
769
457
|
await validateTask(
|
|
770
458
|
{
|
|
771
|
-
task_id:
|
|
459
|
+
task_id: VALID_UUID,
|
|
772
460
|
approved: false,
|
|
773
461
|
validation_notes: 'Tests failing',
|
|
774
462
|
},
|
|
775
463
|
ctx
|
|
776
464
|
);
|
|
777
465
|
|
|
778
|
-
expect(
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
details: 'Tests failing',
|
|
783
|
-
})
|
|
466
|
+
expect(mockApiClient.validateTask).toHaveBeenCalledWith(
|
|
467
|
+
VALID_UUID,
|
|
468
|
+
{ approved: false, validation_notes: 'Tests failing' },
|
|
469
|
+
'session-123'
|
|
784
470
|
);
|
|
785
471
|
});
|
|
472
|
+
});
|
|
786
473
|
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
id: 'task-1',
|
|
792
|
-
title: 'Test Task',
|
|
793
|
-
status: 'completed',
|
|
794
|
-
validated_at: null,
|
|
795
|
-
completed_by_session_id: 'other-session',
|
|
796
|
-
project_id: 'proj-123',
|
|
797
|
-
git_branch: null,
|
|
798
|
-
},
|
|
799
|
-
error: null,
|
|
800
|
-
},
|
|
801
|
-
updateResult: { data: null, error: null },
|
|
802
|
-
insertResult: { data: null, error: null },
|
|
803
|
-
});
|
|
804
|
-
const ctx = createMockContext(supabase);
|
|
805
|
-
|
|
806
|
-
await validateTask(
|
|
807
|
-
{ task_id: '123e4567-e89b-12d3-a456-426614174000', approved: false },
|
|
808
|
-
ctx
|
|
809
|
-
);
|
|
810
|
-
|
|
811
|
-
expect(supabase.insert).toHaveBeenCalledWith(
|
|
812
|
-
expect.objectContaining({
|
|
813
|
-
details: 'Needs more work',
|
|
814
|
-
})
|
|
815
|
-
);
|
|
474
|
+
it('should throw error when validation fails on approval', async () => {
|
|
475
|
+
mockApiClient.validateTask.mockResolvedValue({
|
|
476
|
+
ok: false,
|
|
477
|
+
error: 'Failed to validate task',
|
|
816
478
|
});
|
|
479
|
+
const ctx = createMockContext();
|
|
480
|
+
|
|
481
|
+
await expect(
|
|
482
|
+
validateTask({ task_id: VALID_UUID, approved: true }, ctx)
|
|
483
|
+
).rejects.toThrow('Failed to validate task');
|
|
817
484
|
});
|
|
818
485
|
|
|
819
|
-
it('should throw error when
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
id: 'task-1',
|
|
824
|
-
title: 'Test Task',
|
|
825
|
-
status: 'completed',
|
|
826
|
-
validated_at: null,
|
|
827
|
-
completed_by_session_id: 'other-session',
|
|
828
|
-
project_id: 'proj-1',
|
|
829
|
-
git_branch: null,
|
|
830
|
-
},
|
|
831
|
-
error: null,
|
|
832
|
-
},
|
|
486
|
+
it('should throw error when validation fails on rejection', async () => {
|
|
487
|
+
mockApiClient.validateTask.mockResolvedValue({
|
|
488
|
+
ok: false,
|
|
489
|
+
error: 'Failed to validate task',
|
|
833
490
|
});
|
|
834
|
-
const ctx = createMockContext(
|
|
835
|
-
|
|
836
|
-
// Override update to return error
|
|
837
|
-
vi.mocked(supabase.from('').update).mockReturnValue({
|
|
838
|
-
...supabase,
|
|
839
|
-
eq: vi.fn().mockReturnValue({
|
|
840
|
-
then: (resolve: (val: unknown) => void) =>
|
|
841
|
-
Promise.resolve({ data: null, error: { message: 'Update failed' } }).then(resolve),
|
|
842
|
-
}),
|
|
843
|
-
} as unknown as ReturnType<SupabaseClient['from']>);
|
|
491
|
+
const ctx = createMockContext();
|
|
844
492
|
|
|
845
493
|
await expect(
|
|
846
|
-
validateTask({ task_id:
|
|
494
|
+
validateTask({ task_id: VALID_UUID, approved: false }, ctx)
|
|
847
495
|
).rejects.toThrow('Failed to validate task');
|
|
848
496
|
});
|
|
849
497
|
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
498
|
+
describe('PR requirement', () => {
|
|
499
|
+
it('should return error when PR is required but not present', async () => {
|
|
500
|
+
mockApiClient.validateTask.mockResolvedValue({
|
|
501
|
+
ok: false,
|
|
502
|
+
error: 'pr_required',
|
|
853
503
|
data: {
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
status: 'completed',
|
|
857
|
-
validated_at: null,
|
|
858
|
-
completed_by_session_id: 'other-session',
|
|
859
|
-
project_id: 'proj-1',
|
|
860
|
-
git_branch: null,
|
|
504
|
+
message: 'This project uses git-flow workflow which requires a Pull Request before validation approval.',
|
|
505
|
+
workflow: 'git-flow',
|
|
861
506
|
},
|
|
862
|
-
|
|
863
|
-
|
|
507
|
+
});
|
|
508
|
+
const ctx = createMockContext();
|
|
509
|
+
|
|
510
|
+
const result = await validateTask(
|
|
511
|
+
{ task_id: VALID_UUID, approved: true },
|
|
512
|
+
ctx
|
|
513
|
+
);
|
|
514
|
+
|
|
515
|
+
expect(result.result).toMatchObject({
|
|
516
|
+
error: 'pr_required',
|
|
517
|
+
workflow: 'git-flow',
|
|
518
|
+
action_required: expect.stringContaining('add_task_reference'),
|
|
519
|
+
});
|
|
864
520
|
});
|
|
865
|
-
const ctx = createMockContext(supabase);
|
|
866
521
|
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
522
|
+
it('should pass skip_pr_check to API', async () => {
|
|
523
|
+
mockApiClient.validateTask.mockResolvedValue({
|
|
524
|
+
ok: true,
|
|
525
|
+
data: {
|
|
526
|
+
success: true,
|
|
527
|
+
validated_task_id: VALID_UUID,
|
|
528
|
+
self_validated: false,
|
|
529
|
+
},
|
|
530
|
+
});
|
|
531
|
+
const ctx = createMockContext({ sessionId: 'validator-session' });
|
|
532
|
+
|
|
533
|
+
await validateTask(
|
|
534
|
+
{
|
|
535
|
+
task_id: VALID_UUID,
|
|
536
|
+
approved: true,
|
|
537
|
+
skip_pr_check: true,
|
|
538
|
+
},
|
|
539
|
+
ctx
|
|
540
|
+
);
|
|
875
541
|
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
542
|
+
expect(mockApiClient.validateTask).toHaveBeenCalledWith(
|
|
543
|
+
VALID_UUID,
|
|
544
|
+
{ approved: true, skip_pr_check: true },
|
|
545
|
+
'validator-session'
|
|
546
|
+
);
|
|
547
|
+
});
|
|
879
548
|
});
|
|
880
549
|
});
|