@vibescope/mcp-server 0.0.1
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 +98 -0
- package/dist/cli.d.ts +34 -0
- package/dist/cli.js +356 -0
- package/dist/cli.test.d.ts +1 -0
- package/dist/cli.test.js +367 -0
- package/dist/handlers/__test-utils__.d.ts +72 -0
- package/dist/handlers/__test-utils__.js +176 -0
- package/dist/handlers/blockers.d.ts +18 -0
- package/dist/handlers/blockers.js +81 -0
- package/dist/handlers/bodies-of-work.d.ts +34 -0
- package/dist/handlers/bodies-of-work.js +614 -0
- package/dist/handlers/checkouts.d.ts +37 -0
- package/dist/handlers/checkouts.js +377 -0
- package/dist/handlers/cost.d.ts +39 -0
- package/dist/handlers/cost.js +247 -0
- package/dist/handlers/decisions.d.ts +16 -0
- package/dist/handlers/decisions.js +64 -0
- package/dist/handlers/deployment.d.ts +36 -0
- package/dist/handlers/deployment.js +1062 -0
- package/dist/handlers/discovery.d.ts +14 -0
- package/dist/handlers/discovery.js +870 -0
- package/dist/handlers/fallback.d.ts +18 -0
- package/dist/handlers/fallback.js +216 -0
- package/dist/handlers/findings.d.ts +18 -0
- package/dist/handlers/findings.js +110 -0
- package/dist/handlers/git-issues.d.ts +22 -0
- package/dist/handlers/git-issues.js +247 -0
- package/dist/handlers/ideas.d.ts +19 -0
- package/dist/handlers/ideas.js +188 -0
- package/dist/handlers/index.d.ts +29 -0
- package/dist/handlers/index.js +65 -0
- package/dist/handlers/knowledge-query.d.ts +22 -0
- package/dist/handlers/knowledge-query.js +253 -0
- package/dist/handlers/knowledge.d.ts +12 -0
- package/dist/handlers/knowledge.js +108 -0
- package/dist/handlers/milestones.d.ts +20 -0
- package/dist/handlers/milestones.js +179 -0
- package/dist/handlers/organizations.d.ts +36 -0
- package/dist/handlers/organizations.js +428 -0
- package/dist/handlers/progress.d.ts +14 -0
- package/dist/handlers/progress.js +149 -0
- package/dist/handlers/project.d.ts +20 -0
- package/dist/handlers/project.js +278 -0
- package/dist/handlers/requests.d.ts +16 -0
- package/dist/handlers/requests.js +131 -0
- package/dist/handlers/roles.d.ts +30 -0
- package/dist/handlers/roles.js +281 -0
- package/dist/handlers/session.d.ts +20 -0
- package/dist/handlers/session.js +791 -0
- package/dist/handlers/tasks.d.ts +52 -0
- package/dist/handlers/tasks.js +1111 -0
- package/dist/handlers/tasks.test.d.ts +1 -0
- package/dist/handlers/tasks.test.js +431 -0
- package/dist/handlers/types.d.ts +94 -0
- package/dist/handlers/types.js +1 -0
- package/dist/handlers/validation.d.ts +16 -0
- package/dist/handlers/validation.js +188 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2707 -0
- package/dist/knowledge.d.ts +6 -0
- package/dist/knowledge.js +121 -0
- package/dist/tools.d.ts +2 -0
- package/dist/tools.js +2498 -0
- package/dist/utils.d.ts +149 -0
- package/dist/utils.js +317 -0
- package/dist/utils.test.d.ts +1 -0
- package/dist/utils.test.js +532 -0
- package/dist/validators.d.ts +35 -0
- package/dist/validators.js +111 -0
- package/dist/validators.test.d.ts +1 -0
- package/dist/validators.test.js +176 -0
- package/package.json +44 -0
- package/src/cli.test.ts +442 -0
- package/src/cli.ts +439 -0
- package/src/handlers/__test-utils__.ts +217 -0
- package/src/handlers/blockers.test.ts +390 -0
- package/src/handlers/blockers.ts +110 -0
- package/src/handlers/bodies-of-work.test.ts +1276 -0
- package/src/handlers/bodies-of-work.ts +783 -0
- package/src/handlers/cost.test.ts +436 -0
- package/src/handlers/cost.ts +322 -0
- package/src/handlers/decisions.test.ts +401 -0
- package/src/handlers/decisions.ts +86 -0
- package/src/handlers/deployment.test.ts +516 -0
- package/src/handlers/deployment.ts +1289 -0
- package/src/handlers/discovery.test.ts +254 -0
- package/src/handlers/discovery.ts +969 -0
- package/src/handlers/fallback.test.ts +687 -0
- package/src/handlers/fallback.ts +260 -0
- package/src/handlers/findings.test.ts +565 -0
- package/src/handlers/findings.ts +153 -0
- package/src/handlers/ideas.test.ts +753 -0
- package/src/handlers/ideas.ts +247 -0
- package/src/handlers/index.ts +69 -0
- package/src/handlers/milestones.test.ts +584 -0
- package/src/handlers/milestones.ts +217 -0
- package/src/handlers/organizations.test.ts +997 -0
- package/src/handlers/organizations.ts +550 -0
- package/src/handlers/progress.test.ts +369 -0
- package/src/handlers/progress.ts +188 -0
- package/src/handlers/project.test.ts +562 -0
- package/src/handlers/project.ts +352 -0
- package/src/handlers/requests.test.ts +531 -0
- package/src/handlers/requests.ts +150 -0
- package/src/handlers/session.test.ts +459 -0
- package/src/handlers/session.ts +912 -0
- package/src/handlers/tasks.test.ts +602 -0
- package/src/handlers/tasks.ts +1393 -0
- package/src/handlers/types.ts +88 -0
- package/src/handlers/validation.test.ts +880 -0
- package/src/handlers/validation.ts +223 -0
- package/src/index.ts +3205 -0
- package/src/knowledge.ts +132 -0
- package/src/tmpclaude-0078-cwd +1 -0
- package/src/tmpclaude-0ee1-cwd +1 -0
- package/src/tmpclaude-2dd5-cwd +1 -0
- package/src/tmpclaude-344c-cwd +1 -0
- package/src/tmpclaude-3860-cwd +1 -0
- package/src/tmpclaude-4b63-cwd +1 -0
- package/src/tmpclaude-5c73-cwd +1 -0
- package/src/tmpclaude-5ee3-cwd +1 -0
- package/src/tmpclaude-6795-cwd +1 -0
- package/src/tmpclaude-709e-cwd +1 -0
- package/src/tmpclaude-9839-cwd +1 -0
- package/src/tmpclaude-d829-cwd +1 -0
- package/src/tmpclaude-e072-cwd +1 -0
- package/src/tmpclaude-f6ee-cwd +1 -0
- package/src/utils.test.ts +681 -0
- package/src/utils.ts +375 -0
- package/src/validators.test.ts +223 -0
- package/src/validators.ts +122 -0
- package/tmpclaude-0439-cwd +1 -0
- package/tmpclaude-132f-cwd +1 -0
- package/tmpclaude-15bb-cwd +1 -0
- package/tmpclaude-165a-cwd +1 -0
- package/tmpclaude-1ba9-cwd +1 -0
- package/tmpclaude-21a3-cwd +1 -0
- package/tmpclaude-2a38-cwd +1 -0
- package/tmpclaude-2adf-cwd +1 -0
- package/tmpclaude-2f56-cwd +1 -0
- package/tmpclaude-3626-cwd +1 -0
- package/tmpclaude-3727-cwd +1 -0
- package/tmpclaude-40bc-cwd +1 -0
- package/tmpclaude-436f-cwd +1 -0
- package/tmpclaude-4783-cwd +1 -0
- package/tmpclaude-4b6d-cwd +1 -0
- package/tmpclaude-4ba4-cwd +1 -0
- package/tmpclaude-51e6-cwd +1 -0
- package/tmpclaude-5ecf-cwd +1 -0
- package/tmpclaude-6f97-cwd +1 -0
- package/tmpclaude-7fb2-cwd +1 -0
- package/tmpclaude-825c-cwd +1 -0
- package/tmpclaude-8baf-cwd +1 -0
- package/tmpclaude-8d9f-cwd +1 -0
- package/tmpclaude-975c-cwd +1 -0
- package/tmpclaude-9983-cwd +1 -0
- package/tmpclaude-a045-cwd +1 -0
- package/tmpclaude-ac4a-cwd +1 -0
- package/tmpclaude-b593-cwd +1 -0
- package/tmpclaude-b891-cwd +1 -0
- package/tmpclaude-c032-cwd +1 -0
- package/tmpclaude-cf43-cwd +1 -0
- package/tmpclaude-d040-cwd +1 -0
- package/tmpclaude-dcdd-cwd +1 -0
- package/tmpclaude-dcee-cwd +1 -0
- package/tmpclaude-e16b-cwd +1 -0
- package/tmpclaude-ecd2-cwd +1 -0
- package/tmpclaude-f48d-cwd +1 -0
- package/tsconfig.json +16 -0
- package/vitest.config.ts +13 -0
|
@@ -0,0 +1,562 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import type { SupabaseClient } from '@supabase/supabase-js';
|
|
3
|
+
import type { HandlerContext, TokenUsage } from './types.js';
|
|
4
|
+
import {
|
|
5
|
+
getProjectContext,
|
|
6
|
+
getGitWorkflow,
|
|
7
|
+
createProject,
|
|
8
|
+
updateProject,
|
|
9
|
+
updateProjectReadme,
|
|
10
|
+
} from './project.js';
|
|
11
|
+
import { ValidationError } from '../validators.js';
|
|
12
|
+
|
|
13
|
+
// ============================================================================
|
|
14
|
+
// Test Utilities
|
|
15
|
+
// ============================================================================
|
|
16
|
+
|
|
17
|
+
function createMockSupabase(overrides: {
|
|
18
|
+
selectResult?: { data: unknown; error: unknown };
|
|
19
|
+
insertResult?: { data: unknown; error: unknown };
|
|
20
|
+
updateResult?: { data: unknown; error: unknown };
|
|
21
|
+
} = {}) {
|
|
22
|
+
const defaultResult = { data: null, error: null };
|
|
23
|
+
let currentOperation = 'select';
|
|
24
|
+
let insertThenSelect = false;
|
|
25
|
+
|
|
26
|
+
const mock = {
|
|
27
|
+
from: vi.fn().mockReturnThis(),
|
|
28
|
+
select: vi.fn(() => {
|
|
29
|
+
if (currentOperation === 'insert') {
|
|
30
|
+
insertThenSelect = true;
|
|
31
|
+
} else {
|
|
32
|
+
currentOperation = 'select';
|
|
33
|
+
insertThenSelect = false;
|
|
34
|
+
}
|
|
35
|
+
return mock;
|
|
36
|
+
}),
|
|
37
|
+
insert: vi.fn(() => {
|
|
38
|
+
currentOperation = 'insert';
|
|
39
|
+
insertThenSelect = false;
|
|
40
|
+
return mock;
|
|
41
|
+
}),
|
|
42
|
+
update: vi.fn(() => {
|
|
43
|
+
currentOperation = 'update';
|
|
44
|
+
insertThenSelect = false;
|
|
45
|
+
return mock;
|
|
46
|
+
}),
|
|
47
|
+
delete: vi.fn(() => {
|
|
48
|
+
currentOperation = 'delete';
|
|
49
|
+
insertThenSelect = false;
|
|
50
|
+
return mock;
|
|
51
|
+
}),
|
|
52
|
+
eq: vi.fn().mockReturnThis(),
|
|
53
|
+
neq: vi.fn().mockReturnThis(),
|
|
54
|
+
in: vi.fn().mockReturnThis(),
|
|
55
|
+
is: vi.fn().mockReturnThis(),
|
|
56
|
+
not: vi.fn().mockReturnThis(),
|
|
57
|
+
or: vi.fn().mockReturnThis(),
|
|
58
|
+
gt: vi.fn().mockReturnThis(),
|
|
59
|
+
gte: vi.fn().mockReturnThis(),
|
|
60
|
+
lte: vi.fn().mockReturnThis(),
|
|
61
|
+
lt: vi.fn().mockReturnThis(),
|
|
62
|
+
order: vi.fn().mockReturnThis(),
|
|
63
|
+
limit: vi.fn().mockReturnThis(),
|
|
64
|
+
single: vi.fn(() => {
|
|
65
|
+
if (currentOperation === 'insert' || insertThenSelect) {
|
|
66
|
+
return Promise.resolve(overrides.insertResult ?? defaultResult);
|
|
67
|
+
}
|
|
68
|
+
if (currentOperation === 'select') {
|
|
69
|
+
return Promise.resolve(overrides.selectResult ?? defaultResult);
|
|
70
|
+
}
|
|
71
|
+
if (currentOperation === 'update') {
|
|
72
|
+
return Promise.resolve(overrides.updateResult ?? defaultResult);
|
|
73
|
+
}
|
|
74
|
+
return Promise.resolve(defaultResult);
|
|
75
|
+
}),
|
|
76
|
+
maybeSingle: vi.fn(() => {
|
|
77
|
+
return Promise.resolve(overrides.selectResult ?? defaultResult);
|
|
78
|
+
}),
|
|
79
|
+
then: vi.fn((resolve: (value: unknown) => void) => {
|
|
80
|
+
if (currentOperation === 'insert' || insertThenSelect) {
|
|
81
|
+
return Promise.resolve(overrides.insertResult ?? defaultResult).then(resolve);
|
|
82
|
+
}
|
|
83
|
+
if (currentOperation === 'select') {
|
|
84
|
+
return Promise.resolve(overrides.selectResult ?? defaultResult).then(resolve);
|
|
85
|
+
}
|
|
86
|
+
if (currentOperation === 'update') {
|
|
87
|
+
return Promise.resolve(overrides.updateResult ?? defaultResult).then(resolve);
|
|
88
|
+
}
|
|
89
|
+
return Promise.resolve(defaultResult).then(resolve);
|
|
90
|
+
}),
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
return mock as unknown as SupabaseClient;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function createMockContext(
|
|
97
|
+
supabase: SupabaseClient,
|
|
98
|
+
options: { sessionId?: string | null } = {}
|
|
99
|
+
): HandlerContext {
|
|
100
|
+
const defaultTokenUsage: TokenUsage = {
|
|
101
|
+
callCount: 5,
|
|
102
|
+
totalTokens: 2500,
|
|
103
|
+
byTool: {},
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const sessionId = 'sessionId' in options ? options.sessionId : 'session-123';
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
supabase,
|
|
110
|
+
auth: { userId: 'user-123', apiKeyId: 'api-key-123' },
|
|
111
|
+
session: {
|
|
112
|
+
instanceId: 'instance-abc',
|
|
113
|
+
currentSessionId: sessionId,
|
|
114
|
+
currentPersona: 'Wave',
|
|
115
|
+
tokenUsage: defaultTokenUsage,
|
|
116
|
+
},
|
|
117
|
+
updateSession: vi.fn(),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ============================================================================
|
|
122
|
+
// getProjectContext Tests
|
|
123
|
+
// ============================================================================
|
|
124
|
+
|
|
125
|
+
describe('getProjectContext', () => {
|
|
126
|
+
beforeEach(() => vi.clearAllMocks());
|
|
127
|
+
|
|
128
|
+
it('should list all projects when no project_id or git_url', async () => {
|
|
129
|
+
const mockProjects = [
|
|
130
|
+
{ id: 'proj-1', name: 'Project 1', description: 'Desc 1', status: 'active', git_url: null },
|
|
131
|
+
{ id: 'proj-2', name: 'Project 2', description: 'Desc 2', status: 'active', git_url: 'https://github.com/test' },
|
|
132
|
+
];
|
|
133
|
+
|
|
134
|
+
const supabase = createMockSupabase({
|
|
135
|
+
selectResult: { data: mockProjects, error: null },
|
|
136
|
+
});
|
|
137
|
+
const ctx = createMockContext(supabase);
|
|
138
|
+
|
|
139
|
+
const result = await getProjectContext({}, ctx);
|
|
140
|
+
|
|
141
|
+
expect(result.result).toHaveProperty('projects');
|
|
142
|
+
expect((result.result as { projects: unknown[] }).projects).toEqual(mockProjects);
|
|
143
|
+
expect(supabase.from).toHaveBeenCalledWith('projects');
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('should return found: false when project not found', async () => {
|
|
147
|
+
const supabase = createMockSupabase({
|
|
148
|
+
selectResult: { data: null, error: { message: 'Not found' } },
|
|
149
|
+
});
|
|
150
|
+
const ctx = createMockContext(supabase);
|
|
151
|
+
|
|
152
|
+
const result = await getProjectContext(
|
|
153
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
154
|
+
ctx
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
expect(result.result).toMatchObject({
|
|
158
|
+
found: false,
|
|
159
|
+
message: 'Project not found. Use create_project to create one.',
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('should find project by git_url', async () => {
|
|
164
|
+
const supabase = createMockSupabase({
|
|
165
|
+
selectResult: {
|
|
166
|
+
data: { id: 'proj-1', name: 'Test Project', description: null, goal: null, status: 'active', git_url: 'https://github.com/test', agent_instructions: null, tech_stack: null },
|
|
167
|
+
error: null,
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
const ctx = createMockContext(supabase);
|
|
171
|
+
|
|
172
|
+
const result = await getProjectContext(
|
|
173
|
+
{ git_url: 'https://github.com/test' },
|
|
174
|
+
ctx
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
expect(result.result).toMatchObject({
|
|
178
|
+
found: true,
|
|
179
|
+
});
|
|
180
|
+
expect(supabase.eq).toHaveBeenCalledWith('git_url', 'https://github.com/test');
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('should include active_tasks in result', async () => {
|
|
184
|
+
const supabase = createMockSupabase({
|
|
185
|
+
selectResult: {
|
|
186
|
+
data: { id: 'proj-1', name: 'Test Project', description: null, goal: null, status: 'active', git_url: null, agent_instructions: null, tech_stack: null },
|
|
187
|
+
error: null,
|
|
188
|
+
},
|
|
189
|
+
});
|
|
190
|
+
const ctx = createMockContext(supabase);
|
|
191
|
+
|
|
192
|
+
const result = await getProjectContext(
|
|
193
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
194
|
+
ctx
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
expect(result.result).toHaveProperty('active_tasks');
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// ============================================================================
|
|
202
|
+
// getGitWorkflow Tests
|
|
203
|
+
// ============================================================================
|
|
204
|
+
|
|
205
|
+
describe('getGitWorkflow', () => {
|
|
206
|
+
beforeEach(() => vi.clearAllMocks());
|
|
207
|
+
|
|
208
|
+
it('should throw error for missing project_id', async () => {
|
|
209
|
+
const supabase = createMockSupabase();
|
|
210
|
+
const ctx = createMockContext(supabase);
|
|
211
|
+
|
|
212
|
+
await expect(getGitWorkflow({}, ctx)).rejects.toThrow(ValidationError);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('should throw error for invalid project_id UUID', async () => {
|
|
216
|
+
const supabase = createMockSupabase();
|
|
217
|
+
const ctx = createMockContext(supabase);
|
|
218
|
+
|
|
219
|
+
await expect(
|
|
220
|
+
getGitWorkflow({ project_id: 'invalid' }, ctx)
|
|
221
|
+
).rejects.toThrow(ValidationError);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('should return workflow instructions for github-flow', async () => {
|
|
225
|
+
const supabase = createMockSupabase({
|
|
226
|
+
selectResult: {
|
|
227
|
+
data: {
|
|
228
|
+
git_workflow: 'github-flow',
|
|
229
|
+
git_main_branch: 'main',
|
|
230
|
+
git_develop_branch: null,
|
|
231
|
+
git_auto_branch: true,
|
|
232
|
+
git_auto_tag: false,
|
|
233
|
+
git_url: 'https://github.com/test',
|
|
234
|
+
},
|
|
235
|
+
error: null,
|
|
236
|
+
},
|
|
237
|
+
});
|
|
238
|
+
const ctx = createMockContext(supabase);
|
|
239
|
+
|
|
240
|
+
const result = await getGitWorkflow(
|
|
241
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
242
|
+
ctx
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
expect(result.result).toMatchObject({
|
|
246
|
+
workflow: 'github-flow',
|
|
247
|
+
main_branch: 'main',
|
|
248
|
+
auto_branch: true,
|
|
249
|
+
});
|
|
250
|
+
expect((result.result as { instructions: string[] }).instructions.length).toBeGreaterThan(0);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it('should return workflow instructions for git-flow', async () => {
|
|
254
|
+
const supabase = createMockSupabase({
|
|
255
|
+
selectResult: {
|
|
256
|
+
data: {
|
|
257
|
+
git_workflow: 'git-flow',
|
|
258
|
+
git_main_branch: 'main',
|
|
259
|
+
git_develop_branch: 'develop',
|
|
260
|
+
git_auto_branch: false,
|
|
261
|
+
git_auto_tag: true,
|
|
262
|
+
git_url: 'https://github.com/test',
|
|
263
|
+
},
|
|
264
|
+
error: null,
|
|
265
|
+
},
|
|
266
|
+
});
|
|
267
|
+
const ctx = createMockContext(supabase);
|
|
268
|
+
|
|
269
|
+
const result = await getGitWorkflow(
|
|
270
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
271
|
+
ctx
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
expect(result.result).toMatchObject({
|
|
275
|
+
workflow: 'git-flow',
|
|
276
|
+
main_branch: 'main',
|
|
277
|
+
develop_branch: 'develop',
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it('should return no workflow instructions when workflow is none', async () => {
|
|
282
|
+
const supabase = createMockSupabase({
|
|
283
|
+
selectResult: {
|
|
284
|
+
data: {
|
|
285
|
+
git_workflow: 'none',
|
|
286
|
+
git_main_branch: 'main',
|
|
287
|
+
git_develop_branch: null,
|
|
288
|
+
git_auto_branch: false,
|
|
289
|
+
git_auto_tag: false,
|
|
290
|
+
git_url: null,
|
|
291
|
+
},
|
|
292
|
+
error: null,
|
|
293
|
+
},
|
|
294
|
+
});
|
|
295
|
+
const ctx = createMockContext(supabase);
|
|
296
|
+
|
|
297
|
+
const result = await getGitWorkflow(
|
|
298
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
299
|
+
ctx
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
expect(result.result).toMatchObject({
|
|
303
|
+
workflow: 'none',
|
|
304
|
+
});
|
|
305
|
+
expect((result.result as { instructions: string[] }).instructions[0]).toContain('No git workflow configured');
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it('should throw error when project not found', async () => {
|
|
309
|
+
const supabase = createMockSupabase({
|
|
310
|
+
selectResult: { data: null, error: { message: 'Not found' } },
|
|
311
|
+
});
|
|
312
|
+
const ctx = createMockContext(supabase);
|
|
313
|
+
|
|
314
|
+
await expect(
|
|
315
|
+
getGitWorkflow({ project_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx)
|
|
316
|
+
).rejects.toThrow('Project not found');
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
// ============================================================================
|
|
321
|
+
// createProject Tests
|
|
322
|
+
// ============================================================================
|
|
323
|
+
|
|
324
|
+
describe('createProject', () => {
|
|
325
|
+
beforeEach(() => vi.clearAllMocks());
|
|
326
|
+
|
|
327
|
+
it('should create project successfully with minimal args', async () => {
|
|
328
|
+
const mockProject = {
|
|
329
|
+
id: 'new-proj-1',
|
|
330
|
+
name: 'New Project',
|
|
331
|
+
description: null,
|
|
332
|
+
goal: null,
|
|
333
|
+
git_url: null,
|
|
334
|
+
tech_stack: null,
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
const supabase = createMockSupabase({
|
|
338
|
+
insertResult: { data: mockProject, error: null },
|
|
339
|
+
});
|
|
340
|
+
const ctx = createMockContext(supabase);
|
|
341
|
+
|
|
342
|
+
const result = await createProject({ name: 'New Project' }, ctx);
|
|
343
|
+
|
|
344
|
+
expect(result.result).toMatchObject({
|
|
345
|
+
success: true,
|
|
346
|
+
project: mockProject,
|
|
347
|
+
});
|
|
348
|
+
expect(supabase.insert).toHaveBeenCalled();
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
it('should create project with all optional fields', async () => {
|
|
352
|
+
const mockProject = {
|
|
353
|
+
id: 'new-proj-2',
|
|
354
|
+
name: 'Full Project',
|
|
355
|
+
description: 'A test project',
|
|
356
|
+
goal: 'Complete the test',
|
|
357
|
+
git_url: 'https://github.com/test/repo',
|
|
358
|
+
tech_stack: ['TypeScript', 'Svelte'],
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
const supabase = createMockSupabase({
|
|
362
|
+
insertResult: { data: mockProject, error: null },
|
|
363
|
+
});
|
|
364
|
+
const ctx = createMockContext(supabase);
|
|
365
|
+
|
|
366
|
+
const result = await createProject(
|
|
367
|
+
{
|
|
368
|
+
name: 'Full Project',
|
|
369
|
+
description: 'A test project',
|
|
370
|
+
goal: 'Complete the test',
|
|
371
|
+
git_url: 'https://github.com/test/repo',
|
|
372
|
+
tech_stack: ['TypeScript', 'Svelte'],
|
|
373
|
+
},
|
|
374
|
+
ctx
|
|
375
|
+
);
|
|
376
|
+
|
|
377
|
+
expect(result.result).toMatchObject({
|
|
378
|
+
success: true,
|
|
379
|
+
project: mockProject,
|
|
380
|
+
});
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
it('should throw error when insert fails', async () => {
|
|
384
|
+
const supabase = createMockSupabase({
|
|
385
|
+
insertResult: { data: null, error: { message: 'Insert failed' } },
|
|
386
|
+
});
|
|
387
|
+
const ctx = createMockContext(supabase);
|
|
388
|
+
|
|
389
|
+
await expect(
|
|
390
|
+
createProject({ name: 'Test Project' }, ctx)
|
|
391
|
+
).rejects.toThrow('Failed to create project: Insert failed');
|
|
392
|
+
});
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
// ============================================================================
|
|
396
|
+
// updateProject Tests
|
|
397
|
+
// ============================================================================
|
|
398
|
+
|
|
399
|
+
describe('updateProject', () => {
|
|
400
|
+
beforeEach(() => vi.clearAllMocks());
|
|
401
|
+
|
|
402
|
+
it('should throw error for missing project_id', async () => {
|
|
403
|
+
const supabase = createMockSupabase();
|
|
404
|
+
const ctx = createMockContext(supabase);
|
|
405
|
+
|
|
406
|
+
await expect(updateProject({}, ctx)).rejects.toThrow(ValidationError);
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
it('should throw error for invalid project_id UUID', async () => {
|
|
410
|
+
const supabase = createMockSupabase();
|
|
411
|
+
const ctx = createMockContext(supabase);
|
|
412
|
+
|
|
413
|
+
await expect(
|
|
414
|
+
updateProject({ project_id: 'invalid' }, ctx)
|
|
415
|
+
).rejects.toThrow(ValidationError);
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
it('should throw error for invalid status', async () => {
|
|
419
|
+
const supabase = createMockSupabase();
|
|
420
|
+
const ctx = createMockContext(supabase);
|
|
421
|
+
|
|
422
|
+
await expect(
|
|
423
|
+
updateProject({
|
|
424
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
425
|
+
status: 'invalid_status',
|
|
426
|
+
}, ctx)
|
|
427
|
+
).rejects.toThrow(ValidationError);
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
it('should update project successfully', async () => {
|
|
431
|
+
const supabase = createMockSupabase({
|
|
432
|
+
updateResult: { data: null, error: null },
|
|
433
|
+
});
|
|
434
|
+
const ctx = createMockContext(supabase);
|
|
435
|
+
|
|
436
|
+
const result = await updateProject(
|
|
437
|
+
{
|
|
438
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
439
|
+
name: 'Updated Name',
|
|
440
|
+
status: 'active',
|
|
441
|
+
},
|
|
442
|
+
ctx
|
|
443
|
+
);
|
|
444
|
+
|
|
445
|
+
expect(result.result).toMatchObject({
|
|
446
|
+
success: true,
|
|
447
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
448
|
+
});
|
|
449
|
+
expect(supabase.update).toHaveBeenCalled();
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
it('should update git workflow settings', async () => {
|
|
453
|
+
const supabase = createMockSupabase({
|
|
454
|
+
updateResult: { data: null, error: null },
|
|
455
|
+
});
|
|
456
|
+
const ctx = createMockContext(supabase);
|
|
457
|
+
|
|
458
|
+
const result = await updateProject(
|
|
459
|
+
{
|
|
460
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
461
|
+
git_workflow: 'github-flow',
|
|
462
|
+
git_main_branch: 'main',
|
|
463
|
+
git_auto_branch: true,
|
|
464
|
+
},
|
|
465
|
+
ctx
|
|
466
|
+
);
|
|
467
|
+
|
|
468
|
+
expect(result.result).toMatchObject({
|
|
469
|
+
success: true,
|
|
470
|
+
});
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
it('should throw error when update fails', async () => {
|
|
474
|
+
const supabase = createMockSupabase({
|
|
475
|
+
updateResult: { data: null, error: { message: 'Update failed' } },
|
|
476
|
+
});
|
|
477
|
+
const ctx = createMockContext(supabase);
|
|
478
|
+
|
|
479
|
+
await expect(
|
|
480
|
+
updateProject({
|
|
481
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
482
|
+
name: 'Test',
|
|
483
|
+
}, ctx)
|
|
484
|
+
).rejects.toThrow('Failed to update project: Update failed');
|
|
485
|
+
});
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
// ============================================================================
|
|
489
|
+
// updateProjectReadme Tests
|
|
490
|
+
// ============================================================================
|
|
491
|
+
|
|
492
|
+
describe('updateProjectReadme', () => {
|
|
493
|
+
beforeEach(() => vi.clearAllMocks());
|
|
494
|
+
|
|
495
|
+
it('should throw error for missing project_id', async () => {
|
|
496
|
+
const supabase = createMockSupabase();
|
|
497
|
+
const ctx = createMockContext(supabase);
|
|
498
|
+
|
|
499
|
+
await expect(
|
|
500
|
+
updateProjectReadme({ readme_content: '# README' }, ctx)
|
|
501
|
+
).rejects.toThrow(ValidationError);
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
it('should throw error for missing readme_content', async () => {
|
|
505
|
+
const supabase = createMockSupabase();
|
|
506
|
+
const ctx = createMockContext(supabase);
|
|
507
|
+
|
|
508
|
+
await expect(
|
|
509
|
+
updateProjectReadme({ project_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx)
|
|
510
|
+
).rejects.toThrow(ValidationError);
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
it('should throw error for invalid project_id UUID', async () => {
|
|
514
|
+
const supabase = createMockSupabase();
|
|
515
|
+
const ctx = createMockContext(supabase);
|
|
516
|
+
|
|
517
|
+
await expect(
|
|
518
|
+
updateProjectReadme({
|
|
519
|
+
project_id: 'invalid',
|
|
520
|
+
readme_content: '# README',
|
|
521
|
+
}, ctx)
|
|
522
|
+
).rejects.toThrow(ValidationError);
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
it('should update readme successfully', async () => {
|
|
526
|
+
const supabase = createMockSupabase({
|
|
527
|
+
updateResult: { data: null, error: null },
|
|
528
|
+
});
|
|
529
|
+
const ctx = createMockContext(supabase);
|
|
530
|
+
|
|
531
|
+
const result = await updateProjectReadme(
|
|
532
|
+
{
|
|
533
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
534
|
+
readme_content: '# My Project\n\nDescription here.',
|
|
535
|
+
},
|
|
536
|
+
ctx
|
|
537
|
+
);
|
|
538
|
+
|
|
539
|
+
expect(result.result).toMatchObject({
|
|
540
|
+
success: true,
|
|
541
|
+
});
|
|
542
|
+
expect(supabase.update).toHaveBeenCalledWith(
|
|
543
|
+
expect.objectContaining({
|
|
544
|
+
readme_content: '# My Project\n\nDescription here.',
|
|
545
|
+
})
|
|
546
|
+
);
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
it('should throw error when update fails', async () => {
|
|
550
|
+
const supabase = createMockSupabase({
|
|
551
|
+
updateResult: { data: null, error: { message: 'Update failed' } },
|
|
552
|
+
});
|
|
553
|
+
const ctx = createMockContext(supabase);
|
|
554
|
+
|
|
555
|
+
await expect(
|
|
556
|
+
updateProjectReadme({
|
|
557
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
558
|
+
readme_content: '# README',
|
|
559
|
+
}, ctx)
|
|
560
|
+
).rejects.toThrow('Failed to update README: Update failed');
|
|
561
|
+
});
|
|
562
|
+
});
|