@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,537 +1,537 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import {
|
|
3
|
-
startFallbackActivity,
|
|
4
|
-
stopFallbackActivity,
|
|
5
|
-
getActivityHistory,
|
|
6
|
-
getActivitySchedules,
|
|
7
|
-
} from './fallback.js';
|
|
8
|
-
import { ValidationError } from '../validators.js';
|
|
9
|
-
import { createMockContext } from './__test-utils__.js';
|
|
10
|
-
import { mockApiClient } from './__test-setup__.js';
|
|
11
|
-
|
|
12
|
-
// ============================================================================
|
|
13
|
-
// startFallbackActivity Tests
|
|
14
|
-
// ============================================================================
|
|
15
|
-
|
|
16
|
-
describe('startFallbackActivity', () => {
|
|
17
|
-
beforeEach(() => vi.clearAllMocks());
|
|
18
|
-
|
|
19
|
-
it('should throw error for missing project_id', async () => {
|
|
20
|
-
const ctx = createMockContext();
|
|
21
|
-
|
|
22
|
-
await expect(
|
|
23
|
-
startFallbackActivity({ activity: 'code_review' }, ctx)
|
|
24
|
-
).rejects.toThrow(ValidationError);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it('should throw error for invalid project_id UUID', async () => {
|
|
28
|
-
const ctx = createMockContext();
|
|
29
|
-
|
|
30
|
-
await expect(
|
|
31
|
-
startFallbackActivity({ project_id: 'invalid', activity: 'code_review' }, ctx)
|
|
32
|
-
).rejects.toThrow(ValidationError);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it('should throw error for missing activity', async () => {
|
|
36
|
-
const ctx = createMockContext();
|
|
37
|
-
|
|
38
|
-
await expect(
|
|
39
|
-
startFallbackActivity({ project_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx)
|
|
40
|
-
).rejects.toThrow(ValidationError);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it('should throw error for invalid activity type', async () => {
|
|
44
|
-
const ctx = createMockContext();
|
|
45
|
-
|
|
46
|
-
await expect(
|
|
47
|
-
startFallbackActivity({
|
|
48
|
-
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
49
|
-
activity: 'invalid_activity',
|
|
50
|
-
}, ctx)
|
|
51
|
-
).rejects.toThrow(ValidationError);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it('should start fallback activity successfully', async () => {
|
|
55
|
-
mockApiClient.startFallbackActivity.mockResolvedValue({
|
|
56
|
-
ok: true,
|
|
57
|
-
data: { message: 'Started code_review' },
|
|
58
|
-
});
|
|
59
|
-
const ctx = createMockContext();
|
|
60
|
-
|
|
61
|
-
const result = await startFallbackActivity(
|
|
62
|
-
{
|
|
63
|
-
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
64
|
-
activity: 'code_review',
|
|
65
|
-
},
|
|
66
|
-
ctx
|
|
67
|
-
);
|
|
68
|
-
|
|
69
|
-
expect(result.result).toMatchObject({
|
|
70
|
-
success: true,
|
|
71
|
-
activity: 'code_review',
|
|
72
|
-
});
|
|
73
|
-
expect((result.result as { title: string }).title).toBeDefined();
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it('should call API client with correct parameters', async () => {
|
|
77
|
-
mockApiClient.startFallbackActivity.mockResolvedValue({
|
|
78
|
-
ok: true,
|
|
79
|
-
data: { message: 'Started' },
|
|
80
|
-
});
|
|
81
|
-
const ctx = createMockContext({ sessionId: 'my-session' });
|
|
82
|
-
|
|
83
|
-
await startFallbackActivity(
|
|
84
|
-
{
|
|
85
|
-
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
86
|
-
activity: 'security_review',
|
|
87
|
-
},
|
|
88
|
-
ctx
|
|
89
|
-
);
|
|
90
|
-
|
|
91
|
-
expect(mockApiClient.startFallbackActivity).toHaveBeenCalledWith(
|
|
92
|
-
'123e4567-e89b-12d3-a456-426614174000',
|
|
93
|
-
'security_review',
|
|
94
|
-
'my-session'
|
|
95
|
-
);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it('should accept all valid activity types', async () => {
|
|
99
|
-
const validActivities = [
|
|
100
|
-
'feature_ideation',
|
|
101
|
-
'code_review',
|
|
102
|
-
'performance_audit',
|
|
103
|
-
'ux_review',
|
|
104
|
-
'cost_analysis',
|
|
105
|
-
'security_review',
|
|
106
|
-
'test_coverage',
|
|
107
|
-
'documentation_review',
|
|
108
|
-
'dependency_audit',
|
|
109
|
-
'validate_completed_tasks',
|
|
110
|
-
];
|
|
111
|
-
|
|
112
|
-
for (const activity of validActivities) {
|
|
113
|
-
mockApiClient.startFallbackActivity.mockResolvedValue({
|
|
114
|
-
ok: true,
|
|
115
|
-
data: { message: `Started ${activity}` },
|
|
116
|
-
});
|
|
117
|
-
const ctx = createMockContext();
|
|
118
|
-
|
|
119
|
-
const result = await startFallbackActivity(
|
|
120
|
-
{
|
|
121
|
-
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
122
|
-
activity,
|
|
123
|
-
},
|
|
124
|
-
ctx
|
|
125
|
-
);
|
|
126
|
-
|
|
127
|
-
expect(result.result).toMatchObject({
|
|
128
|
-
success: true,
|
|
129
|
-
activity,
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
it('should return error when API call fails', async () => {
|
|
135
|
-
mockApiClient.startFallbackActivity.mockResolvedValue({
|
|
136
|
-
ok: false,
|
|
137
|
-
error: 'Failed to start activity',
|
|
138
|
-
});
|
|
139
|
-
const ctx = createMockContext();
|
|
140
|
-
|
|
141
|
-
const result = await startFallbackActivity({
|
|
142
|
-
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
143
|
-
activity: 'code_review',
|
|
144
|
-
}, ctx);
|
|
145
|
-
|
|
146
|
-
expect(result.isError).toBe(true);
|
|
147
|
-
expect(result.result).toMatchObject({ error: 'Failed to start activity' });
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
it('should pass through worktree guidance when API returns it', async () => {
|
|
151
|
-
mockApiClient.startFallbackActivity.mockResolvedValue({
|
|
152
|
-
ok: true,
|
|
153
|
-
data: {
|
|
154
|
-
success: true,
|
|
155
|
-
activity: 'code_review',
|
|
156
|
-
message: 'Started code_review',
|
|
157
|
-
git_workflow: {
|
|
158
|
-
workflow: 'git-flow',
|
|
159
|
-
base_branch: 'develop',
|
|
160
|
-
worktree_recommended: true,
|
|
161
|
-
note: 'Fallback activities use the base branch directly (read-only).',
|
|
162
|
-
},
|
|
163
|
-
worktree_setup: {
|
|
164
|
-
message: 'RECOMMENDED: Create a worktree to avoid conflicts.',
|
|
165
|
-
commands: [
|
|
166
|
-
'git checkout develop',
|
|
167
|
-
'git pull origin develop',
|
|
168
|
-
'git worktree add ../Project-code-review develop',
|
|
169
|
-
'cd ../Project-code-review',
|
|
170
|
-
],
|
|
171
|
-
worktree_path: '../Project-code-review',
|
|
172
|
-
branch_name: 'develop',
|
|
173
|
-
cleanup_command: 'git worktree remove ../Project-code-review',
|
|
174
|
-
report_worktree: 'heartbeat(current_worktree_path: "../Project-code-review")',
|
|
175
|
-
},
|
|
176
|
-
next_step: 'After setting up worktree: call heartbeat to report your location.',
|
|
177
|
-
},
|
|
178
|
-
});
|
|
179
|
-
const ctx = createMockContext();
|
|
180
|
-
|
|
181
|
-
const result = await startFallbackActivity(
|
|
182
|
-
{
|
|
183
|
-
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
184
|
-
activity: 'code_review',
|
|
185
|
-
},
|
|
186
|
-
ctx
|
|
187
|
-
);
|
|
188
|
-
|
|
189
|
-
expect(result.result).toMatchObject({
|
|
190
|
-
success: true,
|
|
191
|
-
activity: 'code_review',
|
|
192
|
-
});
|
|
193
|
-
expect((result.result as { git_workflow?: unknown }).git_workflow).toBeDefined();
|
|
194
|
-
expect((result.result as { git_workflow: { workflow: string } }).git_workflow.workflow).toBe('git-flow');
|
|
195
|
-
expect((result.result as { worktree_setup?: unknown }).worktree_setup).toBeDefined();
|
|
196
|
-
expect((result.result as { worktree_setup: { worktree_path: string } }).worktree_setup.worktree_path).toBe('../Project-code-review');
|
|
197
|
-
expect((result.result as { next_step?: string }).next_step).toContain('heartbeat');
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
it('should not include worktree guidance when API does not return it', async () => {
|
|
201
|
-
mockApiClient.startFallbackActivity.mockResolvedValue({
|
|
202
|
-
ok: true,
|
|
203
|
-
data: {
|
|
204
|
-
success: true,
|
|
205
|
-
activity: 'code_review',
|
|
206
|
-
message: 'Started code_review',
|
|
207
|
-
},
|
|
208
|
-
});
|
|
209
|
-
const ctx = createMockContext();
|
|
210
|
-
|
|
211
|
-
const result = await startFallbackActivity(
|
|
212
|
-
{
|
|
213
|
-
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
214
|
-
activity: 'code_review',
|
|
215
|
-
},
|
|
216
|
-
ctx
|
|
217
|
-
);
|
|
218
|
-
|
|
219
|
-
expect(result.result).toMatchObject({
|
|
220
|
-
success: true,
|
|
221
|
-
activity: 'code_review',
|
|
222
|
-
});
|
|
223
|
-
expect((result.result as { git_workflow?: unknown }).git_workflow).toBeUndefined();
|
|
224
|
-
expect((result.result as { worktree_setup?: unknown }).worktree_setup).toBeUndefined();
|
|
225
|
-
expect((result.result as { next_step?: string }).next_step).toBeUndefined();
|
|
226
|
-
});
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
// ============================================================================
|
|
230
|
-
// stopFallbackActivity Tests
|
|
231
|
-
// ============================================================================
|
|
232
|
-
|
|
233
|
-
describe('stopFallbackActivity', () => {
|
|
234
|
-
beforeEach(() => vi.clearAllMocks());
|
|
235
|
-
|
|
236
|
-
it('should throw error for missing project_id', async () => {
|
|
237
|
-
const ctx = createMockContext();
|
|
238
|
-
|
|
239
|
-
await expect(stopFallbackActivity({}, ctx)).rejects.toThrow(ValidationError);
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
it('should throw error for invalid project_id UUID', async () => {
|
|
243
|
-
const ctx = createMockContext();
|
|
244
|
-
|
|
245
|
-
await expect(
|
|
246
|
-
stopFallbackActivity({ project_id: 'invalid' }, ctx)
|
|
247
|
-
).rejects.toThrow(ValidationError);
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
it('should stop fallback activity successfully', async () => {
|
|
251
|
-
mockApiClient.stopFallbackActivity.mockResolvedValue({
|
|
252
|
-
ok: true,
|
|
253
|
-
data: { success: true },
|
|
254
|
-
});
|
|
255
|
-
const ctx = createMockContext();
|
|
256
|
-
|
|
257
|
-
const result = await stopFallbackActivity(
|
|
258
|
-
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
259
|
-
ctx
|
|
260
|
-
);
|
|
261
|
-
|
|
262
|
-
expect(result.result).toMatchObject({
|
|
263
|
-
success: true,
|
|
264
|
-
message: 'Fallback activity stopped',
|
|
265
|
-
});
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
it('should call API client with summary when provided', async () => {
|
|
269
|
-
mockApiClient.stopFallbackActivity.mockResolvedValue({
|
|
270
|
-
ok: true,
|
|
271
|
-
data: { success: true },
|
|
272
|
-
});
|
|
273
|
-
const ctx = createMockContext({ sessionId: 'my-session' });
|
|
274
|
-
|
|
275
|
-
await stopFallbackActivity(
|
|
276
|
-
{
|
|
277
|
-
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
278
|
-
summary: 'Reviewed 5 files, found 3 issues',
|
|
279
|
-
},
|
|
280
|
-
ctx
|
|
281
|
-
);
|
|
282
|
-
|
|
283
|
-
expect(mockApiClient.stopFallbackActivity).toHaveBeenCalledWith(
|
|
284
|
-
'123e4567-e89b-12d3-a456-426614174000',
|
|
285
|
-
'Reviewed 5 files, found 3 issues',
|
|
286
|
-
'my-session'
|
|
287
|
-
);
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
it('should return error when API call fails', async () => {
|
|
291
|
-
mockApiClient.stopFallbackActivity.mockResolvedValue({
|
|
292
|
-
ok: false,
|
|
293
|
-
error: 'Failed to stop activity',
|
|
294
|
-
});
|
|
295
|
-
const ctx = createMockContext();
|
|
296
|
-
|
|
297
|
-
const result = await stopFallbackActivity({
|
|
298
|
-
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
299
|
-
}, ctx);
|
|
300
|
-
|
|
301
|
-
expect(result.isError).toBe(true);
|
|
302
|
-
expect(result.result).toMatchObject({ error: 'Failed to stop activity' });
|
|
303
|
-
});
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
// ============================================================================
|
|
307
|
-
// getActivityHistory Tests
|
|
308
|
-
// ============================================================================
|
|
309
|
-
|
|
310
|
-
describe('getActivityHistory', () => {
|
|
311
|
-
beforeEach(() => vi.clearAllMocks());
|
|
312
|
-
|
|
313
|
-
it('should throw error for missing project_id', async () => {
|
|
314
|
-
const ctx = createMockContext();
|
|
315
|
-
|
|
316
|
-
await expect(getActivityHistory({}, ctx)).rejects.toThrow(ValidationError);
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
it('should throw error for invalid project_id UUID', async () => {
|
|
320
|
-
const ctx = createMockContext();
|
|
321
|
-
|
|
322
|
-
await expect(
|
|
323
|
-
getActivityHistory({ project_id: 'invalid' }, ctx)
|
|
324
|
-
).rejects.toThrow(ValidationError);
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
it('should return empty history when no activities', async () => {
|
|
328
|
-
mockApiClient.proxy.mockResolvedValue({
|
|
329
|
-
ok: true,
|
|
330
|
-
data: {
|
|
331
|
-
history: [],
|
|
332
|
-
latest_by_type: {},
|
|
333
|
-
count: 0,
|
|
334
|
-
},
|
|
335
|
-
});
|
|
336
|
-
const ctx = createMockContext();
|
|
337
|
-
|
|
338
|
-
const result = await getActivityHistory(
|
|
339
|
-
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
340
|
-
ctx
|
|
341
|
-
);
|
|
342
|
-
|
|
343
|
-
expect(result.result).toMatchObject({
|
|
344
|
-
history: [],
|
|
345
|
-
count: 0,
|
|
346
|
-
});
|
|
347
|
-
});
|
|
348
|
-
|
|
349
|
-
it('should return activity history from API', async () => {
|
|
350
|
-
const mockHistory = [
|
|
351
|
-
{
|
|
352
|
-
id: 'h1',
|
|
353
|
-
activity_type: 'code_review',
|
|
354
|
-
completed_at: '2025-01-14T10:00:00Z',
|
|
355
|
-
summary: 'Reviewed auth module',
|
|
356
|
-
},
|
|
357
|
-
{
|
|
358
|
-
id: 'h2',
|
|
359
|
-
activity_type: 'security_review',
|
|
360
|
-
completed_at: '2025-01-14T09:00:00Z',
|
|
361
|
-
summary: null,
|
|
362
|
-
},
|
|
363
|
-
];
|
|
364
|
-
|
|
365
|
-
mockApiClient.proxy.mockResolvedValue({
|
|
366
|
-
ok: true,
|
|
367
|
-
data: {
|
|
368
|
-
history: mockHistory,
|
|
369
|
-
latest_by_type: { code_review: mockHistory[0] },
|
|
370
|
-
count: 2,
|
|
371
|
-
},
|
|
372
|
-
});
|
|
373
|
-
const ctx = createMockContext();
|
|
374
|
-
|
|
375
|
-
const result = await getActivityHistory(
|
|
376
|
-
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
377
|
-
ctx
|
|
378
|
-
);
|
|
379
|
-
|
|
380
|
-
expect((result.result as { history: unknown[] }).history).toHaveLength(2);
|
|
381
|
-
expect((result.result as { count: number }).count).toBe(2);
|
|
382
|
-
});
|
|
383
|
-
|
|
384
|
-
it('should call API proxy with correct parameters', async () => {
|
|
385
|
-
mockApiClient.proxy.mockResolvedValue({
|
|
386
|
-
ok: true,
|
|
387
|
-
data: { history: [], count: 0 },
|
|
388
|
-
});
|
|
389
|
-
const ctx = createMockContext();
|
|
390
|
-
|
|
391
|
-
await getActivityHistory(
|
|
392
|
-
{
|
|
393
|
-
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
394
|
-
activity_type: 'security_review',
|
|
395
|
-
limit: 10,
|
|
396
|
-
},
|
|
397
|
-
ctx
|
|
398
|
-
);
|
|
399
|
-
|
|
400
|
-
expect(mockApiClient.proxy).toHaveBeenCalledWith(
|
|
401
|
-
'get_activity_history',
|
|
402
|
-
{
|
|
403
|
-
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
404
|
-
activity_type: 'security_review',
|
|
405
|
-
limit: 10,
|
|
406
|
-
offset: 0,
|
|
407
|
-
}
|
|
408
|
-
);
|
|
409
|
-
});
|
|
410
|
-
|
|
411
|
-
it('should return error when API call fails', async () => {
|
|
412
|
-
mockApiClient.proxy.mockResolvedValue({
|
|
413
|
-
ok: false,
|
|
414
|
-
error: 'Query failed',
|
|
415
|
-
});
|
|
416
|
-
const ctx = createMockContext();
|
|
417
|
-
|
|
418
|
-
const result = await getActivityHistory({ project_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx);
|
|
419
|
-
|
|
420
|
-
expect(result.isError).toBe(true);
|
|
421
|
-
expect(result.result).toMatchObject({ error: 'Query failed' });
|
|
422
|
-
});
|
|
423
|
-
});
|
|
424
|
-
|
|
425
|
-
// ============================================================================
|
|
426
|
-
// getActivitySchedules Tests
|
|
427
|
-
// ============================================================================
|
|
428
|
-
|
|
429
|
-
describe('getActivitySchedules', () => {
|
|
430
|
-
beforeEach(() => vi.clearAllMocks());
|
|
431
|
-
|
|
432
|
-
it('should throw error for missing project_id', async () => {
|
|
433
|
-
const ctx = createMockContext();
|
|
434
|
-
|
|
435
|
-
await expect(getActivitySchedules({}, ctx)).rejects.toThrow(ValidationError);
|
|
436
|
-
});
|
|
437
|
-
|
|
438
|
-
it('should throw error for invalid project_id UUID', async () => {
|
|
439
|
-
const ctx = createMockContext();
|
|
440
|
-
|
|
441
|
-
await expect(
|
|
442
|
-
getActivitySchedules({ project_id: 'invalid' }, ctx)
|
|
443
|
-
).rejects.toThrow(ValidationError);
|
|
444
|
-
});
|
|
445
|
-
|
|
446
|
-
it('should return empty schedules when none exist', async () => {
|
|
447
|
-
mockApiClient.proxy.mockResolvedValue({
|
|
448
|
-
ok: true,
|
|
449
|
-
data: {
|
|
450
|
-
schedules: [],
|
|
451
|
-
total_count: 0,
|
|
452
|
-
has_more: false,
|
|
453
|
-
},
|
|
454
|
-
});
|
|
455
|
-
const ctx = createMockContext();
|
|
456
|
-
|
|
457
|
-
const result = await getActivitySchedules(
|
|
458
|
-
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
459
|
-
ctx
|
|
460
|
-
);
|
|
461
|
-
|
|
462
|
-
expect(result.result).toMatchObject({
|
|
463
|
-
schedules: [],
|
|
464
|
-
total_count: 0,
|
|
465
|
-
has_more: false,
|
|
466
|
-
});
|
|
467
|
-
});
|
|
468
|
-
|
|
469
|
-
it('should return activity schedules from API', async () => {
|
|
470
|
-
const mockSchedules = [
|
|
471
|
-
{
|
|
472
|
-
id: 's1',
|
|
473
|
-
activity_type: 'code_review',
|
|
474
|
-
schedule_type: 'weekly',
|
|
475
|
-
enabled: true,
|
|
476
|
-
next_run_at: '2025-01-20T10:00:00Z',
|
|
477
|
-
},
|
|
478
|
-
{
|
|
479
|
-
id: 's2',
|
|
480
|
-
activity_type: 'security_review',
|
|
481
|
-
schedule_type: 'monthly',
|
|
482
|
-
enabled: true,
|
|
483
|
-
next_run_at: '2025-02-14T10:00:00Z',
|
|
484
|
-
},
|
|
485
|
-
];
|
|
486
|
-
|
|
487
|
-
mockApiClient.proxy.mockResolvedValue({
|
|
488
|
-
ok: true,
|
|
489
|
-
data: {
|
|
490
|
-
schedules: mockSchedules,
|
|
491
|
-
total_count: 2,
|
|
492
|
-
has_more: false,
|
|
493
|
-
},
|
|
494
|
-
});
|
|
495
|
-
const ctx = createMockContext();
|
|
496
|
-
|
|
497
|
-
const result = await getActivitySchedules(
|
|
498
|
-
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
499
|
-
ctx
|
|
500
|
-
);
|
|
501
|
-
|
|
502
|
-
expect((result.result as { schedules: unknown[] }).schedules).toHaveLength(2);
|
|
503
|
-
expect((result.result as { total_count: number }).total_count).toBe(2);
|
|
504
|
-
expect((result.result as { has_more: boolean }).has_more).toBe(false);
|
|
505
|
-
});
|
|
506
|
-
|
|
507
|
-
it('should call API proxy with correct parameters', async () => {
|
|
508
|
-
mockApiClient.proxy.mockResolvedValue({
|
|
509
|
-
ok: true,
|
|
510
|
-
data: { schedules: [], total_count: 0, has_more: false },
|
|
511
|
-
});
|
|
512
|
-
const ctx = createMockContext();
|
|
513
|
-
|
|
514
|
-
await getActivitySchedules(
|
|
515
|
-
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
516
|
-
ctx
|
|
517
|
-
);
|
|
518
|
-
|
|
519
|
-
expect(mockApiClient.proxy).toHaveBeenCalledWith(
|
|
520
|
-
'get_activity_schedules',
|
|
521
|
-
{ project_id: '123e4567-e89b-12d3-a456-426614174000', limit: 50, offset: 0 }
|
|
522
|
-
);
|
|
523
|
-
});
|
|
524
|
-
|
|
525
|
-
it('should return error when API call fails', async () => {
|
|
526
|
-
mockApiClient.proxy.mockResolvedValue({
|
|
527
|
-
ok: false,
|
|
528
|
-
error: 'Query failed',
|
|
529
|
-
});
|
|
530
|
-
const ctx = createMockContext();
|
|
531
|
-
|
|
532
|
-
const result = await getActivitySchedules({ project_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx);
|
|
533
|
-
|
|
534
|
-
expect(result.isError).toBe(true);
|
|
535
|
-
expect(result.result).toMatchObject({ error: 'Query failed' });
|
|
536
|
-
});
|
|
537
|
-
});
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
startFallbackActivity,
|
|
4
|
+
stopFallbackActivity,
|
|
5
|
+
getActivityHistory,
|
|
6
|
+
getActivitySchedules,
|
|
7
|
+
} from './fallback.js';
|
|
8
|
+
import { ValidationError } from '../validators.js';
|
|
9
|
+
import { createMockContext } from './__test-utils__.js';
|
|
10
|
+
import { mockApiClient } from './__test-setup__.js';
|
|
11
|
+
|
|
12
|
+
// ============================================================================
|
|
13
|
+
// startFallbackActivity Tests
|
|
14
|
+
// ============================================================================
|
|
15
|
+
|
|
16
|
+
describe('startFallbackActivity', () => {
|
|
17
|
+
beforeEach(() => vi.clearAllMocks());
|
|
18
|
+
|
|
19
|
+
it('should throw error for missing project_id', async () => {
|
|
20
|
+
const ctx = createMockContext();
|
|
21
|
+
|
|
22
|
+
await expect(
|
|
23
|
+
startFallbackActivity({ activity: 'code_review' }, ctx)
|
|
24
|
+
).rejects.toThrow(ValidationError);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should throw error for invalid project_id UUID', async () => {
|
|
28
|
+
const ctx = createMockContext();
|
|
29
|
+
|
|
30
|
+
await expect(
|
|
31
|
+
startFallbackActivity({ project_id: 'invalid', activity: 'code_review' }, ctx)
|
|
32
|
+
).rejects.toThrow(ValidationError);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should throw error for missing activity', async () => {
|
|
36
|
+
const ctx = createMockContext();
|
|
37
|
+
|
|
38
|
+
await expect(
|
|
39
|
+
startFallbackActivity({ project_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx)
|
|
40
|
+
).rejects.toThrow(ValidationError);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should throw error for invalid activity type', async () => {
|
|
44
|
+
const ctx = createMockContext();
|
|
45
|
+
|
|
46
|
+
await expect(
|
|
47
|
+
startFallbackActivity({
|
|
48
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
49
|
+
activity: 'invalid_activity',
|
|
50
|
+
}, ctx)
|
|
51
|
+
).rejects.toThrow(ValidationError);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should start fallback activity successfully', async () => {
|
|
55
|
+
mockApiClient.startFallbackActivity.mockResolvedValue({
|
|
56
|
+
ok: true,
|
|
57
|
+
data: { message: 'Started code_review' },
|
|
58
|
+
});
|
|
59
|
+
const ctx = createMockContext();
|
|
60
|
+
|
|
61
|
+
const result = await startFallbackActivity(
|
|
62
|
+
{
|
|
63
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
64
|
+
activity: 'code_review',
|
|
65
|
+
},
|
|
66
|
+
ctx
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
expect(result.result).toMatchObject({
|
|
70
|
+
success: true,
|
|
71
|
+
activity: 'code_review',
|
|
72
|
+
});
|
|
73
|
+
expect((result.result as { title: string }).title).toBeDefined();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should call API client with correct parameters', async () => {
|
|
77
|
+
mockApiClient.startFallbackActivity.mockResolvedValue({
|
|
78
|
+
ok: true,
|
|
79
|
+
data: { message: 'Started' },
|
|
80
|
+
});
|
|
81
|
+
const ctx = createMockContext({ sessionId: 'my-session' });
|
|
82
|
+
|
|
83
|
+
await startFallbackActivity(
|
|
84
|
+
{
|
|
85
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
86
|
+
activity: 'security_review',
|
|
87
|
+
},
|
|
88
|
+
ctx
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
expect(mockApiClient.startFallbackActivity).toHaveBeenCalledWith(
|
|
92
|
+
'123e4567-e89b-12d3-a456-426614174000',
|
|
93
|
+
'security_review',
|
|
94
|
+
'my-session'
|
|
95
|
+
);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should accept all valid activity types', async () => {
|
|
99
|
+
const validActivities = [
|
|
100
|
+
'feature_ideation',
|
|
101
|
+
'code_review',
|
|
102
|
+
'performance_audit',
|
|
103
|
+
'ux_review',
|
|
104
|
+
'cost_analysis',
|
|
105
|
+
'security_review',
|
|
106
|
+
'test_coverage',
|
|
107
|
+
'documentation_review',
|
|
108
|
+
'dependency_audit',
|
|
109
|
+
'validate_completed_tasks',
|
|
110
|
+
];
|
|
111
|
+
|
|
112
|
+
for (const activity of validActivities) {
|
|
113
|
+
mockApiClient.startFallbackActivity.mockResolvedValue({
|
|
114
|
+
ok: true,
|
|
115
|
+
data: { message: `Started ${activity}` },
|
|
116
|
+
});
|
|
117
|
+
const ctx = createMockContext();
|
|
118
|
+
|
|
119
|
+
const result = await startFallbackActivity(
|
|
120
|
+
{
|
|
121
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
122
|
+
activity,
|
|
123
|
+
},
|
|
124
|
+
ctx
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
expect(result.result).toMatchObject({
|
|
128
|
+
success: true,
|
|
129
|
+
activity,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('should return error when API call fails', async () => {
|
|
135
|
+
mockApiClient.startFallbackActivity.mockResolvedValue({
|
|
136
|
+
ok: false,
|
|
137
|
+
error: 'Failed to start activity',
|
|
138
|
+
});
|
|
139
|
+
const ctx = createMockContext();
|
|
140
|
+
|
|
141
|
+
const result = await startFallbackActivity({
|
|
142
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
143
|
+
activity: 'code_review',
|
|
144
|
+
}, ctx);
|
|
145
|
+
|
|
146
|
+
expect(result.isError).toBe(true);
|
|
147
|
+
expect(result.result).toMatchObject({ error: 'Failed to start activity' });
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('should pass through worktree guidance when API returns it', async () => {
|
|
151
|
+
mockApiClient.startFallbackActivity.mockResolvedValue({
|
|
152
|
+
ok: true,
|
|
153
|
+
data: {
|
|
154
|
+
success: true,
|
|
155
|
+
activity: 'code_review',
|
|
156
|
+
message: 'Started code_review',
|
|
157
|
+
git_workflow: {
|
|
158
|
+
workflow: 'git-flow',
|
|
159
|
+
base_branch: 'develop',
|
|
160
|
+
worktree_recommended: true,
|
|
161
|
+
note: 'Fallback activities use the base branch directly (read-only).',
|
|
162
|
+
},
|
|
163
|
+
worktree_setup: {
|
|
164
|
+
message: 'RECOMMENDED: Create a worktree to avoid conflicts.',
|
|
165
|
+
commands: [
|
|
166
|
+
'git checkout develop',
|
|
167
|
+
'git pull origin develop',
|
|
168
|
+
'git worktree add ../Project-code-review develop',
|
|
169
|
+
'cd ../Project-code-review',
|
|
170
|
+
],
|
|
171
|
+
worktree_path: '../Project-code-review',
|
|
172
|
+
branch_name: 'develop',
|
|
173
|
+
cleanup_command: 'git worktree remove ../Project-code-review',
|
|
174
|
+
report_worktree: 'heartbeat(current_worktree_path: "../Project-code-review")',
|
|
175
|
+
},
|
|
176
|
+
next_step: 'After setting up worktree: call heartbeat to report your location.',
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
const ctx = createMockContext();
|
|
180
|
+
|
|
181
|
+
const result = await startFallbackActivity(
|
|
182
|
+
{
|
|
183
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
184
|
+
activity: 'code_review',
|
|
185
|
+
},
|
|
186
|
+
ctx
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
expect(result.result).toMatchObject({
|
|
190
|
+
success: true,
|
|
191
|
+
activity: 'code_review',
|
|
192
|
+
});
|
|
193
|
+
expect((result.result as { git_workflow?: unknown }).git_workflow).toBeDefined();
|
|
194
|
+
expect((result.result as { git_workflow: { workflow: string } }).git_workflow.workflow).toBe('git-flow');
|
|
195
|
+
expect((result.result as { worktree_setup?: unknown }).worktree_setup).toBeDefined();
|
|
196
|
+
expect((result.result as { worktree_setup: { worktree_path: string } }).worktree_setup.worktree_path).toBe('../Project-code-review');
|
|
197
|
+
expect((result.result as { next_step?: string }).next_step).toContain('heartbeat');
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('should not include worktree guidance when API does not return it', async () => {
|
|
201
|
+
mockApiClient.startFallbackActivity.mockResolvedValue({
|
|
202
|
+
ok: true,
|
|
203
|
+
data: {
|
|
204
|
+
success: true,
|
|
205
|
+
activity: 'code_review',
|
|
206
|
+
message: 'Started code_review',
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
const ctx = createMockContext();
|
|
210
|
+
|
|
211
|
+
const result = await startFallbackActivity(
|
|
212
|
+
{
|
|
213
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
214
|
+
activity: 'code_review',
|
|
215
|
+
},
|
|
216
|
+
ctx
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
expect(result.result).toMatchObject({
|
|
220
|
+
success: true,
|
|
221
|
+
activity: 'code_review',
|
|
222
|
+
});
|
|
223
|
+
expect((result.result as { git_workflow?: unknown }).git_workflow).toBeUndefined();
|
|
224
|
+
expect((result.result as { worktree_setup?: unknown }).worktree_setup).toBeUndefined();
|
|
225
|
+
expect((result.result as { next_step?: string }).next_step).toBeUndefined();
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// ============================================================================
|
|
230
|
+
// stopFallbackActivity Tests
|
|
231
|
+
// ============================================================================
|
|
232
|
+
|
|
233
|
+
describe('stopFallbackActivity', () => {
|
|
234
|
+
beforeEach(() => vi.clearAllMocks());
|
|
235
|
+
|
|
236
|
+
it('should throw error for missing project_id', async () => {
|
|
237
|
+
const ctx = createMockContext();
|
|
238
|
+
|
|
239
|
+
await expect(stopFallbackActivity({}, ctx)).rejects.toThrow(ValidationError);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it('should throw error for invalid project_id UUID', async () => {
|
|
243
|
+
const ctx = createMockContext();
|
|
244
|
+
|
|
245
|
+
await expect(
|
|
246
|
+
stopFallbackActivity({ project_id: 'invalid' }, ctx)
|
|
247
|
+
).rejects.toThrow(ValidationError);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('should stop fallback activity successfully', async () => {
|
|
251
|
+
mockApiClient.stopFallbackActivity.mockResolvedValue({
|
|
252
|
+
ok: true,
|
|
253
|
+
data: { success: true },
|
|
254
|
+
});
|
|
255
|
+
const ctx = createMockContext();
|
|
256
|
+
|
|
257
|
+
const result = await stopFallbackActivity(
|
|
258
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
259
|
+
ctx
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
expect(result.result).toMatchObject({
|
|
263
|
+
success: true,
|
|
264
|
+
message: 'Fallback activity stopped',
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it('should call API client with summary when provided', async () => {
|
|
269
|
+
mockApiClient.stopFallbackActivity.mockResolvedValue({
|
|
270
|
+
ok: true,
|
|
271
|
+
data: { success: true },
|
|
272
|
+
});
|
|
273
|
+
const ctx = createMockContext({ sessionId: 'my-session' });
|
|
274
|
+
|
|
275
|
+
await stopFallbackActivity(
|
|
276
|
+
{
|
|
277
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
278
|
+
summary: 'Reviewed 5 files, found 3 issues',
|
|
279
|
+
},
|
|
280
|
+
ctx
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
expect(mockApiClient.stopFallbackActivity).toHaveBeenCalledWith(
|
|
284
|
+
'123e4567-e89b-12d3-a456-426614174000',
|
|
285
|
+
'Reviewed 5 files, found 3 issues',
|
|
286
|
+
'my-session'
|
|
287
|
+
);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('should return error when API call fails', async () => {
|
|
291
|
+
mockApiClient.stopFallbackActivity.mockResolvedValue({
|
|
292
|
+
ok: false,
|
|
293
|
+
error: 'Failed to stop activity',
|
|
294
|
+
});
|
|
295
|
+
const ctx = createMockContext();
|
|
296
|
+
|
|
297
|
+
const result = await stopFallbackActivity({
|
|
298
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
299
|
+
}, ctx);
|
|
300
|
+
|
|
301
|
+
expect(result.isError).toBe(true);
|
|
302
|
+
expect(result.result).toMatchObject({ error: 'Failed to stop activity' });
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
// ============================================================================
|
|
307
|
+
// getActivityHistory Tests
|
|
308
|
+
// ============================================================================
|
|
309
|
+
|
|
310
|
+
describe('getActivityHistory', () => {
|
|
311
|
+
beforeEach(() => vi.clearAllMocks());
|
|
312
|
+
|
|
313
|
+
it('should throw error for missing project_id', async () => {
|
|
314
|
+
const ctx = createMockContext();
|
|
315
|
+
|
|
316
|
+
await expect(getActivityHistory({}, ctx)).rejects.toThrow(ValidationError);
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it('should throw error for invalid project_id UUID', async () => {
|
|
320
|
+
const ctx = createMockContext();
|
|
321
|
+
|
|
322
|
+
await expect(
|
|
323
|
+
getActivityHistory({ project_id: 'invalid' }, ctx)
|
|
324
|
+
).rejects.toThrow(ValidationError);
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
it('should return empty history when no activities', async () => {
|
|
328
|
+
mockApiClient.proxy.mockResolvedValue({
|
|
329
|
+
ok: true,
|
|
330
|
+
data: {
|
|
331
|
+
history: [],
|
|
332
|
+
latest_by_type: {},
|
|
333
|
+
count: 0,
|
|
334
|
+
},
|
|
335
|
+
});
|
|
336
|
+
const ctx = createMockContext();
|
|
337
|
+
|
|
338
|
+
const result = await getActivityHistory(
|
|
339
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
340
|
+
ctx
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
expect(result.result).toMatchObject({
|
|
344
|
+
history: [],
|
|
345
|
+
count: 0,
|
|
346
|
+
});
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it('should return activity history from API', async () => {
|
|
350
|
+
const mockHistory = [
|
|
351
|
+
{
|
|
352
|
+
id: 'h1',
|
|
353
|
+
activity_type: 'code_review',
|
|
354
|
+
completed_at: '2025-01-14T10:00:00Z',
|
|
355
|
+
summary: 'Reviewed auth module',
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
id: 'h2',
|
|
359
|
+
activity_type: 'security_review',
|
|
360
|
+
completed_at: '2025-01-14T09:00:00Z',
|
|
361
|
+
summary: null,
|
|
362
|
+
},
|
|
363
|
+
];
|
|
364
|
+
|
|
365
|
+
mockApiClient.proxy.mockResolvedValue({
|
|
366
|
+
ok: true,
|
|
367
|
+
data: {
|
|
368
|
+
history: mockHistory,
|
|
369
|
+
latest_by_type: { code_review: mockHistory[0] },
|
|
370
|
+
count: 2,
|
|
371
|
+
},
|
|
372
|
+
});
|
|
373
|
+
const ctx = createMockContext();
|
|
374
|
+
|
|
375
|
+
const result = await getActivityHistory(
|
|
376
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
377
|
+
ctx
|
|
378
|
+
);
|
|
379
|
+
|
|
380
|
+
expect((result.result as { history: unknown[] }).history).toHaveLength(2);
|
|
381
|
+
expect((result.result as { count: number }).count).toBe(2);
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
it('should call API proxy with correct parameters', async () => {
|
|
385
|
+
mockApiClient.proxy.mockResolvedValue({
|
|
386
|
+
ok: true,
|
|
387
|
+
data: { history: [], count: 0 },
|
|
388
|
+
});
|
|
389
|
+
const ctx = createMockContext();
|
|
390
|
+
|
|
391
|
+
await getActivityHistory(
|
|
392
|
+
{
|
|
393
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
394
|
+
activity_type: 'security_review',
|
|
395
|
+
limit: 10,
|
|
396
|
+
},
|
|
397
|
+
ctx
|
|
398
|
+
);
|
|
399
|
+
|
|
400
|
+
expect(mockApiClient.proxy).toHaveBeenCalledWith(
|
|
401
|
+
'get_activity_history',
|
|
402
|
+
{
|
|
403
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
404
|
+
activity_type: 'security_review',
|
|
405
|
+
limit: 10,
|
|
406
|
+
offset: 0,
|
|
407
|
+
}
|
|
408
|
+
);
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
it('should return error when API call fails', async () => {
|
|
412
|
+
mockApiClient.proxy.mockResolvedValue({
|
|
413
|
+
ok: false,
|
|
414
|
+
error: 'Query failed',
|
|
415
|
+
});
|
|
416
|
+
const ctx = createMockContext();
|
|
417
|
+
|
|
418
|
+
const result = await getActivityHistory({ project_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx);
|
|
419
|
+
|
|
420
|
+
expect(result.isError).toBe(true);
|
|
421
|
+
expect(result.result).toMatchObject({ error: 'Query failed' });
|
|
422
|
+
});
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
// ============================================================================
|
|
426
|
+
// getActivitySchedules Tests
|
|
427
|
+
// ============================================================================
|
|
428
|
+
|
|
429
|
+
describe('getActivitySchedules', () => {
|
|
430
|
+
beforeEach(() => vi.clearAllMocks());
|
|
431
|
+
|
|
432
|
+
it('should throw error for missing project_id', async () => {
|
|
433
|
+
const ctx = createMockContext();
|
|
434
|
+
|
|
435
|
+
await expect(getActivitySchedules({}, ctx)).rejects.toThrow(ValidationError);
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
it('should throw error for invalid project_id UUID', async () => {
|
|
439
|
+
const ctx = createMockContext();
|
|
440
|
+
|
|
441
|
+
await expect(
|
|
442
|
+
getActivitySchedules({ project_id: 'invalid' }, ctx)
|
|
443
|
+
).rejects.toThrow(ValidationError);
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
it('should return empty schedules when none exist', async () => {
|
|
447
|
+
mockApiClient.proxy.mockResolvedValue({
|
|
448
|
+
ok: true,
|
|
449
|
+
data: {
|
|
450
|
+
schedules: [],
|
|
451
|
+
total_count: 0,
|
|
452
|
+
has_more: false,
|
|
453
|
+
},
|
|
454
|
+
});
|
|
455
|
+
const ctx = createMockContext();
|
|
456
|
+
|
|
457
|
+
const result = await getActivitySchedules(
|
|
458
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
459
|
+
ctx
|
|
460
|
+
);
|
|
461
|
+
|
|
462
|
+
expect(result.result).toMatchObject({
|
|
463
|
+
schedules: [],
|
|
464
|
+
total_count: 0,
|
|
465
|
+
has_more: false,
|
|
466
|
+
});
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
it('should return activity schedules from API', async () => {
|
|
470
|
+
const mockSchedules = [
|
|
471
|
+
{
|
|
472
|
+
id: 's1',
|
|
473
|
+
activity_type: 'code_review',
|
|
474
|
+
schedule_type: 'weekly',
|
|
475
|
+
enabled: true,
|
|
476
|
+
next_run_at: '2025-01-20T10:00:00Z',
|
|
477
|
+
},
|
|
478
|
+
{
|
|
479
|
+
id: 's2',
|
|
480
|
+
activity_type: 'security_review',
|
|
481
|
+
schedule_type: 'monthly',
|
|
482
|
+
enabled: true,
|
|
483
|
+
next_run_at: '2025-02-14T10:00:00Z',
|
|
484
|
+
},
|
|
485
|
+
];
|
|
486
|
+
|
|
487
|
+
mockApiClient.proxy.mockResolvedValue({
|
|
488
|
+
ok: true,
|
|
489
|
+
data: {
|
|
490
|
+
schedules: mockSchedules,
|
|
491
|
+
total_count: 2,
|
|
492
|
+
has_more: false,
|
|
493
|
+
},
|
|
494
|
+
});
|
|
495
|
+
const ctx = createMockContext();
|
|
496
|
+
|
|
497
|
+
const result = await getActivitySchedules(
|
|
498
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
499
|
+
ctx
|
|
500
|
+
);
|
|
501
|
+
|
|
502
|
+
expect((result.result as { schedules: unknown[] }).schedules).toHaveLength(2);
|
|
503
|
+
expect((result.result as { total_count: number }).total_count).toBe(2);
|
|
504
|
+
expect((result.result as { has_more: boolean }).has_more).toBe(false);
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
it('should call API proxy with correct parameters', async () => {
|
|
508
|
+
mockApiClient.proxy.mockResolvedValue({
|
|
509
|
+
ok: true,
|
|
510
|
+
data: { schedules: [], total_count: 0, has_more: false },
|
|
511
|
+
});
|
|
512
|
+
const ctx = createMockContext();
|
|
513
|
+
|
|
514
|
+
await getActivitySchedules(
|
|
515
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
516
|
+
ctx
|
|
517
|
+
);
|
|
518
|
+
|
|
519
|
+
expect(mockApiClient.proxy).toHaveBeenCalledWith(
|
|
520
|
+
'get_activity_schedules',
|
|
521
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000', limit: 50, offset: 0 }
|
|
522
|
+
);
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
it('should return error when API call fails', async () => {
|
|
526
|
+
mockApiClient.proxy.mockResolvedValue({
|
|
527
|
+
ok: false,
|
|
528
|
+
error: 'Query failed',
|
|
529
|
+
});
|
|
530
|
+
const ctx = createMockContext();
|
|
531
|
+
|
|
532
|
+
const result = await getActivitySchedules({ project_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx);
|
|
533
|
+
|
|
534
|
+
expect(result.isError).toBe(true);
|
|
535
|
+
expect(result.result).toMatchObject({ error: 'Query failed' });
|
|
536
|
+
});
|
|
537
|
+
});
|