@vibescope/mcp-server 0.0.1 → 0.1.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 +1114 -0
- package/dist/api-client.js +698 -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 +106 -476
- 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 +112 -828
- package/dist/handlers/discovery.js +31 -0
- package/dist/handlers/fallback.d.ts +2 -0
- package/dist/handlers/fallback.js +39 -134
- package/dist/handlers/findings.js +43 -67
- 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 +99 -585
- package/dist/handlers/sprints.d.ts +32 -0
- package/dist/handlers/sprints.js +274 -0
- package/dist/handlers/tasks.d.ts +7 -10
- package/dist/handlers/tasks.js +230 -900
- package/dist/handlers/tool-docs.d.ts +8 -0
- package/dist/handlers/tool-docs.js +657 -0
- package/dist/handlers/types.d.ts +11 -3
- package/dist/handlers/validation.d.ts +1 -1
- package/dist/handlers/validation.js +26 -153
- package/dist/index.js +473 -160
- package/dist/knowledge.js +106 -9
- package/dist/tools.js +4 -0
- package/dist/validators.d.ts +21 -0
- package/dist/validators.js +91 -0
- package/package.json +2 -3
- package/src/api-client.ts +1752 -0
- package/src/cli.test.ts +128 -302
- package/src/cli.ts +41 -285
- package/src/handlers/__test-setup__.ts +210 -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 +194 -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 +140 -895
- package/src/handlers/discovery.test.ts +20 -67
- package/src/handlers/discovery.ts +32 -0
- package/src/handlers/fallback.test.ts +128 -361
- package/src/handlers/fallback.ts +62 -148
- package/src/handlers/findings.test.ts +127 -345
- package/src/handlers/findings.ts +49 -66
- 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 +241 -206
- package/src/handlers/session.ts +110 -657
- package/src/handlers/sprints.test.ts +711 -0
- package/src/handlers/sprints.ts +497 -0
- package/src/handlers/tasks.test.ts +608 -353
- package/src/handlers/tasks.ts +248 -1025
- package/src/handlers/types.ts +12 -4
- package/src/handlers/validation.test.ts +189 -572
- package/src/handlers/validation.ts +29 -166
- package/src/index.ts +473 -184
- package/src/knowledge.ts +107 -9
- package/src/tools.ts +2506 -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/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,6 +1,4 @@
|
|
|
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
|
addFinding,
|
|
6
4
|
getFindings,
|
|
@@ -8,109 +6,8 @@ import {
|
|
|
8
6
|
deleteFinding,
|
|
9
7
|
} from './findings.js';
|
|
10
8
|
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
|
-
order: vi.fn().mockReturnThis(),
|
|
57
|
-
limit: vi.fn().mockReturnThis(),
|
|
58
|
-
single: vi.fn(() => {
|
|
59
|
-
if (currentOperation === 'insert' || insertThenSelect) {
|
|
60
|
-
return Promise.resolve(overrides.insertResult ?? defaultResult);
|
|
61
|
-
}
|
|
62
|
-
if (currentOperation === 'select') {
|
|
63
|
-
return Promise.resolve(overrides.selectResult ?? defaultResult);
|
|
64
|
-
}
|
|
65
|
-
if (currentOperation === 'update') {
|
|
66
|
-
return Promise.resolve(overrides.updateResult ?? defaultResult);
|
|
67
|
-
}
|
|
68
|
-
return Promise.resolve(defaultResult);
|
|
69
|
-
}),
|
|
70
|
-
then: vi.fn((resolve: (value: unknown) => void) => {
|
|
71
|
-
if (currentOperation === 'insert' || insertThenSelect) {
|
|
72
|
-
return Promise.resolve(overrides.insertResult ?? defaultResult).then(resolve);
|
|
73
|
-
}
|
|
74
|
-
if (currentOperation === 'select') {
|
|
75
|
-
return Promise.resolve(overrides.selectResult ?? defaultResult).then(resolve);
|
|
76
|
-
}
|
|
77
|
-
if (currentOperation === 'update') {
|
|
78
|
-
return Promise.resolve(overrides.updateResult ?? defaultResult).then(resolve);
|
|
79
|
-
}
|
|
80
|
-
if (currentOperation === 'delete') {
|
|
81
|
-
return Promise.resolve(overrides.deleteResult ?? defaultResult).then(resolve);
|
|
82
|
-
}
|
|
83
|
-
return Promise.resolve(defaultResult).then(resolve);
|
|
84
|
-
}),
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
return mock as unknown as SupabaseClient;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function createMockContext(
|
|
91
|
-
supabase: SupabaseClient,
|
|
92
|
-
options: { sessionId?: string | null } = {}
|
|
93
|
-
): HandlerContext {
|
|
94
|
-
const defaultTokenUsage: TokenUsage = {
|
|
95
|
-
callCount: 5,
|
|
96
|
-
totalTokens: 2500,
|
|
97
|
-
byTool: {},
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
const sessionId = 'sessionId' in options ? options.sessionId : 'session-123';
|
|
101
|
-
|
|
102
|
-
return {
|
|
103
|
-
supabase,
|
|
104
|
-
auth: { userId: 'user-123', apiKeyId: 'api-key-123' },
|
|
105
|
-
session: {
|
|
106
|
-
instanceId: 'instance-abc',
|
|
107
|
-
currentSessionId: sessionId,
|
|
108
|
-
currentPersona: 'Wave',
|
|
109
|
-
tokenUsage: defaultTokenUsage,
|
|
110
|
-
},
|
|
111
|
-
updateSession: vi.fn(),
|
|
112
|
-
};
|
|
113
|
-
}
|
|
9
|
+
import { createMockContext } from './__test-utils__.js';
|
|
10
|
+
import { mockApiClient } from './__test-setup__.js';
|
|
114
11
|
|
|
115
12
|
const VALID_UUID = '123e4567-e89b-12d3-a456-426614174000';
|
|
116
13
|
const VALID_UUID_2 = '223e4567-e89b-12d3-a456-426614174001';
|
|
@@ -123,15 +20,13 @@ describe('addFinding', () => {
|
|
|
123
20
|
beforeEach(() => vi.clearAllMocks());
|
|
124
21
|
|
|
125
22
|
it('should throw error for missing project_id', async () => {
|
|
126
|
-
const
|
|
127
|
-
const ctx = createMockContext(supabase);
|
|
23
|
+
const ctx = createMockContext();
|
|
128
24
|
|
|
129
25
|
await expect(addFinding({ title: 'Test Finding' }, ctx)).rejects.toThrow(ValidationError);
|
|
130
26
|
});
|
|
131
27
|
|
|
132
28
|
it('should throw error for invalid project_id UUID', async () => {
|
|
133
|
-
const
|
|
134
|
-
const ctx = createMockContext(supabase);
|
|
29
|
+
const ctx = createMockContext();
|
|
135
30
|
|
|
136
31
|
await expect(
|
|
137
32
|
addFinding({ project_id: 'invalid', title: 'Test' }, ctx)
|
|
@@ -139,8 +34,7 @@ describe('addFinding', () => {
|
|
|
139
34
|
});
|
|
140
35
|
|
|
141
36
|
it('should throw error for missing title', async () => {
|
|
142
|
-
const
|
|
143
|
-
const ctx = createMockContext(supabase);
|
|
37
|
+
const ctx = createMockContext();
|
|
144
38
|
|
|
145
39
|
await expect(
|
|
146
40
|
addFinding({ project_id: VALID_UUID }, ctx)
|
|
@@ -148,19 +42,19 @@ describe('addFinding', () => {
|
|
|
148
42
|
});
|
|
149
43
|
|
|
150
44
|
it('should throw error for invalid related_task_id UUID', async () => {
|
|
151
|
-
const
|
|
152
|
-
const ctx = createMockContext(supabase);
|
|
45
|
+
const ctx = createMockContext();
|
|
153
46
|
|
|
154
47
|
await expect(
|
|
155
48
|
addFinding({ project_id: VALID_UUID, title: 'Test', related_task_id: 'invalid' }, ctx)
|
|
156
49
|
).rejects.toThrow(ValidationError);
|
|
157
50
|
});
|
|
158
51
|
|
|
159
|
-
it('should create finding with required fields
|
|
160
|
-
|
|
161
|
-
|
|
52
|
+
it('should create finding with required fields', async () => {
|
|
53
|
+
mockApiClient.addFinding.mockResolvedValue({
|
|
54
|
+
ok: true,
|
|
55
|
+
data: { success: true, finding_id: 'finding-1', title: 'Performance issue' },
|
|
162
56
|
});
|
|
163
|
-
const ctx = createMockContext(
|
|
57
|
+
const ctx = createMockContext();
|
|
164
58
|
|
|
165
59
|
const result = await addFinding(
|
|
166
60
|
{ project_id: VALID_UUID, title: 'Performance issue' },
|
|
@@ -172,27 +66,14 @@ describe('addFinding', () => {
|
|
|
172
66
|
finding_id: 'finding-1',
|
|
173
67
|
title: 'Performance issue',
|
|
174
68
|
});
|
|
175
|
-
expect(supabase.from).toHaveBeenCalledWith('findings');
|
|
176
|
-
expect(supabase.insert).toHaveBeenCalledWith(
|
|
177
|
-
expect.objectContaining({
|
|
178
|
-
project_id: VALID_UUID,
|
|
179
|
-
title: 'Performance issue',
|
|
180
|
-
category: 'other',
|
|
181
|
-
severity: 'info',
|
|
182
|
-
description: null,
|
|
183
|
-
file_path: null,
|
|
184
|
-
line_number: null,
|
|
185
|
-
related_task_id: null,
|
|
186
|
-
created_by: 'agent',
|
|
187
|
-
})
|
|
188
|
-
);
|
|
189
69
|
});
|
|
190
70
|
|
|
191
|
-
it('should
|
|
192
|
-
|
|
193
|
-
|
|
71
|
+
it('should call API client with all parameters', async () => {
|
|
72
|
+
mockApiClient.addFinding.mockResolvedValue({
|
|
73
|
+
ok: true,
|
|
74
|
+
data: { success: true, finding_id: 'finding-2' },
|
|
194
75
|
});
|
|
195
|
-
const ctx = createMockContext(
|
|
76
|
+
const ctx = createMockContext({ sessionId: 'my-session' });
|
|
196
77
|
|
|
197
78
|
await addFinding(
|
|
198
79
|
{
|
|
@@ -208,8 +89,9 @@ describe('addFinding', () => {
|
|
|
208
89
|
ctx
|
|
209
90
|
);
|
|
210
91
|
|
|
211
|
-
expect(
|
|
212
|
-
|
|
92
|
+
expect(mockApiClient.addFinding).toHaveBeenCalledWith(
|
|
93
|
+
VALID_UUID,
|
|
94
|
+
{
|
|
213
95
|
title: 'SQL Injection vulnerability',
|
|
214
96
|
description: 'User input not sanitized',
|
|
215
97
|
category: 'security',
|
|
@@ -217,19 +99,21 @@ describe('addFinding', () => {
|
|
|
217
99
|
file_path: 'src/api/users.ts',
|
|
218
100
|
line_number: 42,
|
|
219
101
|
related_task_id: VALID_UUID_2,
|
|
220
|
-
}
|
|
102
|
+
},
|
|
103
|
+
'my-session'
|
|
221
104
|
);
|
|
222
105
|
});
|
|
223
106
|
|
|
224
|
-
it('should throw error when
|
|
225
|
-
|
|
226
|
-
|
|
107
|
+
it('should throw error when API call fails', async () => {
|
|
108
|
+
mockApiClient.addFinding.mockResolvedValue({
|
|
109
|
+
ok: false,
|
|
110
|
+
error: 'Insert failed',
|
|
227
111
|
});
|
|
228
|
-
const ctx = createMockContext(
|
|
112
|
+
const ctx = createMockContext();
|
|
229
113
|
|
|
230
114
|
await expect(
|
|
231
115
|
addFinding({ project_id: VALID_UUID, title: 'Test' }, ctx)
|
|
232
|
-
).rejects.toThrow('
|
|
116
|
+
).rejects.toThrow('Insert failed');
|
|
233
117
|
});
|
|
234
118
|
});
|
|
235
119
|
|
|
@@ -241,15 +125,13 @@ describe('getFindings', () => {
|
|
|
241
125
|
beforeEach(() => vi.clearAllMocks());
|
|
242
126
|
|
|
243
127
|
it('should throw error for missing project_id', async () => {
|
|
244
|
-
const
|
|
245
|
-
const ctx = createMockContext(supabase);
|
|
128
|
+
const ctx = createMockContext();
|
|
246
129
|
|
|
247
130
|
await expect(getFindings({}, ctx)).rejects.toThrow(ValidationError);
|
|
248
131
|
});
|
|
249
132
|
|
|
250
133
|
it('should throw error for invalid project_id UUID', async () => {
|
|
251
|
-
const
|
|
252
|
-
const ctx = createMockContext(supabase);
|
|
134
|
+
const ctx = createMockContext();
|
|
253
135
|
|
|
254
136
|
await expect(
|
|
255
137
|
getFindings({ project_id: 'invalid' }, ctx)
|
|
@@ -261,83 +143,68 @@ describe('getFindings', () => {
|
|
|
261
143
|
{ id: 'f1', title: 'Finding 1', category: 'security', severity: 'high', status: 'open', file_path: null, created_at: '2026-01-14' },
|
|
262
144
|
{ id: 'f2', title: 'Finding 2', category: 'performance', severity: 'medium', status: 'addressed', file_path: 'src/app.ts', created_at: '2026-01-13' },
|
|
263
145
|
];
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
146
|
+
mockApiClient.getFindings.mockResolvedValue({
|
|
147
|
+
ok: true,
|
|
148
|
+
data: { findings: mockFindings },
|
|
267
149
|
});
|
|
268
|
-
const ctx = createMockContext(
|
|
150
|
+
const ctx = createMockContext();
|
|
269
151
|
|
|
270
152
|
const result = await getFindings({ project_id: VALID_UUID }, ctx);
|
|
271
153
|
|
|
272
154
|
expect(result.result).toMatchObject({ findings: mockFindings });
|
|
273
|
-
expect(supabase.from).toHaveBeenCalledWith('findings');
|
|
274
|
-
expect(supabase.eq).toHaveBeenCalledWith('project_id', VALID_UUID);
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
it('should filter by category when provided', async () => {
|
|
278
|
-
const supabase = createMockSupabase({
|
|
279
|
-
selectResult: { data: [], error: null },
|
|
280
|
-
});
|
|
281
|
-
const ctx = createMockContext(supabase);
|
|
282
|
-
|
|
283
|
-
await getFindings({ project_id: VALID_UUID, category: 'security' }, ctx);
|
|
284
|
-
|
|
285
|
-
expect(supabase.eq).toHaveBeenCalledWith('category', 'security');
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
it('should filter by severity when provided', async () => {
|
|
289
|
-
const supabase = createMockSupabase({
|
|
290
|
-
selectResult: { data: [], error: null },
|
|
291
|
-
});
|
|
292
|
-
const ctx = createMockContext(supabase);
|
|
293
|
-
|
|
294
|
-
await getFindings({ project_id: VALID_UUID, severity: 'critical' }, ctx);
|
|
295
|
-
|
|
296
|
-
expect(supabase.eq).toHaveBeenCalledWith('severity', 'critical');
|
|
297
155
|
});
|
|
298
156
|
|
|
299
|
-
it('should
|
|
300
|
-
|
|
301
|
-
|
|
157
|
+
it('should pass filters to API client', async () => {
|
|
158
|
+
mockApiClient.getFindings.mockResolvedValue({
|
|
159
|
+
ok: true,
|
|
160
|
+
data: { findings: [] },
|
|
302
161
|
});
|
|
303
|
-
const ctx = createMockContext(
|
|
304
|
-
|
|
305
|
-
await getFindings({
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
162
|
+
const ctx = createMockContext();
|
|
163
|
+
|
|
164
|
+
await getFindings({
|
|
165
|
+
project_id: VALID_UUID,
|
|
166
|
+
category: 'security',
|
|
167
|
+
severity: 'critical',
|
|
168
|
+
status: 'open',
|
|
169
|
+
limit: 10
|
|
170
|
+
}, ctx);
|
|
171
|
+
|
|
172
|
+
expect(mockApiClient.getFindings).toHaveBeenCalledWith(
|
|
173
|
+
VALID_UUID,
|
|
174
|
+
{
|
|
175
|
+
category: 'security',
|
|
176
|
+
severity: 'critical',
|
|
177
|
+
status: 'open',
|
|
178
|
+
limit: 10,
|
|
179
|
+
}
|
|
180
|
+
);
|
|
319
181
|
});
|
|
320
182
|
|
|
321
183
|
it('should use default limit of 50', async () => {
|
|
322
|
-
|
|
323
|
-
|
|
184
|
+
mockApiClient.getFindings.mockResolvedValue({
|
|
185
|
+
ok: true,
|
|
186
|
+
data: { findings: [] },
|
|
324
187
|
});
|
|
325
|
-
const ctx = createMockContext(
|
|
188
|
+
const ctx = createMockContext();
|
|
326
189
|
|
|
327
190
|
await getFindings({ project_id: VALID_UUID }, ctx);
|
|
328
191
|
|
|
329
|
-
expect(
|
|
192
|
+
expect(mockApiClient.getFindings).toHaveBeenCalledWith(
|
|
193
|
+
VALID_UUID,
|
|
194
|
+
expect.objectContaining({ limit: 50 })
|
|
195
|
+
);
|
|
330
196
|
});
|
|
331
197
|
|
|
332
|
-
it('should throw error when
|
|
333
|
-
|
|
334
|
-
|
|
198
|
+
it('should throw error when API call fails', async () => {
|
|
199
|
+
mockApiClient.getFindings.mockResolvedValue({
|
|
200
|
+
ok: false,
|
|
201
|
+
error: 'Query failed',
|
|
335
202
|
});
|
|
336
|
-
const ctx = createMockContext(
|
|
203
|
+
const ctx = createMockContext();
|
|
337
204
|
|
|
338
205
|
await expect(
|
|
339
206
|
getFindings({ project_id: VALID_UUID }, ctx)
|
|
340
|
-
).rejects.toThrow('
|
|
207
|
+
).rejects.toThrow('Query failed');
|
|
341
208
|
});
|
|
342
209
|
});
|
|
343
210
|
|
|
@@ -349,15 +216,13 @@ describe('updateFinding', () => {
|
|
|
349
216
|
beforeEach(() => vi.clearAllMocks());
|
|
350
217
|
|
|
351
218
|
it('should throw error for missing finding_id', async () => {
|
|
352
|
-
const
|
|
353
|
-
const ctx = createMockContext(supabase);
|
|
219
|
+
const ctx = createMockContext();
|
|
354
220
|
|
|
355
221
|
await expect(updateFinding({}, ctx)).rejects.toThrow(ValidationError);
|
|
356
222
|
});
|
|
357
223
|
|
|
358
224
|
it('should throw error for invalid finding_id UUID', async () => {
|
|
359
|
-
const
|
|
360
|
-
const ctx = createMockContext(supabase);
|
|
225
|
+
const ctx = createMockContext();
|
|
361
226
|
|
|
362
227
|
await expect(
|
|
363
228
|
updateFinding({ finding_id: 'invalid' }, ctx)
|
|
@@ -365,10 +230,11 @@ describe('updateFinding', () => {
|
|
|
365
230
|
});
|
|
366
231
|
|
|
367
232
|
it('should update title', async () => {
|
|
368
|
-
|
|
369
|
-
|
|
233
|
+
mockApiClient.updateFinding.mockResolvedValue({
|
|
234
|
+
ok: true,
|
|
235
|
+
data: { success: true, finding_id: VALID_UUID },
|
|
370
236
|
});
|
|
371
|
-
const ctx = createMockContext(
|
|
237
|
+
const ctx = createMockContext();
|
|
372
238
|
|
|
373
239
|
const result = await updateFinding(
|
|
374
240
|
{ finding_id: VALID_UUID, title: 'Updated Title' },
|
|
@@ -376,142 +242,49 @@ describe('updateFinding', () => {
|
|
|
376
242
|
);
|
|
377
243
|
|
|
378
244
|
expect(result.result).toMatchObject({ success: true, finding_id: VALID_UUID });
|
|
379
|
-
expect(supabase.update).toHaveBeenCalledWith(
|
|
380
|
-
expect.objectContaining({ title: 'Updated Title' })
|
|
381
|
-
);
|
|
382
245
|
});
|
|
383
246
|
|
|
384
|
-
it('should update
|
|
385
|
-
|
|
386
|
-
|
|
247
|
+
it('should call API client with all update fields', async () => {
|
|
248
|
+
mockApiClient.updateFinding.mockResolvedValue({
|
|
249
|
+
ok: true,
|
|
250
|
+
data: { success: true },
|
|
387
251
|
});
|
|
388
|
-
const ctx = createMockContext(
|
|
252
|
+
const ctx = createMockContext();
|
|
389
253
|
|
|
390
254
|
await updateFinding(
|
|
391
|
-
{
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
expect.objectContaining({ severity: 'high' })
|
|
397
|
-
);
|
|
398
|
-
});
|
|
399
|
-
|
|
400
|
-
it('should set addressed_at when status changed to addressed', async () => {
|
|
401
|
-
const supabase = createMockSupabase({
|
|
402
|
-
updateResult: { data: null, error: null },
|
|
403
|
-
});
|
|
404
|
-
const ctx = createMockContext(supabase, { sessionId: 'resolver-session' });
|
|
405
|
-
|
|
406
|
-
await updateFinding(
|
|
407
|
-
{ finding_id: VALID_UUID, status: 'addressed' },
|
|
408
|
-
ctx
|
|
409
|
-
);
|
|
410
|
-
|
|
411
|
-
expect(supabase.update).toHaveBeenCalledWith(
|
|
412
|
-
expect.objectContaining({
|
|
255
|
+
{
|
|
256
|
+
finding_id: VALID_UUID,
|
|
257
|
+
title: 'New Title',
|
|
258
|
+
description: 'New description',
|
|
259
|
+
severity: 'high',
|
|
413
260
|
status: 'addressed',
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
})
|
|
417
|
-
);
|
|
418
|
-
});
|
|
419
|
-
|
|
420
|
-
it('should set addressed_at when status changed to dismissed', async () => {
|
|
421
|
-
const supabase = createMockSupabase({
|
|
422
|
-
updateResult: { data: null, error: null },
|
|
423
|
-
});
|
|
424
|
-
const ctx = createMockContext(supabase);
|
|
425
|
-
|
|
426
|
-
await updateFinding(
|
|
427
|
-
{ finding_id: VALID_UUID, status: 'dismissed' },
|
|
428
|
-
ctx
|
|
429
|
-
);
|
|
430
|
-
|
|
431
|
-
expect(supabase.update).toHaveBeenCalledWith(
|
|
432
|
-
expect.objectContaining({
|
|
433
|
-
status: 'dismissed',
|
|
434
|
-
addressed_at: expect.any(String),
|
|
435
|
-
})
|
|
436
|
-
);
|
|
437
|
-
});
|
|
438
|
-
|
|
439
|
-
it('should set addressed_at when status changed to wontfix', async () => {
|
|
440
|
-
const supabase = createMockSupabase({
|
|
441
|
-
updateResult: { data: null, error: null },
|
|
442
|
-
});
|
|
443
|
-
const ctx = createMockContext(supabase);
|
|
444
|
-
|
|
445
|
-
await updateFinding(
|
|
446
|
-
{ finding_id: VALID_UUID, status: 'wontfix' },
|
|
447
|
-
ctx
|
|
448
|
-
);
|
|
449
|
-
|
|
450
|
-
expect(supabase.update).toHaveBeenCalledWith(
|
|
451
|
-
expect.objectContaining({
|
|
452
|
-
status: 'wontfix',
|
|
453
|
-
addressed_at: expect.any(String),
|
|
454
|
-
})
|
|
455
|
-
);
|
|
456
|
-
});
|
|
457
|
-
|
|
458
|
-
it('should not set addressed_at when status changed to open', async () => {
|
|
459
|
-
const supabase = createMockSupabase({
|
|
460
|
-
updateResult: { data: null, error: null },
|
|
461
|
-
});
|
|
462
|
-
const ctx = createMockContext(supabase);
|
|
463
|
-
|
|
464
|
-
await updateFinding(
|
|
465
|
-
{ finding_id: VALID_UUID, status: 'open' },
|
|
466
|
-
ctx
|
|
467
|
-
);
|
|
468
|
-
|
|
469
|
-
const updateCall = vi.mocked(supabase.update).mock.calls[0][0] as Record<string, unknown>;
|
|
470
|
-
expect(updateCall.status).toBe('open');
|
|
471
|
-
expect(updateCall.addressed_at).toBeUndefined();
|
|
472
|
-
});
|
|
473
|
-
|
|
474
|
-
it('should update resolution_note', async () => {
|
|
475
|
-
const supabase = createMockSupabase({
|
|
476
|
-
updateResult: { data: null, error: null },
|
|
477
|
-
});
|
|
478
|
-
const ctx = createMockContext(supabase);
|
|
479
|
-
|
|
480
|
-
await updateFinding(
|
|
481
|
-
{ finding_id: VALID_UUID, resolution_note: 'Fixed by sanitizing input' },
|
|
482
|
-
ctx
|
|
483
|
-
);
|
|
484
|
-
|
|
485
|
-
expect(supabase.update).toHaveBeenCalledWith(
|
|
486
|
-
expect.objectContaining({ resolution_note: 'Fixed by sanitizing input' })
|
|
487
|
-
);
|
|
488
|
-
});
|
|
489
|
-
|
|
490
|
-
it('should always update updated_at timestamp', async () => {
|
|
491
|
-
const supabase = createMockSupabase({
|
|
492
|
-
updateResult: { data: null, error: null },
|
|
493
|
-
});
|
|
494
|
-
const ctx = createMockContext(supabase);
|
|
495
|
-
|
|
496
|
-
await updateFinding(
|
|
497
|
-
{ finding_id: VALID_UUID, title: 'Test' },
|
|
261
|
+
resolution_note: 'Fixed by sanitizing input'
|
|
262
|
+
},
|
|
498
263
|
ctx
|
|
499
264
|
);
|
|
500
265
|
|
|
501
|
-
expect(
|
|
502
|
-
|
|
266
|
+
expect(mockApiClient.updateFinding).toHaveBeenCalledWith(
|
|
267
|
+
VALID_UUID,
|
|
268
|
+
{
|
|
269
|
+
title: 'New Title',
|
|
270
|
+
description: 'New description',
|
|
271
|
+
severity: 'high',
|
|
272
|
+
status: 'addressed',
|
|
273
|
+
resolution_note: 'Fixed by sanitizing input',
|
|
274
|
+
}
|
|
503
275
|
);
|
|
504
276
|
});
|
|
505
277
|
|
|
506
|
-
it('should throw error when
|
|
507
|
-
|
|
508
|
-
|
|
278
|
+
it('should throw error when API call fails', async () => {
|
|
279
|
+
mockApiClient.updateFinding.mockResolvedValue({
|
|
280
|
+
ok: false,
|
|
281
|
+
error: 'Update failed',
|
|
509
282
|
});
|
|
510
|
-
const ctx = createMockContext(
|
|
283
|
+
const ctx = createMockContext();
|
|
511
284
|
|
|
512
285
|
await expect(
|
|
513
286
|
updateFinding({ finding_id: VALID_UUID, title: 'Test' }, ctx)
|
|
514
|
-
).rejects.toThrow('
|
|
287
|
+
).rejects.toThrow('Update failed');
|
|
515
288
|
});
|
|
516
289
|
});
|
|
517
290
|
|
|
@@ -523,15 +296,13 @@ describe('deleteFinding', () => {
|
|
|
523
296
|
beforeEach(() => vi.clearAllMocks());
|
|
524
297
|
|
|
525
298
|
it('should throw error for missing finding_id', async () => {
|
|
526
|
-
const
|
|
527
|
-
const ctx = createMockContext(supabase);
|
|
299
|
+
const ctx = createMockContext();
|
|
528
300
|
|
|
529
301
|
await expect(deleteFinding({}, ctx)).rejects.toThrow(ValidationError);
|
|
530
302
|
});
|
|
531
303
|
|
|
532
304
|
it('should throw error for invalid finding_id UUID', async () => {
|
|
533
|
-
const
|
|
534
|
-
const ctx = createMockContext(supabase);
|
|
305
|
+
const ctx = createMockContext();
|
|
535
306
|
|
|
536
307
|
await expect(
|
|
537
308
|
deleteFinding({ finding_id: 'invalid' }, ctx)
|
|
@@ -539,27 +310,38 @@ describe('deleteFinding', () => {
|
|
|
539
310
|
});
|
|
540
311
|
|
|
541
312
|
it('should delete finding successfully', async () => {
|
|
542
|
-
|
|
543
|
-
|
|
313
|
+
mockApiClient.deleteFinding.mockResolvedValue({
|
|
314
|
+
ok: true,
|
|
315
|
+
data: { success: true },
|
|
544
316
|
});
|
|
545
|
-
const ctx = createMockContext(
|
|
317
|
+
const ctx = createMockContext();
|
|
546
318
|
|
|
547
319
|
const result = await deleteFinding({ finding_id: VALID_UUID }, ctx);
|
|
548
320
|
|
|
549
321
|
expect(result.result).toMatchObject({ success: true });
|
|
550
|
-
expect(supabase.from).toHaveBeenCalledWith('findings');
|
|
551
|
-
expect(supabase.delete).toHaveBeenCalled();
|
|
552
|
-
expect(supabase.eq).toHaveBeenCalledWith('id', VALID_UUID);
|
|
553
322
|
});
|
|
554
323
|
|
|
555
|
-
it('should
|
|
556
|
-
|
|
557
|
-
|
|
324
|
+
it('should call API client deleteFinding', async () => {
|
|
325
|
+
mockApiClient.deleteFinding.mockResolvedValue({
|
|
326
|
+
ok: true,
|
|
327
|
+
data: { success: true },
|
|
328
|
+
});
|
|
329
|
+
const ctx = createMockContext();
|
|
330
|
+
|
|
331
|
+
await deleteFinding({ finding_id: VALID_UUID }, ctx);
|
|
332
|
+
|
|
333
|
+
expect(mockApiClient.deleteFinding).toHaveBeenCalledWith(VALID_UUID);
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
it('should throw error when API call fails', async () => {
|
|
337
|
+
mockApiClient.deleteFinding.mockResolvedValue({
|
|
338
|
+
ok: false,
|
|
339
|
+
error: 'Delete failed',
|
|
558
340
|
});
|
|
559
|
-
const ctx = createMockContext(
|
|
341
|
+
const ctx = createMockContext();
|
|
560
342
|
|
|
561
343
|
await expect(
|
|
562
344
|
deleteFinding({ finding_id: VALID_UUID }, ctx)
|
|
563
|
-
).rejects.toThrow('
|
|
345
|
+
).rejects.toThrow('Delete failed');
|
|
564
346
|
});
|
|
565
347
|
});
|