@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,687 @@
|
|
|
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
|
+
import {
|
|
5
|
+
startFallbackActivity,
|
|
6
|
+
stopFallbackActivity,
|
|
7
|
+
getActivityHistory,
|
|
8
|
+
getActivitySchedules,
|
|
9
|
+
} from './fallback.js';
|
|
10
|
+
import { ValidationError } from '../validators.js';
|
|
11
|
+
|
|
12
|
+
// ============================================================================
|
|
13
|
+
// Test Utilities
|
|
14
|
+
// ============================================================================
|
|
15
|
+
|
|
16
|
+
function createMockSupabase(overrides: {
|
|
17
|
+
selectResult?: { data: unknown; error: unknown };
|
|
18
|
+
insertResult?: { data: unknown; error: unknown };
|
|
19
|
+
updateResult?: { data: unknown; error: unknown };
|
|
20
|
+
deleteResult?: { data: unknown; error: unknown };
|
|
21
|
+
} = {}) {
|
|
22
|
+
const defaultResult = { data: null, error: null };
|
|
23
|
+
let currentOperation = 'select';
|
|
24
|
+
let insertThenSelect = false;
|
|
25
|
+
|
|
26
|
+
const mock = {
|
|
27
|
+
from: vi.fn().mockReturnThis(),
|
|
28
|
+
select: vi.fn(() => {
|
|
29
|
+
if (currentOperation === 'insert') {
|
|
30
|
+
insertThenSelect = true;
|
|
31
|
+
} else {
|
|
32
|
+
currentOperation = 'select';
|
|
33
|
+
insertThenSelect = false;
|
|
34
|
+
}
|
|
35
|
+
return mock;
|
|
36
|
+
}),
|
|
37
|
+
insert: vi.fn(() => {
|
|
38
|
+
currentOperation = 'insert';
|
|
39
|
+
insertThenSelect = false;
|
|
40
|
+
return mock;
|
|
41
|
+
}),
|
|
42
|
+
update: vi.fn(() => {
|
|
43
|
+
currentOperation = 'update';
|
|
44
|
+
insertThenSelect = false;
|
|
45
|
+
return mock;
|
|
46
|
+
}),
|
|
47
|
+
delete: vi.fn(() => {
|
|
48
|
+
currentOperation = 'delete';
|
|
49
|
+
insertThenSelect = false;
|
|
50
|
+
return mock;
|
|
51
|
+
}),
|
|
52
|
+
eq: vi.fn().mockReturnThis(),
|
|
53
|
+
neq: vi.fn().mockReturnThis(),
|
|
54
|
+
in: vi.fn().mockReturnThis(),
|
|
55
|
+
is: vi.fn().mockReturnThis(),
|
|
56
|
+
not: vi.fn().mockReturnThis(),
|
|
57
|
+
or: vi.fn().mockReturnThis(),
|
|
58
|
+
gt: vi.fn().mockReturnThis(),
|
|
59
|
+
gte: vi.fn().mockReturnThis(),
|
|
60
|
+
lte: vi.fn().mockReturnThis(),
|
|
61
|
+
lt: vi.fn().mockReturnThis(),
|
|
62
|
+
order: vi.fn().mockReturnThis(),
|
|
63
|
+
limit: vi.fn().mockReturnThis(),
|
|
64
|
+
single: vi.fn(() => {
|
|
65
|
+
if (currentOperation === 'insert' || insertThenSelect) {
|
|
66
|
+
return Promise.resolve(overrides.insertResult ?? defaultResult);
|
|
67
|
+
}
|
|
68
|
+
if (currentOperation === 'select') {
|
|
69
|
+
return Promise.resolve(overrides.selectResult ?? defaultResult);
|
|
70
|
+
}
|
|
71
|
+
if (currentOperation === 'update') {
|
|
72
|
+
return Promise.resolve(overrides.updateResult ?? defaultResult);
|
|
73
|
+
}
|
|
74
|
+
return Promise.resolve(defaultResult);
|
|
75
|
+
}),
|
|
76
|
+
maybeSingle: vi.fn(() => {
|
|
77
|
+
return Promise.resolve(overrides.selectResult ?? defaultResult);
|
|
78
|
+
}),
|
|
79
|
+
then: vi.fn((resolve: (value: unknown) => void) => {
|
|
80
|
+
if (currentOperation === 'insert' || insertThenSelect) {
|
|
81
|
+
return Promise.resolve(overrides.insertResult ?? defaultResult).then(resolve);
|
|
82
|
+
}
|
|
83
|
+
if (currentOperation === 'select') {
|
|
84
|
+
return Promise.resolve(overrides.selectResult ?? defaultResult).then(resolve);
|
|
85
|
+
}
|
|
86
|
+
if (currentOperation === 'update') {
|
|
87
|
+
return Promise.resolve(overrides.updateResult ?? defaultResult).then(resolve);
|
|
88
|
+
}
|
|
89
|
+
if (currentOperation === 'delete') {
|
|
90
|
+
return Promise.resolve(overrides.deleteResult ?? defaultResult).then(resolve);
|
|
91
|
+
}
|
|
92
|
+
return Promise.resolve(defaultResult).then(resolve);
|
|
93
|
+
}),
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
return mock as unknown as SupabaseClient;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function createMockContext(
|
|
100
|
+
supabase: SupabaseClient,
|
|
101
|
+
options: { sessionId?: string | null } = {}
|
|
102
|
+
): HandlerContext {
|
|
103
|
+
const defaultTokenUsage: TokenUsage = {
|
|
104
|
+
callCount: 5,
|
|
105
|
+
totalTokens: 2500,
|
|
106
|
+
byTool: {},
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const sessionId = 'sessionId' in options ? options.sessionId : 'session-123';
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
supabase,
|
|
113
|
+
auth: { userId: 'user-123', apiKeyId: 'api-key-123' },
|
|
114
|
+
session: {
|
|
115
|
+
instanceId: 'instance-abc',
|
|
116
|
+
currentSessionId: sessionId,
|
|
117
|
+
currentPersona: 'Wave',
|
|
118
|
+
tokenUsage: defaultTokenUsage,
|
|
119
|
+
},
|
|
120
|
+
updateSession: vi.fn(),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ============================================================================
|
|
125
|
+
// startFallbackActivity Tests
|
|
126
|
+
// ============================================================================
|
|
127
|
+
|
|
128
|
+
describe('startFallbackActivity', () => {
|
|
129
|
+
beforeEach(() => vi.clearAllMocks());
|
|
130
|
+
|
|
131
|
+
it('should throw error for missing project_id', async () => {
|
|
132
|
+
const supabase = createMockSupabase();
|
|
133
|
+
const ctx = createMockContext(supabase);
|
|
134
|
+
|
|
135
|
+
await expect(
|
|
136
|
+
startFallbackActivity({ activity: 'code_review' }, ctx)
|
|
137
|
+
).rejects.toThrow(ValidationError);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('should throw error for invalid project_id UUID', async () => {
|
|
141
|
+
const supabase = createMockSupabase();
|
|
142
|
+
const ctx = createMockContext(supabase);
|
|
143
|
+
|
|
144
|
+
await expect(
|
|
145
|
+
startFallbackActivity({ project_id: 'invalid', activity: 'code_review' }, ctx)
|
|
146
|
+
).rejects.toThrow(ValidationError);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('should throw error for missing activity', async () => {
|
|
150
|
+
const supabase = createMockSupabase();
|
|
151
|
+
const ctx = createMockContext(supabase);
|
|
152
|
+
|
|
153
|
+
await expect(
|
|
154
|
+
startFallbackActivity({ project_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx)
|
|
155
|
+
).rejects.toThrow(ValidationError);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('should throw error for invalid activity type', async () => {
|
|
159
|
+
const supabase = createMockSupabase();
|
|
160
|
+
const ctx = createMockContext(supabase);
|
|
161
|
+
|
|
162
|
+
await expect(
|
|
163
|
+
startFallbackActivity({
|
|
164
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
165
|
+
activity: 'invalid_activity',
|
|
166
|
+
}, ctx)
|
|
167
|
+
).rejects.toThrow('Invalid activity');
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('should start fallback activity successfully', async () => {
|
|
171
|
+
const supabase = createMockSupabase({
|
|
172
|
+
updateResult: { data: null, error: null },
|
|
173
|
+
});
|
|
174
|
+
const ctx = createMockContext(supabase);
|
|
175
|
+
|
|
176
|
+
const result = await startFallbackActivity(
|
|
177
|
+
{
|
|
178
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
179
|
+
activity: 'code_review',
|
|
180
|
+
},
|
|
181
|
+
ctx
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
expect(result.result).toMatchObject({
|
|
185
|
+
success: true,
|
|
186
|
+
activity: 'code_review',
|
|
187
|
+
});
|
|
188
|
+
expect((result.result as { title: string }).title).toBeDefined();
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('should update agent_sessions with fallback activity', async () => {
|
|
192
|
+
const supabase = createMockSupabase({
|
|
193
|
+
updateResult: { data: null, error: null },
|
|
194
|
+
});
|
|
195
|
+
const ctx = createMockContext(supabase, { sessionId: 'my-session' });
|
|
196
|
+
|
|
197
|
+
await startFallbackActivity(
|
|
198
|
+
{
|
|
199
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
200
|
+
activity: 'security_review',
|
|
201
|
+
},
|
|
202
|
+
ctx
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
expect(supabase.from).toHaveBeenCalledWith('agent_sessions');
|
|
206
|
+
expect(supabase.update).toHaveBeenCalledWith(
|
|
207
|
+
expect.objectContaining({
|
|
208
|
+
current_fallback_activity: 'security_review',
|
|
209
|
+
current_task_id: null,
|
|
210
|
+
status: 'active',
|
|
211
|
+
})
|
|
212
|
+
);
|
|
213
|
+
expect(supabase.eq).toHaveBeenCalledWith('id', 'my-session');
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('should accept all valid activity types', async () => {
|
|
217
|
+
const validActivities = [
|
|
218
|
+
'feature_ideation',
|
|
219
|
+
'code_review',
|
|
220
|
+
'performance_audit',
|
|
221
|
+
'ux_review',
|
|
222
|
+
'cost_analysis',
|
|
223
|
+
'security_review',
|
|
224
|
+
'test_coverage',
|
|
225
|
+
'documentation_review',
|
|
226
|
+
'dependency_audit',
|
|
227
|
+
'validate_completed_tasks',
|
|
228
|
+
];
|
|
229
|
+
|
|
230
|
+
for (const activity of validActivities) {
|
|
231
|
+
const supabase = createMockSupabase({
|
|
232
|
+
updateResult: { data: null, error: null },
|
|
233
|
+
});
|
|
234
|
+
const ctx = createMockContext(supabase);
|
|
235
|
+
|
|
236
|
+
const result = await startFallbackActivity(
|
|
237
|
+
{
|
|
238
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
239
|
+
activity,
|
|
240
|
+
},
|
|
241
|
+
ctx
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
expect(result.result).toMatchObject({
|
|
245
|
+
success: true,
|
|
246
|
+
activity,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('should throw error when update fails', async () => {
|
|
252
|
+
const supabase = createMockSupabase();
|
|
253
|
+
const ctx = createMockContext(supabase);
|
|
254
|
+
|
|
255
|
+
// Override update to return error
|
|
256
|
+
vi.mocked(supabase.from('').update).mockReturnValue({
|
|
257
|
+
...supabase,
|
|
258
|
+
eq: vi.fn().mockReturnValue({
|
|
259
|
+
then: (resolve: (val: unknown) => void) =>
|
|
260
|
+
Promise.resolve({ data: null, error: { message: 'Update failed' } }).then(resolve),
|
|
261
|
+
}),
|
|
262
|
+
} as unknown as ReturnType<SupabaseClient['from']>);
|
|
263
|
+
|
|
264
|
+
await expect(
|
|
265
|
+
startFallbackActivity({
|
|
266
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
267
|
+
activity: 'code_review',
|
|
268
|
+
}, ctx)
|
|
269
|
+
).rejects.toThrow();
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
// ============================================================================
|
|
274
|
+
// stopFallbackActivity Tests
|
|
275
|
+
// ============================================================================
|
|
276
|
+
|
|
277
|
+
describe('stopFallbackActivity', () => {
|
|
278
|
+
beforeEach(() => vi.clearAllMocks());
|
|
279
|
+
|
|
280
|
+
it('should throw error for missing project_id', async () => {
|
|
281
|
+
const supabase = createMockSupabase();
|
|
282
|
+
const ctx = createMockContext(supabase);
|
|
283
|
+
|
|
284
|
+
await expect(stopFallbackActivity({}, ctx)).rejects.toThrow(ValidationError);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it('should throw error for invalid project_id UUID', async () => {
|
|
288
|
+
const supabase = createMockSupabase();
|
|
289
|
+
const ctx = createMockContext(supabase);
|
|
290
|
+
|
|
291
|
+
await expect(
|
|
292
|
+
stopFallbackActivity({ project_id: 'invalid' }, ctx)
|
|
293
|
+
).rejects.toThrow(ValidationError);
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it('should stop fallback activity successfully when activity exists', async () => {
|
|
297
|
+
const supabase = createMockSupabase({
|
|
298
|
+
selectResult: { data: { current_fallback_activity: 'code_review' }, error: null },
|
|
299
|
+
insertResult: { data: null, error: null },
|
|
300
|
+
updateResult: { data: null, error: null },
|
|
301
|
+
});
|
|
302
|
+
const ctx = createMockContext(supabase);
|
|
303
|
+
|
|
304
|
+
const result = await stopFallbackActivity(
|
|
305
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
306
|
+
ctx
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
expect(result.result).toMatchObject({
|
|
310
|
+
success: true,
|
|
311
|
+
});
|
|
312
|
+
expect((result.result as { message: string }).message).toContain('code_review');
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it('should stop fallback activity when no activity was active', async () => {
|
|
316
|
+
const supabase = createMockSupabase({
|
|
317
|
+
selectResult: { data: { current_fallback_activity: null }, error: null },
|
|
318
|
+
updateResult: { data: null, error: null },
|
|
319
|
+
});
|
|
320
|
+
const ctx = createMockContext(supabase);
|
|
321
|
+
|
|
322
|
+
const result = await stopFallbackActivity(
|
|
323
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
324
|
+
ctx
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
expect(result.result).toMatchObject({
|
|
328
|
+
success: true,
|
|
329
|
+
message: 'Fallback activity stopped',
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
it('should include summary in history when provided', async () => {
|
|
334
|
+
const supabase = createMockSupabase({
|
|
335
|
+
selectResult: { data: { current_fallback_activity: 'code_review' }, error: null },
|
|
336
|
+
insertResult: { data: null, error: null },
|
|
337
|
+
updateResult: { data: null, error: null },
|
|
338
|
+
});
|
|
339
|
+
const ctx = createMockContext(supabase);
|
|
340
|
+
|
|
341
|
+
await stopFallbackActivity(
|
|
342
|
+
{
|
|
343
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
344
|
+
summary: 'Reviewed 5 files, found 3 issues',
|
|
345
|
+
},
|
|
346
|
+
ctx
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
expect(supabase.from).toHaveBeenCalledWith('background_activity_history');
|
|
350
|
+
expect(supabase.insert).toHaveBeenCalledWith(
|
|
351
|
+
expect.objectContaining({
|
|
352
|
+
summary: 'Reviewed 5 files, found 3 issues',
|
|
353
|
+
})
|
|
354
|
+
);
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
it('should clear session fallback activity', async () => {
|
|
358
|
+
const supabase = createMockSupabase({
|
|
359
|
+
selectResult: { data: { current_fallback_activity: null }, error: null },
|
|
360
|
+
updateResult: { data: null, error: null },
|
|
361
|
+
});
|
|
362
|
+
const ctx = createMockContext(supabase, { sessionId: 'my-session' });
|
|
363
|
+
|
|
364
|
+
await stopFallbackActivity(
|
|
365
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
366
|
+
ctx
|
|
367
|
+
);
|
|
368
|
+
|
|
369
|
+
expect(supabase.update).toHaveBeenCalledWith(
|
|
370
|
+
expect.objectContaining({
|
|
371
|
+
current_fallback_activity: null,
|
|
372
|
+
status: 'idle',
|
|
373
|
+
})
|
|
374
|
+
);
|
|
375
|
+
});
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
// ============================================================================
|
|
379
|
+
// getActivityHistory Tests
|
|
380
|
+
// ============================================================================
|
|
381
|
+
|
|
382
|
+
describe('getActivityHistory', () => {
|
|
383
|
+
beforeEach(() => vi.clearAllMocks());
|
|
384
|
+
|
|
385
|
+
it('should throw error for missing project_id', async () => {
|
|
386
|
+
const supabase = createMockSupabase();
|
|
387
|
+
const ctx = createMockContext(supabase);
|
|
388
|
+
|
|
389
|
+
await expect(getActivityHistory({}, ctx)).rejects.toThrow(ValidationError);
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
it('should throw error for invalid project_id UUID', async () => {
|
|
393
|
+
const supabase = createMockSupabase();
|
|
394
|
+
const ctx = createMockContext(supabase);
|
|
395
|
+
|
|
396
|
+
await expect(
|
|
397
|
+
getActivityHistory({ project_id: 'invalid' }, ctx)
|
|
398
|
+
).rejects.toThrow(ValidationError);
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
it('should return empty history when no activities', async () => {
|
|
402
|
+
const supabase = createMockSupabase({
|
|
403
|
+
selectResult: { data: [], error: null },
|
|
404
|
+
});
|
|
405
|
+
const ctx = createMockContext(supabase);
|
|
406
|
+
|
|
407
|
+
const result = await getActivityHistory(
|
|
408
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
409
|
+
ctx
|
|
410
|
+
);
|
|
411
|
+
|
|
412
|
+
expect(result.result).toMatchObject({
|
|
413
|
+
history: [],
|
|
414
|
+
count: 0,
|
|
415
|
+
});
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
it('should return activity history', async () => {
|
|
419
|
+
const mockHistory = [
|
|
420
|
+
{
|
|
421
|
+
id: 'h1',
|
|
422
|
+
activity_type: 'code_review',
|
|
423
|
+
completed_at: '2025-01-14T10:00:00Z',
|
|
424
|
+
summary: 'Reviewed auth module',
|
|
425
|
+
agent_sessions: { agent_name: 'Wave' },
|
|
426
|
+
},
|
|
427
|
+
{
|
|
428
|
+
id: 'h2',
|
|
429
|
+
activity_type: 'security_review',
|
|
430
|
+
completed_at: '2025-01-14T09:00:00Z',
|
|
431
|
+
summary: null,
|
|
432
|
+
agent_sessions: { agent_name: 'Glitch' },
|
|
433
|
+
},
|
|
434
|
+
];
|
|
435
|
+
|
|
436
|
+
const supabase = createMockSupabase({
|
|
437
|
+
selectResult: { data: mockHistory, error: null },
|
|
438
|
+
});
|
|
439
|
+
const ctx = createMockContext(supabase);
|
|
440
|
+
|
|
441
|
+
const result = await getActivityHistory(
|
|
442
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
443
|
+
ctx
|
|
444
|
+
);
|
|
445
|
+
|
|
446
|
+
expect((result.result as { history: unknown[] }).history).toHaveLength(2);
|
|
447
|
+
expect((result.result as { count: number }).count).toBe(2);
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
it('should include latest_by_type in response', async () => {
|
|
451
|
+
const mockHistory = [
|
|
452
|
+
{
|
|
453
|
+
id: 'h1',
|
|
454
|
+
activity_type: 'code_review',
|
|
455
|
+
completed_at: '2025-01-14T10:00:00Z',
|
|
456
|
+
agent_sessions: null,
|
|
457
|
+
},
|
|
458
|
+
{
|
|
459
|
+
id: 'h2',
|
|
460
|
+
activity_type: 'code_review',
|
|
461
|
+
completed_at: '2025-01-14T09:00:00Z',
|
|
462
|
+
agent_sessions: null,
|
|
463
|
+
},
|
|
464
|
+
];
|
|
465
|
+
|
|
466
|
+
const supabase = createMockSupabase({
|
|
467
|
+
selectResult: { data: mockHistory, error: null },
|
|
468
|
+
});
|
|
469
|
+
const ctx = createMockContext(supabase);
|
|
470
|
+
|
|
471
|
+
const result = await getActivityHistory(
|
|
472
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
473
|
+
ctx
|
|
474
|
+
);
|
|
475
|
+
|
|
476
|
+
const latestByType = (result.result as { latest_by_type: Record<string, unknown> }).latest_by_type;
|
|
477
|
+
expect(latestByType['code_review']).toBeDefined();
|
|
478
|
+
expect((latestByType['code_review'] as { id: string }).id).toBe('h1');
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
it('should filter by activity_type when provided', async () => {
|
|
482
|
+
const supabase = createMockSupabase({
|
|
483
|
+
selectResult: { data: [], error: null },
|
|
484
|
+
});
|
|
485
|
+
const ctx = createMockContext(supabase);
|
|
486
|
+
|
|
487
|
+
await getActivityHistory(
|
|
488
|
+
{
|
|
489
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
490
|
+
activity_type: 'security_review',
|
|
491
|
+
},
|
|
492
|
+
ctx
|
|
493
|
+
);
|
|
494
|
+
|
|
495
|
+
expect(supabase.eq).toHaveBeenCalledWith('activity_type', 'security_review');
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
it('should respect limit parameter', async () => {
|
|
499
|
+
const supabase = createMockSupabase({
|
|
500
|
+
selectResult: { data: [], error: null },
|
|
501
|
+
});
|
|
502
|
+
const ctx = createMockContext(supabase);
|
|
503
|
+
|
|
504
|
+
await getActivityHistory(
|
|
505
|
+
{
|
|
506
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
507
|
+
limit: 10,
|
|
508
|
+
},
|
|
509
|
+
ctx
|
|
510
|
+
);
|
|
511
|
+
|
|
512
|
+
expect(supabase.limit).toHaveBeenCalledWith(10);
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
it('should query background_activity_history table', async () => {
|
|
516
|
+
const supabase = createMockSupabase({
|
|
517
|
+
selectResult: { data: [], error: null },
|
|
518
|
+
});
|
|
519
|
+
const ctx = createMockContext(supabase);
|
|
520
|
+
|
|
521
|
+
await getActivityHistory(
|
|
522
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
523
|
+
ctx
|
|
524
|
+
);
|
|
525
|
+
|
|
526
|
+
expect(supabase.from).toHaveBeenCalledWith('background_activity_history');
|
|
527
|
+
});
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
// ============================================================================
|
|
531
|
+
// getActivitySchedules Tests
|
|
532
|
+
// ============================================================================
|
|
533
|
+
|
|
534
|
+
describe('getActivitySchedules', () => {
|
|
535
|
+
beforeEach(() => vi.clearAllMocks());
|
|
536
|
+
|
|
537
|
+
it('should throw error for missing project_id', async () => {
|
|
538
|
+
const supabase = createMockSupabase();
|
|
539
|
+
const ctx = createMockContext(supabase);
|
|
540
|
+
|
|
541
|
+
await expect(getActivitySchedules({}, ctx)).rejects.toThrow(ValidationError);
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
it('should throw error for invalid project_id UUID', async () => {
|
|
545
|
+
const supabase = createMockSupabase();
|
|
546
|
+
const ctx = createMockContext(supabase);
|
|
547
|
+
|
|
548
|
+
await expect(
|
|
549
|
+
getActivitySchedules({ project_id: 'invalid' }, ctx)
|
|
550
|
+
).rejects.toThrow(ValidationError);
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
it('should return empty schedules when none exist', async () => {
|
|
554
|
+
const supabase = createMockSupabase({
|
|
555
|
+
selectResult: { data: [], error: null },
|
|
556
|
+
});
|
|
557
|
+
const ctx = createMockContext(supabase);
|
|
558
|
+
|
|
559
|
+
const result = await getActivitySchedules(
|
|
560
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
561
|
+
ctx
|
|
562
|
+
);
|
|
563
|
+
|
|
564
|
+
expect(result.result).toMatchObject({
|
|
565
|
+
schedules: [],
|
|
566
|
+
due_activities: [],
|
|
567
|
+
count: 0,
|
|
568
|
+
});
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
it('should return activity schedules', async () => {
|
|
572
|
+
const mockSchedules = [
|
|
573
|
+
{
|
|
574
|
+
id: 's1',
|
|
575
|
+
activity_type: 'code_review',
|
|
576
|
+
schedule_type: 'weekly',
|
|
577
|
+
enabled: true,
|
|
578
|
+
next_run_at: '2025-01-20T10:00:00Z',
|
|
579
|
+
},
|
|
580
|
+
{
|
|
581
|
+
id: 's2',
|
|
582
|
+
activity_type: 'security_review',
|
|
583
|
+
schedule_type: 'monthly',
|
|
584
|
+
enabled: true,
|
|
585
|
+
next_run_at: '2025-02-14T10:00:00Z',
|
|
586
|
+
},
|
|
587
|
+
];
|
|
588
|
+
|
|
589
|
+
const supabase = createMockSupabase({
|
|
590
|
+
selectResult: { data: mockSchedules, error: null },
|
|
591
|
+
});
|
|
592
|
+
const ctx = createMockContext(supabase);
|
|
593
|
+
|
|
594
|
+
const result = await getActivitySchedules(
|
|
595
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
596
|
+
ctx
|
|
597
|
+
);
|
|
598
|
+
|
|
599
|
+
expect((result.result as { schedules: unknown[] }).schedules).toHaveLength(2);
|
|
600
|
+
expect((result.result as { count: number }).count).toBe(2);
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
it('should identify due activities', async () => {
|
|
604
|
+
const pastDate = new Date(Date.now() - 3600000).toISOString(); // 1 hour ago
|
|
605
|
+
const futureDate = new Date(Date.now() + 3600000).toISOString(); // 1 hour from now
|
|
606
|
+
|
|
607
|
+
const mockSchedules = [
|
|
608
|
+
{
|
|
609
|
+
id: 's1',
|
|
610
|
+
activity_type: 'code_review',
|
|
611
|
+
enabled: true,
|
|
612
|
+
next_run_at: pastDate, // Due
|
|
613
|
+
},
|
|
614
|
+
{
|
|
615
|
+
id: 's2',
|
|
616
|
+
activity_type: 'security_review',
|
|
617
|
+
enabled: true,
|
|
618
|
+
next_run_at: futureDate, // Not due
|
|
619
|
+
},
|
|
620
|
+
{
|
|
621
|
+
id: 's3',
|
|
622
|
+
activity_type: 'test_coverage',
|
|
623
|
+
enabled: false, // Disabled
|
|
624
|
+
next_run_at: pastDate,
|
|
625
|
+
},
|
|
626
|
+
];
|
|
627
|
+
|
|
628
|
+
const supabase = createMockSupabase({
|
|
629
|
+
selectResult: { data: mockSchedules, error: null },
|
|
630
|
+
});
|
|
631
|
+
const ctx = createMockContext(supabase);
|
|
632
|
+
|
|
633
|
+
const result = await getActivitySchedules(
|
|
634
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
635
|
+
ctx
|
|
636
|
+
);
|
|
637
|
+
|
|
638
|
+
const dueActivities = (result.result as { due_activities: string[] }).due_activities;
|
|
639
|
+
expect(dueActivities).toContain('code_review');
|
|
640
|
+
expect(dueActivities).not.toContain('security_review');
|
|
641
|
+
expect(dueActivities).not.toContain('test_coverage');
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
it('should query background_activity_schedules table', async () => {
|
|
645
|
+
const supabase = createMockSupabase({
|
|
646
|
+
selectResult: { data: [], error: null },
|
|
647
|
+
});
|
|
648
|
+
const ctx = createMockContext(supabase);
|
|
649
|
+
|
|
650
|
+
await getActivitySchedules(
|
|
651
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
652
|
+
ctx
|
|
653
|
+
);
|
|
654
|
+
|
|
655
|
+
expect(supabase.from).toHaveBeenCalledWith('background_activity_schedules');
|
|
656
|
+
});
|
|
657
|
+
|
|
658
|
+
it('should order schedules by activity_type', async () => {
|
|
659
|
+
const supabase = createMockSupabase({
|
|
660
|
+
selectResult: { data: [], error: null },
|
|
661
|
+
});
|
|
662
|
+
const ctx = createMockContext(supabase);
|
|
663
|
+
|
|
664
|
+
await getActivitySchedules(
|
|
665
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
666
|
+
ctx
|
|
667
|
+
);
|
|
668
|
+
|
|
669
|
+
expect(supabase.order).toHaveBeenCalledWith('activity_type');
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
it('should throw error when query fails', async () => {
|
|
673
|
+
const supabase = createMockSupabase();
|
|
674
|
+
const ctx = createMockContext(supabase);
|
|
675
|
+
|
|
676
|
+
// Override to return error
|
|
677
|
+
vi.mocked(supabase.from('').select).mockReturnValue({
|
|
678
|
+
...supabase,
|
|
679
|
+
then: (resolve: (val: unknown) => void) =>
|
|
680
|
+
Promise.resolve({ data: null, error: { message: 'Query failed' } }).then(resolve),
|
|
681
|
+
} as unknown as ReturnType<SupabaseClient['from']>);
|
|
682
|
+
|
|
683
|
+
await expect(
|
|
684
|
+
getActivitySchedules({ project_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx)
|
|
685
|
+
).rejects.toThrow();
|
|
686
|
+
});
|
|
687
|
+
});
|