@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.
Files changed (173) hide show
  1. package/README.md +113 -98
  2. package/dist/api-client.d.ts +1169 -0
  3. package/dist/api-client.js +713 -0
  4. package/dist/cli.d.ts +1 -6
  5. package/dist/cli.js +39 -240
  6. package/dist/config/tool-categories.d.ts +31 -0
  7. package/dist/config/tool-categories.js +253 -0
  8. package/dist/handlers/blockers.js +57 -58
  9. package/dist/handlers/bodies-of-work.d.ts +2 -0
  10. package/dist/handlers/bodies-of-work.js +108 -477
  11. package/dist/handlers/cost.d.ts +1 -0
  12. package/dist/handlers/cost.js +35 -113
  13. package/dist/handlers/decisions.d.ts +2 -0
  14. package/dist/handlers/decisions.js +28 -27
  15. package/dist/handlers/deployment.js +113 -828
  16. package/dist/handlers/discovery.d.ts +3 -0
  17. package/dist/handlers/discovery.js +26 -627
  18. package/dist/handlers/fallback.d.ts +2 -0
  19. package/dist/handlers/fallback.js +56 -142
  20. package/dist/handlers/findings.d.ts +8 -1
  21. package/dist/handlers/findings.js +65 -68
  22. package/dist/handlers/git-issues.d.ts +9 -13
  23. package/dist/handlers/git-issues.js +80 -225
  24. package/dist/handlers/ideas.d.ts +3 -0
  25. package/dist/handlers/ideas.js +53 -134
  26. package/dist/handlers/index.d.ts +2 -0
  27. package/dist/handlers/index.js +6 -0
  28. package/dist/handlers/milestones.d.ts +2 -0
  29. package/dist/handlers/milestones.js +51 -98
  30. package/dist/handlers/organizations.js +79 -275
  31. package/dist/handlers/progress.d.ts +2 -0
  32. package/dist/handlers/progress.js +25 -123
  33. package/dist/handlers/project.js +42 -221
  34. package/dist/handlers/requests.d.ts +2 -0
  35. package/dist/handlers/requests.js +23 -83
  36. package/dist/handlers/session.js +119 -590
  37. package/dist/handlers/sprints.d.ts +32 -0
  38. package/dist/handlers/sprints.js +275 -0
  39. package/dist/handlers/tasks.d.ts +7 -10
  40. package/dist/handlers/tasks.js +245 -894
  41. package/dist/handlers/tool-docs.d.ts +9 -0
  42. package/dist/handlers/tool-docs.js +904 -0
  43. package/dist/handlers/types.d.ts +11 -3
  44. package/dist/handlers/validation.d.ts +1 -1
  45. package/dist/handlers/validation.js +38 -153
  46. package/dist/index.js +493 -162
  47. package/dist/knowledge.js +106 -9
  48. package/dist/tools.js +34 -4
  49. package/dist/validators.d.ts +21 -0
  50. package/dist/validators.js +91 -0
  51. package/package.json +2 -3
  52. package/src/api-client.ts +1822 -0
  53. package/src/cli.test.ts +128 -302
  54. package/src/cli.ts +41 -285
  55. package/src/handlers/__test-setup__.ts +215 -0
  56. package/src/handlers/__test-utils__.ts +4 -134
  57. package/src/handlers/blockers.test.ts +114 -124
  58. package/src/handlers/blockers.ts +68 -70
  59. package/src/handlers/bodies-of-work.test.ts +236 -831
  60. package/src/handlers/bodies-of-work.ts +210 -525
  61. package/src/handlers/cost.test.ts +149 -113
  62. package/src/handlers/cost.ts +44 -132
  63. package/src/handlers/decisions.test.ts +111 -209
  64. package/src/handlers/decisions.ts +35 -27
  65. package/src/handlers/deployment.test.ts +193 -239
  66. package/src/handlers/deployment.ts +143 -896
  67. package/src/handlers/discovery.test.ts +20 -67
  68. package/src/handlers/discovery.ts +29 -714
  69. package/src/handlers/fallback.test.ts +206 -361
  70. package/src/handlers/fallback.ts +81 -156
  71. package/src/handlers/findings.test.ts +229 -320
  72. package/src/handlers/findings.ts +76 -64
  73. package/src/handlers/git-issues.test.ts +623 -0
  74. package/src/handlers/git-issues.ts +174 -0
  75. package/src/handlers/ideas.test.ts +229 -343
  76. package/src/handlers/ideas.ts +69 -143
  77. package/src/handlers/index.ts +6 -0
  78. package/src/handlers/milestones.test.ts +167 -281
  79. package/src/handlers/milestones.ts +54 -93
  80. package/src/handlers/organizations.test.ts +275 -467
  81. package/src/handlers/organizations.ts +84 -294
  82. package/src/handlers/progress.test.ts +112 -218
  83. package/src/handlers/progress.ts +29 -142
  84. package/src/handlers/project.test.ts +203 -226
  85. package/src/handlers/project.ts +48 -238
  86. package/src/handlers/requests.test.ts +74 -342
  87. package/src/handlers/requests.ts +25 -83
  88. package/src/handlers/session.test.ts +276 -206
  89. package/src/handlers/session.ts +136 -662
  90. package/src/handlers/sprints.test.ts +711 -0
  91. package/src/handlers/sprints.ts +510 -0
  92. package/src/handlers/tasks.test.ts +669 -353
  93. package/src/handlers/tasks.ts +263 -1015
  94. package/src/handlers/tool-docs.ts +1024 -0
  95. package/src/handlers/types.ts +12 -4
  96. package/src/handlers/validation.test.ts +237 -568
  97. package/src/handlers/validation.ts +43 -167
  98. package/src/index.ts +493 -186
  99. package/src/tools.ts +2532 -0
  100. package/src/validators.test.ts +223 -223
  101. package/src/validators.ts +127 -0
  102. package/tsconfig.json +1 -1
  103. package/vitest.config.ts +14 -13
  104. package/dist/cli.test.d.ts +0 -1
  105. package/dist/cli.test.js +0 -367
  106. package/dist/handlers/__test-utils__.d.ts +0 -72
  107. package/dist/handlers/__test-utils__.js +0 -176
  108. package/dist/handlers/checkouts.d.ts +0 -37
  109. package/dist/handlers/checkouts.js +0 -377
  110. package/dist/handlers/knowledge-query.d.ts +0 -22
  111. package/dist/handlers/knowledge-query.js +0 -253
  112. package/dist/handlers/knowledge.d.ts +0 -12
  113. package/dist/handlers/knowledge.js +0 -108
  114. package/dist/handlers/roles.d.ts +0 -30
  115. package/dist/handlers/roles.js +0 -281
  116. package/dist/handlers/tasks.test.d.ts +0 -1
  117. package/dist/handlers/tasks.test.js +0 -431
  118. package/dist/utils.test.d.ts +0 -1
  119. package/dist/utils.test.js +0 -532
  120. package/dist/validators.test.d.ts +0 -1
  121. package/dist/validators.test.js +0 -176
  122. package/src/knowledge.ts +0 -132
  123. package/src/tmpclaude-0078-cwd +0 -1
  124. package/src/tmpclaude-0ee1-cwd +0 -1
  125. package/src/tmpclaude-2dd5-cwd +0 -1
  126. package/src/tmpclaude-344c-cwd +0 -1
  127. package/src/tmpclaude-3860-cwd +0 -1
  128. package/src/tmpclaude-4b63-cwd +0 -1
  129. package/src/tmpclaude-5c73-cwd +0 -1
  130. package/src/tmpclaude-5ee3-cwd +0 -1
  131. package/src/tmpclaude-6795-cwd +0 -1
  132. package/src/tmpclaude-709e-cwd +0 -1
  133. package/src/tmpclaude-9839-cwd +0 -1
  134. package/src/tmpclaude-d829-cwd +0 -1
  135. package/src/tmpclaude-e072-cwd +0 -1
  136. package/src/tmpclaude-f6ee-cwd +0 -1
  137. package/tmpclaude-0439-cwd +0 -1
  138. package/tmpclaude-132f-cwd +0 -1
  139. package/tmpclaude-15bb-cwd +0 -1
  140. package/tmpclaude-165a-cwd +0 -1
  141. package/tmpclaude-1ba9-cwd +0 -1
  142. package/tmpclaude-21a3-cwd +0 -1
  143. package/tmpclaude-2a38-cwd +0 -1
  144. package/tmpclaude-2adf-cwd +0 -1
  145. package/tmpclaude-2f56-cwd +0 -1
  146. package/tmpclaude-3626-cwd +0 -1
  147. package/tmpclaude-3727-cwd +0 -1
  148. package/tmpclaude-40bc-cwd +0 -1
  149. package/tmpclaude-436f-cwd +0 -1
  150. package/tmpclaude-4783-cwd +0 -1
  151. package/tmpclaude-4b6d-cwd +0 -1
  152. package/tmpclaude-4ba4-cwd +0 -1
  153. package/tmpclaude-51e6-cwd +0 -1
  154. package/tmpclaude-5ecf-cwd +0 -1
  155. package/tmpclaude-6f97-cwd +0 -1
  156. package/tmpclaude-7fb2-cwd +0 -1
  157. package/tmpclaude-825c-cwd +0 -1
  158. package/tmpclaude-8baf-cwd +0 -1
  159. package/tmpclaude-8d9f-cwd +0 -1
  160. package/tmpclaude-975c-cwd +0 -1
  161. package/tmpclaude-9983-cwd +0 -1
  162. package/tmpclaude-a045-cwd +0 -1
  163. package/tmpclaude-ac4a-cwd +0 -1
  164. package/tmpclaude-b593-cwd +0 -1
  165. package/tmpclaude-b891-cwd +0 -1
  166. package/tmpclaude-c032-cwd +0 -1
  167. package/tmpclaude-cf43-cwd +0 -1
  168. package/tmpclaude-d040-cwd +0 -1
  169. package/tmpclaude-dcdd-cwd +0 -1
  170. package/tmpclaude-dcee-cwd +0 -1
  171. package/tmpclaude-e16b-cwd +0 -1
  172. package/tmpclaude-ecd2-cwd +0 -1
  173. 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
