@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,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decisions Handlers
|
|
3
|
+
*
|
|
4
|
+
* Handles architectural/technical decisions:
|
|
5
|
+
* - log_decision
|
|
6
|
+
* - get_decisions
|
|
7
|
+
* - delete_decision
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { Handler, HandlerRegistry } from './types.js';
|
|
11
|
+
import { validateRequired, validateUUID } from '../validators.js';
|
|
12
|
+
|
|
13
|
+
export const logDecision: Handler = async (args, ctx) => {
|
|
14
|
+
const { project_id, title, description, rationale, alternatives_considered } = args as {
|
|
15
|
+
project_id: string;
|
|
16
|
+
title: string;
|
|
17
|
+
description: string;
|
|
18
|
+
rationale?: string;
|
|
19
|
+
alternatives_considered?: string[];
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
validateRequired(project_id, 'project_id');
|
|
23
|
+
validateUUID(project_id, 'project_id');
|
|
24
|
+
validateRequired(title, 'title');
|
|
25
|
+
validateRequired(description, 'description');
|
|
26
|
+
|
|
27
|
+
const { supabase, session } = ctx;
|
|
28
|
+
|
|
29
|
+
const { error } = await supabase
|
|
30
|
+
.from('decisions')
|
|
31
|
+
.insert({
|
|
32
|
+
project_id,
|
|
33
|
+
title,
|
|
34
|
+
description,
|
|
35
|
+
rationale: rationale || null,
|
|
36
|
+
alternatives_considered: alternatives_considered || null,
|
|
37
|
+
created_by: 'agent',
|
|
38
|
+
created_by_session_id: session.currentSessionId,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
if (error) throw new Error(`Failed to log decision: ${error.message}`);
|
|
42
|
+
|
|
43
|
+
return { result: { success: true, title } };
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const getDecisions: Handler = async (args, ctx) => {
|
|
47
|
+
const { project_id } = args as { project_id: string };
|
|
48
|
+
|
|
49
|
+
validateRequired(project_id, 'project_id');
|
|
50
|
+
validateUUID(project_id, 'project_id');
|
|
51
|
+
|
|
52
|
+
const { data, error } = await ctx.supabase
|
|
53
|
+
.from('decisions')
|
|
54
|
+
.select('id, title, description, rationale, created_at')
|
|
55
|
+
.eq('project_id', project_id)
|
|
56
|
+
.order('created_at', { ascending: false });
|
|
57
|
+
|
|
58
|
+
if (error) throw new Error(`Failed to fetch decisions: ${error.message}`);
|
|
59
|
+
|
|
60
|
+
return { result: { decisions: data || [] } };
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const deleteDecision: Handler = async (args, ctx) => {
|
|
64
|
+
const { decision_id } = args as { decision_id: string };
|
|
65
|
+
|
|
66
|
+
validateRequired(decision_id, 'decision_id');
|
|
67
|
+
validateUUID(decision_id, 'decision_id');
|
|
68
|
+
|
|
69
|
+
const { error } = await ctx.supabase
|
|
70
|
+
.from('decisions')
|
|
71
|
+
.delete()
|
|
72
|
+
.eq('id', decision_id);
|
|
73
|
+
|
|
74
|
+
if (error) throw new Error(`Failed to delete decision: ${error.message}`);
|
|
75
|
+
|
|
76
|
+
return { result: { success: true } };
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Decisions handlers registry
|
|
81
|
+
*/
|
|
82
|
+
export const decisionHandlers: HandlerRegistry = {
|
|
83
|
+
log_decision: logDecision,
|
|
84
|
+
get_decisions: getDecisions,
|
|
85
|
+
delete_decision: deleteDecision,
|
|
86
|
+
};
|
|
@@ -0,0 +1,516 @@
|
|
|
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
|
+
claimDeploymentValidation,
|
|
6
|
+
reportValidation,
|
|
7
|
+
checkDeploymentStatus,
|
|
8
|
+
startDeployment,
|
|
9
|
+
completeDeployment,
|
|
10
|
+
cancelDeployment,
|
|
11
|
+
addDeploymentRequirement,
|
|
12
|
+
getDeploymentRequirements,
|
|
13
|
+
} from './deployment.js';
|
|
14
|
+
import { ValidationError } from '../validators.js';
|
|
15
|
+
|
|
16
|
+
// ============================================================================
|
|
17
|
+
// Test Utilities
|
|
18
|
+
// ============================================================================
|
|
19
|
+
|
|
20
|
+
function createMockSupabase(overrides: {
|
|
21
|
+
selectResult?: { data: unknown; error: unknown };
|
|
22
|
+
insertResult?: { data: unknown; error: unknown };
|
|
23
|
+
updateResult?: { data: unknown; error: unknown };
|
|
24
|
+
} = {}) {
|
|
25
|
+
const defaultResult = { data: null, error: null };
|
|
26
|
+
let currentOperation = 'select';
|
|
27
|
+
let insertThenSelect = false;
|
|
28
|
+
|
|
29
|
+
const mock = {
|
|
30
|
+
from: vi.fn().mockReturnThis(),
|
|
31
|
+
select: vi.fn(() => {
|
|
32
|
+
if (currentOperation === 'insert') {
|
|
33
|
+
insertThenSelect = true;
|
|
34
|
+
} else {
|
|
35
|
+
currentOperation = 'select';
|
|
36
|
+
insertThenSelect = false;
|
|
37
|
+
}
|
|
38
|
+
return mock;
|
|
39
|
+
}),
|
|
40
|
+
insert: vi.fn(() => {
|
|
41
|
+
currentOperation = 'insert';
|
|
42
|
+
insertThenSelect = false;
|
|
43
|
+
return mock;
|
|
44
|
+
}),
|
|
45
|
+
update: vi.fn(() => {
|
|
46
|
+
currentOperation = 'update';
|
|
47
|
+
insertThenSelect = false;
|
|
48
|
+
return mock;
|
|
49
|
+
}),
|
|
50
|
+
delete: vi.fn(() => {
|
|
51
|
+
currentOperation = 'delete';
|
|
52
|
+
insertThenSelect = false;
|
|
53
|
+
return mock;
|
|
54
|
+
}),
|
|
55
|
+
eq: vi.fn().mockReturnThis(),
|
|
56
|
+
neq: vi.fn().mockReturnThis(),
|
|
57
|
+
in: vi.fn().mockReturnThis(),
|
|
58
|
+
is: vi.fn().mockReturnThis(),
|
|
59
|
+
not: vi.fn().mockReturnThis(),
|
|
60
|
+
or: vi.fn().mockReturnThis(),
|
|
61
|
+
gte: vi.fn().mockReturnThis(),
|
|
62
|
+
lte: vi.fn().mockReturnThis(),
|
|
63
|
+
lt: vi.fn().mockReturnThis(),
|
|
64
|
+
order: vi.fn().mockReturnThis(),
|
|
65
|
+
limit: vi.fn().mockReturnThis(),
|
|
66
|
+
single: vi.fn(() => {
|
|
67
|
+
if (currentOperation === 'insert' || insertThenSelect) {
|
|
68
|
+
return Promise.resolve(overrides.insertResult ?? defaultResult);
|
|
69
|
+
}
|
|
70
|
+
if (currentOperation === 'select') {
|
|
71
|
+
return Promise.resolve(overrides.selectResult ?? defaultResult);
|
|
72
|
+
}
|
|
73
|
+
if (currentOperation === 'update') {
|
|
74
|
+
return Promise.resolve(overrides.updateResult ?? defaultResult);
|
|
75
|
+
}
|
|
76
|
+
return Promise.resolve(defaultResult);
|
|
77
|
+
}),
|
|
78
|
+
maybeSingle: vi.fn(() => {
|
|
79
|
+
return Promise.resolve(overrides.selectResult ?? defaultResult);
|
|
80
|
+
}),
|
|
81
|
+
then: vi.fn((resolve: (value: unknown) => void) => {
|
|
82
|
+
if (currentOperation === 'insert' || insertThenSelect) {
|
|
83
|
+
return Promise.resolve(overrides.insertResult ?? defaultResult).then(resolve);
|
|
84
|
+
}
|
|
85
|
+
if (currentOperation === 'select') {
|
|
86
|
+
return Promise.resolve(overrides.selectResult ?? defaultResult).then(resolve);
|
|
87
|
+
}
|
|
88
|
+
if (currentOperation === 'update') {
|
|
89
|
+
return Promise.resolve(overrides.updateResult ?? defaultResult).then(resolve);
|
|
90
|
+
}
|
|
91
|
+
return Promise.resolve(defaultResult).then(resolve);
|
|
92
|
+
}),
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
return mock as unknown as SupabaseClient;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function createMockContext(
|
|
99
|
+
supabase: SupabaseClient,
|
|
100
|
+
options: { sessionId?: string | null } = {}
|
|
101
|
+
): HandlerContext {
|
|
102
|
+
const defaultTokenUsage: TokenUsage = {
|
|
103
|
+
callCount: 5,
|
|
104
|
+
totalTokens: 2500,
|
|
105
|
+
byTool: {},
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const sessionId = 'sessionId' in options ? options.sessionId : 'session-123';
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
supabase,
|
|
112
|
+
auth: { userId: 'user-123', apiKeyId: 'api-key-123' },
|
|
113
|
+
session: {
|
|
114
|
+
instanceId: 'instance-abc',
|
|
115
|
+
currentSessionId: sessionId,
|
|
116
|
+
currentPersona: 'Wave',
|
|
117
|
+
tokenUsage: defaultTokenUsage,
|
|
118
|
+
},
|
|
119
|
+
updateSession: vi.fn(),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ============================================================================
|
|
124
|
+
// claimDeploymentValidation Tests
|
|
125
|
+
// ============================================================================
|
|
126
|
+
|
|
127
|
+
describe('claimDeploymentValidation', () => {
|
|
128
|
+
beforeEach(() => vi.clearAllMocks());
|
|
129
|
+
|
|
130
|
+
it('should throw error for missing project_id', async () => {
|
|
131
|
+
const supabase = createMockSupabase();
|
|
132
|
+
const ctx = createMockContext(supabase);
|
|
133
|
+
|
|
134
|
+
await expect(claimDeploymentValidation({}, ctx)).rejects.toThrow(ValidationError);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should throw error for invalid project_id UUID', async () => {
|
|
138
|
+
const supabase = createMockSupabase();
|
|
139
|
+
const ctx = createMockContext(supabase);
|
|
140
|
+
|
|
141
|
+
await expect(
|
|
142
|
+
claimDeploymentValidation({ project_id: 'invalid' }, ctx)
|
|
143
|
+
).rejects.toThrow(ValidationError);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('should return error when no pending deployment', async () => {
|
|
147
|
+
const supabase = createMockSupabase({
|
|
148
|
+
selectResult: { data: null, error: { message: 'Not found' } },
|
|
149
|
+
});
|
|
150
|
+
const ctx = createMockContext(supabase);
|
|
151
|
+
|
|
152
|
+
const result = await claimDeploymentValidation(
|
|
153
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
154
|
+
ctx
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
expect(result.result).toMatchObject({
|
|
158
|
+
success: false,
|
|
159
|
+
error: 'No pending deployment found',
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// ============================================================================
|
|
165
|
+
// reportValidation Tests
|
|
166
|
+
// ============================================================================
|
|
167
|
+
|
|
168
|
+
describe('reportValidation', () => {
|
|
169
|
+
beforeEach(() => vi.clearAllMocks());
|
|
170
|
+
|
|
171
|
+
it('should throw error for missing project_id', async () => {
|
|
172
|
+
const supabase = createMockSupabase();
|
|
173
|
+
const ctx = createMockContext(supabase);
|
|
174
|
+
|
|
175
|
+
await expect(
|
|
176
|
+
reportValidation({ build_passed: true }, ctx)
|
|
177
|
+
).rejects.toThrow(ValidationError);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('should throw error for missing build_passed', async () => {
|
|
181
|
+
const supabase = createMockSupabase();
|
|
182
|
+
const ctx = createMockContext(supabase);
|
|
183
|
+
|
|
184
|
+
await expect(
|
|
185
|
+
reportValidation({ project_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx)
|
|
186
|
+
).rejects.toThrow(ValidationError);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('should return error when no deployment being validated', async () => {
|
|
190
|
+
const supabase = createMockSupabase({
|
|
191
|
+
selectResult: { data: null, error: { message: 'Not found' } },
|
|
192
|
+
});
|
|
193
|
+
const ctx = createMockContext(supabase);
|
|
194
|
+
|
|
195
|
+
const result = await reportValidation(
|
|
196
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000', build_passed: true },
|
|
197
|
+
ctx
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
expect(result.result).toMatchObject({
|
|
201
|
+
success: false,
|
|
202
|
+
error: 'No deployment being validated. Use claim_deployment_validation first.',
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// ============================================================================
|
|
208
|
+
// checkDeploymentStatus Tests
|
|
209
|
+
// ============================================================================
|
|
210
|
+
|
|
211
|
+
describe('checkDeploymentStatus', () => {
|
|
212
|
+
beforeEach(() => vi.clearAllMocks());
|
|
213
|
+
|
|
214
|
+
it('should throw error for missing project_id', async () => {
|
|
215
|
+
const supabase = createMockSupabase();
|
|
216
|
+
const ctx = createMockContext(supabase);
|
|
217
|
+
|
|
218
|
+
await expect(checkDeploymentStatus({}, ctx)).rejects.toThrow(ValidationError);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('should return no deployment when none found', async () => {
|
|
222
|
+
const supabase = createMockSupabase({
|
|
223
|
+
selectResult: { data: null, error: { message: 'Not found' } },
|
|
224
|
+
});
|
|
225
|
+
const ctx = createMockContext(supabase);
|
|
226
|
+
|
|
227
|
+
const result = await checkDeploymentStatus(
|
|
228
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
229
|
+
ctx
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
expect(result.result).toMatchObject({
|
|
233
|
+
has_deployment: false,
|
|
234
|
+
message: 'No deployments found for this project',
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('should return deployment details when found', async () => {
|
|
239
|
+
const mockDeployment = {
|
|
240
|
+
id: 'deploy-1',
|
|
241
|
+
status: 'deployed',
|
|
242
|
+
environment: 'production',
|
|
243
|
+
requested_by: 'agent',
|
|
244
|
+
build_passed: true,
|
|
245
|
+
tests_passed: true,
|
|
246
|
+
created_at: '2025-01-14T10:00:00Z',
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
const supabase = createMockSupabase({
|
|
250
|
+
selectResult: { data: mockDeployment, error: null },
|
|
251
|
+
});
|
|
252
|
+
const ctx = createMockContext(supabase);
|
|
253
|
+
|
|
254
|
+
const result = await checkDeploymentStatus(
|
|
255
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
256
|
+
ctx
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
expect(result.result).toMatchObject({
|
|
260
|
+
has_deployment: true,
|
|
261
|
+
});
|
|
262
|
+
expect((result.result as { deployment: { id: string } }).deployment.id).toBe('deploy-1');
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
// ============================================================================
|
|
267
|
+
// startDeployment Tests
|
|
268
|
+
// ============================================================================
|
|
269
|
+
|
|
270
|
+
describe('startDeployment', () => {
|
|
271
|
+
beforeEach(() => vi.clearAllMocks());
|
|
272
|
+
|
|
273
|
+
it('should throw error for missing project_id', async () => {
|
|
274
|
+
const supabase = createMockSupabase();
|
|
275
|
+
const ctx = createMockContext(supabase);
|
|
276
|
+
|
|
277
|
+
await expect(startDeployment({}, ctx)).rejects.toThrow(ValidationError);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it('should return error when no deployment ready', async () => {
|
|
281
|
+
const supabase = createMockSupabase({
|
|
282
|
+
selectResult: { data: null, error: { message: 'Not found' } },
|
|
283
|
+
});
|
|
284
|
+
const ctx = createMockContext(supabase);
|
|
285
|
+
|
|
286
|
+
const result = await startDeployment(
|
|
287
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
288
|
+
ctx
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
expect(result.result).toMatchObject({
|
|
292
|
+
success: false,
|
|
293
|
+
error: 'No deployment ready. Must pass validation first.',
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// ============================================================================
|
|
299
|
+
// completeDeployment Tests
|
|
300
|
+
// ============================================================================
|
|
301
|
+
|
|
302
|
+
describe('completeDeployment', () => {
|
|
303
|
+
beforeEach(() => vi.clearAllMocks());
|
|
304
|
+
|
|
305
|
+
it('should throw error for missing project_id', async () => {
|
|
306
|
+
const supabase = createMockSupabase();
|
|
307
|
+
const ctx = createMockContext(supabase);
|
|
308
|
+
|
|
309
|
+
await expect(
|
|
310
|
+
completeDeployment({ success: true }, ctx)
|
|
311
|
+
).rejects.toThrow(ValidationError);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it('should throw error for missing success', async () => {
|
|
315
|
+
const supabase = createMockSupabase();
|
|
316
|
+
const ctx = createMockContext(supabase);
|
|
317
|
+
|
|
318
|
+
await expect(
|
|
319
|
+
completeDeployment({ project_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx)
|
|
320
|
+
).rejects.toThrow(ValidationError);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it('should return error when no deployment in progress', async () => {
|
|
324
|
+
const supabase = createMockSupabase({
|
|
325
|
+
selectResult: { data: null, error: { message: 'Not found' } },
|
|
326
|
+
});
|
|
327
|
+
const ctx = createMockContext(supabase);
|
|
328
|
+
|
|
329
|
+
const result = await completeDeployment(
|
|
330
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000', success: true },
|
|
331
|
+
ctx
|
|
332
|
+
);
|
|
333
|
+
|
|
334
|
+
expect(result.result).toMatchObject({
|
|
335
|
+
success: false,
|
|
336
|
+
error: 'No deployment in progress. Use start_deployment first.',
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
// ============================================================================
|
|
342
|
+
// cancelDeployment Tests
|
|
343
|
+
// ============================================================================
|
|
344
|
+
|
|
345
|
+
describe('cancelDeployment', () => {
|
|
346
|
+
beforeEach(() => vi.clearAllMocks());
|
|
347
|
+
|
|
348
|
+
it('should throw error for missing project_id', async () => {
|
|
349
|
+
const supabase = createMockSupabase();
|
|
350
|
+
const ctx = createMockContext(supabase);
|
|
351
|
+
|
|
352
|
+
await expect(cancelDeployment({}, ctx)).rejects.toThrow(ValidationError);
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
it('should return error when no active deployment', async () => {
|
|
356
|
+
const supabase = createMockSupabase({
|
|
357
|
+
selectResult: { data: null, error: { message: 'Not found' } },
|
|
358
|
+
});
|
|
359
|
+
const ctx = createMockContext(supabase);
|
|
360
|
+
|
|
361
|
+
const result = await cancelDeployment(
|
|
362
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
363
|
+
ctx
|
|
364
|
+
);
|
|
365
|
+
|
|
366
|
+
expect(result.result).toMatchObject({
|
|
367
|
+
success: false,
|
|
368
|
+
error: 'No active deployment',
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
// ============================================================================
|
|
374
|
+
// addDeploymentRequirement Tests
|
|
375
|
+
// ============================================================================
|
|
376
|
+
|
|
377
|
+
describe('addDeploymentRequirement', () => {
|
|
378
|
+
beforeEach(() => vi.clearAllMocks());
|
|
379
|
+
|
|
380
|
+
it('should throw error for missing project_id', async () => {
|
|
381
|
+
const supabase = createMockSupabase();
|
|
382
|
+
const ctx = createMockContext(supabase);
|
|
383
|
+
|
|
384
|
+
await expect(
|
|
385
|
+
addDeploymentRequirement({ type: 'migration', title: 'Test' }, ctx)
|
|
386
|
+
).rejects.toThrow(ValidationError);
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
it('should throw error for missing type', async () => {
|
|
390
|
+
const supabase = createMockSupabase();
|
|
391
|
+
const ctx = createMockContext(supabase);
|
|
392
|
+
|
|
393
|
+
await expect(
|
|
394
|
+
addDeploymentRequirement({
|
|
395
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
396
|
+
title: 'Test',
|
|
397
|
+
}, ctx)
|
|
398
|
+
).rejects.toThrow(ValidationError);
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
it('should throw error for missing title', async () => {
|
|
402
|
+
const supabase = createMockSupabase();
|
|
403
|
+
const ctx = createMockContext(supabase);
|
|
404
|
+
|
|
405
|
+
await expect(
|
|
406
|
+
addDeploymentRequirement({
|
|
407
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
408
|
+
type: 'migration',
|
|
409
|
+
}, ctx)
|
|
410
|
+
).rejects.toThrow(ValidationError);
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
it('should throw error for invalid type', async () => {
|
|
414
|
+
const supabase = createMockSupabase();
|
|
415
|
+
const ctx = createMockContext(supabase);
|
|
416
|
+
|
|
417
|
+
await expect(
|
|
418
|
+
addDeploymentRequirement({
|
|
419
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
420
|
+
type: 'invalid_type',
|
|
421
|
+
title: 'Test',
|
|
422
|
+
}, ctx)
|
|
423
|
+
).rejects.toThrow(ValidationError);
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
it('should throw error for invalid stage', async () => {
|
|
427
|
+
const supabase = createMockSupabase();
|
|
428
|
+
const ctx = createMockContext(supabase);
|
|
429
|
+
|
|
430
|
+
await expect(
|
|
431
|
+
addDeploymentRequirement({
|
|
432
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
433
|
+
type: 'migration',
|
|
434
|
+
title: 'Test',
|
|
435
|
+
stage: 'invalid_stage',
|
|
436
|
+
}, ctx)
|
|
437
|
+
).rejects.toThrow(ValidationError);
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
it('should add requirement successfully', async () => {
|
|
441
|
+
const supabase = createMockSupabase({
|
|
442
|
+
insertResult: {
|
|
443
|
+
data: { id: 'req-1', type: 'migration', title: 'Test Migration', stage: 'preparation', blocking: false },
|
|
444
|
+
error: null,
|
|
445
|
+
},
|
|
446
|
+
});
|
|
447
|
+
const ctx = createMockContext(supabase);
|
|
448
|
+
|
|
449
|
+
const result = await addDeploymentRequirement(
|
|
450
|
+
{
|
|
451
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
452
|
+
type: 'migration',
|
|
453
|
+
title: 'Test Migration',
|
|
454
|
+
},
|
|
455
|
+
ctx
|
|
456
|
+
);
|
|
457
|
+
|
|
458
|
+
expect(result.result).toMatchObject({
|
|
459
|
+
success: true,
|
|
460
|
+
requirement_id: 'req-1',
|
|
461
|
+
stage: 'preparation',
|
|
462
|
+
});
|
|
463
|
+
});
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
// ============================================================================
|
|
467
|
+
// getDeploymentRequirements Tests
|
|
468
|
+
// ============================================================================
|
|
469
|
+
|
|
470
|
+
describe('getDeploymentRequirements', () => {
|
|
471
|
+
beforeEach(() => vi.clearAllMocks());
|
|
472
|
+
|
|
473
|
+
it('should throw error for missing project_id', async () => {
|
|
474
|
+
const supabase = createMockSupabase();
|
|
475
|
+
const ctx = createMockContext(supabase);
|
|
476
|
+
|
|
477
|
+
await expect(getDeploymentRequirements({}, ctx)).rejects.toThrow(ValidationError);
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
it('should return empty list when no requirements', async () => {
|
|
481
|
+
const supabase = createMockSupabase({
|
|
482
|
+
selectResult: { data: [], error: null },
|
|
483
|
+
});
|
|
484
|
+
const ctx = createMockContext(supabase);
|
|
485
|
+
|
|
486
|
+
// Need to mock the .then() to return the array result
|
|
487
|
+
vi.mocked(supabase.from('').select).mockReturnValue({
|
|
488
|
+
...supabase,
|
|
489
|
+
then: (resolve: (val: unknown) => void) =>
|
|
490
|
+
Promise.resolve({ data: [], error: null }).then(resolve),
|
|
491
|
+
} as unknown as ReturnType<SupabaseClient['from']>);
|
|
492
|
+
|
|
493
|
+
const result = await getDeploymentRequirements(
|
|
494
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
495
|
+
ctx
|
|
496
|
+
);
|
|
497
|
+
|
|
498
|
+
expect(result.result).toHaveProperty('requirements');
|
|
499
|
+
expect(result.result).toHaveProperty('deployment_blocked');
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
it('should query with correct project_id', async () => {
|
|
503
|
+
const supabase = createMockSupabase({
|
|
504
|
+
selectResult: { data: [], error: null },
|
|
505
|
+
});
|
|
506
|
+
const ctx = createMockContext(supabase);
|
|
507
|
+
|
|
508
|
+
await getDeploymentRequirements(
|
|
509
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
510
|
+
ctx
|
|
511
|
+
);
|
|
512
|
+
|
|
513
|
+
expect(supabase.from).toHaveBeenCalledWith('deployment_requirements');
|
|
514
|
+
expect(supabase.eq).toHaveBeenCalled();
|
|
515
|
+
});
|
|
516
|
+
});
|