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