@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
package/dist/cli.test.js
DELETED
|
@@ -1,367 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import { createHash } from 'crypto';
|
|
3
|
-
// Mock child_process before importing cli module
|
|
4
|
-
vi.mock('child_process', () => ({
|
|
5
|
-
execSync: vi.fn(),
|
|
6
|
-
}));
|
|
7
|
-
// Mock @supabase/supabase-js
|
|
8
|
-
vi.mock('@supabase/supabase-js', () => ({
|
|
9
|
-
createClient: vi.fn(),
|
|
10
|
-
}));
|
|
11
|
-
import { execSync } from 'child_process';
|
|
12
|
-
import { hashApiKey, detectGitUrl, validateApiKey, } from './cli.js';
|
|
13
|
-
describe('CLI verification logic', () => {
|
|
14
|
-
beforeEach(() => {
|
|
15
|
-
vi.clearAllMocks();
|
|
16
|
-
});
|
|
17
|
-
describe('hashApiKey', () => {
|
|
18
|
-
it('should return SHA256 hash of the key', () => {
|
|
19
|
-
const key = 'test-api-key';
|
|
20
|
-
const expected = createHash('sha256').update(key).digest('hex');
|
|
21
|
-
expect(hashApiKey(key)).toBe(expected);
|
|
22
|
-
});
|
|
23
|
-
it('should return different hashes for different keys', () => {
|
|
24
|
-
const hash1 = hashApiKey('key1');
|
|
25
|
-
const hash2 = hashApiKey('key2');
|
|
26
|
-
expect(hash1).not.toBe(hash2);
|
|
27
|
-
});
|
|
28
|
-
it('should return same hash for same key', () => {
|
|
29
|
-
const hash1 = hashApiKey('same-key');
|
|
30
|
-
const hash2 = hashApiKey('same-key');
|
|
31
|
-
expect(hash1).toBe(hash2);
|
|
32
|
-
});
|
|
33
|
-
it('should return 64-character hex string', () => {
|
|
34
|
-
const hash = hashApiKey('any-key');
|
|
35
|
-
expect(hash).toMatch(/^[a-f0-9]{64}$/);
|
|
36
|
-
});
|
|
37
|
-
});
|
|
38
|
-
describe('detectGitUrl', () => {
|
|
39
|
-
it('should return normalized git URL when git command succeeds', () => {
|
|
40
|
-
vi.mocked(execSync).mockReturnValue('git@github.com:user/repo.git\n');
|
|
41
|
-
const result = detectGitUrl();
|
|
42
|
-
expect(result).toBe('https://github.com/user/repo');
|
|
43
|
-
});
|
|
44
|
-
it('should return null when git command fails', () => {
|
|
45
|
-
vi.mocked(execSync).mockImplementation(() => {
|
|
46
|
-
throw new Error('Not a git repository');
|
|
47
|
-
});
|
|
48
|
-
const result = detectGitUrl();
|
|
49
|
-
expect(result).toBeNull();
|
|
50
|
-
});
|
|
51
|
-
it('should handle HTTPS URLs', () => {
|
|
52
|
-
vi.mocked(execSync).mockReturnValue('https://github.com/user/repo.git\n');
|
|
53
|
-
const result = detectGitUrl();
|
|
54
|
-
expect(result).toBe('https://github.com/user/repo');
|
|
55
|
-
});
|
|
56
|
-
it('should call execSync with correct options', () => {
|
|
57
|
-
vi.mocked(execSync).mockReturnValue('https://github.com/user/repo.git');
|
|
58
|
-
detectGitUrl();
|
|
59
|
-
expect(execSync).toHaveBeenCalledWith('git config --get remote.origin.url', {
|
|
60
|
-
encoding: 'utf8',
|
|
61
|
-
timeout: 5000,
|
|
62
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
63
|
-
});
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
|
-
describe('validateApiKey', () => {
|
|
67
|
-
it('should return auth context for valid API key', async () => {
|
|
68
|
-
const mockSupabase = {
|
|
69
|
-
from: vi.fn().mockReturnThis(),
|
|
70
|
-
select: vi.fn().mockReturnThis(),
|
|
71
|
-
eq: vi.fn().mockReturnThis(),
|
|
72
|
-
single: vi.fn().mockResolvedValue({
|
|
73
|
-
data: { id: 'key-id-123', user_id: 'user-id-456' },
|
|
74
|
-
error: null,
|
|
75
|
-
}),
|
|
76
|
-
};
|
|
77
|
-
const result = await validateApiKey(mockSupabase, 'test-api-key');
|
|
78
|
-
expect(result).toEqual({
|
|
79
|
-
userId: 'user-id-456',
|
|
80
|
-
apiKeyId: 'key-id-123',
|
|
81
|
-
});
|
|
82
|
-
});
|
|
83
|
-
it('should return null for invalid API key', async () => {
|
|
84
|
-
const mockSupabase = {
|
|
85
|
-
from: vi.fn().mockReturnThis(),
|
|
86
|
-
select: vi.fn().mockReturnThis(),
|
|
87
|
-
eq: vi.fn().mockReturnThis(),
|
|
88
|
-
single: vi.fn().mockResolvedValue({
|
|
89
|
-
data: null,
|
|
90
|
-
error: { message: 'Not found' },
|
|
91
|
-
}),
|
|
92
|
-
};
|
|
93
|
-
const result = await validateApiKey(mockSupabase, 'invalid-key');
|
|
94
|
-
expect(result).toBeNull();
|
|
95
|
-
});
|
|
96
|
-
it('should query api_keys table with hashed key', async () => {
|
|
97
|
-
const mockFrom = vi.fn().mockReturnThis();
|
|
98
|
-
const mockSelect = vi.fn().mockReturnThis();
|
|
99
|
-
const mockEq = vi.fn().mockReturnThis();
|
|
100
|
-
const mockSingle = vi.fn().mockResolvedValue({ data: null, error: { message: 'Not found' } });
|
|
101
|
-
const mockSupabase = {
|
|
102
|
-
from: mockFrom,
|
|
103
|
-
select: mockSelect,
|
|
104
|
-
eq: mockEq,
|
|
105
|
-
single: mockSingle,
|
|
106
|
-
};
|
|
107
|
-
await validateApiKey(mockSupabase, 'test-key');
|
|
108
|
-
expect(mockFrom).toHaveBeenCalledWith('api_keys');
|
|
109
|
-
expect(mockSelect).toHaveBeenCalledWith('id, user_id');
|
|
110
|
-
expect(mockEq).toHaveBeenCalledWith('key_hash', hashApiKey('test-key'));
|
|
111
|
-
});
|
|
112
|
-
it('should return null when data is missing', async () => {
|
|
113
|
-
const mockSupabase = {
|
|
114
|
-
from: vi.fn().mockReturnThis(),
|
|
115
|
-
select: vi.fn().mockReturnThis(),
|
|
116
|
-
eq: vi.fn().mockReturnThis(),
|
|
117
|
-
single: vi.fn().mockResolvedValue({
|
|
118
|
-
data: undefined,
|
|
119
|
-
error: null,
|
|
120
|
-
}),
|
|
121
|
-
};
|
|
122
|
-
const result = await validateApiKey(mockSupabase, 'test-key');
|
|
123
|
-
expect(result).toBeNull();
|
|
124
|
-
});
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
describe('verify function integration', () => {
|
|
128
|
-
const originalEnv = process.env;
|
|
129
|
-
beforeEach(() => {
|
|
130
|
-
vi.clearAllMocks();
|
|
131
|
-
// Reset process.env for each test
|
|
132
|
-
process.env = { ...originalEnv };
|
|
133
|
-
});
|
|
134
|
-
afterEach(() => {
|
|
135
|
-
process.env = originalEnv;
|
|
136
|
-
});
|
|
137
|
-
// Note: The verify function reads environment variables at module load time,
|
|
138
|
-
// so we need to test through dynamic imports or by mocking at a higher level.
|
|
139
|
-
// For now, we test the exported functions individually.
|
|
140
|
-
describe('environment variable validation', () => {
|
|
141
|
-
it('hashApiKey should work without env vars', () => {
|
|
142
|
-
// hashApiKey is a pure function that doesn't depend on env vars
|
|
143
|
-
expect(hashApiKey('test')).toBeTruthy();
|
|
144
|
-
});
|
|
145
|
-
it('detectGitUrl should work without env vars', () => {
|
|
146
|
-
vi.mocked(execSync).mockReturnValue('https://github.com/user/repo.git');
|
|
147
|
-
expect(detectGitUrl()).toBe('https://github.com/user/repo');
|
|
148
|
-
});
|
|
149
|
-
});
|
|
150
|
-
describe('validateApiKey error handling', () => {
|
|
151
|
-
it('should handle database errors gracefully', async () => {
|
|
152
|
-
const mockSupabase = {
|
|
153
|
-
from: vi.fn().mockReturnThis(),
|
|
154
|
-
select: vi.fn().mockReturnThis(),
|
|
155
|
-
eq: vi.fn().mockReturnThis(),
|
|
156
|
-
single: vi.fn().mockRejectedValue(new Error('Database connection failed')),
|
|
157
|
-
};
|
|
158
|
-
await expect(validateApiKey(mockSupabase, 'test-key')).rejects.toThrow('Database connection failed');
|
|
159
|
-
});
|
|
160
|
-
it('should handle empty string API key', async () => {
|
|
161
|
-
const mockSupabase = {
|
|
162
|
-
from: vi.fn().mockReturnThis(),
|
|
163
|
-
select: vi.fn().mockReturnThis(),
|
|
164
|
-
eq: vi.fn().mockReturnThis(),
|
|
165
|
-
single: vi.fn().mockResolvedValue({ data: null, error: null }),
|
|
166
|
-
};
|
|
167
|
-
const result = await validateApiKey(mockSupabase, '');
|
|
168
|
-
expect(result).toBeNull();
|
|
169
|
-
});
|
|
170
|
-
});
|
|
171
|
-
});
|
|
172
|
-
describe('VerificationResult types', () => {
|
|
173
|
-
it('should have correct status types', () => {
|
|
174
|
-
const compliant = {
|
|
175
|
-
status: 'compliant',
|
|
176
|
-
reason: 'All good',
|
|
177
|
-
};
|
|
178
|
-
expect(compliant.status).toBe('compliant');
|
|
179
|
-
const nonCompliant = {
|
|
180
|
-
status: 'non_compliant',
|
|
181
|
-
reason: 'Tasks in progress',
|
|
182
|
-
continuation_prompt: 'Complete your tasks',
|
|
183
|
-
};
|
|
184
|
-
expect(nonCompliant.status).toBe('non_compliant');
|
|
185
|
-
const error = {
|
|
186
|
-
status: 'error',
|
|
187
|
-
reason: 'Missing env vars',
|
|
188
|
-
};
|
|
189
|
-
expect(error.status).toBe('error');
|
|
190
|
-
const noSession = {
|
|
191
|
-
status: 'no_session',
|
|
192
|
-
reason: 'No git URL detected',
|
|
193
|
-
};
|
|
194
|
-
expect(noSession.status).toBe('no_session');
|
|
195
|
-
});
|
|
196
|
-
it('should support optional details', () => {
|
|
197
|
-
const result = {
|
|
198
|
-
status: 'compliant',
|
|
199
|
-
reason: 'All good',
|
|
200
|
-
details: {
|
|
201
|
-
session_started: true,
|
|
202
|
-
project_id: 'proj-123',
|
|
203
|
-
project_name: 'Test Project',
|
|
204
|
-
git_url: 'https://github.com/user/repo',
|
|
205
|
-
in_progress_tasks: 0,
|
|
206
|
-
tasks_completed_this_session: 5,
|
|
207
|
-
progress_logs_this_session: 3,
|
|
208
|
-
blockers_logged_this_session: 1,
|
|
209
|
-
session_duration_minutes: 45,
|
|
210
|
-
},
|
|
211
|
-
};
|
|
212
|
-
expect(result.details?.session_started).toBe(true);
|
|
213
|
-
expect(result.details?.tasks_completed_this_session).toBe(5);
|
|
214
|
-
});
|
|
215
|
-
});
|
|
216
|
-
describe('AuthContext type', () => {
|
|
217
|
-
it('should have required fields', () => {
|
|
218
|
-
const auth = {
|
|
219
|
-
userId: 'user-123',
|
|
220
|
-
apiKeyId: 'key-456',
|
|
221
|
-
};
|
|
222
|
-
expect(auth.userId).toBe('user-123');
|
|
223
|
-
expect(auth.apiKeyId).toBe('key-456');
|
|
224
|
-
});
|
|
225
|
-
});
|
|
226
|
-
describe('verify function scenarios', () => {
|
|
227
|
-
const originalEnv = { ...process.env };
|
|
228
|
-
beforeEach(() => {
|
|
229
|
-
vi.clearAllMocks();
|
|
230
|
-
// Set up required env vars
|
|
231
|
-
process.env.SUPABASE_URL = 'https://test.supabase.co';
|
|
232
|
-
process.env.SUPABASE_SERVICE_KEY = 'test-service-key';
|
|
233
|
-
process.env.VIBESCOPE_API_KEY = 'test-api-key';
|
|
234
|
-
});
|
|
235
|
-
afterEach(() => {
|
|
236
|
-
process.env = { ...originalEnv };
|
|
237
|
-
});
|
|
238
|
-
// Helper to create a mock Supabase client with chainable methods
|
|
239
|
-
function createMockSupabase(overrides = {}) {
|
|
240
|
-
const defaultApiKey = { data: { id: 'key-id', user_id: 'user-id' }, error: null };
|
|
241
|
-
const defaultProject = {
|
|
242
|
-
data: { id: 'proj-id', name: 'Test Project', git_url: 'https://github.com/test/repo' },
|
|
243
|
-
error: null,
|
|
244
|
-
};
|
|
245
|
-
const defaultSession = {
|
|
246
|
-
data: {
|
|
247
|
-
id: 'session-id',
|
|
248
|
-
created_at: new Date(Date.now() - 10 * 60 * 1000).toISOString(), // 10 mins ago
|
|
249
|
-
last_synced_at: new Date().toISOString(),
|
|
250
|
-
},
|
|
251
|
-
error: null,
|
|
252
|
-
};
|
|
253
|
-
const defaultTasks = { data: [], error: null };
|
|
254
|
-
const defaultCompleted = { count: 1, error: null };
|
|
255
|
-
const defaultProgress = { count: 0, error: null };
|
|
256
|
-
const defaultBlocker = { count: 0, error: null };
|
|
257
|
-
let currentTable = '';
|
|
258
|
-
let selectCount = false;
|
|
259
|
-
const mock = {
|
|
260
|
-
from: vi.fn((table) => {
|
|
261
|
-
currentTable = table;
|
|
262
|
-
return mock;
|
|
263
|
-
}),
|
|
264
|
-
select: vi.fn((cols, opts) => {
|
|
265
|
-
selectCount = !!opts?.count;
|
|
266
|
-
return mock;
|
|
267
|
-
}),
|
|
268
|
-
eq: vi.fn().mockReturnThis(),
|
|
269
|
-
gte: vi.fn().mockReturnThis(),
|
|
270
|
-
single: vi.fn(() => {
|
|
271
|
-
if (currentTable === 'api_keys') {
|
|
272
|
-
return Promise.resolve(overrides.apiKeyResult ?? defaultApiKey);
|
|
273
|
-
}
|
|
274
|
-
if (currentTable === 'projects') {
|
|
275
|
-
return Promise.resolve(overrides.projectResult ?? defaultProject);
|
|
276
|
-
}
|
|
277
|
-
if (currentTable === 'agent_sessions') {
|
|
278
|
-
return Promise.resolve(overrides.sessionResult ?? defaultSession);
|
|
279
|
-
}
|
|
280
|
-
return Promise.resolve({ data: null, error: null });
|
|
281
|
-
}),
|
|
282
|
-
then: vi.fn((resolve) => {
|
|
283
|
-
if (currentTable === 'tasks') {
|
|
284
|
-
if (selectCount) {
|
|
285
|
-
return Promise.resolve(overrides.completedResult ?? defaultCompleted).then(resolve);
|
|
286
|
-
}
|
|
287
|
-
return Promise.resolve(overrides.tasksResult ?? defaultTasks).then(resolve);
|
|
288
|
-
}
|
|
289
|
-
if (currentTable === 'progress_logs') {
|
|
290
|
-
return Promise.resolve(overrides.progressResult ?? defaultProgress).then(resolve);
|
|
291
|
-
}
|
|
292
|
-
if (currentTable === 'blockers') {
|
|
293
|
-
return Promise.resolve(overrides.blockerResult ?? defaultBlocker).then(resolve);
|
|
294
|
-
}
|
|
295
|
-
return Promise.resolve({ data: null, count: 0 }).then(resolve);
|
|
296
|
-
}),
|
|
297
|
-
};
|
|
298
|
-
return mock;
|
|
299
|
-
}
|
|
300
|
-
it('should validate API key and return auth context', async () => {
|
|
301
|
-
const mockSupabase = createMockSupabase();
|
|
302
|
-
const result = await validateApiKey(mockSupabase, 'test-key');
|
|
303
|
-
expect(result).toEqual({
|
|
304
|
-
userId: 'user-id',
|
|
305
|
-
apiKeyId: 'key-id',
|
|
306
|
-
});
|
|
307
|
-
});
|
|
308
|
-
it('should return null for invalid API key', async () => {
|
|
309
|
-
const mockSupabase = createMockSupabase({
|
|
310
|
-
apiKeyResult: { data: null, error: { message: 'Not found' } },
|
|
311
|
-
});
|
|
312
|
-
const result = await validateApiKey(mockSupabase, 'invalid-key');
|
|
313
|
-
expect(result).toBeNull();
|
|
314
|
-
});
|
|
315
|
-
it('should handle database query errors', async () => {
|
|
316
|
-
const mockSupabase = {
|
|
317
|
-
from: vi.fn().mockReturnThis(),
|
|
318
|
-
select: vi.fn().mockReturnThis(),
|
|
319
|
-
eq: vi.fn().mockReturnThis(),
|
|
320
|
-
single: vi.fn().mockRejectedValue(new Error('Connection timeout')),
|
|
321
|
-
};
|
|
322
|
-
await expect(validateApiKey(mockSupabase, 'key')).rejects.toThrow('Connection timeout');
|
|
323
|
-
});
|
|
324
|
-
});
|
|
325
|
-
describe('detectGitUrl edge cases', () => {
|
|
326
|
-
beforeEach(() => {
|
|
327
|
-
vi.clearAllMocks();
|
|
328
|
-
});
|
|
329
|
-
it('should trim whitespace from git URL', () => {
|
|
330
|
-
vi.mocked(execSync).mockReturnValue(' https://github.com/user/repo.git \n');
|
|
331
|
-
const result = detectGitUrl();
|
|
332
|
-
expect(result).toBe('https://github.com/user/repo');
|
|
333
|
-
});
|
|
334
|
-
it('should handle GitLab SSH URLs', () => {
|
|
335
|
-
vi.mocked(execSync).mockReturnValue('git@gitlab.com:user/repo.git');
|
|
336
|
-
const result = detectGitUrl();
|
|
337
|
-
expect(result).toBe('https://gitlab.com/user/repo');
|
|
338
|
-
});
|
|
339
|
-
it('should handle Azure DevOps SSH URLs (not normalized)', () => {
|
|
340
|
-
// Azure DevOps URLs are not normalized by the current implementation
|
|
341
|
-
// The URL is trimmed but SSH format is preserved
|
|
342
|
-
vi.mocked(execSync).mockReturnValue('git@ssh.dev.azure.com:v3/org/project/repo');
|
|
343
|
-
const result = detectGitUrl();
|
|
344
|
-
expect(result).toBe('git@ssh.dev.azure.com:v3/org/project/repo');
|
|
345
|
-
});
|
|
346
|
-
it('should handle Bitbucket SSH URLs', () => {
|
|
347
|
-
vi.mocked(execSync).mockReturnValue('git@bitbucket.org:user/repo.git');
|
|
348
|
-
const result = detectGitUrl();
|
|
349
|
-
expect(result).toBe('https://bitbucket.org/user/repo');
|
|
350
|
-
});
|
|
351
|
-
it('should return null on timeout', () => {
|
|
352
|
-
vi.mocked(execSync).mockImplementation(() => {
|
|
353
|
-
const error = new Error('Command timed out');
|
|
354
|
-
error.code = 'ETIMEDOUT';
|
|
355
|
-
throw error;
|
|
356
|
-
});
|
|
357
|
-
const result = detectGitUrl();
|
|
358
|
-
expect(result).toBeNull();
|
|
359
|
-
});
|
|
360
|
-
it('should return null when not in a git repo', () => {
|
|
361
|
-
vi.mocked(execSync).mockImplementation(() => {
|
|
362
|
-
throw new Error('fatal: not a git repository');
|
|
363
|
-
});
|
|
364
|
-
const result = detectGitUrl();
|
|
365
|
-
expect(result).toBeNull();
|
|
366
|
-
});
|
|
367
|
-
});
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared Test Utilities
|
|
3
|
-
*
|
|
4
|
-
* Common mock factories and test helpers used across handler tests.
|
|
5
|
-
* This eliminates ~85 lines of duplicate code per test file.
|
|
6
|
-
*/
|
|
7
|
-
import type { SupabaseClient } from '@supabase/supabase-js';
|
|
8
|
-
import type { HandlerContext } from './types.js';
|
|
9
|
-
export interface MockSupabaseOverrides {
|
|
10
|
-
selectResult?: {
|
|
11
|
-
data: unknown;
|
|
12
|
-
error: unknown;
|
|
13
|
-
};
|
|
14
|
-
insertResult?: {
|
|
15
|
-
data: unknown;
|
|
16
|
-
error: unknown;
|
|
17
|
-
};
|
|
18
|
-
updateResult?: {
|
|
19
|
-
data: unknown;
|
|
20
|
-
error: unknown;
|
|
21
|
-
};
|
|
22
|
-
deleteResult?: {
|
|
23
|
-
data: unknown;
|
|
24
|
-
error: unknown;
|
|
25
|
-
};
|
|
26
|
-
sessionsResult?: {
|
|
27
|
-
data: unknown;
|
|
28
|
-
error: unknown;
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Create a mock Supabase client for testing.
|
|
33
|
-
*
|
|
34
|
-
* The mock tracks which operation is being performed and returns
|
|
35
|
-
* the appropriate result from overrides.
|
|
36
|
-
*
|
|
37
|
-
* @example
|
|
38
|
-
* ```ts
|
|
39
|
-
* const supabase = createMockSupabase({
|
|
40
|
-
* insertResult: { data: { id: 'new-id' }, error: null }
|
|
41
|
-
* });
|
|
42
|
-
* ```
|
|
43
|
-
*/
|
|
44
|
-
export declare function createMockSupabase(overrides?: MockSupabaseOverrides): SupabaseClient;
|
|
45
|
-
export interface MockContextOptions {
|
|
46
|
-
sessionId?: string | null;
|
|
47
|
-
userId?: string;
|
|
48
|
-
apiKeyId?: string;
|
|
49
|
-
instanceId?: string;
|
|
50
|
-
persona?: string;
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Create a mock HandlerContext for testing.
|
|
54
|
-
*
|
|
55
|
-
* @example
|
|
56
|
-
* ```ts
|
|
57
|
-
* const ctx = createMockContext(supabase, { sessionId: 'test-session' });
|
|
58
|
-
* ```
|
|
59
|
-
*/
|
|
60
|
-
export declare function createMockContext(supabase: SupabaseClient, options?: MockContextOptions): HandlerContext;
|
|
61
|
-
/**
|
|
62
|
-
* Generate a valid UUID for testing.
|
|
63
|
-
*/
|
|
64
|
-
export declare function testUUID(): string;
|
|
65
|
-
/**
|
|
66
|
-
* Generate a random-ish UUID for testing (deterministic based on seed).
|
|
67
|
-
*/
|
|
68
|
-
export declare function testUUIDSeeded(seed: number): string;
|
|
69
|
-
/**
|
|
70
|
-
* Create a mock timestamp for consistent testing.
|
|
71
|
-
*/
|
|
72
|
-
export declare function testTimestamp(offsetMinutes?: number): string;
|
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared Test Utilities
|
|
3
|
-
*
|
|
4
|
-
* Common mock factories and test helpers used across handler tests.
|
|
5
|
-
* This eliminates ~85 lines of duplicate code per test file.
|
|
6
|
-
*/
|
|
7
|
-
import { vi } from 'vitest';
|
|
8
|
-
/**
|
|
9
|
-
* Create a mock Supabase client for testing.
|
|
10
|
-
*
|
|
11
|
-
* The mock tracks which operation is being performed and returns
|
|
12
|
-
* the appropriate result from overrides.
|
|
13
|
-
*
|
|
14
|
-
* @example
|
|
15
|
-
* ```ts
|
|
16
|
-
* const supabase = createMockSupabase({
|
|
17
|
-
* insertResult: { data: { id: 'new-id' }, error: null }
|
|
18
|
-
* });
|
|
19
|
-
* ```
|
|
20
|
-
*/
|
|
21
|
-
export function createMockSupabase(overrides = {}) {
|
|
22
|
-
const defaultResult = { data: null, error: null };
|
|
23
|
-
// Use an object to track state so it persists across all mock function calls
|
|
24
|
-
const state = {
|
|
25
|
-
currentOperation: 'select',
|
|
26
|
-
currentTable: '',
|
|
27
|
-
insertThenSelect: false,
|
|
28
|
-
updateCalled: false,
|
|
29
|
-
};
|
|
30
|
-
const mock = {
|
|
31
|
-
from: vi.fn((table) => {
|
|
32
|
-
state.currentTable = table;
|
|
33
|
-
// Reset state for new query chain
|
|
34
|
-
state.currentOperation = 'select';
|
|
35
|
-
state.insertThenSelect = false;
|
|
36
|
-
state.updateCalled = false;
|
|
37
|
-
return mock;
|
|
38
|
-
}),
|
|
39
|
-
select: vi.fn(() => {
|
|
40
|
-
if (state.currentOperation === 'insert') {
|
|
41
|
-
state.insertThenSelect = true;
|
|
42
|
-
}
|
|
43
|
-
else if (!state.updateCalled) {
|
|
44
|
-
state.currentOperation = 'select';
|
|
45
|
-
state.insertThenSelect = false;
|
|
46
|
-
}
|
|
47
|
-
return mock;
|
|
48
|
-
}),
|
|
49
|
-
insert: vi.fn(() => {
|
|
50
|
-
state.currentOperation = 'insert';
|
|
51
|
-
state.insertThenSelect = false;
|
|
52
|
-
return mock;
|
|
53
|
-
}),
|
|
54
|
-
update: vi.fn(() => {
|
|
55
|
-
state.currentOperation = 'update';
|
|
56
|
-
state.updateCalled = true;
|
|
57
|
-
state.insertThenSelect = false;
|
|
58
|
-
return mock;
|
|
59
|
-
}),
|
|
60
|
-
delete: vi.fn(() => {
|
|
61
|
-
state.currentOperation = 'delete';
|
|
62
|
-
state.insertThenSelect = false;
|
|
63
|
-
return mock;
|
|
64
|
-
}),
|
|
65
|
-
eq: vi.fn().mockReturnThis(),
|
|
66
|
-
neq: vi.fn().mockReturnThis(),
|
|
67
|
-
in: vi.fn().mockReturnThis(),
|
|
68
|
-
is: vi.fn().mockReturnThis(),
|
|
69
|
-
not: vi.fn().mockReturnThis(),
|
|
70
|
-
or: vi.fn().mockReturnThis(),
|
|
71
|
-
gt: vi.fn().mockReturnThis(),
|
|
72
|
-
gte: vi.fn().mockReturnThis(),
|
|
73
|
-
lte: vi.fn().mockReturnThis(),
|
|
74
|
-
lt: vi.fn().mockReturnThis(),
|
|
75
|
-
order: vi.fn().mockReturnThis(),
|
|
76
|
-
limit: vi.fn().mockReturnThis(),
|
|
77
|
-
single: vi.fn(() => {
|
|
78
|
-
if (state.currentOperation === 'insert' || state.insertThenSelect) {
|
|
79
|
-
return Promise.resolve(overrides.insertResult ?? defaultResult);
|
|
80
|
-
}
|
|
81
|
-
if (state.updateCalled) {
|
|
82
|
-
return Promise.resolve(overrides.updateResult ?? defaultResult);
|
|
83
|
-
}
|
|
84
|
-
if (state.currentOperation === 'select') {
|
|
85
|
-
return Promise.resolve(overrides.selectResult ?? defaultResult);
|
|
86
|
-
}
|
|
87
|
-
if (state.currentOperation === 'update') {
|
|
88
|
-
return Promise.resolve(overrides.updateResult ?? defaultResult);
|
|
89
|
-
}
|
|
90
|
-
return Promise.resolve(defaultResult);
|
|
91
|
-
}),
|
|
92
|
-
maybeSingle: vi.fn(() => {
|
|
93
|
-
return Promise.resolve(overrides.selectResult ?? defaultResult);
|
|
94
|
-
}),
|
|
95
|
-
then: vi.fn((resolve) => {
|
|
96
|
-
// Handle special table cases
|
|
97
|
-
if (state.currentTable === 'agent_sessions' && overrides.sessionsResult) {
|
|
98
|
-
return Promise.resolve(overrides.sessionsResult).then(resolve);
|
|
99
|
-
}
|
|
100
|
-
if (state.currentOperation === 'insert' || state.insertThenSelect) {
|
|
101
|
-
return Promise.resolve(overrides.insertResult ?? defaultResult).then(resolve);
|
|
102
|
-
}
|
|
103
|
-
if (state.updateCalled) {
|
|
104
|
-
return Promise.resolve(overrides.updateResult ?? defaultResult).then(resolve);
|
|
105
|
-
}
|
|
106
|
-
if (state.currentOperation === 'select') {
|
|
107
|
-
return Promise.resolve(overrides.selectResult ?? defaultResult).then(resolve);
|
|
108
|
-
}
|
|
109
|
-
if (state.currentOperation === 'update') {
|
|
110
|
-
return Promise.resolve(overrides.updateResult ?? defaultResult).then(resolve);
|
|
111
|
-
}
|
|
112
|
-
if (state.currentOperation === 'delete') {
|
|
113
|
-
return Promise.resolve(overrides.deleteResult ?? defaultResult).then(resolve);
|
|
114
|
-
}
|
|
115
|
-
return Promise.resolve(defaultResult).then(resolve);
|
|
116
|
-
}),
|
|
117
|
-
};
|
|
118
|
-
return mock;
|
|
119
|
-
}
|
|
120
|
-
/**
|
|
121
|
-
* Create a mock HandlerContext for testing.
|
|
122
|
-
*
|
|
123
|
-
* @example
|
|
124
|
-
* ```ts
|
|
125
|
-
* const ctx = createMockContext(supabase, { sessionId: 'test-session' });
|
|
126
|
-
* ```
|
|
127
|
-
*/
|
|
128
|
-
export function createMockContext(supabase, options = {}) {
|
|
129
|
-
const defaultTokenUsage = {
|
|
130
|
-
callCount: 5,
|
|
131
|
-
totalTokens: 2500,
|
|
132
|
-
byTool: {},
|
|
133
|
-
byModel: {},
|
|
134
|
-
currentModel: null,
|
|
135
|
-
};
|
|
136
|
-
const sessionId = 'sessionId' in options ? (options.sessionId ?? null) : 'session-123';
|
|
137
|
-
return {
|
|
138
|
-
supabase,
|
|
139
|
-
auth: {
|
|
140
|
-
userId: options.userId ?? 'user-123',
|
|
141
|
-
apiKeyId: options.apiKeyId ?? 'api-key-123',
|
|
142
|
-
scope: 'personal',
|
|
143
|
-
},
|
|
144
|
-
session: {
|
|
145
|
-
instanceId: options.instanceId ?? 'instance-abc',
|
|
146
|
-
currentSessionId: sessionId,
|
|
147
|
-
currentPersona: options.persona ?? 'Wave',
|
|
148
|
-
tokenUsage: defaultTokenUsage,
|
|
149
|
-
},
|
|
150
|
-
updateSession: vi.fn(),
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
// ============================================================================
|
|
154
|
-
// Test Data Generators
|
|
155
|
-
// ============================================================================
|
|
156
|
-
/**
|
|
157
|
-
* Generate a valid UUID for testing.
|
|
158
|
-
*/
|
|
159
|
-
export function testUUID() {
|
|
160
|
-
return '123e4567-e89b-12d3-a456-426614174000';
|
|
161
|
-
}
|
|
162
|
-
/**
|
|
163
|
-
* Generate a random-ish UUID for testing (deterministic based on seed).
|
|
164
|
-
*/
|
|
165
|
-
export function testUUIDSeeded(seed) {
|
|
166
|
-
const hex = seed.toString(16).padStart(8, '0');
|
|
167
|
-
return `${hex}-e89b-12d3-a456-426614174000`;
|
|
168
|
-
}
|
|
169
|
-
/**
|
|
170
|
-
* Create a mock timestamp for consistent testing.
|
|
171
|
-
*/
|
|
172
|
-
export function testTimestamp(offsetMinutes = 0) {
|
|
173
|
-
const date = new Date('2025-01-14T12:00:00Z');
|
|
174
|
-
date.setMinutes(date.getMinutes() + offsetMinutes);
|
|
175
|
-
return date.toISOString();
|
|
176
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* File Checkout Handlers
|
|
3
|
-
*
|
|
4
|
-
* Handles file checkout/checkin for multi-agent coordination:
|
|
5
|
-
* - checkout_file: Reserve a file for editing
|
|
6
|
-
* - checkin_file: Release a file after editing
|
|
7
|
-
* - get_file_checkouts: List active and recent checkouts
|
|
8
|
-
* - abandon_checkout: Force-release a checkout
|
|
9
|
-
*/
|
|
10
|
-
import type { Handler, HandlerRegistry } from './types.js';
|
|
11
|
-
/**
|
|
12
|
-
* Checkout a file for editing
|
|
13
|
-
* Prevents other agents from editing the same file
|
|
14
|
-
*/
|
|
15
|
-
export declare const checkoutFile: Handler;
|
|
16
|
-
/**
|
|
17
|
-
* Checkin a file after editing
|
|
18
|
-
* Releases the file for other agents to edit
|
|
19
|
-
*/
|
|
20
|
-
export declare const checkinFile: Handler;
|
|
21
|
-
/**
|
|
22
|
-
* Get file checkouts for a project
|
|
23
|
-
*/
|
|
24
|
-
export declare const getFileCheckouts: Handler;
|
|
25
|
-
/**
|
|
26
|
-
* Abandon a checkout (force release)
|
|
27
|
-
* Use when the original agent session died or is stuck
|
|
28
|
-
*/
|
|
29
|
-
export declare const abandonCheckout: Handler;
|
|
30
|
-
/**
|
|
31
|
-
* Check if a file is available for checkout
|
|
32
|
-
*/
|
|
33
|
-
export declare const isFileAvailable: Handler;
|
|
34
|
-
/**
|
|
35
|
-
* Checkout handlers registry
|
|
36
|
-
*/
|
|
37
|
-
export declare const checkoutHandlers: HandlerRegistry;
|