@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,382 +1,382 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import { logDecision, getDecisions, getDecisionsStats, deleteDecision } from './decisions.js';
|
|
3
|
-
import { ValidationError } from '../validators.js';
|
|
4
|
-
import { createMockContext } from './__test-utils__.js';
|
|
5
|
-
import { mockApiClient } from './__test-setup__.js';
|
|
6
|
-
|
|
7
|
-
// ============================================================================
|
|
8
|
-
// logDecision Tests
|
|
9
|
-
// ============================================================================
|
|
10
|
-
|
|
11
|
-
describe('logDecision', () => {
|
|
12
|
-
beforeEach(() => vi.clearAllMocks());
|
|
13
|
-
|
|
14
|
-
it('should throw error for missing project_id', async () => {
|
|
15
|
-
const ctx = createMockContext();
|
|
16
|
-
|
|
17
|
-
await expect(
|
|
18
|
-
logDecision({ title: 'Test', description: 'Desc' }, ctx)
|
|
19
|
-
).rejects.toThrow(ValidationError);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it('should throw error for invalid project_id UUID', async () => {
|
|
23
|
-
const ctx = createMockContext();
|
|
24
|
-
|
|
25
|
-
await expect(
|
|
26
|
-
logDecision({ project_id: 'invalid', title: 'Test', description: 'Desc' }, ctx)
|
|
27
|
-
).rejects.toThrow(ValidationError);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it('should throw error for missing title', async () => {
|
|
31
|
-
const ctx = createMockContext();
|
|
32
|
-
|
|
33
|
-
await expect(
|
|
34
|
-
logDecision({ project_id: '123e4567-e89b-12d3-a456-426614174000', description: 'Desc' }, ctx)
|
|
35
|
-
).rejects.toThrow(ValidationError);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('should throw error for missing description', async () => {
|
|
39
|
-
const ctx = createMockContext();
|
|
40
|
-
|
|
41
|
-
await expect(
|
|
42
|
-
logDecision({ project_id: '123e4567-e89b-12d3-a456-426614174000', title: 'Test' }, ctx)
|
|
43
|
-
).rejects.toThrow(ValidationError);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it('should log decision successfully', async () => {
|
|
47
|
-
mockApiClient.logDecision.mockResolvedValue({
|
|
48
|
-
ok: true,
|
|
49
|
-
data: { decision_id: 'dec-123' },
|
|
50
|
-
});
|
|
51
|
-
const ctx = createMockContext();
|
|
52
|
-
|
|
53
|
-
const result = await logDecision(
|
|
54
|
-
{
|
|
55
|
-
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
56
|
-
title: 'Use TypeScript',
|
|
57
|
-
description: 'We will use TypeScript for type safety',
|
|
58
|
-
},
|
|
59
|
-
ctx
|
|
60
|
-
);
|
|
61
|
-
|
|
62
|
-
expect(result.result).toMatchObject({
|
|
63
|
-
success: true,
|
|
64
|
-
title: 'Use TypeScript',
|
|
65
|
-
decision_id: 'dec-123',
|
|
66
|
-
});
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('should log decision with rationale and alternatives', async () => {
|
|
70
|
-
mockApiClient.logDecision.mockResolvedValue({
|
|
71
|
-
ok: true,
|
|
72
|
-
data: { decision_id: 'dec-456' },
|
|
73
|
-
});
|
|
74
|
-
const ctx = createMockContext();
|
|
75
|
-
|
|
76
|
-
await logDecision(
|
|
77
|
-
{
|
|
78
|
-
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
79
|
-
title: 'Use PostgreSQL',
|
|
80
|
-
description: 'We will use PostgreSQL as our database',
|
|
81
|
-
rationale: 'Better JSON support and performance',
|
|
82
|
-
alternatives_considered: ['MySQL', 'MongoDB'],
|
|
83
|
-
},
|
|
84
|
-
ctx
|
|
85
|
-
);
|
|
86
|
-
|
|
87
|
-
expect(mockApiClient.logDecision).toHaveBeenCalledWith(
|
|
88
|
-
'123e4567-e89b-12d3-a456-426614174000',
|
|
89
|
-
{
|
|
90
|
-
title: 'Use PostgreSQL',
|
|
91
|
-
description: 'We will use PostgreSQL as our database',
|
|
92
|
-
rationale: 'Better JSON support and performance',
|
|
93
|
-
alternatives_considered: ['MySQL', 'MongoDB'],
|
|
94
|
-
},
|
|
95
|
-
'session-123'
|
|
96
|
-
);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it('should include session_id in API call', async () => {
|
|
100
|
-
mockApiClient.logDecision.mockResolvedValue({
|
|
101
|
-
ok: true,
|
|
102
|
-
data: { decision_id: 'dec-789' },
|
|
103
|
-
});
|
|
104
|
-
const ctx = createMockContext({ sessionId: 'my-session' });
|
|
105
|
-
|
|
106
|
-
await logDecision(
|
|
107
|
-
{
|
|
108
|
-
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
109
|
-
title: 'Test Decision',
|
|
110
|
-
description: 'Test description',
|
|
111
|
-
},
|
|
112
|
-
ctx
|
|
113
|
-
);
|
|
114
|
-
|
|
115
|
-
expect(mockApiClient.logDecision).toHaveBeenCalledWith(
|
|
116
|
-
'123e4567-e89b-12d3-a456-426614174000',
|
|
117
|
-
expect.any(Object),
|
|
118
|
-
'my-session'
|
|
119
|
-
);
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
it('should return error when API call fails', async () => {
|
|
123
|
-
mockApiClient.logDecision.mockResolvedValue({
|
|
124
|
-
ok: false,
|
|
125
|
-
error: 'Insert failed',
|
|
126
|
-
});
|
|
127
|
-
const ctx = createMockContext();
|
|
128
|
-
|
|
129
|
-
const result = await logDecision(
|
|
130
|
-
{
|
|
131
|
-
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
132
|
-
title: 'Test',
|
|
133
|
-
description: 'Test',
|
|
134
|
-
},
|
|
135
|
-
ctx
|
|
136
|
-
);
|
|
137
|
-
|
|
138
|
-
expect(result.isError).toBe(true);
|
|
139
|
-
expect(result.result).toMatchObject({
|
|
140
|
-
error: 'Insert failed',
|
|
141
|
-
});
|
|
142
|
-
});
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
// ============================================================================
|
|
146
|
-
// getDecisions Tests
|
|
147
|
-
// ============================================================================
|
|
148
|
-
|
|
149
|
-
describe('getDecisions', () => {
|
|
150
|
-
beforeEach(() => vi.clearAllMocks());
|
|
151
|
-
|
|
152
|
-
it('should throw error for missing project_id', async () => {
|
|
153
|
-
const ctx = createMockContext();
|
|
154
|
-
|
|
155
|
-
await expect(getDecisions({}, ctx)).rejects.toThrow(ValidationError);
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
it('should throw error for invalid project_id UUID', async () => {
|
|
159
|
-
const ctx = createMockContext();
|
|
160
|
-
|
|
161
|
-
await expect(
|
|
162
|
-
getDecisions({ project_id: 'invalid' }, ctx)
|
|
163
|
-
).rejects.toThrow(ValidationError);
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
it('should return empty list when no decisions', async () => {
|
|
167
|
-
mockApiClient.getDecisions.mockResolvedValue({
|
|
168
|
-
ok: true,
|
|
169
|
-
data: { decisions: [] },
|
|
170
|
-
});
|
|
171
|
-
const ctx = createMockContext();
|
|
172
|
-
|
|
173
|
-
const result = await getDecisions(
|
|
174
|
-
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
175
|
-
ctx
|
|
176
|
-
);
|
|
177
|
-
|
|
178
|
-
expect(result.result).toMatchObject({
|
|
179
|
-
decisions: [],
|
|
180
|
-
});
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
it('should return decisions list', async () => {
|
|
184
|
-
const mockDecisions = [
|
|
185
|
-
{ id: 'd1', title: 'Decision 1', description: 'Desc 1' },
|
|
186
|
-
{ id: 'd2', title: 'Decision 2', description: 'Desc 2' },
|
|
187
|
-
];
|
|
188
|
-
mockApiClient.getDecisions.mockResolvedValue({
|
|
189
|
-
ok: true,
|
|
190
|
-
data: { decisions: mockDecisions },
|
|
191
|
-
});
|
|
192
|
-
const ctx = createMockContext();
|
|
193
|
-
|
|
194
|
-
const result = await getDecisions(
|
|
195
|
-
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
196
|
-
ctx
|
|
197
|
-
);
|
|
198
|
-
|
|
199
|
-
expect(result.result).toMatchObject({
|
|
200
|
-
decisions: mockDecisions,
|
|
201
|
-
});
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
it('should call API client getDecisions with options', async () => {
|
|
205
|
-
mockApiClient.getDecisions.mockResolvedValue({
|
|
206
|
-
ok: true,
|
|
207
|
-
data: { decisions: [] },
|
|
208
|
-
});
|
|
209
|
-
const ctx = createMockContext();
|
|
210
|
-
|
|
211
|
-
await getDecisions(
|
|
212
|
-
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
213
|
-
ctx
|
|
214
|
-
);
|
|
215
|
-
|
|
216
|
-
expect(mockApiClient.getDecisions).toHaveBeenCalledWith(
|
|
217
|
-
'123e4567-e89b-12d3-a456-426614174000',
|
|
218
|
-
{ limit: 10, offset: 0, search_query: undefined }
|
|
219
|
-
);
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
it('should return error when query fails', async () => {
|
|
223
|
-
mockApiClient.getDecisions.mockResolvedValue({
|
|
224
|
-
ok: false,
|
|
225
|
-
error: 'Query failed',
|
|
226
|
-
});
|
|
227
|
-
const ctx = createMockContext();
|
|
228
|
-
|
|
229
|
-
const result = await getDecisions(
|
|
230
|
-
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
231
|
-
ctx
|
|
232
|
-
);
|
|
233
|
-
|
|
234
|
-
expect(result.isError).toBe(true);
|
|
235
|
-
expect(result.result).toMatchObject({
|
|
236
|
-
error: 'Query failed',
|
|
237
|
-
});
|
|
238
|
-
});
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
// ============================================================================
|
|
242
|
-
// deleteDecision Tests
|
|
243
|
-
// ============================================================================
|
|
244
|
-
|
|
245
|
-
describe('deleteDecision', () => {
|
|
246
|
-
beforeEach(() => vi.clearAllMocks());
|
|
247
|
-
|
|
248
|
-
it('should throw error for missing decision_id', async () => {
|
|
249
|
-
const ctx = createMockContext();
|
|
250
|
-
|
|
251
|
-
await expect(deleteDecision({}, ctx)).rejects.toThrow(ValidationError);
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
it('should throw error for invalid decision_id UUID', async () => {
|
|
255
|
-
const ctx = createMockContext();
|
|
256
|
-
|
|
257
|
-
await expect(
|
|
258
|
-
deleteDecision({ decision_id: 'invalid' }, ctx)
|
|
259
|
-
).rejects.toThrow(ValidationError);
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
it('should delete decision successfully', async () => {
|
|
263
|
-
mockApiClient.deleteDecision.mockResolvedValue({
|
|
264
|
-
ok: true,
|
|
265
|
-
data: { success: true },
|
|
266
|
-
});
|
|
267
|
-
const ctx = createMockContext();
|
|
268
|
-
|
|
269
|
-
const result = await deleteDecision(
|
|
270
|
-
{ decision_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
271
|
-
ctx
|
|
272
|
-
);
|
|
273
|
-
|
|
274
|
-
expect(result.result).toMatchObject({
|
|
275
|
-
success: true,
|
|
276
|
-
});
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
it('should call API client deleteDecision', async () => {
|
|
280
|
-
mockApiClient.deleteDecision.mockResolvedValue({
|
|
281
|
-
ok: true,
|
|
282
|
-
data: { success: true },
|
|
283
|
-
});
|
|
284
|
-
const ctx = createMockContext();
|
|
285
|
-
|
|
286
|
-
await deleteDecision(
|
|
287
|
-
{ decision_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
288
|
-
ctx
|
|
289
|
-
);
|
|
290
|
-
|
|
291
|
-
expect(mockApiClient.deleteDecision).toHaveBeenCalledWith(
|
|
292
|
-
'123e4567-e89b-12d3-a456-426614174000'
|
|
293
|
-
);
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
it('should return error when delete fails', async () => {
|
|
297
|
-
mockApiClient.deleteDecision.mockResolvedValue({
|
|
298
|
-
ok: false,
|
|
299
|
-
error: 'Delete failed',
|
|
300
|
-
});
|
|
301
|
-
const ctx = createMockContext();
|
|
302
|
-
|
|
303
|
-
const result = await deleteDecision(
|
|
304
|
-
{ decision_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
305
|
-
ctx
|
|
306
|
-
);
|
|
307
|
-
|
|
308
|
-
expect(result.isError).toBe(true);
|
|
309
|
-
expect(result.result).toMatchObject({
|
|
310
|
-
error: 'Delete failed',
|
|
311
|
-
});
|
|
312
|
-
});
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
// ============================================================================
|
|
316
|
-
// getDecisionsStats Tests
|
|
317
|
-
// ============================================================================
|
|
318
|
-
|
|
319
|
-
describe('getDecisionsStats', () => {
|
|
320
|
-
beforeEach(() => vi.clearAllMocks());
|
|
321
|
-
|
|
322
|
-
it('should throw error for missing project_id', async () => {
|
|
323
|
-
const ctx = createMockContext();
|
|
324
|
-
|
|
325
|
-
await expect(getDecisionsStats({}, ctx)).rejects.toThrow(ValidationError);
|
|
326
|
-
});
|
|
327
|
-
|
|
328
|
-
it('should throw error for invalid project_id UUID', async () => {
|
|
329
|
-
const ctx = createMockContext();
|
|
330
|
-
|
|
331
|
-
await expect(
|
|
332
|
-
getDecisionsStats({ project_id: 'invalid' }, ctx)
|
|
333
|
-
).rejects.toThrow(ValidationError);
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
it('should return decisions stats', async () => {
|
|
337
|
-
mockApiClient.getDecisionsStats.mockResolvedValue({
|
|
338
|
-
ok: true,
|
|
339
|
-
data: { total: 10 },
|
|
340
|
-
});
|
|
341
|
-
const ctx = createMockContext();
|
|
342
|
-
|
|
343
|
-
const result = await getDecisionsStats(
|
|
344
|
-
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
345
|
-
ctx
|
|
346
|
-
);
|
|
347
|
-
|
|
348
|
-
expect(result.result).toMatchObject({ total: 10 });
|
|
349
|
-
});
|
|
350
|
-
|
|
351
|
-
it('should call API client getDecisionsStats', async () => {
|
|
352
|
-
mockApiClient.getDecisionsStats.mockResolvedValue({
|
|
353
|
-
ok: true,
|
|
354
|
-
data: { total: 0 },
|
|
355
|
-
});
|
|
356
|
-
const ctx = createMockContext();
|
|
357
|
-
|
|
358
|
-
await getDecisionsStats(
|
|
359
|
-
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
360
|
-
ctx
|
|
361
|
-
);
|
|
362
|
-
|
|
363
|
-
expect(mockApiClient.getDecisionsStats).toHaveBeenCalledWith(
|
|
364
|
-
'123e4567-e89b-12d3-a456-426614174000'
|
|
365
|
-
);
|
|
366
|
-
});
|
|
367
|
-
|
|
368
|
-
it('should return error when API call fails', async () => {
|
|
369
|
-
mockApiClient.getDecisionsStats.mockResolvedValue({
|
|
370
|
-
ok: false,
|
|
371
|
-
error: 'Query failed',
|
|
372
|
-
});
|
|
373
|
-
const ctx = createMockContext();
|
|
374
|
-
|
|
375
|
-
const result = await getDecisionsStats({ project_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx);
|
|
376
|
-
|
|
377
|
-
expect(result.isError).toBe(true);
|
|
378
|
-
expect(result.result).toMatchObject({
|
|
379
|
-
error: 'Query failed',
|
|
380
|
-
});
|
|
381
|
-
});
|
|
382
|
-
});
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { logDecision, getDecisions, getDecisionsStats, deleteDecision } from './decisions.js';
|
|
3
|
+
import { ValidationError } from '../validators.js';
|
|
4
|
+
import { createMockContext } from './__test-utils__.js';
|
|
5
|
+
import { mockApiClient } from './__test-setup__.js';
|
|
6
|
+
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// logDecision Tests
|
|
9
|
+
// ============================================================================
|
|
10
|
+
|
|
11
|
+
describe('logDecision', () => {
|
|
12
|
+
beforeEach(() => vi.clearAllMocks());
|
|
13
|
+
|
|
14
|
+
it('should throw error for missing project_id', async () => {
|
|
15
|
+
const ctx = createMockContext();
|
|
16
|
+
|
|
17
|
+
await expect(
|
|
18
|
+
logDecision({ title: 'Test', description: 'Desc' }, ctx)
|
|
19
|
+
).rejects.toThrow(ValidationError);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should throw error for invalid project_id UUID', async () => {
|
|
23
|
+
const ctx = createMockContext();
|
|
24
|
+
|
|
25
|
+
await expect(
|
|
26
|
+
logDecision({ project_id: 'invalid', title: 'Test', description: 'Desc' }, ctx)
|
|
27
|
+
).rejects.toThrow(ValidationError);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should throw error for missing title', async () => {
|
|
31
|
+
const ctx = createMockContext();
|
|
32
|
+
|
|
33
|
+
await expect(
|
|
34
|
+
logDecision({ project_id: '123e4567-e89b-12d3-a456-426614174000', description: 'Desc' }, ctx)
|
|
35
|
+
).rejects.toThrow(ValidationError);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should throw error for missing description', async () => {
|
|
39
|
+
const ctx = createMockContext();
|
|
40
|
+
|
|
41
|
+
await expect(
|
|
42
|
+
logDecision({ project_id: '123e4567-e89b-12d3-a456-426614174000', title: 'Test' }, ctx)
|
|
43
|
+
).rejects.toThrow(ValidationError);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should log decision successfully', async () => {
|
|
47
|
+
mockApiClient.logDecision.mockResolvedValue({
|
|
48
|
+
ok: true,
|
|
49
|
+
data: { decision_id: 'dec-123' },
|
|
50
|
+
});
|
|
51
|
+
const ctx = createMockContext();
|
|
52
|
+
|
|
53
|
+
const result = await logDecision(
|
|
54
|
+
{
|
|
55
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
56
|
+
title: 'Use TypeScript',
|
|
57
|
+
description: 'We will use TypeScript for type safety',
|
|
58
|
+
},
|
|
59
|
+
ctx
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
expect(result.result).toMatchObject({
|
|
63
|
+
success: true,
|
|
64
|
+
title: 'Use TypeScript',
|
|
65
|
+
decision_id: 'dec-123',
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('should log decision with rationale and alternatives', async () => {
|
|
70
|
+
mockApiClient.logDecision.mockResolvedValue({
|
|
71
|
+
ok: true,
|
|
72
|
+
data: { decision_id: 'dec-456' },
|
|
73
|
+
});
|
|
74
|
+
const ctx = createMockContext();
|
|
75
|
+
|
|
76
|
+
await logDecision(
|
|
77
|
+
{
|
|
78
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
79
|
+
title: 'Use PostgreSQL',
|
|
80
|
+
description: 'We will use PostgreSQL as our database',
|
|
81
|
+
rationale: 'Better JSON support and performance',
|
|
82
|
+
alternatives_considered: ['MySQL', 'MongoDB'],
|
|
83
|
+
},
|
|
84
|
+
ctx
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
expect(mockApiClient.logDecision).toHaveBeenCalledWith(
|
|
88
|
+
'123e4567-e89b-12d3-a456-426614174000',
|
|
89
|
+
{
|
|
90
|
+
title: 'Use PostgreSQL',
|
|
91
|
+
description: 'We will use PostgreSQL as our database',
|
|
92
|
+
rationale: 'Better JSON support and performance',
|
|
93
|
+
alternatives_considered: ['MySQL', 'MongoDB'],
|
|
94
|
+
},
|
|
95
|
+
'session-123'
|
|
96
|
+
);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should include session_id in API call', async () => {
|
|
100
|
+
mockApiClient.logDecision.mockResolvedValue({
|
|
101
|
+
ok: true,
|
|
102
|
+
data: { decision_id: 'dec-789' },
|
|
103
|
+
});
|
|
104
|
+
const ctx = createMockContext({ sessionId: 'my-session' });
|
|
105
|
+
|
|
106
|
+
await logDecision(
|
|
107
|
+
{
|
|
108
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
109
|
+
title: 'Test Decision',
|
|
110
|
+
description: 'Test description',
|
|
111
|
+
},
|
|
112
|
+
ctx
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
expect(mockApiClient.logDecision).toHaveBeenCalledWith(
|
|
116
|
+
'123e4567-e89b-12d3-a456-426614174000',
|
|
117
|
+
expect.any(Object),
|
|
118
|
+
'my-session'
|
|
119
|
+
);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('should return error when API call fails', async () => {
|
|
123
|
+
mockApiClient.logDecision.mockResolvedValue({
|
|
124
|
+
ok: false,
|
|
125
|
+
error: 'Insert failed',
|
|
126
|
+
});
|
|
127
|
+
const ctx = createMockContext();
|
|
128
|
+
|
|
129
|
+
const result = await logDecision(
|
|
130
|
+
{
|
|
131
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
132
|
+
title: 'Test',
|
|
133
|
+
description: 'Test',
|
|
134
|
+
},
|
|
135
|
+
ctx
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
expect(result.isError).toBe(true);
|
|
139
|
+
expect(result.result).toMatchObject({
|
|
140
|
+
error: 'Insert failed',
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// ============================================================================
|
|
146
|
+
// getDecisions Tests
|
|
147
|
+
// ============================================================================
|
|
148
|
+
|
|
149
|
+
describe('getDecisions', () => {
|
|
150
|
+
beforeEach(() => vi.clearAllMocks());
|
|
151
|
+
|
|
152
|
+
it('should throw error for missing project_id', async () => {
|
|
153
|
+
const ctx = createMockContext();
|
|
154
|
+
|
|
155
|
+
await expect(getDecisions({}, ctx)).rejects.toThrow(ValidationError);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('should throw error for invalid project_id UUID', async () => {
|
|
159
|
+
const ctx = createMockContext();
|
|
160
|
+
|
|
161
|
+
await expect(
|
|
162
|
+
getDecisions({ project_id: 'invalid' }, ctx)
|
|
163
|
+
).rejects.toThrow(ValidationError);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('should return empty list when no decisions', async () => {
|
|
167
|
+
mockApiClient.getDecisions.mockResolvedValue({
|
|
168
|
+
ok: true,
|
|
169
|
+
data: { decisions: [] },
|
|
170
|
+
});
|
|
171
|
+
const ctx = createMockContext();
|
|
172
|
+
|
|
173
|
+
const result = await getDecisions(
|
|
174
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
175
|
+
ctx
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
expect(result.result).toMatchObject({
|
|
179
|
+
decisions: [],
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('should return decisions list', async () => {
|
|
184
|
+
const mockDecisions = [
|
|
185
|
+
{ id: 'd1', title: 'Decision 1', description: 'Desc 1' },
|
|
186
|
+
{ id: 'd2', title: 'Decision 2', description: 'Desc 2' },
|
|
187
|
+
];
|
|
188
|
+
mockApiClient.getDecisions.mockResolvedValue({
|
|
189
|
+
ok: true,
|
|
190
|
+
data: { decisions: mockDecisions },
|
|
191
|
+
});
|
|
192
|
+
const ctx = createMockContext();
|
|
193
|
+
|
|
194
|
+
const result = await getDecisions(
|
|
195
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
196
|
+
ctx
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
expect(result.result).toMatchObject({
|
|
200
|
+
decisions: mockDecisions,
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('should call API client getDecisions with options', async () => {
|
|
205
|
+
mockApiClient.getDecisions.mockResolvedValue({
|
|
206
|
+
ok: true,
|
|
207
|
+
data: { decisions: [] },
|
|
208
|
+
});
|
|
209
|
+
const ctx = createMockContext();
|
|
210
|
+
|
|
211
|
+
await getDecisions(
|
|
212
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
213
|
+
ctx
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
expect(mockApiClient.getDecisions).toHaveBeenCalledWith(
|
|
217
|
+
'123e4567-e89b-12d3-a456-426614174000',
|
|
218
|
+
{ limit: 10, offset: 0, search_query: undefined }
|
|
219
|
+
);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('should return error when query fails', async () => {
|
|
223
|
+
mockApiClient.getDecisions.mockResolvedValue({
|
|
224
|
+
ok: false,
|
|
225
|
+
error: 'Query failed',
|
|
226
|
+
});
|
|
227
|
+
const ctx = createMockContext();
|
|
228
|
+
|
|
229
|
+
const result = await getDecisions(
|
|
230
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
231
|
+
ctx
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
expect(result.isError).toBe(true);
|
|
235
|
+
expect(result.result).toMatchObject({
|
|
236
|
+
error: 'Query failed',
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// ============================================================================
|
|
242
|
+
// deleteDecision Tests
|
|
243
|
+
// ============================================================================
|
|
244
|
+
|
|
245
|
+
describe('deleteDecision', () => {
|
|
246
|
+
beforeEach(() => vi.clearAllMocks());
|
|
247
|
+
|
|
248
|
+
it('should throw error for missing decision_id', async () => {
|
|
249
|
+
const ctx = createMockContext();
|
|
250
|
+
|
|
251
|
+
await expect(deleteDecision({}, ctx)).rejects.toThrow(ValidationError);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it('should throw error for invalid decision_id UUID', async () => {
|
|
255
|
+
const ctx = createMockContext();
|
|
256
|
+
|
|
257
|
+
await expect(
|
|
258
|
+
deleteDecision({ decision_id: 'invalid' }, ctx)
|
|
259
|
+
).rejects.toThrow(ValidationError);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('should delete decision successfully', async () => {
|
|
263
|
+
mockApiClient.deleteDecision.mockResolvedValue({
|
|
264
|
+
ok: true,
|
|
265
|
+
data: { success: true },
|
|
266
|
+
});
|
|
267
|
+
const ctx = createMockContext();
|
|
268
|
+
|
|
269
|
+
const result = await deleteDecision(
|
|
270
|
+
{ decision_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
271
|
+
ctx
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
expect(result.result).toMatchObject({
|
|
275
|
+
success: true,
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('should call API client deleteDecision', async () => {
|
|
280
|
+
mockApiClient.deleteDecision.mockResolvedValue({
|
|
281
|
+
ok: true,
|
|
282
|
+
data: { success: true },
|
|
283
|
+
});
|
|
284
|
+
const ctx = createMockContext();
|
|
285
|
+
|
|
286
|
+
await deleteDecision(
|
|
287
|
+
{ decision_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
288
|
+
ctx
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
expect(mockApiClient.deleteDecision).toHaveBeenCalledWith(
|
|
292
|
+
'123e4567-e89b-12d3-a456-426614174000'
|
|
293
|
+
);
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it('should return error when delete fails', async () => {
|
|
297
|
+
mockApiClient.deleteDecision.mockResolvedValue({
|
|
298
|
+
ok: false,
|
|
299
|
+
error: 'Delete failed',
|
|
300
|
+
});
|
|
301
|
+
const ctx = createMockContext();
|
|
302
|
+
|
|
303
|
+
const result = await deleteDecision(
|
|
304
|
+
{ decision_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
305
|
+
ctx
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
expect(result.isError).toBe(true);
|
|
309
|
+
expect(result.result).toMatchObject({
|
|
310
|
+
error: 'Delete failed',
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
// ============================================================================
|
|
316
|
+
// getDecisionsStats Tests
|
|
317
|
+
// ============================================================================
|
|
318
|
+
|
|
319
|
+
describe('getDecisionsStats', () => {
|
|
320
|
+
beforeEach(() => vi.clearAllMocks());
|
|
321
|
+
|
|
322
|
+
it('should throw error for missing project_id', async () => {
|
|
323
|
+
const ctx = createMockContext();
|
|
324
|
+
|
|
325
|
+
await expect(getDecisionsStats({}, ctx)).rejects.toThrow(ValidationError);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
it('should throw error for invalid project_id UUID', async () => {
|
|
329
|
+
const ctx = createMockContext();
|
|
330
|
+
|
|
331
|
+
await expect(
|
|
332
|
+
getDecisionsStats({ project_id: 'invalid' }, ctx)
|
|
333
|
+
).rejects.toThrow(ValidationError);
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
it('should return decisions stats', async () => {
|
|
337
|
+
mockApiClient.getDecisionsStats.mockResolvedValue({
|
|
338
|
+
ok: true,
|
|
339
|
+
data: { total: 10 },
|
|
340
|
+
});
|
|
341
|
+
const ctx = createMockContext();
|
|
342
|
+
|
|
343
|
+
const result = await getDecisionsStats(
|
|
344
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
345
|
+
ctx
|
|
346
|
+
);
|
|
347
|
+
|
|
348
|
+
expect(result.result).toMatchObject({ total: 10 });
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
it('should call API client getDecisionsStats', async () => {
|
|
352
|
+
mockApiClient.getDecisionsStats.mockResolvedValue({
|
|
353
|
+
ok: true,
|
|
354
|
+
data: { total: 0 },
|
|
355
|
+
});
|
|
356
|
+
const ctx = createMockContext();
|
|
357
|
+
|
|
358
|
+
await getDecisionsStats(
|
|
359
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
360
|
+
ctx
|
|
361
|
+
);
|
|
362
|
+
|
|
363
|
+
expect(mockApiClient.getDecisionsStats).toHaveBeenCalledWith(
|
|
364
|
+
'123e4567-e89b-12d3-a456-426614174000'
|
|
365
|
+
);
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
it('should return error when API call fails', async () => {
|
|
369
|
+
mockApiClient.getDecisionsStats.mockResolvedValue({
|
|
370
|
+
ok: false,
|
|
371
|
+
error: 'Query failed',
|
|
372
|
+
});
|
|
373
|
+
const ctx = createMockContext();
|
|
374
|
+
|
|
375
|
+
const result = await getDecisionsStats({ project_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx);
|
|
376
|
+
|
|
377
|
+
expect(result.isError).toBe(true);
|
|
378
|
+
expect(result.result).toMatchObject({
|
|
379
|
+
error: 'Query failed',
|
|
380
|
+
});
|
|
381
|
+
});
|
|
382
|
+
});
|