- * This eliminates ~85 lines of duplicate code per test file.
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(supabase, { sessionId: 'test-session' });
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 { createMockSupabase, createMockContext } from './__test-utils__.js';
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 supabase = createMockSupabase();
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 supabase = createMockSupabase();
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 supabase = createMockSupabase();
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
- const supabase = createMockSupabase({
47
- insertResult: { data: { id: 'blocker-1' }, error: null },
44
+ mockApiClient.addBlocker.mockResolvedValue({
45
+ ok: true,
46
+ data: { success: true, blocker_id: 'blocker-1' },
48
47
  });
49
- const ctx = createMockContext(supabase);
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 insert', async () => {
66
- const supabase = createMockSupabase({
67
- insertResult: { data: { id: 'blocker-1' }, error: null },
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(supabase, { sessionId: 'my-session' });
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(supabase.insert).toHaveBeenCalledWith(
80
- expect.objectContaining({
81
- created_by: 'agent',
82
- created_by_session_id: 'my-session',
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 insert fails', async () => {
88
- const supabase = createMockSupabase({
89
- insertResult: { data: null, error: { message: 'Insert failed' } },
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(supabase);
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('Failed to add blocker: Insert failed');
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 supabase = createMockSupabase();
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 supabase = createMockSupabase();
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
- const supabase = createMockSupabase({
127
- updateResult: { data: null, error: null },
124
+ mockApiClient.resolveBlocker.mockResolvedValue({
125
+ ok: true,
126
+ data: { success: true, blocker_id: '123e4567-e89b-12d3-a456-426614174000' },
128
127
  });
129
- const ctx = createMockContext(supabase);
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
- const supabase = createMockSupabase({
144
- updateResult: { data: null, error: null },
142
+ mockApiClient.resolveBlocker.mockResolvedValue({
143
+ ok: true,
144
+ data: { success: true, blocker_id: '123e4567-e89b-12d3-a456-426614174000' },
145
145
  });
146
- const ctx = createMockContext(supabase);
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(supabase.update).toHaveBeenCalledWith(
157
- expect.objectContaining({
158
- status: 'resolved',
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 set resolved_at timestamp', async () => {
165
- const supabase = createMockSupabase({
166
- updateResult: { data: null, error: null },
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(supabase);
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(supabase.update).toHaveBeenCalledWith(
176
- expect.objectContaining({
177
- resolved_at: expect.any(String),
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 update fails', async () => {
183
- const supabase = createMockSupabase();
184
- const ctx = createMockContext(supabase);
185
-
186
- // Override update to return error
187
- vi.mocked(supabase.from('').update).mockReturnValue({
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('Failed to resolve blocker');
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 supabase = createMockSupabase();
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 supabase = createMockSupabase();
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
- const supabase = createMockSupabase({
226
- selectResult: { data: [], error: null },
215
+ mockApiClient.getBlockers.mockResolvedValue({
216
+ ok: true,
217
+ data: { blockers: [] },
227
218
  });
228
- const ctx = createMockContext(supabase);
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
- const supabase = createMockSupabase({
247
- selectResult: { data: mockBlockers, error: null },
236
+ mockApiClient.getBlockers.mockResolvedValue({
237
+ ok: true,
238
+ data: { blockers: mockBlockers },
248
239
  });
249
- const ctx = createMockContext(supabase);
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 filter by status (default: open)', async () => {
260
- const supabase = createMockSupabase({
261
- selectResult: { data: [], error: null },
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(supabase);
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(supabase.eq).toHaveBeenCalledWith('status', 'open');
262
+ expect(mockApiClient.getBlockers).toHaveBeenCalledWith(
263
+ '123e4567-e89b-12d3-a456-426614174000',
264
+ expect.objectContaining({ status: 'open' })
265
+ );
271
266
  });
272
267
 
273
- it('should filter by custom status', async () => {
274
- const supabase = createMockSupabase({
275
- selectResult: { data: [], error: null },
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(supabase);
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(supabase.eq).toHaveBeenCalledWith('status', 'resolved');
280
+ expect(mockApiClient.getBlockers).toHaveBeenCalledWith(
281
+ '123e4567-e89b-12d3-a456-426614174000',
282
+ expect.objectContaining({ status: 'resolved' })
283
+ );
285
284
  });
286
285
 
287
- it('should query blockers table', async () => {
288
- const supabase = createMockSupabase({
289
- selectResult: { data: [], error: null },
286
+ it('should call API client getBlockers', async () => {
287
+ mockApiClient.getBlockers.mockResolvedValue({
288
+ ok: true,
289
+ data: { blockers: [] },
290
290
  });
291
- const ctx = createMockContext(supabase);
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(supabase.from).toHaveBeenCalledWith('blockers');
298
+ expect(mockApiClient.getBlockers).toHaveBeenCalled();
299
299
  });
300
300
 
301
- it('should throw error when query fails', async () => {
302
- const supabase = createMockSupabase();
303
- const ctx = createMockContext(supabase);
304
-
305
- // Override to return error
306
- vi.mocked(supabase.from('').select).mockReturnValue({
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('Failed to fetch blockers');
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 supabase = createMockSupabase();
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 supabase = createMockSupabase();
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
- const supabase = createMockSupabase({
343
- deleteResult: { data: null, error: null },
336
+ mockApiClient.deleteBlocker.mockResolvedValue({
337
+ ok: true,
338
+ data: { success: true },
344
339
  });
345
- const ctx = createMockContext(supabase);
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 delete on blockers table', async () => {
358
- const supabase = createMockSupabase({
359
- deleteResult: { data: null, error: null },
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(supabase);
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(supabase.from).toHaveBeenCalledWith('blockers');
369
- expect(supabase.delete).toHaveBeenCalled();
370
- expect(supabase.eq).toHaveBeenCalledWith('id', '123e4567-e89b-12d3-a456-426614174000');
364
+ expect(mockApiClient.deleteBlocker).toHaveBeenCalledWith(
365
+ '123e4567-e89b-12d3-a456-426614174000'
366
+ );
371
367
  });
372
368
 
373
- it('should throw error when delete fails', async () => {
374
- const supabase = createMockSupabase();
375
- const ctx = createMockContext(supabase);
376
-
377
- // Override delete to return error
378
- vi.mocked(supabase.from('').delete).mockReturnValue({
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('Failed to delete blocker');
378
+ ).rejects.toThrow('Delete failed');
389
379
  });
390
380
  });