@vibescope/mcp-server 0.4.5 → 0.4.7
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/CHANGELOG.md +84 -84
- package/README.md +194 -194
- package/dist/api-client/project.d.ts +1 -0
- package/dist/api-client.d.ts +4 -1
- package/dist/api-client.js +24 -7
- package/dist/cli-init.js +25 -24
- package/dist/cli.js +26 -26
- package/dist/handlers/chat.d.ts +2 -0
- package/dist/handlers/chat.js +25 -0
- package/dist/handlers/discovery.js +12 -0
- package/dist/handlers/project.js +4 -2
- package/dist/handlers/tool-docs.js +1203 -1137
- package/dist/handlers/version.js +1 -1
- package/dist/index.js +159 -87
- package/dist/setup.js +13 -7
- package/dist/templates/agent-guidelines.d.ts +1 -1
- package/dist/templates/agent-guidelines.js +205 -187
- package/dist/templates/help-content.js +1621 -1621
- package/dist/tools/bodies-of-work.js +6 -6
- package/dist/tools/chat.d.ts +1 -0
- package/dist/tools/chat.js +24 -0
- package/dist/tools/cloud-agents.js +22 -22
- package/dist/tools/features.d.ts +13 -0
- package/dist/tools/features.js +151 -0
- package/dist/tools/index.d.ts +3 -1
- package/dist/tools/index.js +4 -1
- package/dist/tools/milestones.js +2 -2
- package/dist/tools/project.js +4 -0
- package/dist/tools/requests.js +1 -1
- package/dist/tools/session.js +11 -11
- package/dist/tools/sprints.js +9 -9
- package/dist/tools/tasks.js +35 -35
- package/dist/tools/worktrees.js +14 -14
- package/dist/tools.d.ts +2 -0
- package/dist/tools.js +3602 -0
- package/dist/utils.js +11 -11
- package/dist/version.d.ts +9 -3
- package/dist/version.js +56 -8
- package/docs/TOOLS.md +2663 -2559
- package/package.json +53 -53
- package/scripts/generate-docs.ts +212 -212
- package/scripts/version-bump.ts +203 -203
- package/src/api-client/blockers.ts +86 -86
- package/src/api-client/bodies-of-work.ts +194 -194
- package/src/api-client/chat.ts +50 -50
- package/src/api-client/connectors.ts +152 -152
- package/src/api-client/cost.ts +185 -185
- package/src/api-client/decisions.ts +87 -87
- package/src/api-client/deployment.ts +313 -313
- package/src/api-client/discovery.ts +81 -81
- package/src/api-client/fallback.ts +52 -52
- package/src/api-client/file-checkouts.ts +115 -115
- package/src/api-client/findings.ts +100 -100
- package/src/api-client/git-issues.ts +88 -88
- package/src/api-client/ideas.ts +112 -112
- package/src/api-client/index.ts +592 -592
- package/src/api-client/milestones.ts +83 -83
- package/src/api-client/organizations.ts +185 -185
- package/src/api-client/progress.ts +94 -94
- package/src/api-client/project.ts +180 -179
- package/src/api-client/requests.ts +54 -54
- package/src/api-client/session.ts +220 -220
- package/src/api-client/sprints.ts +227 -227
- package/src/api-client/subtasks.ts +57 -57
- package/src/api-client/tasks.ts +450 -450
- package/src/api-client/types.ts +32 -32
- package/src/api-client/validation.ts +60 -60
- package/src/api-client/worktrees.ts +53 -53
- package/src/api-client.test.ts +847 -847
- package/src/api-client.ts +2723 -2706
- package/src/cli-init.ts +558 -557
- package/src/cli.test.ts +284 -284
- package/src/cli.ts +204 -204
- package/src/handlers/__test-setup__.ts +240 -240
- package/src/handlers/__test-utils__.ts +89 -89
- package/src/handlers/blockers.test.ts +468 -468
- package/src/handlers/blockers.ts +172 -172
- package/src/handlers/bodies-of-work.test.ts +704 -704
- package/src/handlers/bodies-of-work.ts +526 -526
- package/src/handlers/chat.test.ts +185 -185
- package/src/handlers/chat.ts +101 -69
- package/src/handlers/cloud-agents.test.ts +438 -438
- package/src/handlers/cloud-agents.ts +156 -156
- package/src/handlers/connectors.test.ts +834 -834
- package/src/handlers/connectors.ts +229 -229
- package/src/handlers/cost.test.ts +462 -462
- package/src/handlers/cost.ts +285 -285
- package/src/handlers/decisions.test.ts +382 -382
- package/src/handlers/decisions.ts +153 -153
- package/src/handlers/deployment.test.ts +551 -551
- package/src/handlers/deployment.ts +570 -570
- package/src/handlers/discovery.test.ts +206 -206
- package/src/handlers/discovery.ts +427 -415
- package/src/handlers/fallback.test.ts +537 -537
- package/src/handlers/fallback.ts +194 -194
- package/src/handlers/file-checkouts.test.ts +750 -750
- package/src/handlers/file-checkouts.ts +185 -185
- package/src/handlers/findings.test.ts +633 -633
- package/src/handlers/findings.ts +239 -239
- package/src/handlers/git-issues.test.ts +631 -631
- package/src/handlers/git-issues.ts +136 -136
- package/src/handlers/ideas.test.ts +644 -644
- package/src/handlers/ideas.ts +207 -207
- package/src/handlers/index.ts +93 -93
- package/src/handlers/milestones.test.ts +475 -475
- package/src/handlers/milestones.ts +180 -180
- package/src/handlers/organizations.test.ts +826 -826
- package/src/handlers/organizations.ts +315 -315
- package/src/handlers/progress.test.ts +269 -269
- package/src/handlers/progress.ts +77 -77
- package/src/handlers/project.test.ts +546 -546
- package/src/handlers/project.ts +242 -239
- package/src/handlers/requests.test.ts +303 -303
- package/src/handlers/requests.ts +99 -99
- package/src/handlers/roles.test.ts +305 -305
- package/src/handlers/roles.ts +219 -219
- package/src/handlers/session.test.ts +998 -998
- package/src/handlers/session.ts +1105 -1105
- package/src/handlers/sprints.test.ts +732 -732
- package/src/handlers/sprints.ts +537 -537
- package/src/handlers/tasks.test.ts +931 -931
- package/src/handlers/tasks.ts +1133 -1133
- package/src/handlers/tool-categories.test.ts +66 -66
- package/src/handlers/tool-docs.test.ts +511 -511
- package/src/handlers/tool-docs.ts +1571 -1499
- package/src/handlers/types.test.ts +259 -259
- package/src/handlers/types.ts +176 -176
- package/src/handlers/validation.test.ts +582 -582
- package/src/handlers/validation.ts +164 -164
- package/src/handlers/version.ts +63 -63
- package/src/index.test.ts +674 -674
- package/src/index.ts +884 -807
- package/src/setup.test.ts +243 -233
- package/src/setup.ts +410 -404
- package/src/templates/agent-guidelines.ts +233 -215
- package/src/templates/help-content.ts +1751 -1751
- package/src/token-tracking.test.ts +463 -463
- package/src/token-tracking.ts +167 -167
- package/src/tools/blockers.ts +122 -122
- package/src/tools/bodies-of-work.ts +283 -283
- package/src/tools/chat.ts +72 -46
- package/src/tools/cloud-agents.ts +101 -101
- package/src/tools/connectors.ts +191 -191
- package/src/tools/cost.ts +111 -111
- package/src/tools/decisions.ts +111 -111
- package/src/tools/deployment.ts +455 -455
- package/src/tools/discovery.ts +76 -76
- package/src/tools/fallback.ts +111 -111
- package/src/tools/features.ts +154 -0
- package/src/tools/file-checkouts.ts +145 -145
- package/src/tools/findings.ts +101 -101
- package/src/tools/git-issues.ts +130 -130
- package/src/tools/ideas.ts +162 -162
- package/src/tools/index.ts +141 -137
- package/src/tools/milestones.ts +118 -118
- package/src/tools/organizations.ts +224 -224
- package/src/tools/progress.ts +73 -73
- package/src/tools/project.ts +206 -202
- package/src/tools/requests.ts +68 -68
- package/src/tools/roles.ts +112 -112
- package/src/tools/session.ts +181 -181
- package/src/tools/sprints.ts +298 -298
- package/src/tools/tasks.ts +550 -550
- package/src/tools/tools.test.ts +222 -222
- package/src/tools/types.ts +9 -9
- package/src/tools/validation.ts +75 -75
- package/src/tools/version.ts +34 -34
- package/src/tools/worktrees.ts +66 -66
- package/src/tools.test.ts +416 -416
- package/src/utils.test.ts +1014 -1014
- package/src/utils.ts +586 -586
- package/src/validators.test.ts +223 -223
- package/src/validators.ts +249 -249
- package/src/version.ts +162 -109
- package/tsconfig.json +16 -16
- package/vitest.config.ts +14 -14
|
@@ -1,551 +1,551 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import {
|
|
3
|
-
claimDeploymentValidation,
|
|
4
|
-
reportValidation,
|
|
5
|
-
checkDeploymentStatus,
|
|
6
|
-
startDeployment,
|
|
7
|
-
completeDeployment,
|
|
8
|
-
cancelDeployment,
|
|
9
|
-
addDeploymentRequirement,
|
|
10
|
-
getDeploymentRequirements,
|
|
11
|
-
getDeploymentRequirementsStats,
|
|
12
|
-
} from './deployment.js';
|
|
13
|
-
import { ValidationError } from '../validators.js';
|
|
14
|
-
import { createMockContext } from './__test-utils__.js';
|
|
15
|
-
import { mockApiClient } from './__test-setup__.js';
|
|
16
|
-
|
|
17
|
-
// ============================================================================
|
|
18
|
-
// Test Utilities
|
|
19
|
-
// ============================================================================
|
|
20
|
-
|
|
21
|
-
const VALID_UUID = '123e4567-e89b-12d3-a456-426614174000';
|
|
22
|
-
|
|
23
|
-
// ============================================================================
|
|
24
|
-
// claimDeploymentValidation Tests
|
|
25
|
-
// ============================================================================
|
|
26
|
-
|
|
27
|
-
describe('claimDeploymentValidation', () => {
|
|
28
|
-
beforeEach(() => vi.clearAllMocks());
|
|
29
|
-
|
|
30
|
-
it('should throw error for missing project_id', async () => {
|
|
31
|
-
const ctx = createMockContext();
|
|
32
|
-
await expect(claimDeploymentValidation({}, ctx)).rejects.toThrow(ValidationError);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it('should throw error for invalid project_id UUID', async () => {
|
|
36
|
-
const ctx = createMockContext();
|
|
37
|
-
await expect(
|
|
38
|
-
claimDeploymentValidation({ project_id: 'invalid' }, ctx)
|
|
39
|
-
).rejects.toThrow(ValidationError);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it('should return error when no pending deployment', async () => {
|
|
43
|
-
mockApiClient.claimDeploymentValidation.mockResolvedValue({
|
|
44
|
-
ok: true,
|
|
45
|
-
data: { success: false, error: 'No pending deployment found' },
|
|
46
|
-
});
|
|
47
|
-
const ctx = createMockContext();
|
|
48
|
-
|
|
49
|
-
const result = await claimDeploymentValidation({ project_id: VALID_UUID }, ctx);
|
|
50
|
-
|
|
51
|
-
expect(result.result).toMatchObject({
|
|
52
|
-
success: false,
|
|
53
|
-
error: 'No pending deployment found',
|
|
54
|
-
});
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it('should claim deployment successfully', async () => {
|
|
58
|
-
mockApiClient.claimDeploymentValidation.mockResolvedValue({
|
|
59
|
-
ok: true,
|
|
60
|
-
data: { success: true, deployment_id: 'deploy-1' },
|
|
61
|
-
});
|
|
62
|
-
const ctx = createMockContext();
|
|
63
|
-
|
|
64
|
-
const result = await claimDeploymentValidation({ project_id: VALID_UUID }, ctx);
|
|
65
|
-
|
|
66
|
-
expect(result.result).toMatchObject({
|
|
67
|
-
success: true,
|
|
68
|
-
deployment_id: 'deploy-1',
|
|
69
|
-
});
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('should pass session_id to API client', async () => {
|
|
73
|
-
mockApiClient.claimDeploymentValidation.mockResolvedValue({
|
|
74
|
-
ok: true,
|
|
75
|
-
data: { success: true },
|
|
76
|
-
});
|
|
77
|
-
const ctx = createMockContext({ sessionId: 'my-session' });
|
|
78
|
-
|
|
79
|
-
await claimDeploymentValidation({ project_id: VALID_UUID }, ctx);
|
|
80
|
-
|
|
81
|
-
expect(mockApiClient.claimDeploymentValidation).toHaveBeenCalledWith(VALID_UUID, 'my-session');
|
|
82
|
-
});
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
// ============================================================================
|
|
86
|
-
// reportValidation Tests
|
|
87
|
-
// ============================================================================
|
|
88
|
-
|
|
89
|
-
describe('reportValidation', () => {
|
|
90
|
-
beforeEach(() => vi.clearAllMocks());
|
|
91
|
-
|
|
92
|
-
it('should throw error for missing project_id', async () => {
|
|
93
|
-
const ctx = createMockContext();
|
|
94
|
-
await expect(
|
|
95
|
-
reportValidation({ build_passed: true }, ctx)
|
|
96
|
-
).rejects.toThrow(ValidationError);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it('should throw error for missing build_passed', async () => {
|
|
100
|
-
const ctx = createMockContext();
|
|
101
|
-
await expect(
|
|
102
|
-
reportValidation({ project_id: VALID_UUID }, ctx)
|
|
103
|
-
).rejects.toThrow(ValidationError);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it('should return error when no deployment being validated', async () => {
|
|
107
|
-
mockApiClient.reportValidation.mockResolvedValue({
|
|
108
|
-
ok: true,
|
|
109
|
-
data: { success: false, error: 'No deployment being validated. Use claim_deployment_validation first.' },
|
|
110
|
-
});
|
|
111
|
-
const ctx = createMockContext();
|
|
112
|
-
|
|
113
|
-
const result = await reportValidation(
|
|
114
|
-
{ project_id: VALID_UUID, build_passed: true },
|
|
115
|
-
ctx
|
|
116
|
-
);
|
|
117
|
-
|
|
118
|
-
expect(result.result).toMatchObject({
|
|
119
|
-
success: false,
|
|
120
|
-
error: 'No deployment being validated. Use claim_deployment_validation first.',
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
it('should report validation successfully', async () => {
|
|
125
|
-
mockApiClient.reportValidation.mockResolvedValue({
|
|
126
|
-
ok: true,
|
|
127
|
-
data: { success: true, status: 'ready' },
|
|
128
|
-
});
|
|
129
|
-
const ctx = createMockContext();
|
|
130
|
-
|
|
131
|
-
const result = await reportValidation(
|
|
132
|
-
{ project_id: VALID_UUID, build_passed: true, tests_passed: true },
|
|
133
|
-
ctx
|
|
134
|
-
);
|
|
135
|
-
|
|
136
|
-
expect(result.result).toMatchObject({
|
|
137
|
-
success: true,
|
|
138
|
-
status: 'ready',
|
|
139
|
-
});
|
|
140
|
-
});
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
// ============================================================================
|
|
144
|
-
// checkDeploymentStatus Tests
|
|
145
|
-
// ============================================================================
|
|
146
|
-
|
|
147
|
-
describe('checkDeploymentStatus', () => {
|
|
148
|
-
beforeEach(() => vi.clearAllMocks());
|
|
149
|
-
|
|
150
|
-
it('should throw error for missing project_id', async () => {
|
|
151
|
-
const ctx = createMockContext();
|
|
152
|
-
await expect(checkDeploymentStatus({}, ctx)).rejects.toThrow(ValidationError);
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
it('should return no deployment when none found', async () => {
|
|
156
|
-
mockApiClient.checkDeploymentStatus.mockResolvedValue({
|
|
157
|
-
ok: true,
|
|
158
|
-
data: { has_deployment: false, message: 'No deployments found for this project' },
|
|
159
|
-
});
|
|
160
|
-
const ctx = createMockContext();
|
|
161
|
-
|
|
162
|
-
const result = await checkDeploymentStatus({ project_id: VALID_UUID }, ctx);
|
|
163
|
-
|
|
164
|
-
expect(result.result).toMatchObject({
|
|
165
|
-
has_deployment: false,
|
|
166
|
-
message: 'No deployments found for this project',
|
|
167
|
-
});
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
it('should return deployment details when found', async () => {
|
|
171
|
-
const mockDeployment = {
|
|
172
|
-
id: 'deploy-1',
|
|
173
|
-
status: 'deployed',
|
|
174
|
-
environment: 'production',
|
|
175
|
-
};
|
|
176
|
-
mockApiClient.checkDeploymentStatus.mockResolvedValue({
|
|
177
|
-
ok: true,
|
|
178
|
-
data: { has_deployment: true, deployment: mockDeployment },
|
|
179
|
-
});
|
|
180
|
-
const ctx = createMockContext();
|
|
181
|
-
|
|
182
|
-
const result = await checkDeploymentStatus({ project_id: VALID_UUID }, ctx);
|
|
183
|
-
|
|
184
|
-
expect(result.result).toMatchObject({
|
|
185
|
-
has_deployment: true,
|
|
186
|
-
});
|
|
187
|
-
expect((result.result as { deployment: { id: string } }).deployment.id).toBe('deploy-1');
|
|
188
|
-
});
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
// ============================================================================
|
|
192
|
-
// startDeployment Tests
|
|
193
|
-
// ============================================================================
|
|
194
|
-
|
|
195
|
-
describe('startDeployment', () => {
|
|
196
|
-
beforeEach(() => vi.clearAllMocks());
|
|
197
|
-
|
|
198
|
-
it('should throw error for missing project_id', async () => {
|
|
199
|
-
const ctx = createMockContext();
|
|
200
|
-
await expect(startDeployment({}, ctx)).rejects.toThrow(ValidationError);
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
it('should return error when no deployment ready', async () => {
|
|
204
|
-
mockApiClient.startDeployment.mockResolvedValue({
|
|
205
|
-
ok: true,
|
|
206
|
-
data: { success: false, error: 'No deployment ready. Must pass validation first.' },
|
|
207
|
-
});
|
|
208
|
-
const ctx = createMockContext();
|
|
209
|
-
|
|
210
|
-
const result = await startDeployment({ project_id: VALID_UUID }, ctx);
|
|
211
|
-
|
|
212
|
-
expect(result.result).toMatchObject({
|
|
213
|
-
success: false,
|
|
214
|
-
error: 'No deployment ready. Must pass validation first.',
|
|
215
|
-
});
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
it('should start deployment successfully', async () => {
|
|
219
|
-
mockApiClient.startDeployment.mockResolvedValue({
|
|
220
|
-
ok: true,
|
|
221
|
-
data: { success: true, status: 'deploying' },
|
|
222
|
-
});
|
|
223
|
-
const ctx = createMockContext();
|
|
224
|
-
|
|
225
|
-
const result = await startDeployment({ project_id: VALID_UUID }, ctx);
|
|
226
|
-
|
|
227
|
-
expect(result.result).toMatchObject({
|
|
228
|
-
success: true,
|
|
229
|
-
status: 'deploying',
|
|
230
|
-
});
|
|
231
|
-
});
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
// ============================================================================
|
|
235
|
-
// completeDeployment Tests
|
|
236
|
-
// ============================================================================
|
|
237
|
-
|
|
238
|
-
describe('completeDeployment', () => {
|
|
239
|
-
beforeEach(() => vi.clearAllMocks());
|
|
240
|
-
|
|
241
|
-
it('should throw error for missing project_id', async () => {
|
|
242
|
-
const ctx = createMockContext();
|
|
243
|
-
await expect(
|
|
244
|
-
completeDeployment({ success: true }, ctx)
|
|
245
|
-
).rejects.toThrow(ValidationError);
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
it('should throw error for missing success', async () => {
|
|
249
|
-
const ctx = createMockContext();
|
|
250
|
-
await expect(
|
|
251
|
-
completeDeployment({ project_id: VALID_UUID }, ctx)
|
|
252
|
-
).rejects.toThrow(ValidationError);
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
it('should return error when no deployment in progress', async () => {
|
|
256
|
-
mockApiClient.completeDeployment.mockResolvedValue({
|
|
257
|
-
ok: true,
|
|
258
|
-
data: { success: false, error: 'No deployment in progress. Use start_deployment first.' },
|
|
259
|
-
});
|
|
260
|
-
const ctx = createMockContext();
|
|
261
|
-
|
|
262
|
-
const result = await completeDeployment(
|
|
263
|
-
{ project_id: VALID_UUID, success: true },
|
|
264
|
-
ctx
|
|
265
|
-
);
|
|
266
|
-
|
|
267
|
-
expect(result.result).toMatchObject({
|
|
268
|
-
success: false,
|
|
269
|
-
error: 'No deployment in progress. Use start_deployment first.',
|
|
270
|
-
});
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
it('should complete deployment successfully', async () => {
|
|
274
|
-
mockApiClient.completeDeployment.mockResolvedValue({
|
|
275
|
-
ok: true,
|
|
276
|
-
data: { success: true, status: 'deployed' },
|
|
277
|
-
});
|
|
278
|
-
const ctx = createMockContext();
|
|
279
|
-
|
|
280
|
-
const result = await completeDeployment(
|
|
281
|
-
{ project_id: VALID_UUID, success: true, summary: 'Deployed v1.2.0' },
|
|
282
|
-
ctx
|
|
283
|
-
);
|
|
284
|
-
|
|
285
|
-
expect(result.result).toMatchObject({
|
|
286
|
-
success: true,
|
|
287
|
-
status: 'deployed',
|
|
288
|
-
});
|
|
289
|
-
});
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
// ============================================================================
|
|
293
|
-
// cancelDeployment Tests
|
|
294
|
-
// ============================================================================
|
|
295
|
-
|
|
296
|
-
describe('cancelDeployment', () => {
|
|
297
|
-
beforeEach(() => vi.clearAllMocks());
|
|
298
|
-
|
|
299
|
-
it('should throw error for missing project_id', async () => {
|
|
300
|
-
const ctx = createMockContext();
|
|
301
|
-
await expect(cancelDeployment({}, ctx)).rejects.toThrow(ValidationError);
|
|
302
|
-
});
|
|
303
|
-
|
|
304
|
-
it('should return error when no active deployment', async () => {
|
|
305
|
-
mockApiClient.cancelDeployment.mockResolvedValue({
|
|
306
|
-
ok: true,
|
|
307
|
-
data: { success: false, error: 'No active deployment' },
|
|
308
|
-
});
|
|
309
|
-
const ctx = createMockContext();
|
|
310
|
-
|
|
311
|
-
const result = await cancelDeployment({ project_id: VALID_UUID }, ctx);
|
|
312
|
-
|
|
313
|
-
expect(result.result).toMatchObject({
|
|
314
|
-
success: false,
|
|
315
|
-
error: 'No active deployment',
|
|
316
|
-
});
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
it('should cancel deployment successfully', async () => {
|
|
320
|
-
mockApiClient.cancelDeployment.mockResolvedValue({
|
|
321
|
-
ok: true,
|
|
322
|
-
data: { success: true, status: 'failed' },
|
|
323
|
-
});
|
|
324
|
-
const ctx = createMockContext();
|
|
325
|
-
|
|
326
|
-
const result = await cancelDeployment(
|
|
327
|
-
{ project_id: VALID_UUID, reason: 'Found critical bug' },
|
|
328
|
-
ctx
|
|
329
|
-
);
|
|
330
|
-
|
|
331
|
-
expect(result.result).toMatchObject({
|
|
332
|
-
success: true,
|
|
333
|
-
status: 'failed',
|
|
334
|
-
});
|
|
335
|
-
});
|
|
336
|
-
});
|
|
337
|
-
|
|
338
|
-
// ============================================================================
|
|
339
|
-
// addDeploymentRequirement Tests
|
|
340
|
-
// ============================================================================
|
|
341
|
-
|
|
342
|
-
describe('addDeploymentRequirement', () => {
|
|
343
|
-
beforeEach(() => vi.clearAllMocks());
|
|
344
|
-
|
|
345
|
-
it('should throw error for missing project_id', async () => {
|
|
346
|
-
const ctx = createMockContext();
|
|
347
|
-
await expect(
|
|
348
|
-
addDeploymentRequirement({ type: 'migration', title: 'Test' }, ctx)
|
|
349
|
-
).rejects.toThrow(ValidationError);
|
|
350
|
-
});
|
|
351
|
-
|
|
352
|
-
it('should throw error for missing type', async () => {
|
|
353
|
-
const ctx = createMockContext();
|
|
354
|
-
await expect(
|
|
355
|
-
addDeploymentRequirement({ project_id: VALID_UUID, title: 'Test' }, ctx)
|
|
356
|
-
).rejects.toThrow(ValidationError);
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
it('should throw error for missing title', async () => {
|
|
360
|
-
const ctx = createMockContext();
|
|
361
|
-
await expect(
|
|
362
|
-
addDeploymentRequirement({ project_id: VALID_UUID, type: 'migration' }, ctx)
|
|
363
|
-
).rejects.toThrow(ValidationError);
|
|
364
|
-
});
|
|
365
|
-
|
|
366
|
-
it('should throw error for invalid type', async () => {
|
|
367
|
-
const ctx = createMockContext();
|
|
368
|
-
await expect(
|
|
369
|
-
addDeploymentRequirement({
|
|
370
|
-
project_id: VALID_UUID,
|
|
371
|
-
type: 'invalid_type',
|
|
372
|
-
title: 'Test',
|
|
373
|
-
}, ctx)
|
|
374
|
-
).rejects.toThrow(ValidationError);
|
|
375
|
-
});
|
|
376
|
-
|
|
377
|
-
it('should throw error for invalid stage', async () => {
|
|
378
|
-
const ctx = createMockContext();
|
|
379
|
-
await expect(
|
|
380
|
-
addDeploymentRequirement({
|
|
381
|
-
project_id: VALID_UUID,
|
|
382
|
-
type: 'migration',
|
|
383
|
-
title: 'Test',
|
|
384
|
-
stage: 'invalid_stage',
|
|
385
|
-
}, ctx)
|
|
386
|
-
).rejects.toThrow(ValidationError);
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
it('should add requirement successfully', async () => {
|
|
390
|
-
mockApiClient.addDeploymentRequirement.mockResolvedValue({
|
|
391
|
-
ok: true,
|
|
392
|
-
data: { success: true, requirement_id: 'req-1', stage: 'preparation' },
|
|
393
|
-
});
|
|
394
|
-
const ctx = createMockContext();
|
|
395
|
-
|
|
396
|
-
const result = await addDeploymentRequirement(
|
|
397
|
-
{
|
|
398
|
-
project_id: VALID_UUID,
|
|
399
|
-
type: 'migration',
|
|
400
|
-
title: 'Test Migration',
|
|
401
|
-
},
|
|
402
|
-
ctx
|
|
403
|
-
);
|
|
404
|
-
|
|
405
|
-
expect(result.result).toMatchObject({
|
|
406
|
-
success: true,
|
|
407
|
-
requirement_id: 'req-1',
|
|
408
|
-
stage: 'preparation',
|
|
409
|
-
});
|
|
410
|
-
});
|
|
411
|
-
});
|
|
412
|
-
|
|
413
|
-
// ============================================================================
|
|
414
|
-
// getDeploymentRequirements Tests
|
|
415
|
-
// ============================================================================
|
|
416
|
-
|
|
417
|
-
describe('getDeploymentRequirements', () => {
|
|
418
|
-
beforeEach(() => vi.clearAllMocks());
|
|
419
|
-
|
|
420
|
-
it('should throw error for missing project_id', async () => {
|
|
421
|
-
const ctx = createMockContext();
|
|
422
|
-
await expect(getDeploymentRequirements({}, ctx)).rejects.toThrow(ValidationError);
|
|
423
|
-
});
|
|
424
|
-
|
|
425
|
-
it('should return empty list when no requirements', async () => {
|
|
426
|
-
mockApiClient.getDeploymentRequirements.mockResolvedValue({
|
|
427
|
-
ok: true,
|
|
428
|
-
data: { requirements: [], deployment_blocked: false },
|
|
429
|
-
});
|
|
430
|
-
const ctx = createMockContext();
|
|
431
|
-
|
|
432
|
-
const result = await getDeploymentRequirements({ project_id: VALID_UUID }, ctx);
|
|
433
|
-
|
|
434
|
-
expect(result.result).toMatchObject({
|
|
435
|
-
requirements: [],
|
|
436
|
-
deployment_blocked: false,
|
|
437
|
-
});
|
|
438
|
-
});
|
|
439
|
-
|
|
440
|
-
it('should query with correct project_id', async () => {
|
|
441
|
-
mockApiClient.getDeploymentRequirements.mockResolvedValue({
|
|
442
|
-
ok: true,
|
|
443
|
-
data: { requirements: [], deployment_blocked: false },
|
|
444
|
-
});
|
|
445
|
-
const ctx = createMockContext();
|
|
446
|
-
|
|
447
|
-
await getDeploymentRequirements({ project_id: VALID_UUID }, ctx);
|
|
448
|
-
|
|
449
|
-
expect(mockApiClient.getDeploymentRequirements).toHaveBeenCalledWith(
|
|
450
|
-
VALID_UUID,
|
|
451
|
-
expect.any(Object)
|
|
452
|
-
);
|
|
453
|
-
});
|
|
454
|
-
|
|
455
|
-
it('should return requirements list', async () => {
|
|
456
|
-
const mockReqs = [
|
|
457
|
-
{ id: 'req-1', type: 'migration', title: 'Run migrations' },
|
|
458
|
-
{ id: 'req-2', type: 'env_var', title: 'Set API_KEY' },
|
|
459
|
-
];
|
|
460
|
-
mockApiClient.getDeploymentRequirements.mockResolvedValue({
|
|
461
|
-
ok: true,
|
|
462
|
-
data: { requirements: mockReqs, deployment_blocked: true },
|
|
463
|
-
});
|
|
464
|
-
const ctx = createMockContext();
|
|
465
|
-
|
|
466
|
-
const result = await getDeploymentRequirements({ project_id: VALID_UUID }, ctx);
|
|
467
|
-
|
|
468
|
-
expect((result.result as { requirements: unknown[] }).requirements).toHaveLength(2);
|
|
469
|
-
expect(result.result).toHaveProperty('deployment_blocked', true);
|
|
470
|
-
});
|
|
471
|
-
|
|
472
|
-
it('should pass pagination params to API client', async () => {
|
|
473
|
-
mockApiClient.getDeploymentRequirements.mockResolvedValue({
|
|
474
|
-
ok: true,
|
|
475
|
-
data: { requirements: [], deployment_blocked: false },
|
|
476
|
-
});
|
|
477
|
-
const ctx = createMockContext();
|
|
478
|
-
|
|
479
|
-
await getDeploymentRequirements(
|
|
480
|
-
{ project_id: VALID_UUID, limit: 10, offset: 5 },
|
|
481
|
-
ctx
|
|
482
|
-
);
|
|
483
|
-
|
|
484
|
-
expect(mockApiClient.getDeploymentRequirements).toHaveBeenCalledWith(
|
|
485
|
-
VALID_UUID,
|
|
486
|
-
expect.objectContaining({ limit: 10, offset: 5 })
|
|
487
|
-
);
|
|
488
|
-
});
|
|
489
|
-
});
|
|
490
|
-
|
|
491
|
-
// ============================================================================
|
|
492
|
-
// getDeploymentRequirementsStats Tests
|
|
493
|
-
// ============================================================================
|
|
494
|
-
|
|
495
|
-
describe('getDeploymentRequirementsStats', () => {
|
|
496
|
-
beforeEach(() => vi.clearAllMocks());
|
|
497
|
-
|
|
498
|
-
it('should throw error for missing project_id', async () => {
|
|
499
|
-
const ctx = createMockContext();
|
|
500
|
-
await expect(getDeploymentRequirementsStats({}, ctx)).rejects.toThrow(ValidationError);
|
|
501
|
-
});
|
|
502
|
-
|
|
503
|
-
it('should throw error for invalid project_id UUID', async () => {
|
|
504
|
-
const ctx = createMockContext();
|
|
505
|
-
await expect(
|
|
506
|
-
getDeploymentRequirementsStats({ project_id: 'invalid' }, ctx)
|
|
507
|
-
).rejects.toThrow(ValidationError);
|
|
508
|
-
});
|
|
509
|
-
|
|
510
|
-
it('should return stats from API client', async () => {
|
|
511
|
-
const mockStats = {
|
|
512
|
-
total: 5,
|
|
513
|
-
by_status: { pending: 3, completed: 2 },
|
|
514
|
-
by_stage: { preparation: 2, deployment: 2, verification: 1 },
|
|
515
|
-
by_type: { migration: 2, env_var: 2, manual: 1 },
|
|
516
|
-
};
|
|
517
|
-
mockApiClient.getDeploymentRequirementsStats.mockResolvedValue({
|
|
518
|
-
ok: true,
|
|
519
|
-
data: mockStats,
|
|
520
|
-
});
|
|
521
|
-
const ctx = createMockContext();
|
|
522
|
-
|
|
523
|
-
const result = await getDeploymentRequirementsStats({ project_id: VALID_UUID }, ctx);
|
|
524
|
-
|
|
525
|
-
expect(result.result).toMatchObject(mockStats);
|
|
526
|
-
});
|
|
527
|
-
|
|
528
|
-
it('should call API client with correct project_id', async () => {
|
|
529
|
-
mockApiClient.getDeploymentRequirementsStats.mockResolvedValue({
|
|
530
|
-
ok: true,
|
|
531
|
-
data: { total: 0, by_status: {}, by_stage: {}, by_type: {} },
|
|
532
|
-
});
|
|
533
|
-
const ctx = createMockContext();
|
|
534
|
-
|
|
535
|
-
await getDeploymentRequirementsStats({ project_id: VALID_UUID }, ctx);
|
|
536
|
-
|
|
537
|
-
expect(mockApiClient.getDeploymentRequirementsStats).toHaveBeenCalledWith(VALID_UUID);
|
|
538
|
-
});
|
|
539
|
-
|
|
540
|
-
it('should handle API error', async () => {
|
|
541
|
-
mockApiClient.getDeploymentRequirementsStats.mockResolvedValue({
|
|
542
|
-
ok: false,
|
|
543
|
-
error: 'Database error',
|
|
544
|
-
});
|
|
545
|
-
const ctx = createMockContext();
|
|
546
|
-
|
|
547
|
-
const result = await getDeploymentRequirementsStats({ project_id: VALID_UUID }, ctx);
|
|
548
|
-
|
|
549
|
-
expect(result.result).toHaveProperty('error');
|
|
550
|
-
});
|
|
551
|
-
});
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
claimDeploymentValidation,
|
|
4
|
+
reportValidation,
|
|
5
|
+
checkDeploymentStatus,
|
|
6
|
+
startDeployment,
|
|
7
|
+
completeDeployment,
|
|
8
|
+
cancelDeployment,
|
|
9
|
+
addDeploymentRequirement,
|
|
10
|
+
getDeploymentRequirements,
|
|
11
|
+
getDeploymentRequirementsStats,
|
|
12
|
+
} from './deployment.js';
|
|
13
|
+
import { ValidationError } from '../validators.js';
|
|
14
|
+
import { createMockContext } from './__test-utils__.js';
|
|
15
|
+
import { mockApiClient } from './__test-setup__.js';
|
|
16
|
+
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// Test Utilities
|
|
19
|
+
// ============================================================================
|
|
20
|
+
|
|
21
|
+
const VALID_UUID = '123e4567-e89b-12d3-a456-426614174000';
|
|
22
|
+
|
|
23
|
+
// ============================================================================
|
|
24
|
+
// claimDeploymentValidation Tests
|
|
25
|
+
// ============================================================================
|
|
26
|
+
|
|
27
|
+
describe('claimDeploymentValidation', () => {
|
|
28
|
+
beforeEach(() => vi.clearAllMocks());
|
|
29
|
+
|
|
30
|
+
it('should throw error for missing project_id', async () => {
|
|
31
|
+
const ctx = createMockContext();
|
|
32
|
+
await expect(claimDeploymentValidation({}, ctx)).rejects.toThrow(ValidationError);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should throw error for invalid project_id UUID', async () => {
|
|
36
|
+
const ctx = createMockContext();
|
|
37
|
+
await expect(
|
|
38
|
+
claimDeploymentValidation({ project_id: 'invalid' }, ctx)
|
|
39
|
+
).rejects.toThrow(ValidationError);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should return error when no pending deployment', async () => {
|
|
43
|
+
mockApiClient.claimDeploymentValidation.mockResolvedValue({
|
|
44
|
+
ok: true,
|
|
45
|
+
data: { success: false, error: 'No pending deployment found' },
|
|
46
|
+
});
|
|
47
|
+
const ctx = createMockContext();
|
|
48
|
+
|
|
49
|
+
const result = await claimDeploymentValidation({ project_id: VALID_UUID }, ctx);
|
|
50
|
+
|
|
51
|
+
expect(result.result).toMatchObject({
|
|
52
|
+
success: false,
|
|
53
|
+
error: 'No pending deployment found',
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should claim deployment successfully', async () => {
|
|
58
|
+
mockApiClient.claimDeploymentValidation.mockResolvedValue({
|
|
59
|
+
ok: true,
|
|
60
|
+
data: { success: true, deployment_id: 'deploy-1' },
|
|
61
|
+
});
|
|
62
|
+
const ctx = createMockContext();
|
|
63
|
+
|
|
64
|
+
const result = await claimDeploymentValidation({ project_id: VALID_UUID }, ctx);
|
|
65
|
+
|
|
66
|
+
expect(result.result).toMatchObject({
|
|
67
|
+
success: true,
|
|
68
|
+
deployment_id: 'deploy-1',
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should pass session_id to API client', async () => {
|
|
73
|
+
mockApiClient.claimDeploymentValidation.mockResolvedValue({
|
|
74
|
+
ok: true,
|
|
75
|
+
data: { success: true },
|
|
76
|
+
});
|
|
77
|
+
const ctx = createMockContext({ sessionId: 'my-session' });
|
|
78
|
+
|
|
79
|
+
await claimDeploymentValidation({ project_id: VALID_UUID }, ctx);
|
|
80
|
+
|
|
81
|
+
expect(mockApiClient.claimDeploymentValidation).toHaveBeenCalledWith(VALID_UUID, 'my-session');
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// ============================================================================
|
|
86
|
+
// reportValidation Tests
|
|
87
|
+
// ============================================================================
|
|
88
|
+
|
|
89
|
+
describe('reportValidation', () => {
|
|
90
|
+
beforeEach(() => vi.clearAllMocks());
|
|
91
|
+
|
|
92
|
+
it('should throw error for missing project_id', async () => {
|
|
93
|
+
const ctx = createMockContext();
|
|
94
|
+
await expect(
|
|
95
|
+
reportValidation({ build_passed: true }, ctx)
|
|
96
|
+
).rejects.toThrow(ValidationError);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should throw error for missing build_passed', async () => {
|
|
100
|
+
const ctx = createMockContext();
|
|
101
|
+
await expect(
|
|
102
|
+
reportValidation({ project_id: VALID_UUID }, ctx)
|
|
103
|
+
).rejects.toThrow(ValidationError);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should return error when no deployment being validated', async () => {
|
|
107
|
+
mockApiClient.reportValidation.mockResolvedValue({
|
|
108
|
+
ok: true,
|
|
109
|
+
data: { success: false, error: 'No deployment being validated. Use claim_deployment_validation first.' },
|
|
110
|
+
});
|
|
111
|
+
const ctx = createMockContext();
|
|
112
|
+
|
|
113
|
+
const result = await reportValidation(
|
|
114
|
+
{ project_id: VALID_UUID, build_passed: true },
|
|
115
|
+
ctx
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
expect(result.result).toMatchObject({
|
|
119
|
+
success: false,
|
|
120
|
+
error: 'No deployment being validated. Use claim_deployment_validation first.',
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('should report validation successfully', async () => {
|
|
125
|
+
mockApiClient.reportValidation.mockResolvedValue({
|
|
126
|
+
ok: true,
|
|
127
|
+
data: { success: true, status: 'ready' },
|
|
128
|
+
});
|
|
129
|
+
const ctx = createMockContext();
|
|
130
|
+
|
|
131
|
+
const result = await reportValidation(
|
|
132
|
+
{ project_id: VALID_UUID, build_passed: true, tests_passed: true },
|
|
133
|
+
ctx
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
expect(result.result).toMatchObject({
|
|
137
|
+
success: true,
|
|
138
|
+
status: 'ready',
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// ============================================================================
|
|
144
|
+
// checkDeploymentStatus Tests
|
|
145
|
+
// ============================================================================
|
|
146
|
+
|
|
147
|
+
describe('checkDeploymentStatus', () => {
|
|
148
|
+
beforeEach(() => vi.clearAllMocks());
|
|
149
|
+
|
|
150
|
+
it('should throw error for missing project_id', async () => {
|
|
151
|
+
const ctx = createMockContext();
|
|
152
|
+
await expect(checkDeploymentStatus({}, ctx)).rejects.toThrow(ValidationError);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('should return no deployment when none found', async () => {
|
|
156
|
+
mockApiClient.checkDeploymentStatus.mockResolvedValue({
|
|
157
|
+
ok: true,
|
|
158
|
+
data: { has_deployment: false, message: 'No deployments found for this project' },
|
|
159
|
+
});
|
|
160
|
+
const ctx = createMockContext();
|
|
161
|
+
|
|
162
|
+
const result = await checkDeploymentStatus({ project_id: VALID_UUID }, ctx);
|
|
163
|
+
|
|
164
|
+
expect(result.result).toMatchObject({
|
|
165
|
+
has_deployment: false,
|
|
166
|
+
message: 'No deployments found for this project',
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('should return deployment details when found', async () => {
|
|
171
|
+
const mockDeployment = {
|
|
172
|
+
id: 'deploy-1',
|
|
173
|
+
status: 'deployed',
|
|
174
|
+
environment: 'production',
|
|
175
|
+
};
|
|
176
|
+
mockApiClient.checkDeploymentStatus.mockResolvedValue({
|
|
177
|
+
ok: true,
|
|
178
|
+
data: { has_deployment: true, deployment: mockDeployment },
|
|
179
|
+
});
|
|
180
|
+
const ctx = createMockContext();
|
|
181
|
+
|
|
182
|
+
const result = await checkDeploymentStatus({ project_id: VALID_UUID }, ctx);
|
|
183
|
+
|
|
184
|
+
expect(result.result).toMatchObject({
|
|
185
|
+
has_deployment: true,
|
|
186
|
+
});
|
|
187
|
+
expect((result.result as { deployment: { id: string } }).deployment.id).toBe('deploy-1');
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// ============================================================================
|
|
192
|
+
// startDeployment Tests
|
|
193
|
+
// ============================================================================
|
|
194
|
+
|
|
195
|
+
describe('startDeployment', () => {
|
|
196
|
+
beforeEach(() => vi.clearAllMocks());
|
|
197
|
+
|
|
198
|
+
it('should throw error for missing project_id', async () => {
|
|
199
|
+
const ctx = createMockContext();
|
|
200
|
+
await expect(startDeployment({}, ctx)).rejects.toThrow(ValidationError);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('should return error when no deployment ready', async () => {
|
|
204
|
+
mockApiClient.startDeployment.mockResolvedValue({
|
|
205
|
+
ok: true,
|
|
206
|
+
data: { success: false, error: 'No deployment ready. Must pass validation first.' },
|
|
207
|
+
});
|
|
208
|
+
const ctx = createMockContext();
|
|
209
|
+
|
|
210
|
+
const result = await startDeployment({ project_id: VALID_UUID }, ctx);
|
|
211
|
+
|
|
212
|
+
expect(result.result).toMatchObject({
|
|
213
|
+
success: false,
|
|
214
|
+
error: 'No deployment ready. Must pass validation first.',
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('should start deployment successfully', async () => {
|
|
219
|
+
mockApiClient.startDeployment.mockResolvedValue({
|
|
220
|
+
ok: true,
|
|
221
|
+
data: { success: true, status: 'deploying' },
|
|
222
|
+
});
|
|
223
|
+
const ctx = createMockContext();
|
|
224
|
+
|
|
225
|
+
const result = await startDeployment({ project_id: VALID_UUID }, ctx);
|
|
226
|
+
|
|
227
|
+
expect(result.result).toMatchObject({
|
|
228
|
+
success: true,
|
|
229
|
+
status: 'deploying',
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// ============================================================================
|
|
235
|
+
// completeDeployment Tests
|
|
236
|
+
// ============================================================================
|
|
237
|
+
|
|
238
|
+
describe('completeDeployment', () => {
|
|
239
|
+
beforeEach(() => vi.clearAllMocks());
|
|
240
|
+
|
|
241
|
+
it('should throw error for missing project_id', async () => {
|
|
242
|
+
const ctx = createMockContext();
|
|
243
|
+
await expect(
|
|
244
|
+
completeDeployment({ success: true }, ctx)
|
|
245
|
+
).rejects.toThrow(ValidationError);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('should throw error for missing success', async () => {
|
|
249
|
+
const ctx = createMockContext();
|
|
250
|
+
await expect(
|
|
251
|
+
completeDeployment({ project_id: VALID_UUID }, ctx)
|
|
252
|
+
).rejects.toThrow(ValidationError);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it('should return error when no deployment in progress', async () => {
|
|
256
|
+
mockApiClient.completeDeployment.mockResolvedValue({
|
|
257
|
+
ok: true,
|
|
258
|
+
data: { success: false, error: 'No deployment in progress. Use start_deployment first.' },
|
|
259
|
+
});
|
|
260
|
+
const ctx = createMockContext();
|
|
261
|
+
|
|
262
|
+
const result = await completeDeployment(
|
|
263
|
+
{ project_id: VALID_UUID, success: true },
|
|
264
|
+
ctx
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
expect(result.result).toMatchObject({
|
|
268
|
+
success: false,
|
|
269
|
+
error: 'No deployment in progress. Use start_deployment first.',
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it('should complete deployment successfully', async () => {
|
|
274
|
+
mockApiClient.completeDeployment.mockResolvedValue({
|
|
275
|
+
ok: true,
|
|
276
|
+
data: { success: true, status: 'deployed' },
|
|
277
|
+
});
|
|
278
|
+
const ctx = createMockContext();
|
|
279
|
+
|
|
280
|
+
const result = await completeDeployment(
|
|
281
|
+
{ project_id: VALID_UUID, success: true, summary: 'Deployed v1.2.0' },
|
|
282
|
+
ctx
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
expect(result.result).toMatchObject({
|
|
286
|
+
success: true,
|
|
287
|
+
status: 'deployed',
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
// ============================================================================
|
|
293
|
+
// cancelDeployment Tests
|
|
294
|
+
// ============================================================================
|
|
295
|
+
|
|
296
|
+
describe('cancelDeployment', () => {
|
|
297
|
+
beforeEach(() => vi.clearAllMocks());
|
|
298
|
+
|
|
299
|
+
it('should throw error for missing project_id', async () => {
|
|
300
|
+
const ctx = createMockContext();
|
|
301
|
+
await expect(cancelDeployment({}, ctx)).rejects.toThrow(ValidationError);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it('should return error when no active deployment', async () => {
|
|
305
|
+
mockApiClient.cancelDeployment.mockResolvedValue({
|
|
306
|
+
ok: true,
|
|
307
|
+
data: { success: false, error: 'No active deployment' },
|
|
308
|
+
});
|
|
309
|
+
const ctx = createMockContext();
|
|
310
|
+
|
|
311
|
+
const result = await cancelDeployment({ project_id: VALID_UUID }, ctx);
|
|
312
|
+
|
|
313
|
+
expect(result.result).toMatchObject({
|
|
314
|
+
success: false,
|
|
315
|
+
error: 'No active deployment',
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it('should cancel deployment successfully', async () => {
|
|
320
|
+
mockApiClient.cancelDeployment.mockResolvedValue({
|
|
321
|
+
ok: true,
|
|
322
|
+
data: { success: true, status: 'failed' },
|
|
323
|
+
});
|
|
324
|
+
const ctx = createMockContext();
|
|
325
|
+
|
|
326
|
+
const result = await cancelDeployment(
|
|
327
|
+
{ project_id: VALID_UUID, reason: 'Found critical bug' },
|
|
328
|
+
ctx
|
|
329
|
+
);
|
|
330
|
+
|
|
331
|
+
expect(result.result).toMatchObject({
|
|
332
|
+
success: true,
|
|
333
|
+
status: 'failed',
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
// ============================================================================
|
|
339
|
+
// addDeploymentRequirement Tests
|
|
340
|
+
// ============================================================================
|
|
341
|
+
|
|
342
|
+
describe('addDeploymentRequirement', () => {
|
|
343
|
+
beforeEach(() => vi.clearAllMocks());
|
|
344
|
+
|
|
345
|
+
it('should throw error for missing project_id', async () => {
|
|
346
|
+
const ctx = createMockContext();
|
|
347
|
+
await expect(
|
|
348
|
+
addDeploymentRequirement({ type: 'migration', title: 'Test' }, ctx)
|
|
349
|
+
).rejects.toThrow(ValidationError);
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
it('should throw error for missing type', async () => {
|
|
353
|
+
const ctx = createMockContext();
|
|
354
|
+
await expect(
|
|
355
|
+
addDeploymentRequirement({ project_id: VALID_UUID, title: 'Test' }, ctx)
|
|
356
|
+
).rejects.toThrow(ValidationError);
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
it('should throw error for missing title', async () => {
|
|
360
|
+
const ctx = createMockContext();
|
|
361
|
+
await expect(
|
|
362
|
+
addDeploymentRequirement({ project_id: VALID_UUID, type: 'migration' }, ctx)
|
|
363
|
+
).rejects.toThrow(ValidationError);
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
it('should throw error for invalid type', async () => {
|
|
367
|
+
const ctx = createMockContext();
|
|
368
|
+
await expect(
|
|
369
|
+
addDeploymentRequirement({
|
|
370
|
+
project_id: VALID_UUID,
|
|
371
|
+
type: 'invalid_type',
|
|
372
|
+
title: 'Test',
|
|
373
|
+
}, ctx)
|
|
374
|
+
).rejects.toThrow(ValidationError);
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
it('should throw error for invalid stage', async () => {
|
|
378
|
+
const ctx = createMockContext();
|
|
379
|
+
await expect(
|
|
380
|
+
addDeploymentRequirement({
|
|
381
|
+
project_id: VALID_UUID,
|
|
382
|
+
type: 'migration',
|
|
383
|
+
title: 'Test',
|
|
384
|
+
stage: 'invalid_stage',
|
|
385
|
+
}, ctx)
|
|
386
|
+
).rejects.toThrow(ValidationError);
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
it('should add requirement successfully', async () => {
|
|
390
|
+
mockApiClient.addDeploymentRequirement.mockResolvedValue({
|
|
391
|
+
ok: true,
|
|
392
|
+
data: { success: true, requirement_id: 'req-1', stage: 'preparation' },
|
|
393
|
+
});
|
|
394
|
+
const ctx = createMockContext();
|
|
395
|
+
|
|
396
|
+
const result = await addDeploymentRequirement(
|
|
397
|
+
{
|
|
398
|
+
project_id: VALID_UUID,
|
|
399
|
+
type: 'migration',
|
|
400
|
+
title: 'Test Migration',
|
|
401
|
+
},
|
|
402
|
+
ctx
|
|
403
|
+
);
|
|
404
|
+
|
|
405
|
+
expect(result.result).toMatchObject({
|
|
406
|
+
success: true,
|
|
407
|
+
requirement_id: 'req-1',
|
|
408
|
+
stage: 'preparation',
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
// ============================================================================
|
|
414
|
+
// getDeploymentRequirements Tests
|
|
415
|
+
// ============================================================================
|
|
416
|
+
|
|
417
|
+
describe('getDeploymentRequirements', () => {
|
|
418
|
+
beforeEach(() => vi.clearAllMocks());
|
|
419
|
+
|
|
420
|
+
it('should throw error for missing project_id', async () => {
|
|
421
|
+
const ctx = createMockContext();
|
|
422
|
+
await expect(getDeploymentRequirements({}, ctx)).rejects.toThrow(ValidationError);
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
it('should return empty list when no requirements', async () => {
|
|
426
|
+
mockApiClient.getDeploymentRequirements.mockResolvedValue({
|
|
427
|
+
ok: true,
|
|
428
|
+
data: { requirements: [], deployment_blocked: false },
|
|
429
|
+
});
|
|
430
|
+
const ctx = createMockContext();
|
|
431
|
+
|
|
432
|
+
const result = await getDeploymentRequirements({ project_id: VALID_UUID }, ctx);
|
|
433
|
+
|
|
434
|
+
expect(result.result).toMatchObject({
|
|
435
|
+
requirements: [],
|
|
436
|
+
deployment_blocked: false,
|
|
437
|
+
});
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
it('should query with correct project_id', async () => {
|
|
441
|
+
mockApiClient.getDeploymentRequirements.mockResolvedValue({
|
|
442
|
+
ok: true,
|
|
443
|
+
data: { requirements: [], deployment_blocked: false },
|
|
444
|
+
});
|
|
445
|
+
const ctx = createMockContext();
|
|
446
|
+
|
|
447
|
+
await getDeploymentRequirements({ project_id: VALID_UUID }, ctx);
|
|
448
|
+
|
|
449
|
+
expect(mockApiClient.getDeploymentRequirements).toHaveBeenCalledWith(
|
|
450
|
+
VALID_UUID,
|
|
451
|
+
expect.any(Object)
|
|
452
|
+
);
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
it('should return requirements list', async () => {
|
|
456
|
+
const mockReqs = [
|
|
457
|
+
{ id: 'req-1', type: 'migration', title: 'Run migrations' },
|
|
458
|
+
{ id: 'req-2', type: 'env_var', title: 'Set API_KEY' },
|
|
459
|
+
];
|
|
460
|
+
mockApiClient.getDeploymentRequirements.mockResolvedValue({
|
|
461
|
+
ok: true,
|
|
462
|
+
data: { requirements: mockReqs, deployment_blocked: true },
|
|
463
|
+
});
|
|
464
|
+
const ctx = createMockContext();
|
|
465
|
+
|
|
466
|
+
const result = await getDeploymentRequirements({ project_id: VALID_UUID }, ctx);
|
|
467
|
+
|
|
468
|
+
expect((result.result as { requirements: unknown[] }).requirements).toHaveLength(2);
|
|
469
|
+
expect(result.result).toHaveProperty('deployment_blocked', true);
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
it('should pass pagination params to API client', async () => {
|
|
473
|
+
mockApiClient.getDeploymentRequirements.mockResolvedValue({
|
|
474
|
+
ok: true,
|
|
475
|
+
data: { requirements: [], deployment_blocked: false },
|
|
476
|
+
});
|
|
477
|
+
const ctx = createMockContext();
|
|
478
|
+
|
|
479
|
+
await getDeploymentRequirements(
|
|
480
|
+
{ project_id: VALID_UUID, limit: 10, offset: 5 },
|
|
481
|
+
ctx
|
|
482
|
+
);
|
|
483
|
+
|
|
484
|
+
expect(mockApiClient.getDeploymentRequirements).toHaveBeenCalledWith(
|
|
485
|
+
VALID_UUID,
|
|
486
|
+
expect.objectContaining({ limit: 10, offset: 5 })
|
|
487
|
+
);
|
|
488
|
+
});
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
// ============================================================================
|
|
492
|
+
// getDeploymentRequirementsStats Tests
|
|
493
|
+
// ============================================================================
|
|
494
|
+
|
|
495
|
+
describe('getDeploymentRequirementsStats', () => {
|
|
496
|
+
beforeEach(() => vi.clearAllMocks());
|
|
497
|
+
|
|
498
|
+
it('should throw error for missing project_id', async () => {
|
|
499
|
+
const ctx = createMockContext();
|
|
500
|
+
await expect(getDeploymentRequirementsStats({}, ctx)).rejects.toThrow(ValidationError);
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
it('should throw error for invalid project_id UUID', async () => {
|
|
504
|
+
const ctx = createMockContext();
|
|
505
|
+
await expect(
|
|
506
|
+
getDeploymentRequirementsStats({ project_id: 'invalid' }, ctx)
|
|
507
|
+
).rejects.toThrow(ValidationError);
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
it('should return stats from API client', async () => {
|
|
511
|
+
const mockStats = {
|
|
512
|
+
total: 5,
|
|
513
|
+
by_status: { pending: 3, completed: 2 },
|
|
514
|
+
by_stage: { preparation: 2, deployment: 2, verification: 1 },
|
|
515
|
+
by_type: { migration: 2, env_var: 2, manual: 1 },
|
|
516
|
+
};
|
|
517
|
+
mockApiClient.getDeploymentRequirementsStats.mockResolvedValue({
|
|
518
|
+
ok: true,
|
|
519
|
+
data: mockStats,
|
|
520
|
+
});
|
|
521
|
+
const ctx = createMockContext();
|
|
522
|
+
|
|
523
|
+
const result = await getDeploymentRequirementsStats({ project_id: VALID_UUID }, ctx);
|
|
524
|
+
|
|
525
|
+
expect(result.result).toMatchObject(mockStats);
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
it('should call API client with correct project_id', async () => {
|
|
529
|
+
mockApiClient.getDeploymentRequirementsStats.mockResolvedValue({
|
|
530
|
+
ok: true,
|
|
531
|
+
data: { total: 0, by_status: {}, by_stage: {}, by_type: {} },
|
|
532
|
+
});
|
|
533
|
+
const ctx = createMockContext();
|
|
534
|
+
|
|
535
|
+
await getDeploymentRequirementsStats({ project_id: VALID_UUID }, ctx);
|
|
536
|
+
|
|
537
|
+
expect(mockApiClient.getDeploymentRequirementsStats).toHaveBeenCalledWith(VALID_UUID);
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
it('should handle API error', async () => {
|
|
541
|
+
mockApiClient.getDeploymentRequirementsStats.mockResolvedValue({
|
|
542
|
+
ok: false,
|
|
543
|
+
error: 'Database error',
|
|
544
|
+
});
|
|
545
|
+
const ctx = createMockContext();
|
|
546
|
+
|
|
547
|
+
const result = await getDeploymentRequirementsStats({ project_id: VALID_UUID }, ctx);
|
|
548
|
+
|
|
549
|
+
expect(result.result).toHaveProperty('error');
|
|
550
|
+
});
|
|
551
|
+
});
|