@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
|
@@ -2,141 +2,12 @@
|
|
|
2
2
|
* Shared Test Utilities
|
|
3
3
|
*
|
|
4
4
|
* Common mock factories and test helpers used across handler tests.
|
|
5
|
-
*
|
|
5
|
+
* Handlers now use API client instead of direct Supabase access.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { vi } from 'vitest';
|
|
9
|
-
import type { SupabaseClient } from '@supabase/supabase-js';
|
|
10
9
|
import type { HandlerContext, TokenUsage } from './types.js';
|
|
11
10
|
|
|
12
|
-
// ============================================================================
|
|
13
|
-
// Mock Supabase Factory
|
|
14
|
-
// ============================================================================
|
|
15
|
-
|
|
16
|
-
export interface MockSupabaseOverrides {
|
|
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
|
-
sessionsResult?: { data: unknown; error: unknown };
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Create a mock Supabase client for testing.
|
|
26
|
-
*
|
|
27
|
-
* The mock tracks which operation is being performed and returns
|
|
28
|
-
* the appropriate result from overrides.
|
|
29
|
-
*
|
|
30
|
-
* @example
|
|
31
|
-
* ```ts
|
|
32
|
-
* const supabase = createMockSupabase({
|
|
33
|
-
* insertResult: { data: { id: 'new-id' }, error: null }
|
|
34
|
-
* });
|
|
35
|
-
* ```
|
|
36
|
-
*/
|
|
37
|
-
export function createMockSupabase(overrides: MockSupabaseOverrides = {}) {
|
|
38
|
-
const defaultResult = { data: null, error: null };
|
|
39
|
-
|
|
40
|
-
// Use an object to track state so it persists across all mock function calls
|
|
41
|
-
const state = {
|
|
42
|
-
currentOperation: 'select' as string,
|
|
43
|
-
currentTable: '' as string,
|
|
44
|
-
insertThenSelect: false,
|
|
45
|
-
updateCalled: false,
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
const mock = {
|
|
49
|
-
from: vi.fn((table: string) => {
|
|
50
|
-
state.currentTable = table;
|
|
51
|
-
// Reset state for new query chain
|
|
52
|
-
state.currentOperation = 'select';
|
|
53
|
-
state.insertThenSelect = false;
|
|
54
|
-
state.updateCalled = false;
|
|
55
|
-
return mock;
|
|
56
|
-
}),
|
|
57
|
-
select: vi.fn(() => {
|
|
58
|
-
if (state.currentOperation === 'insert') {
|
|
59
|
-
state.insertThenSelect = true;
|
|
60
|
-
} else if (!state.updateCalled) {
|
|
61
|
-
state.currentOperation = 'select';
|
|
62
|
-
state.insertThenSelect = false;
|
|
63
|
-
}
|
|
64
|
-
return mock;
|
|
65
|
-
}),
|
|
66
|
-
insert: vi.fn(() => {
|
|
67
|
-
state.currentOperation = 'insert';
|
|
68
|
-
state.insertThenSelect = false;
|
|
69
|
-
return mock;
|
|
70
|
-
}),
|
|
71
|
-
update: vi.fn(() => {
|
|
72
|
-
state.currentOperation = 'update';
|
|
73
|
-
state.updateCalled = true;
|
|
74
|
-
state.insertThenSelect = false;
|
|
75
|
-
return mock;
|
|
76
|
-
}),
|
|
77
|
-
delete: vi.fn(() => {
|
|
78
|
-
state.currentOperation = 'delete';
|
|
79
|
-
state.insertThenSelect = false;
|
|
80
|
-
return mock;
|
|
81
|
-
}),
|
|
82
|
-
eq: vi.fn().mockReturnThis(),
|
|
83
|
-
neq: vi.fn().mockReturnThis(),
|
|
84
|
-
in: vi.fn().mockReturnThis(),
|
|
85
|
-
is: vi.fn().mockReturnThis(),
|
|
86
|
-
not: vi.fn().mockReturnThis(),
|
|
87
|
-
or: vi.fn().mockReturnThis(),
|
|
88
|
-
gt: vi.fn().mockReturnThis(),
|
|
89
|
-
gte: vi.fn().mockReturnThis(),
|
|
90
|
-
lte: vi.fn().mockReturnThis(),
|
|
91
|
-
lt: vi.fn().mockReturnThis(),
|
|
92
|
-
order: vi.fn().mockReturnThis(),
|
|
93
|
-
limit: vi.fn().mockReturnThis(),
|
|
94
|
-
single: vi.fn(() => {
|
|
95
|
-
if (state.currentOperation === 'insert' || state.insertThenSelect) {
|
|
96
|
-
return Promise.resolve(overrides.insertResult ?? defaultResult);
|
|
97
|
-
}
|
|
98
|
-
if (state.updateCalled) {
|
|
99
|
-
return Promise.resolve(overrides.updateResult ?? defaultResult);
|
|
100
|
-
}
|
|
101
|
-
if (state.currentOperation === 'select') {
|
|
102
|
-
return Promise.resolve(overrides.selectResult ?? defaultResult);
|
|
103
|
-
}
|
|
104
|
-
if (state.currentOperation === 'update') {
|
|
105
|
-
return Promise.resolve(overrides.updateResult ?? defaultResult);
|
|
106
|
-
}
|
|
107
|
-
return Promise.resolve(defaultResult);
|
|
108
|
-
}),
|
|
109
|
-
maybeSingle: vi.fn(() => {
|
|
110
|
-
return Promise.resolve(overrides.selectResult ?? defaultResult);
|
|
111
|
-
}),
|
|
112
|
-
then: vi.fn((resolve: (value: unknown) => void) => {
|
|
113
|
-
// Handle special table cases
|
|
114
|
-
if (state.currentTable === 'agent_sessions' && overrides.sessionsResult) {
|
|
115
|
-
return Promise.resolve(overrides.sessionsResult).then(resolve);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
if (state.currentOperation === 'insert' || state.insertThenSelect) {
|
|
119
|
-
return Promise.resolve(overrides.insertResult ?? defaultResult).then(resolve);
|
|
120
|
-
}
|
|
121
|
-
if (state.updateCalled) {
|
|
122
|
-
return Promise.resolve(overrides.updateResult ?? defaultResult).then(resolve);
|
|
123
|
-
}
|
|
124
|
-
if (state.currentOperation === 'select') {
|
|
125
|
-
return Promise.resolve(overrides.selectResult ?? defaultResult).then(resolve);
|
|
126
|
-
}
|
|
127
|
-
if (state.currentOperation === 'update') {
|
|
128
|
-
return Promise.resolve(overrides.updateResult ?? defaultResult).then(resolve);
|
|
129
|
-
}
|
|
130
|
-
if (state.currentOperation === 'delete') {
|
|
131
|
-
return Promise.resolve(overrides.deleteResult ?? defaultResult).then(resolve);
|
|
132
|
-
}
|
|
133
|
-
return Promise.resolve(defaultResult).then(resolve);
|
|
134
|
-
}),
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
return mock as unknown as SupabaseClient;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
11
|
// ============================================================================
|
|
141
12
|
// Mock Handler Context Factory
|
|
142
13
|
// ============================================================================
|
|
@@ -147,6 +18,7 @@ export interface MockContextOptions {
|
|
|
147
18
|
apiKeyId?: string;
|
|
148
19
|
instanceId?: string;
|
|
149
20
|
persona?: string;
|
|
21
|
+
tokenUsage?: TokenUsage;
|
|
150
22
|
}
|
|
151
23
|
|
|
152
24
|
/**
|
|
@@ -154,11 +26,10 @@ export interface MockContextOptions {
|
|
|
154
26
|
*
|
|
155
27
|
* @example
|
|
156
28
|
* ```ts
|
|
157
|
-
* const ctx = createMockContext(
|
|
29
|
+
* const ctx = createMockContext({ sessionId: 'test-session' });
|
|
158
30
|
* ```
|
|
159
31
|
*/
|
|
160
32
|
export function createMockContext(
|
|
161
|
-
supabase: SupabaseClient,
|
|
162
33
|
options: MockContextOptions = {}
|
|
163
34
|
): HandlerContext {
|
|
164
35
|
const defaultTokenUsage: TokenUsage = {
|
|
@@ -172,7 +43,6 @@ export function createMockContext(
|
|
|
172
43
|
const sessionId = 'sessionId' in options ? (options.sessionId ?? null) : 'session-123';
|
|
173
44
|
|
|
174
45
|
return {
|
|
175
|
-
supabase,
|
|
176
46
|
auth: {
|
|
177
47
|
userId: options.userId ?? 'user-123',
|
|
178
48
|
apiKeyId: options.apiKeyId ?? 'api-key-123',
|
|
@@ -182,7 +52,7 @@ export function createMockContext(
|
|
|
182
52
|
instanceId: options.instanceId ?? 'instance-abc',
|
|
183
53
|
currentSessionId: sessionId,
|
|
184
54
|
currentPersona: options.persona ?? 'Wave',
|
|
185
|
-
tokenUsage: defaultTokenUsage,
|
|
55
|
+
tokenUsage: options.tokenUsage ?? defaultTokenUsage,
|
|
186
56
|
},
|
|
187
57
|
updateSession: vi.fn(),
|
|
188
58
|
};
|
|
@@ -6,7 +6,8 @@ import {
|
|
|
6
6
|
deleteBlocker,
|
|
7
7
|
} from './blockers.js';
|
|
8
8
|
import { ValidationError } from '../validators.js';
|
|
9
|
-
import {
|
|
9
|
+
import { createMockContext } from './__test-utils__.js';
|
|
10
|
+
import { mockApiClient } from './__test-setup__.js';
|
|
10
11
|
|
|
11
12
|
// ============================================================================
|
|
12
13
|
// addBlocker Tests
|
|
@@ -16,8 +17,7 @@ describe('addBlocker', () => {
|
|
|
16
17
|
beforeEach(() => vi.clearAllMocks());
|
|
17
18
|
|
|
18
19
|
it('should throw error for missing project_id', async () => {
|
|
19
|
-
const
|
|
20
|
-
const ctx = createMockContext(supabase);
|
|
20
|
+
const ctx = createMockContext();
|
|
21
21
|
|
|
22
22
|
await expect(
|
|
23
23
|
addBlocker({ description: 'Some blocker' }, ctx)
|
|
@@ -25,8 +25,7 @@ describe('addBlocker', () => {
|
|
|
25
25
|
});
|
|
26
26
|
|
|
27
27
|
it('should throw error for invalid project_id UUID', async () => {
|
|
28
|
-
const
|
|
29
|
-
const ctx = createMockContext(supabase);
|
|
28
|
+
const ctx = createMockContext();
|
|
30
29
|
|
|
31
30
|
await expect(
|
|
32
31
|
addBlocker({ project_id: 'invalid', description: 'Some blocker' }, ctx)
|
|
@@ -34,8 +33,7 @@ describe('addBlocker', () => {
|
|
|
34
33
|
});
|
|
35
34
|
|
|
36
35
|
it('should throw error for missing description', async () => {
|
|
37
|
-
const
|
|
38
|
-
const ctx = createMockContext(supabase);
|
|
36
|
+
const ctx = createMockContext();
|
|
39
37
|
|
|
40
38
|
await expect(
|
|
41
39
|
addBlocker({ project_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx)
|
|
@@ -43,10 +41,11 @@ describe('addBlocker', () => {
|
|
|
43
41
|
});
|
|
44
42
|
|
|
45
43
|
it('should add blocker successfully', async () => {
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
mockApiClient.addBlocker.mockResolvedValue({
|
|
45
|
+
ok: true,
|
|
46
|
+
data: { success: true, blocker_id: 'blocker-1' },
|
|
48
47
|
});
|
|
49
|
-
const ctx = createMockContext(
|
|
48
|
+
const ctx = createMockContext();
|
|
50
49
|
|
|
51
50
|
const result = await addBlocker(
|
|
52
51
|
{
|
|
@@ -62,11 +61,12 @@ describe('addBlocker', () => {
|
|
|
62
61
|
});
|
|
63
62
|
});
|
|
64
63
|
|
|
65
|
-
it('should include session_id in
|
|
66
|
-
|
|
67
|
-
|
|
64
|
+
it('should include session_id in API call', async () => {
|
|
65
|
+
mockApiClient.addBlocker.mockResolvedValue({
|
|
66
|
+
ok: true,
|
|
67
|
+
data: { success: true, blocker_id: 'blocker-1' },
|
|
68
68
|
});
|
|
69
|
-
const ctx = createMockContext(
|
|
69
|
+
const ctx = createMockContext({ sessionId: 'my-session' });
|
|
70
70
|
|
|
71
71
|
await addBlocker(
|
|
72
72
|
{
|
|
@@ -76,26 +76,26 @@ describe('addBlocker', () => {
|
|
|
76
76
|
ctx
|
|
77
77
|
);
|
|
78
78
|
|
|
79
|
-
expect(
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
})
|
|
79
|
+
expect(mockApiClient.addBlocker).toHaveBeenCalledWith(
|
|
80
|
+
'123e4567-e89b-12d3-a456-426614174000',
|
|
81
|
+
'Blocked by dependency',
|
|
82
|
+
'my-session'
|
|
84
83
|
);
|
|
85
84
|
});
|
|
86
85
|
|
|
87
|
-
it('should throw error when
|
|
88
|
-
|
|
89
|
-
|
|
86
|
+
it('should throw error when API call fails', async () => {
|
|
87
|
+
mockApiClient.addBlocker.mockResolvedValue({
|
|
88
|
+
ok: false,
|
|
89
|
+
error: 'Insert failed',
|
|
90
90
|
});
|
|
91
|
-
const ctx = createMockContext(
|
|
91
|
+
const ctx = createMockContext();
|
|
92
92
|
|
|
93
93
|
await expect(
|
|
94
94
|
addBlocker({
|
|
95
95
|
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
96
96
|
description: 'Test blocker',
|
|
97
97
|
}, ctx)
|
|
98
|
-
).rejects.toThrow('
|
|
98
|
+
).rejects.toThrow('Insert failed');
|
|
99
99
|
});
|
|
100
100
|
});
|
|
101
101
|
|
|
@@ -107,15 +107,13 @@ describe('resolveBlocker', () => {
|
|
|
107
107
|
beforeEach(() => vi.clearAllMocks());
|
|
108
108
|
|
|
109
109
|
it('should throw error for missing blocker_id', async () => {
|
|
110
|
-
const
|
|
111
|
-
const ctx = createMockContext(supabase);
|
|
110
|
+
const ctx = createMockContext();
|
|
112
111
|
|
|
113
112
|
await expect(resolveBlocker({}, ctx)).rejects.toThrow(ValidationError);
|
|
114
113
|
});
|
|
115
114
|
|
|
116
115
|
it('should throw error for invalid blocker_id UUID', async () => {
|
|
117
|
-
const
|
|
118
|
-
const ctx = createMockContext(supabase);
|
|
116
|
+
const ctx = createMockContext();
|
|
119
117
|
|
|
120
118
|
await expect(
|
|
121
119
|
resolveBlocker({ blocker_id: 'invalid' }, ctx)
|
|
@@ -123,10 +121,11 @@ describe('resolveBlocker', () => {
|
|
|
123
121
|
});
|
|
124
122
|
|
|
125
123
|
it('should resolve blocker successfully', async () => {
|
|
126
|
-
|
|
127
|
-
|
|
124
|
+
mockApiClient.resolveBlocker.mockResolvedValue({
|
|
125
|
+
ok: true,
|
|
126
|
+
data: { success: true, blocker_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
128
127
|
});
|
|
129
|
-
const ctx = createMockContext(
|
|
128
|
+
const ctx = createMockContext();
|
|
130
129
|
|
|
131
130
|
const result = await resolveBlocker(
|
|
132
131
|
{ blocker_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
@@ -140,10 +139,11 @@ describe('resolveBlocker', () => {
|
|
|
140
139
|
});
|
|
141
140
|
|
|
142
141
|
it('should resolve blocker with resolution note', async () => {
|
|
143
|
-
|
|
144
|
-
|
|
142
|
+
mockApiClient.resolveBlocker.mockResolvedValue({
|
|
143
|
+
ok: true,
|
|
144
|
+
data: { success: true, blocker_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
145
145
|
});
|
|
146
|
-
const ctx = createMockContext(
|
|
146
|
+
const ctx = createMockContext();
|
|
147
147
|
|
|
148
148
|
await resolveBlocker(
|
|
149
149
|
{
|
|
@@ -153,48 +153,40 @@ describe('resolveBlocker', () => {
|
|
|
153
153
|
ctx
|
|
154
154
|
);
|
|
155
155
|
|
|
156
|
-
expect(
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
resolution_note: 'Fixed by upgrading dependency',
|
|
160
|
-
})
|
|
156
|
+
expect(mockApiClient.resolveBlocker).toHaveBeenCalledWith(
|
|
157
|
+
'123e4567-e89b-12d3-a456-426614174000',
|
|
158
|
+
'Fixed by upgrading dependency'
|
|
161
159
|
);
|
|
162
160
|
});
|
|
163
161
|
|
|
164
|
-
it('should
|
|
165
|
-
|
|
166
|
-
|
|
162
|
+
it('should call API client resolveBlocker', async () => {
|
|
163
|
+
mockApiClient.resolveBlocker.mockResolvedValue({
|
|
164
|
+
ok: true,
|
|
165
|
+
data: { success: true, blocker_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
167
166
|
});
|
|
168
|
-
const ctx = createMockContext(
|
|
167
|
+
const ctx = createMockContext();
|
|
169
168
|
|
|
170
169
|
await resolveBlocker(
|
|
171
170
|
{ blocker_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
172
171
|
ctx
|
|
173
172
|
);
|
|
174
173
|
|
|
175
|
-
expect(
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
})
|
|
174
|
+
expect(mockApiClient.resolveBlocker).toHaveBeenCalledWith(
|
|
175
|
+
'123e4567-e89b-12d3-a456-426614174000',
|
|
176
|
+
undefined
|
|
179
177
|
);
|
|
180
178
|
});
|
|
181
179
|
|
|
182
|
-
it('should throw error when
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
...supabase,
|
|
189
|
-
eq: vi.fn().mockReturnValue({
|
|
190
|
-
then: (resolve: (val: unknown) => void) =>
|
|
191
|
-
Promise.resolve({ data: null, error: { message: 'Update failed' } }).then(resolve),
|
|
192
|
-
}),
|
|
193
|
-
} as unknown as ReturnType<SupabaseClient['from']>);
|
|
180
|
+
it('should throw error when API call fails', async () => {
|
|
181
|
+
mockApiClient.resolveBlocker.mockResolvedValue({
|
|
182
|
+
ok: false,
|
|
183
|
+
error: 'Update failed',
|
|
184
|
+
});
|
|
185
|
+
const ctx = createMockContext();
|
|
194
186
|
|
|
195
187
|
await expect(
|
|
196
188
|
resolveBlocker({ blocker_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx)
|
|
197
|
-
).rejects.toThrow('
|
|
189
|
+
).rejects.toThrow('Update failed');
|
|
198
190
|
});
|
|
199
191
|
});
|
|
200
192
|
|
|
@@ -206,15 +198,13 @@ describe('getBlockers', () => {
|
|
|
206
198
|
beforeEach(() => vi.clearAllMocks());
|
|
207
199
|
|
|
208
200
|
it('should throw error for missing project_id', async () => {
|
|
209
|
-
const
|
|
210
|
-
const ctx = createMockContext(supabase);
|
|
201
|
+
const ctx = createMockContext();
|
|
211
202
|
|
|
212
203
|
await expect(getBlockers({}, ctx)).rejects.toThrow(ValidationError);
|
|
213
204
|
});
|
|
214
205
|
|
|
215
206
|
it('should throw error for invalid project_id UUID', async () => {
|
|
216
|
-
const
|
|
217
|
-
const ctx = createMockContext(supabase);
|
|
207
|
+
const ctx = createMockContext();
|
|
218
208
|
|
|
219
209
|
await expect(
|
|
220
210
|
getBlockers({ project_id: 'invalid' }, ctx)
|
|
@@ -222,10 +212,11 @@ describe('getBlockers', () => {
|
|
|
222
212
|
});
|
|
223
213
|
|
|
224
214
|
it('should return empty list when no blockers', async () => {
|
|
225
|
-
|
|
226
|
-
|
|
215
|
+
mockApiClient.getBlockers.mockResolvedValue({
|
|
216
|
+
ok: true,
|
|
217
|
+
data: { blockers: [] },
|
|
227
218
|
});
|
|
228
|
-
const ctx = createMockContext(
|
|
219
|
+
const ctx = createMockContext();
|
|
229
220
|
|
|
230
221
|
const result = await getBlockers(
|
|
231
222
|
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
@@ -242,11 +233,11 @@ describe('getBlockers', () => {
|
|
|
242
233
|
{ id: 'b1', description: 'Blocker 1', status: 'open', created_at: '2025-01-14T10:00:00Z' },
|
|
243
234
|
{ id: 'b2', description: 'Blocker 2', status: 'open', created_at: '2025-01-14T11:00:00Z' },
|
|
244
235
|
];
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
236
|
+
mockApiClient.getBlockers.mockResolvedValue({
|
|
237
|
+
ok: true,
|
|
238
|
+
data: { blockers: mockBlockers },
|
|
248
239
|
});
|
|
249
|
-
const ctx = createMockContext(
|
|
240
|
+
const ctx = createMockContext();
|
|
250
241
|
|
|
251
242
|
const result = await getBlockers(
|
|
252
243
|
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
@@ -256,62 +247,67 @@ describe('getBlockers', () => {
|
|
|
256
247
|
expect((result.result as { blockers: unknown[] }).blockers).toHaveLength(2);
|
|
257
248
|
});
|
|
258
249
|
|
|
259
|
-
it('should
|
|
260
|
-
|
|
261
|
-
|
|
250
|
+
it('should pass status parameter to API (default: open)', async () => {
|
|
251
|
+
mockApiClient.getBlockers.mockResolvedValue({
|
|
252
|
+
ok: true,
|
|
253
|
+
data: { blockers: [] },
|
|
262
254
|
});
|
|
263
|
-
const ctx = createMockContext(
|
|
255
|
+
const ctx = createMockContext();
|
|
264
256
|
|
|
265
257
|
await getBlockers(
|
|
266
258
|
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
267
259
|
ctx
|
|
268
260
|
);
|
|
269
261
|
|
|
270
|
-
expect(
|
|
262
|
+
expect(mockApiClient.getBlockers).toHaveBeenCalledWith(
|
|
263
|
+
'123e4567-e89b-12d3-a456-426614174000',
|
|
264
|
+
expect.objectContaining({ status: 'open' })
|
|
265
|
+
);
|
|
271
266
|
});
|
|
272
267
|
|
|
273
|
-
it('should
|
|
274
|
-
|
|
275
|
-
|
|
268
|
+
it('should pass custom status to API', async () => {
|
|
269
|
+
mockApiClient.getBlockers.mockResolvedValue({
|
|
270
|
+
ok: true,
|
|
271
|
+
data: { blockers: [] },
|
|
276
272
|
});
|
|
277
|
-
const ctx = createMockContext(
|
|
273
|
+
const ctx = createMockContext();
|
|
278
274
|
|
|
279
275
|
await getBlockers(
|
|
280
276
|
{ project_id: '123e4567-e89b-12d3-a456-426614174000', status: 'resolved' },
|
|
281
277
|
ctx
|
|
282
278
|
);
|
|
283
279
|
|
|
284
|
-
expect(
|
|
280
|
+
expect(mockApiClient.getBlockers).toHaveBeenCalledWith(
|
|
281
|
+
'123e4567-e89b-12d3-a456-426614174000',
|
|
282
|
+
expect.objectContaining({ status: 'resolved' })
|
|
283
|
+
);
|
|
285
284
|
});
|
|
286
285
|
|
|
287
|
-
it('should
|
|
288
|
-
|
|
289
|
-
|
|
286
|
+
it('should call API client getBlockers', async () => {
|
|
287
|
+
mockApiClient.getBlockers.mockResolvedValue({
|
|
288
|
+
ok: true,
|
|
289
|
+
data: { blockers: [] },
|
|
290
290
|
});
|
|
291
|
-
const ctx = createMockContext(
|
|
291
|
+
const ctx = createMockContext();
|
|
292
292
|
|
|
293
293
|
await getBlockers(
|
|
294
294
|
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
295
295
|
ctx
|
|
296
296
|
);
|
|
297
297
|
|
|
298
|
-
expect(
|
|
298
|
+
expect(mockApiClient.getBlockers).toHaveBeenCalled();
|
|
299
299
|
});
|
|
300
300
|
|
|
301
|
-
it('should throw error when
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
...supabase,
|
|
308
|
-
then: (resolve: (val: unknown) => void) =>
|
|
309
|
-
Promise.resolve({ data: null, error: { message: 'Query failed' } }).then(resolve),
|
|
310
|
-
} as unknown as ReturnType<SupabaseClient['from']>);
|
|
301
|
+
it('should throw error when API call fails', async () => {
|
|
302
|
+
mockApiClient.getBlockers.mockResolvedValue({
|
|
303
|
+
ok: false,
|
|
304
|
+
error: 'Query failed',
|
|
305
|
+
});
|
|
306
|
+
const ctx = createMockContext();
|
|
311
307
|
|
|
312
308
|
await expect(
|
|
313
309
|
getBlockers({ project_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx)
|
|
314
|
-
).rejects.toThrow('
|
|
310
|
+
).rejects.toThrow('Query failed');
|
|
315
311
|
});
|
|
316
312
|
});
|
|
317
313
|
|
|
@@ -323,15 +319,13 @@ describe('deleteBlocker', () => {
|
|
|
323
319
|
beforeEach(() => vi.clearAllMocks());
|
|
324
320
|
|
|
325
321
|
it('should throw error for missing blocker_id', async () => {
|
|
326
|
-
const
|
|
327
|
-
const ctx = createMockContext(supabase);
|
|
322
|
+
const ctx = createMockContext();
|
|
328
323
|
|
|
329
324
|
await expect(deleteBlocker({}, ctx)).rejects.toThrow(ValidationError);
|
|
330
325
|
});
|
|
331
326
|
|
|
332
327
|
it('should throw error for invalid blocker_id UUID', async () => {
|
|
333
|
-
const
|
|
334
|
-
const ctx = createMockContext(supabase);
|
|
328
|
+
const ctx = createMockContext();
|
|
335
329
|
|
|
336
330
|
await expect(
|
|
337
331
|
deleteBlocker({ blocker_id: 'invalid' }, ctx)
|
|
@@ -339,10 +333,11 @@ describe('deleteBlocker', () => {
|
|
|
339
333
|
});
|
|
340
334
|
|
|
341
335
|
it('should delete blocker successfully', async () => {
|
|
342
|
-
|
|
343
|
-
|
|
336
|
+
mockApiClient.deleteBlocker.mockResolvedValue({
|
|
337
|
+
ok: true,
|
|
338
|
+
data: { success: true },
|
|
344
339
|
});
|
|
345
|
-
const ctx = createMockContext(
|
|
340
|
+
const ctx = createMockContext();
|
|
346
341
|
|
|
347
342
|
const result = await deleteBlocker(
|
|
348
343
|
{ blocker_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
@@ -354,37 +349,32 @@ describe('deleteBlocker', () => {
|
|
|
354
349
|
});
|
|
355
350
|
});
|
|
356
351
|
|
|
357
|
-
it('should call
|
|
358
|
-
|
|
359
|
-
|
|
352
|
+
it('should call API client deleteBlocker', async () => {
|
|
353
|
+
mockApiClient.deleteBlocker.mockResolvedValue({
|
|
354
|
+
ok: true,
|
|
355
|
+
data: { success: true },
|
|
360
356
|
});
|
|
361
|
-
const ctx = createMockContext(
|
|
357
|
+
const ctx = createMockContext();
|
|
362
358
|
|
|
363
359
|
await deleteBlocker(
|
|
364
360
|
{ blocker_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
365
361
|
ctx
|
|
366
362
|
);
|
|
367
363
|
|
|
368
|
-
expect(
|
|
369
|
-
|
|
370
|
-
|
|
364
|
+
expect(mockApiClient.deleteBlocker).toHaveBeenCalledWith(
|
|
365
|
+
'123e4567-e89b-12d3-a456-426614174000'
|
|
366
|
+
);
|
|
371
367
|
});
|
|
372
368
|
|
|
373
|
-
it('should throw error when
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
...supabase,
|
|
380
|
-
eq: vi.fn().mockReturnValue({
|
|
381
|
-
then: (resolve: (val: unknown) => void) =>
|
|
382
|
-
Promise.resolve({ data: null, error: { message: 'Delete failed' } }).then(resolve),
|
|
383
|
-
}),
|
|
384
|
-
} as unknown as ReturnType<SupabaseClient['from']>);
|
|
369
|
+
it('should throw error when API call fails', async () => {
|
|
370
|
+
mockApiClient.deleteBlocker.mockResolvedValue({
|
|
371
|
+
ok: false,
|
|
372
|
+
error: 'Delete failed',
|
|
373
|
+
});
|
|
374
|
+
const ctx = createMockContext();
|
|
385
375
|
|
|
386
376
|
await expect(
|
|
387
377
|
deleteBlocker({ blocker_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx)
|
|
388
|
-
).rejects.toThrow('
|
|
378
|
+
).rejects.toThrow('Delete failed');
|
|
389
379
|
});
|
|
390
380
|
});
|