@vibescope/mcp-server 0.2.5 → 0.2.6
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/cli.js +26 -26
- package/dist/handlers/tool-docs.js +828 -828
- package/dist/index.js +73 -73
- package/dist/templates/agent-guidelines.js +185 -185
- package/dist/token-tracking.js +4 -2
- package/dist/tools.js +65 -65
- package/dist/utils.js +11 -11
- package/docs/TOOLS.md +2053 -2053
- package/package.json +1 -1
- package/scripts/generate-docs.ts +212 -212
- package/scripts/version-bump.ts +203 -203
- package/src/api-client.test.ts +723 -723
- package/src/api-client.ts +2499 -2499
- package/src/cli.ts +212 -212
- package/src/handlers/__test-setup__.ts +236 -236
- package/src/handlers/__test-utils__.ts +87 -87
- package/src/handlers/blockers.test.ts +468 -468
- package/src/handlers/blockers.ts +163 -163
- package/src/handlers/bodies-of-work.test.ts +704 -704
- package/src/handlers/bodies-of-work.ts +526 -526
- 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 +541 -541
- package/src/handlers/discovery.test.ts +206 -206
- package/src/handlers/discovery.ts +390 -390
- 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 +84 -84
- 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 +239 -239
- package/src/handlers/requests.test.ts +303 -303
- package/src/handlers/requests.ts +99 -99
- package/src/handlers/roles.test.ts +303 -303
- package/src/handlers/roles.ts +226 -226
- package/src/handlers/session.test.ts +875 -875
- package/src/handlers/session.ts +738 -738
- package/src/handlers/sprints.test.ts +732 -732
- package/src/handlers/sprints.ts +537 -537
- package/src/handlers/tasks.test.ts +907 -907
- package/src/handlers/tasks.ts +945 -945
- package/src/handlers/tool-categories.test.ts +66 -66
- package/src/handlers/tool-docs.ts +1096 -1096
- package/src/handlers/types.test.ts +259 -259
- package/src/handlers/types.ts +175 -175
- package/src/handlers/validation.test.ts +582 -582
- package/src/handlers/validation.ts +97 -97
- package/src/index.ts +792 -792
- package/src/setup.test.ts +233 -231
- package/src/setup.ts +370 -370
- package/src/templates/agent-guidelines.ts +210 -210
- package/src/token-tracking.test.ts +463 -453
- package/src/token-tracking.ts +166 -164
- package/src/tools.ts +3562 -3562
- package/src/utils.test.ts +683 -683
- package/src/utils.ts +436 -436
- package/src/validators.test.ts +223 -223
- package/src/validators.ts +249 -249
- package/tsconfig.json +16 -16
- package/vitest.config.ts +14 -14
- package/dist/knowledge.d.ts +0 -6
- package/dist/knowledge.js +0 -218
|
@@ -1,631 +1,631 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import {
|
|
3
|
-
addGitIssue,
|
|
4
|
-
resolveGitIssue,
|
|
5
|
-
getGitIssues,
|
|
6
|
-
deleteGitIssue,
|
|
7
|
-
} from './git-issues.js';
|
|
8
|
-
import { ValidationError } from '../validators.js';
|
|
9
|
-
import { createMockContext } from './__test-utils__.js';
|
|
10
|
-
import { mockApiClient } from './__test-setup__.js';
|
|
11
|
-
|
|
12
|
-
const VALID_PROJECT_ID = '123e4567-e89b-12d3-a456-426614174000';
|
|
13
|
-
const VALID_GIT_ISSUE_ID = '987e6543-e21b-12d3-a456-426614174000';
|
|
14
|
-
|
|
15
|
-
// ============================================================================
|
|
16
|
-
// addGitIssue Tests
|
|
17
|
-
// ============================================================================
|
|
18
|
-
|
|
19
|
-
describe('addGitIssue', () => {
|
|
20
|
-
beforeEach(() => vi.clearAllMocks());
|
|
21
|
-
|
|
22
|
-
it('should throw error for missing project_id', async () => {
|
|
23
|
-
const ctx = createMockContext();
|
|
24
|
-
|
|
25
|
-
await expect(
|
|
26
|
-
addGitIssue({ issue_type: 'merge_conflict', branch: 'feature/test' }, ctx)
|
|
27
|
-
).rejects.toThrow(ValidationError);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it('should throw error for invalid project_id UUID', async () => {
|
|
31
|
-
const ctx = createMockContext();
|
|
32
|
-
|
|
33
|
-
await expect(
|
|
34
|
-
addGitIssue({ project_id: 'invalid', issue_type: 'merge_conflict', branch: 'feature/test' }, ctx)
|
|
35
|
-
).rejects.toThrow(ValidationError);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('should throw error for missing issue_type', async () => {
|
|
39
|
-
const ctx = createMockContext();
|
|
40
|
-
|
|
41
|
-
await expect(
|
|
42
|
-
addGitIssue({ project_id: VALID_PROJECT_ID, branch: 'feature/test' }, ctx)
|
|
43
|
-
).rejects.toThrow(ValidationError);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it('should throw error for missing branch', async () => {
|
|
47
|
-
const ctx = createMockContext();
|
|
48
|
-
|
|
49
|
-
await expect(
|
|
50
|
-
addGitIssue({ project_id: VALID_PROJECT_ID, issue_type: 'merge_conflict' }, ctx)
|
|
51
|
-
).rejects.toThrow(ValidationError);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it('should throw error for invalid issue_type', async () => {
|
|
55
|
-
const ctx = createMockContext();
|
|
56
|
-
|
|
57
|
-
await expect(
|
|
58
|
-
addGitIssue({
|
|
59
|
-
project_id: VALID_PROJECT_ID,
|
|
60
|
-
issue_type: 'invalid_type',
|
|
61
|
-
branch: 'feature/test',
|
|
62
|
-
}, ctx)
|
|
63
|
-
).rejects.toThrow(ValidationError);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it('should throw error for invalid task_id UUID', async () => {
|
|
67
|
-
const ctx = createMockContext();
|
|
68
|
-
|
|
69
|
-
await expect(
|
|
70
|
-
addGitIssue({
|
|
71
|
-
project_id: VALID_PROJECT_ID,
|
|
72
|
-
issue_type: 'merge_conflict',
|
|
73
|
-
branch: 'feature/test',
|
|
74
|
-
task_id: 'invalid-uuid',
|
|
75
|
-
}, ctx)
|
|
76
|
-
).rejects.toThrow(ValidationError);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('should add git issue successfully', async () => {
|
|
80
|
-
mockApiClient.addGitIssue.mockResolvedValue({
|
|
81
|
-
ok: true,
|
|
82
|
-
data: { success: true, git_issue_id: VALID_GIT_ISSUE_ID },
|
|
83
|
-
});
|
|
84
|
-
const ctx = createMockContext();
|
|
85
|
-
|
|
86
|
-
const result = await addGitIssue(
|
|
87
|
-
{
|
|
88
|
-
project_id: VALID_PROJECT_ID,
|
|
89
|
-
issue_type: 'merge_conflict',
|
|
90
|
-
branch: 'feature/test',
|
|
91
|
-
},
|
|
92
|
-
ctx
|
|
93
|
-
);
|
|
94
|
-
|
|
95
|
-
expect(result.result).toMatchObject({
|
|
96
|
-
success: true,
|
|
97
|
-
git_issue_id: VALID_GIT_ISSUE_ID,
|
|
98
|
-
});
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it('should add git issue with all optional fields', async () => {
|
|
102
|
-
mockApiClient.addGitIssue.mockResolvedValue({
|
|
103
|
-
ok: true,
|
|
104
|
-
data: { success: true, git_issue_id: VALID_GIT_ISSUE_ID },
|
|
105
|
-
});
|
|
106
|
-
const ctx = createMockContext({ sessionId: 'test-session' });
|
|
107
|
-
|
|
108
|
-
await addGitIssue(
|
|
109
|
-
{
|
|
110
|
-
project_id: VALID_PROJECT_ID,
|
|
111
|
-
issue_type: 'pr_not_mergeable',
|
|
112
|
-
branch: 'feature/test',
|
|
113
|
-
target_branch: 'main',
|
|
114
|
-
pr_url: 'https://github.com/org/repo/pull/123',
|
|
115
|
-
conflicting_files: ['src/file1.ts', 'src/file2.ts'],
|
|
116
|
-
error_message: 'Merge conflict in file1.ts',
|
|
117
|
-
task_id: VALID_GIT_ISSUE_ID,
|
|
118
|
-
},
|
|
119
|
-
ctx
|
|
120
|
-
);
|
|
121
|
-
|
|
122
|
-
expect(mockApiClient.addGitIssue).toHaveBeenCalledWith(
|
|
123
|
-
VALID_PROJECT_ID,
|
|
124
|
-
{
|
|
125
|
-
issue_type: 'pr_not_mergeable',
|
|
126
|
-
branch: 'feature/test',
|
|
127
|
-
target_branch: 'main',
|
|
128
|
-
pr_url: 'https://github.com/org/repo/pull/123',
|
|
129
|
-
conflicting_files: ['src/file1.ts', 'src/file2.ts'],
|
|
130
|
-
error_message: 'Merge conflict in file1.ts',
|
|
131
|
-
task_id: VALID_GIT_ISSUE_ID,
|
|
132
|
-
},
|
|
133
|
-
'test-session'
|
|
134
|
-
);
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
it('should include session_id in API call', async () => {
|
|
138
|
-
mockApiClient.addGitIssue.mockResolvedValue({
|
|
139
|
-
ok: true,
|
|
140
|
-
data: { success: true, git_issue_id: VALID_GIT_ISSUE_ID },
|
|
141
|
-
});
|
|
142
|
-
const ctx = createMockContext({ sessionId: 'my-session' });
|
|
143
|
-
|
|
144
|
-
await addGitIssue(
|
|
145
|
-
{
|
|
146
|
-
project_id: VALID_PROJECT_ID,
|
|
147
|
-
issue_type: 'push_failed',
|
|
148
|
-
branch: 'feature/test',
|
|
149
|
-
},
|
|
150
|
-
ctx
|
|
151
|
-
);
|
|
152
|
-
|
|
153
|
-
expect(mockApiClient.addGitIssue).toHaveBeenCalledWith(
|
|
154
|
-
VALID_PROJECT_ID,
|
|
155
|
-
expect.objectContaining({
|
|
156
|
-
issue_type: 'push_failed',
|
|
157
|
-
branch: 'feature/test',
|
|
158
|
-
}),
|
|
159
|
-
'my-session'
|
|
160
|
-
);
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
it('should accept all valid issue types', async () => {
|
|
164
|
-
const validTypes = ['merge_conflict', 'push_failed', 'rebase_needed', 'branch_diverged', 'pr_not_mergeable'];
|
|
165
|
-
const ctx = createMockContext();
|
|
166
|
-
|
|
167
|
-
for (const issueType of validTypes) {
|
|
168
|
-
mockApiClient.addGitIssue.mockResolvedValue({
|
|
169
|
-
ok: true,
|
|
170
|
-
data: { success: true, git_issue_id: VALID_GIT_ISSUE_ID },
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
await expect(
|
|
174
|
-
addGitIssue({
|
|
175
|
-
project_id: VALID_PROJECT_ID,
|
|
176
|
-
issue_type: issueType,
|
|
177
|
-
branch: 'feature/test',
|
|
178
|
-
}, ctx)
|
|
179
|
-
).resolves.not.toThrow();
|
|
180
|
-
}
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
it('should return error when API call fails', async () => {
|
|
184
|
-
mockApiClient.addGitIssue.mockResolvedValue({
|
|
185
|
-
ok: false,
|
|
186
|
-
error: 'Insert failed',
|
|
187
|
-
});
|
|
188
|
-
const ctx = createMockContext();
|
|
189
|
-
|
|
190
|
-
const result = await addGitIssue({
|
|
191
|
-
project_id: VALID_PROJECT_ID,
|
|
192
|
-
issue_type: 'merge_conflict',
|
|
193
|
-
branch: 'feature/test',
|
|
194
|
-
}, ctx);
|
|
195
|
-
|
|
196
|
-
expect(result.isError).toBe(true);
|
|
197
|
-
expect(result.result).toMatchObject({ error: 'Insert failed' });
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
it('should return default error message when API fails without error', async () => {
|
|
201
|
-
mockApiClient.addGitIssue.mockResolvedValue({
|
|
202
|
-
ok: false,
|
|
203
|
-
});
|
|
204
|
-
const ctx = createMockContext();
|
|
205
|
-
|
|
206
|
-
const result = await addGitIssue({
|
|
207
|
-
project_id: VALID_PROJECT_ID,
|
|
208
|
-
issue_type: 'merge_conflict',
|
|
209
|
-
branch: 'feature/test',
|
|
210
|
-
}, ctx);
|
|
211
|
-
|
|
212
|
-
expect(result.isError).toBe(true);
|
|
213
|
-
expect(result.result).toMatchObject({ error: 'Failed to add git issue' });
|
|
214
|
-
});
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
// ============================================================================
|
|
218
|
-
// resolveGitIssue Tests
|
|
219
|
-
// ============================================================================
|
|
220
|
-
|
|
221
|
-
describe('resolveGitIssue', () => {
|
|
222
|
-
beforeEach(() => vi.clearAllMocks());
|
|
223
|
-
|
|
224
|
-
it('should throw error for missing git_issue_id', async () => {
|
|
225
|
-
const ctx = createMockContext();
|
|
226
|
-
|
|
227
|
-
await expect(resolveGitIssue({}, ctx)).rejects.toThrow(ValidationError);
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
it('should throw error for invalid git_issue_id UUID', async () => {
|
|
231
|
-
const ctx = createMockContext();
|
|
232
|
-
|
|
233
|
-
await expect(
|
|
234
|
-
resolveGitIssue({ git_issue_id: 'invalid' }, ctx)
|
|
235
|
-
).rejects.toThrow(ValidationError);
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
it('should resolve git issue successfully', async () => {
|
|
239
|
-
mockApiClient.resolveGitIssue.mockResolvedValue({
|
|
240
|
-
ok: true,
|
|
241
|
-
data: { success: true, git_issue_id: VALID_GIT_ISSUE_ID },
|
|
242
|
-
});
|
|
243
|
-
const ctx = createMockContext();
|
|
244
|
-
|
|
245
|
-
const result = await resolveGitIssue(
|
|
246
|
-
{ git_issue_id: VALID_GIT_ISSUE_ID },
|
|
247
|
-
ctx
|
|
248
|
-
);
|
|
249
|
-
|
|
250
|
-
expect(result.result).toMatchObject({
|
|
251
|
-
success: true,
|
|
252
|
-
git_issue_id: VALID_GIT_ISSUE_ID,
|
|
253
|
-
});
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
it('should resolve git issue with resolution_note', async () => {
|
|
257
|
-
mockApiClient.resolveGitIssue.mockResolvedValue({
|
|
258
|
-
ok: true,
|
|
259
|
-
data: { success: true, git_issue_id: VALID_GIT_ISSUE_ID },
|
|
260
|
-
});
|
|
261
|
-
const ctx = createMockContext({ sessionId: 'test-session' });
|
|
262
|
-
|
|
263
|
-
await resolveGitIssue(
|
|
264
|
-
{
|
|
265
|
-
git_issue_id: VALID_GIT_ISSUE_ID,
|
|
266
|
-
resolution_note: 'Resolved by rebasing',
|
|
267
|
-
},
|
|
268
|
-
ctx
|
|
269
|
-
);
|
|
270
|
-
|
|
271
|
-
expect(mockApiClient.resolveGitIssue).toHaveBeenCalledWith(
|
|
272
|
-
VALID_GIT_ISSUE_ID,
|
|
273
|
-
{ resolution_note: 'Resolved by rebasing', auto_resolved: undefined },
|
|
274
|
-
'test-session'
|
|
275
|
-
);
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
it('should resolve git issue with auto_resolved flag', async () => {
|
|
279
|
-
mockApiClient.resolveGitIssue.mockResolvedValue({
|
|
280
|
-
ok: true,
|
|
281
|
-
data: { success: true, git_issue_id: VALID_GIT_ISSUE_ID },
|
|
282
|
-
});
|
|
283
|
-
const ctx = createMockContext({ sessionId: 'test-session' });
|
|
284
|
-
|
|
285
|
-
await resolveGitIssue(
|
|
286
|
-
{
|
|
287
|
-
git_issue_id: VALID_GIT_ISSUE_ID,
|
|
288
|
-
auto_resolved: true,
|
|
289
|
-
},
|
|
290
|
-
ctx
|
|
291
|
-
);
|
|
292
|
-
|
|
293
|
-
expect(mockApiClient.resolveGitIssue).toHaveBeenCalledWith(
|
|
294
|
-
VALID_GIT_ISSUE_ID,
|
|
295
|
-
{ resolution_note: undefined, auto_resolved: true },
|
|
296
|
-
'test-session'
|
|
297
|
-
);
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
it('should include session_id in API call', async () => {
|
|
301
|
-
mockApiClient.resolveGitIssue.mockResolvedValue({
|
|
302
|
-
ok: true,
|
|
303
|
-
data: { success: true, git_issue_id: VALID_GIT_ISSUE_ID },
|
|
304
|
-
});
|
|
305
|
-
const ctx = createMockContext({ sessionId: 'my-session' });
|
|
306
|
-
|
|
307
|
-
await resolveGitIssue(
|
|
308
|
-
{ git_issue_id: VALID_GIT_ISSUE_ID },
|
|
309
|
-
ctx
|
|
310
|
-
);
|
|
311
|
-
|
|
312
|
-
expect(mockApiClient.resolveGitIssue).toHaveBeenCalledWith(
|
|
313
|
-
VALID_GIT_ISSUE_ID,
|
|
314
|
-
expect.any(Object),
|
|
315
|
-
'my-session'
|
|
316
|
-
);
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
it('should return error when API call fails', async () => {
|
|
320
|
-
mockApiClient.resolveGitIssue.mockResolvedValue({
|
|
321
|
-
ok: false,
|
|
322
|
-
error: 'Update failed',
|
|
323
|
-
});
|
|
324
|
-
const ctx = createMockContext();
|
|
325
|
-
|
|
326
|
-
const result = await resolveGitIssue({ git_issue_id: VALID_GIT_ISSUE_ID }, ctx);
|
|
327
|
-
|
|
328
|
-
expect(result.isError).toBe(true);
|
|
329
|
-
expect(result.result).toMatchObject({ error: 'Update failed' });
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
it('should return default error message when API fails without error', async () => {
|
|
333
|
-
mockApiClient.resolveGitIssue.mockResolvedValue({
|
|
334
|
-
ok: false,
|
|
335
|
-
});
|
|
336
|
-
const ctx = createMockContext();
|
|
337
|
-
|
|
338
|
-
const result = await resolveGitIssue({ git_issue_id: VALID_GIT_ISSUE_ID }, ctx);
|
|
339
|
-
|
|
340
|
-
expect(result.isError).toBe(true);
|
|
341
|
-
expect(result.result).toMatchObject({ error: 'Failed to resolve git issue' });
|
|
342
|
-
});
|
|
343
|
-
});
|
|
344
|
-
|
|
345
|
-
// ============================================================================
|
|
346
|
-
// getGitIssues Tests
|
|
347
|
-
// ============================================================================
|
|
348
|
-
|
|
349
|
-
describe('getGitIssues', () => {
|
|
350
|
-
beforeEach(() => vi.clearAllMocks());
|
|
351
|
-
|
|
352
|
-
it('should throw error for missing project_id', async () => {
|
|
353
|
-
const ctx = createMockContext();
|
|
354
|
-
|
|
355
|
-
await expect(getGitIssues({}, ctx)).rejects.toThrow(ValidationError);
|
|
356
|
-
});
|
|
357
|
-
|
|
358
|
-
it('should throw error for invalid project_id UUID', async () => {
|
|
359
|
-
const ctx = createMockContext();
|
|
360
|
-
|
|
361
|
-
await expect(
|
|
362
|
-
getGitIssues({ project_id: 'invalid' }, ctx)
|
|
363
|
-
).rejects.toThrow(ValidationError);
|
|
364
|
-
});
|
|
365
|
-
|
|
366
|
-
it('should throw error for invalid status', async () => {
|
|
367
|
-
const ctx = createMockContext();
|
|
368
|
-
|
|
369
|
-
await expect(
|
|
370
|
-
getGitIssues({ project_id: VALID_PROJECT_ID, status: 'invalid_status' }, ctx)
|
|
371
|
-
).rejects.toThrow(ValidationError);
|
|
372
|
-
});
|
|
373
|
-
|
|
374
|
-
it('should throw error for invalid issue_type filter', async () => {
|
|
375
|
-
const ctx = createMockContext();
|
|
376
|
-
|
|
377
|
-
await expect(
|
|
378
|
-
getGitIssues({ project_id: VALID_PROJECT_ID, issue_type: 'invalid_type' }, ctx)
|
|
379
|
-
).rejects.toThrow(ValidationError);
|
|
380
|
-
});
|
|
381
|
-
|
|
382
|
-
it('should return empty list when no git issues', async () => {
|
|
383
|
-
mockApiClient.getGitIssues.mockResolvedValue({
|
|
384
|
-
ok: true,
|
|
385
|
-
data: { git_issues: [] },
|
|
386
|
-
});
|
|
387
|
-
const ctx = createMockContext();
|
|
388
|
-
|
|
389
|
-
const result = await getGitIssues(
|
|
390
|
-
{ project_id: VALID_PROJECT_ID },
|
|
391
|
-
ctx
|
|
392
|
-
);
|
|
393
|
-
|
|
394
|
-
expect(result.result).toMatchObject({
|
|
395
|
-
git_issues: [],
|
|
396
|
-
});
|
|
397
|
-
});
|
|
398
|
-
|
|
399
|
-
it('should return git issues list', async () => {
|
|
400
|
-
const mockGitIssues = [
|
|
401
|
-
{ id: 'gi1', issue_type: 'merge_conflict', branch: 'feature/a', status: 'open', created_at: '2025-01-14T10:00:00Z' },
|
|
402
|
-
{ id: 'gi2', issue_type: 'push_failed', branch: 'feature/b', status: 'open', created_at: '2025-01-14T11:00:00Z' },
|
|
403
|
-
];
|
|
404
|
-
mockApiClient.getGitIssues.mockResolvedValue({
|
|
405
|
-
ok: true,
|
|
406
|
-
data: { git_issues: mockGitIssues },
|
|
407
|
-
});
|
|
408
|
-
const ctx = createMockContext();
|
|
409
|
-
|
|
410
|
-
const result = await getGitIssues(
|
|
411
|
-
{ project_id: VALID_PROJECT_ID },
|
|
412
|
-
ctx
|
|
413
|
-
);
|
|
414
|
-
|
|
415
|
-
expect((result.result as { git_issues: unknown[] }).git_issues).toHaveLength(2);
|
|
416
|
-
});
|
|
417
|
-
|
|
418
|
-
it('should pass status parameter to API (default: open)', async () => {
|
|
419
|
-
mockApiClient.getGitIssues.mockResolvedValue({
|
|
420
|
-
ok: true,
|
|
421
|
-
data: { git_issues: [] },
|
|
422
|
-
});
|
|
423
|
-
const ctx = createMockContext();
|
|
424
|
-
|
|
425
|
-
await getGitIssues(
|
|
426
|
-
{ project_id: VALID_PROJECT_ID },
|
|
427
|
-
ctx
|
|
428
|
-
);
|
|
429
|
-
|
|
430
|
-
expect(mockApiClient.getGitIssues).toHaveBeenCalledWith(
|
|
431
|
-
VALID_PROJECT_ID,
|
|
432
|
-
expect.objectContaining({ status: 'open' })
|
|
433
|
-
);
|
|
434
|
-
});
|
|
435
|
-
|
|
436
|
-
it('should pass custom status to API', async () => {
|
|
437
|
-
mockApiClient.getGitIssues.mockResolvedValue({
|
|
438
|
-
ok: true,
|
|
439
|
-
data: { git_issues: [] },
|
|
440
|
-
});
|
|
441
|
-
const ctx = createMockContext();
|
|
442
|
-
|
|
443
|
-
await getGitIssues(
|
|
444
|
-
{ project_id: VALID_PROJECT_ID, status: 'resolved' },
|
|
445
|
-
ctx
|
|
446
|
-
);
|
|
447
|
-
|
|
448
|
-
expect(mockApiClient.getGitIssues).toHaveBeenCalledWith(
|
|
449
|
-
VALID_PROJECT_ID,
|
|
450
|
-
expect.objectContaining({ status: 'resolved' })
|
|
451
|
-
);
|
|
452
|
-
});
|
|
453
|
-
|
|
454
|
-
it('should pass issue_type filter to API', async () => {
|
|
455
|
-
mockApiClient.getGitIssues.mockResolvedValue({
|
|
456
|
-
ok: true,
|
|
457
|
-
data: { git_issues: [] },
|
|
458
|
-
});
|
|
459
|
-
const ctx = createMockContext();
|
|
460
|
-
|
|
461
|
-
await getGitIssues(
|
|
462
|
-
{ project_id: VALID_PROJECT_ID, issue_type: 'merge_conflict' },
|
|
463
|
-
ctx
|
|
464
|
-
);
|
|
465
|
-
|
|
466
|
-
expect(mockApiClient.getGitIssues).toHaveBeenCalledWith(
|
|
467
|
-
VALID_PROJECT_ID,
|
|
468
|
-
expect.objectContaining({ issue_type: 'merge_conflict' })
|
|
469
|
-
);
|
|
470
|
-
});
|
|
471
|
-
|
|
472
|
-
it('should pass branch filter to API', async () => {
|
|
473
|
-
mockApiClient.getGitIssues.mockResolvedValue({
|
|
474
|
-
ok: true,
|
|
475
|
-
data: { git_issues: [] },
|
|
476
|
-
});
|
|
477
|
-
const ctx = createMockContext();
|
|
478
|
-
|
|
479
|
-
await getGitIssues(
|
|
480
|
-
{ project_id: VALID_PROJECT_ID, branch: 'feature/test' },
|
|
481
|
-
ctx
|
|
482
|
-
);
|
|
483
|
-
|
|
484
|
-
expect(mockApiClient.getGitIssues).toHaveBeenCalledWith(
|
|
485
|
-
VALID_PROJECT_ID,
|
|
486
|
-
expect.objectContaining({ branch: 'feature/test' })
|
|
487
|
-
);
|
|
488
|
-
});
|
|
489
|
-
|
|
490
|
-
it('should pass limit parameter to API (default: 50)', async () => {
|
|
491
|
-
mockApiClient.getGitIssues.mockResolvedValue({
|
|
492
|
-
ok: true,
|
|
493
|
-
data: { git_issues: [] },
|
|
494
|
-
});
|
|
495
|
-
const ctx = createMockContext();
|
|
496
|
-
|
|
497
|
-
await getGitIssues(
|
|
498
|
-
{ project_id: VALID_PROJECT_ID },
|
|
499
|
-
ctx
|
|
500
|
-
);
|
|
501
|
-
|
|
502
|
-
expect(mockApiClient.getGitIssues).toHaveBeenCalledWith(
|
|
503
|
-
VALID_PROJECT_ID,
|
|
504
|
-
expect.objectContaining({ limit: 50, offset: 0 })
|
|
505
|
-
);
|
|
506
|
-
});
|
|
507
|
-
|
|
508
|
-
it('should pass custom limit to API', async () => {
|
|
509
|
-
mockApiClient.getGitIssues.mockResolvedValue({
|
|
510
|
-
ok: true,
|
|
511
|
-
data: { git_issues: [] },
|
|
512
|
-
});
|
|
513
|
-
const ctx = createMockContext();
|
|
514
|
-
|
|
515
|
-
await getGitIssues(
|
|
516
|
-
{ project_id: VALID_PROJECT_ID, limit: 10 },
|
|
517
|
-
ctx
|
|
518
|
-
);
|
|
519
|
-
|
|
520
|
-
expect(mockApiClient.getGitIssues).toHaveBeenCalledWith(
|
|
521
|
-
VALID_PROJECT_ID,
|
|
522
|
-
expect.objectContaining({ limit: 10, offset: 0 })
|
|
523
|
-
);
|
|
524
|
-
});
|
|
525
|
-
|
|
526
|
-
it('should return error when API call fails', async () => {
|
|
527
|
-
mockApiClient.getGitIssues.mockResolvedValue({
|
|
528
|
-
ok: false,
|
|
529
|
-
error: 'Query failed',
|
|
530
|
-
});
|
|
531
|
-
const ctx = createMockContext();
|
|
532
|
-
|
|
533
|
-
const result = await getGitIssues({ project_id: VALID_PROJECT_ID }, ctx);
|
|
534
|
-
|
|
535
|
-
expect(result.isError).toBe(true);
|
|
536
|
-
expect(result.result).toMatchObject({ error: 'Query failed' });
|
|
537
|
-
});
|
|
538
|
-
|
|
539
|
-
it('should return default error message when API fails without error', async () => {
|
|
540
|
-
mockApiClient.getGitIssues.mockResolvedValue({
|
|
541
|
-
ok: false,
|
|
542
|
-
});
|
|
543
|
-
const ctx = createMockContext();
|
|
544
|
-
|
|
545
|
-
const result = await getGitIssues({ project_id: VALID_PROJECT_ID }, ctx);
|
|
546
|
-
|
|
547
|
-
expect(result.isError).toBe(true);
|
|
548
|
-
expect(result.result).toMatchObject({ error: 'Failed to fetch git issues' });
|
|
549
|
-
});
|
|
550
|
-
});
|
|
551
|
-
|
|
552
|
-
// ============================================================================
|
|
553
|
-
// deleteGitIssue Tests
|
|
554
|
-
// ============================================================================
|
|
555
|
-
|
|
556
|
-
describe('deleteGitIssue', () => {
|
|
557
|
-
beforeEach(() => vi.clearAllMocks());
|
|
558
|
-
|
|
559
|
-
it('should throw error for missing git_issue_id', async () => {
|
|
560
|
-
const ctx = createMockContext();
|
|
561
|
-
|
|
562
|
-
await expect(deleteGitIssue({}, ctx)).rejects.toThrow(ValidationError);
|
|
563
|
-
});
|
|
564
|
-
|
|
565
|
-
it('should throw error for invalid git_issue_id UUID', async () => {
|
|
566
|
-
const ctx = createMockContext();
|
|
567
|
-
|
|
568
|
-
await expect(
|
|
569
|
-
deleteGitIssue({ git_issue_id: 'invalid' }, ctx)
|
|
570
|
-
).rejects.toThrow(ValidationError);
|
|
571
|
-
});
|
|
572
|
-
|
|
573
|
-
it('should delete git issue successfully', async () => {
|
|
574
|
-
mockApiClient.deleteGitIssue.mockResolvedValue({
|
|
575
|
-
ok: true,
|
|
576
|
-
data: { success: true },
|
|
577
|
-
});
|
|
578
|
-
const ctx = createMockContext();
|
|
579
|
-
|
|
580
|
-
const result = await deleteGitIssue(
|
|
581
|
-
{ git_issue_id: VALID_GIT_ISSUE_ID },
|
|
582
|
-
ctx
|
|
583
|
-
);
|
|
584
|
-
|
|
585
|
-
expect(result.result).toMatchObject({
|
|
586
|
-
success: true,
|
|
587
|
-
});
|
|
588
|
-
});
|
|
589
|
-
|
|
590
|
-
it('should call API client deleteGitIssue', async () => {
|
|
591
|
-
mockApiClient.deleteGitIssue.mockResolvedValue({
|
|
592
|
-
ok: true,
|
|
593
|
-
data: { success: true },
|
|
594
|
-
});
|
|
595
|
-
const ctx = createMockContext();
|
|
596
|
-
|
|
597
|
-
await deleteGitIssue(
|
|
598
|
-
{ git_issue_id: VALID_GIT_ISSUE_ID },
|
|
599
|
-
ctx
|
|
600
|
-
);
|
|
601
|
-
|
|
602
|
-
expect(mockApiClient.deleteGitIssue).toHaveBeenCalledWith(
|
|
603
|
-
VALID_GIT_ISSUE_ID
|
|
604
|
-
);
|
|
605
|
-
});
|
|
606
|
-
|
|
607
|
-
it('should return error when API call fails', async () => {
|
|
608
|
-
mockApiClient.deleteGitIssue.mockResolvedValue({
|
|
609
|
-
ok: false,
|
|
610
|
-
error: 'Delete failed',
|
|
611
|
-
});
|
|
612
|
-
const ctx = createMockContext();
|
|
613
|
-
|
|
614
|
-
const result = await deleteGitIssue({ git_issue_id: VALID_GIT_ISSUE_ID }, ctx);
|
|
615
|
-
|
|
616
|
-
expect(result.isError).toBe(true);
|
|
617
|
-
expect(result.result).toMatchObject({ error: 'Delete failed' });
|
|
618
|
-
});
|
|
619
|
-
|
|
620
|
-
it('should return default error message when API fails without error', async () => {
|
|
621
|
-
mockApiClient.deleteGitIssue.mockResolvedValue({
|
|
622
|
-
ok: false,
|
|
623
|
-
});
|
|
624
|
-
const ctx = createMockContext();
|
|
625
|
-
|
|
626
|
-
const result = await deleteGitIssue({ git_issue_id: VALID_GIT_ISSUE_ID }, ctx);
|
|
627
|
-
|
|
628
|
-
expect(result.isError).toBe(true);
|
|
629
|
-
expect(result.result).toMatchObject({ error: 'Failed to delete git issue' });
|
|
630
|
-
});
|
|
631
|
-
});
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
addGitIssue,
|
|
4
|
+
resolveGitIssue,
|
|
5
|
+
getGitIssues,
|
|
6
|
+
deleteGitIssue,
|
|
7
|
+
} from './git-issues.js';
|
|
8
|
+
import { ValidationError } from '../validators.js';
|
|
9
|
+
import { createMockContext } from './__test-utils__.js';
|
|
10
|
+
import { mockApiClient } from './__test-setup__.js';
|
|
11
|
+
|
|
12
|
+
const VALID_PROJECT_ID = '123e4567-e89b-12d3-a456-426614174000';
|
|
13
|
+
const VALID_GIT_ISSUE_ID = '987e6543-e21b-12d3-a456-426614174000';
|
|
14
|
+
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// addGitIssue Tests
|
|
17
|
+
// ============================================================================
|
|
18
|
+
|
|
19
|
+
describe('addGitIssue', () => {
|
|
20
|
+
beforeEach(() => vi.clearAllMocks());
|
|
21
|
+
|
|
22
|
+
it('should throw error for missing project_id', async () => {
|
|
23
|
+
const ctx = createMockContext();
|
|
24
|
+
|
|
25
|
+
await expect(
|
|
26
|
+
addGitIssue({ issue_type: 'merge_conflict', branch: 'feature/test' }, ctx)
|
|
27
|
+
).rejects.toThrow(ValidationError);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should throw error for invalid project_id UUID', async () => {
|
|
31
|
+
const ctx = createMockContext();
|
|
32
|
+
|
|
33
|
+
await expect(
|
|
34
|
+
addGitIssue({ project_id: 'invalid', issue_type: 'merge_conflict', branch: 'feature/test' }, ctx)
|
|
35
|
+
).rejects.toThrow(ValidationError);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should throw error for missing issue_type', async () => {
|
|
39
|
+
const ctx = createMockContext();
|
|
40
|
+
|
|
41
|
+
await expect(
|
|
42
|
+
addGitIssue({ project_id: VALID_PROJECT_ID, branch: 'feature/test' }, ctx)
|
|
43
|
+
).rejects.toThrow(ValidationError);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should throw error for missing branch', async () => {
|
|
47
|
+
const ctx = createMockContext();
|
|
48
|
+
|
|
49
|
+
await expect(
|
|
50
|
+
addGitIssue({ project_id: VALID_PROJECT_ID, issue_type: 'merge_conflict' }, ctx)
|
|
51
|
+
).rejects.toThrow(ValidationError);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should throw error for invalid issue_type', async () => {
|
|
55
|
+
const ctx = createMockContext();
|
|
56
|
+
|
|
57
|
+
await expect(
|
|
58
|
+
addGitIssue({
|
|
59
|
+
project_id: VALID_PROJECT_ID,
|
|
60
|
+
issue_type: 'invalid_type',
|
|
61
|
+
branch: 'feature/test',
|
|
62
|
+
}, ctx)
|
|
63
|
+
).rejects.toThrow(ValidationError);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should throw error for invalid task_id UUID', async () => {
|
|
67
|
+
const ctx = createMockContext();
|
|
68
|
+
|
|
69
|
+
await expect(
|
|
70
|
+
addGitIssue({
|
|
71
|
+
project_id: VALID_PROJECT_ID,
|
|
72
|
+
issue_type: 'merge_conflict',
|
|
73
|
+
branch: 'feature/test',
|
|
74
|
+
task_id: 'invalid-uuid',
|
|
75
|
+
}, ctx)
|
|
76
|
+
).rejects.toThrow(ValidationError);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should add git issue successfully', async () => {
|
|
80
|
+
mockApiClient.addGitIssue.mockResolvedValue({
|
|
81
|
+
ok: true,
|
|
82
|
+
data: { success: true, git_issue_id: VALID_GIT_ISSUE_ID },
|
|
83
|
+
});
|
|
84
|
+
const ctx = createMockContext();
|
|
85
|
+
|
|
86
|
+
const result = await addGitIssue(
|
|
87
|
+
{
|
|
88
|
+
project_id: VALID_PROJECT_ID,
|
|
89
|
+
issue_type: 'merge_conflict',
|
|
90
|
+
branch: 'feature/test',
|
|
91
|
+
},
|
|
92
|
+
ctx
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
expect(result.result).toMatchObject({
|
|
96
|
+
success: true,
|
|
97
|
+
git_issue_id: VALID_GIT_ISSUE_ID,
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('should add git issue with all optional fields', async () => {
|
|
102
|
+
mockApiClient.addGitIssue.mockResolvedValue({
|
|
103
|
+
ok: true,
|
|
104
|
+
data: { success: true, git_issue_id: VALID_GIT_ISSUE_ID },
|
|
105
|
+
});
|
|
106
|
+
const ctx = createMockContext({ sessionId: 'test-session' });
|
|
107
|
+
|
|
108
|
+
await addGitIssue(
|
|
109
|
+
{
|
|
110
|
+
project_id: VALID_PROJECT_ID,
|
|
111
|
+
issue_type: 'pr_not_mergeable',
|
|
112
|
+
branch: 'feature/test',
|
|
113
|
+
target_branch: 'main',
|
|
114
|
+
pr_url: 'https://github.com/org/repo/pull/123',
|
|
115
|
+
conflicting_files: ['src/file1.ts', 'src/file2.ts'],
|
|
116
|
+
error_message: 'Merge conflict in file1.ts',
|
|
117
|
+
task_id: VALID_GIT_ISSUE_ID,
|
|
118
|
+
},
|
|
119
|
+
ctx
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
expect(mockApiClient.addGitIssue).toHaveBeenCalledWith(
|
|
123
|
+
VALID_PROJECT_ID,
|
|
124
|
+
{
|
|
125
|
+
issue_type: 'pr_not_mergeable',
|
|
126
|
+
branch: 'feature/test',
|
|
127
|
+
target_branch: 'main',
|
|
128
|
+
pr_url: 'https://github.com/org/repo/pull/123',
|
|
129
|
+
conflicting_files: ['src/file1.ts', 'src/file2.ts'],
|
|
130
|
+
error_message: 'Merge conflict in file1.ts',
|
|
131
|
+
task_id: VALID_GIT_ISSUE_ID,
|
|
132
|
+
},
|
|
133
|
+
'test-session'
|
|
134
|
+
);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should include session_id in API call', async () => {
|
|
138
|
+
mockApiClient.addGitIssue.mockResolvedValue({
|
|
139
|
+
ok: true,
|
|
140
|
+
data: { success: true, git_issue_id: VALID_GIT_ISSUE_ID },
|
|
141
|
+
});
|
|
142
|
+
const ctx = createMockContext({ sessionId: 'my-session' });
|
|
143
|
+
|
|
144
|
+
await addGitIssue(
|
|
145
|
+
{
|
|
146
|
+
project_id: VALID_PROJECT_ID,
|
|
147
|
+
issue_type: 'push_failed',
|
|
148
|
+
branch: 'feature/test',
|
|
149
|
+
},
|
|
150
|
+
ctx
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
expect(mockApiClient.addGitIssue).toHaveBeenCalledWith(
|
|
154
|
+
VALID_PROJECT_ID,
|
|
155
|
+
expect.objectContaining({
|
|
156
|
+
issue_type: 'push_failed',
|
|
157
|
+
branch: 'feature/test',
|
|
158
|
+
}),
|
|
159
|
+
'my-session'
|
|
160
|
+
);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('should accept all valid issue types', async () => {
|
|
164
|
+
const validTypes = ['merge_conflict', 'push_failed', 'rebase_needed', 'branch_diverged', 'pr_not_mergeable'];
|
|
165
|
+
const ctx = createMockContext();
|
|
166
|
+
|
|
167
|
+
for (const issueType of validTypes) {
|
|
168
|
+
mockApiClient.addGitIssue.mockResolvedValue({
|
|
169
|
+
ok: true,
|
|
170
|
+
data: { success: true, git_issue_id: VALID_GIT_ISSUE_ID },
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
await expect(
|
|
174
|
+
addGitIssue({
|
|
175
|
+
project_id: VALID_PROJECT_ID,
|
|
176
|
+
issue_type: issueType,
|
|
177
|
+
branch: 'feature/test',
|
|
178
|
+
}, ctx)
|
|
179
|
+
).resolves.not.toThrow();
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('should return error when API call fails', async () => {
|
|
184
|
+
mockApiClient.addGitIssue.mockResolvedValue({
|
|
185
|
+
ok: false,
|
|
186
|
+
error: 'Insert failed',
|
|
187
|
+
});
|
|
188
|
+
const ctx = createMockContext();
|
|
189
|
+
|
|
190
|
+
const result = await addGitIssue({
|
|
191
|
+
project_id: VALID_PROJECT_ID,
|
|
192
|
+
issue_type: 'merge_conflict',
|
|
193
|
+
branch: 'feature/test',
|
|
194
|
+
}, ctx);
|
|
195
|
+
|
|
196
|
+
expect(result.isError).toBe(true);
|
|
197
|
+
expect(result.result).toMatchObject({ error: 'Insert failed' });
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('should return default error message when API fails without error', async () => {
|
|
201
|
+
mockApiClient.addGitIssue.mockResolvedValue({
|
|
202
|
+
ok: false,
|
|
203
|
+
});
|
|
204
|
+
const ctx = createMockContext();
|
|
205
|
+
|
|
206
|
+
const result = await addGitIssue({
|
|
207
|
+
project_id: VALID_PROJECT_ID,
|
|
208
|
+
issue_type: 'merge_conflict',
|
|
209
|
+
branch: 'feature/test',
|
|
210
|
+
}, ctx);
|
|
211
|
+
|
|
212
|
+
expect(result.isError).toBe(true);
|
|
213
|
+
expect(result.result).toMatchObject({ error: 'Failed to add git issue' });
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// ============================================================================
|
|
218
|
+
// resolveGitIssue Tests
|
|
219
|
+
// ============================================================================
|
|
220
|
+
|
|
221
|
+
describe('resolveGitIssue', () => {
|
|
222
|
+
beforeEach(() => vi.clearAllMocks());
|
|
223
|
+
|
|
224
|
+
it('should throw error for missing git_issue_id', async () => {
|
|
225
|
+
const ctx = createMockContext();
|
|
226
|
+
|
|
227
|
+
await expect(resolveGitIssue({}, ctx)).rejects.toThrow(ValidationError);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('should throw error for invalid git_issue_id UUID', async () => {
|
|
231
|
+
const ctx = createMockContext();
|
|
232
|
+
|
|
233
|
+
await expect(
|
|
234
|
+
resolveGitIssue({ git_issue_id: 'invalid' }, ctx)
|
|
235
|
+
).rejects.toThrow(ValidationError);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('should resolve git issue successfully', async () => {
|
|
239
|
+
mockApiClient.resolveGitIssue.mockResolvedValue({
|
|
240
|
+
ok: true,
|
|
241
|
+
data: { success: true, git_issue_id: VALID_GIT_ISSUE_ID },
|
|
242
|
+
});
|
|
243
|
+
const ctx = createMockContext();
|
|
244
|
+
|
|
245
|
+
const result = await resolveGitIssue(
|
|
246
|
+
{ git_issue_id: VALID_GIT_ISSUE_ID },
|
|
247
|
+
ctx
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
expect(result.result).toMatchObject({
|
|
251
|
+
success: true,
|
|
252
|
+
git_issue_id: VALID_GIT_ISSUE_ID,
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it('should resolve git issue with resolution_note', async () => {
|
|
257
|
+
mockApiClient.resolveGitIssue.mockResolvedValue({
|
|
258
|
+
ok: true,
|
|
259
|
+
data: { success: true, git_issue_id: VALID_GIT_ISSUE_ID },
|
|
260
|
+
});
|
|
261
|
+
const ctx = createMockContext({ sessionId: 'test-session' });
|
|
262
|
+
|
|
263
|
+
await resolveGitIssue(
|
|
264
|
+
{
|
|
265
|
+
git_issue_id: VALID_GIT_ISSUE_ID,
|
|
266
|
+
resolution_note: 'Resolved by rebasing',
|
|
267
|
+
},
|
|
268
|
+
ctx
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
expect(mockApiClient.resolveGitIssue).toHaveBeenCalledWith(
|
|
272
|
+
VALID_GIT_ISSUE_ID,
|
|
273
|
+
{ resolution_note: 'Resolved by rebasing', auto_resolved: undefined },
|
|
274
|
+
'test-session'
|
|
275
|
+
);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it('should resolve git issue with auto_resolved flag', async () => {
|
|
279
|
+
mockApiClient.resolveGitIssue.mockResolvedValue({
|
|
280
|
+
ok: true,
|
|
281
|
+
data: { success: true, git_issue_id: VALID_GIT_ISSUE_ID },
|
|
282
|
+
});
|
|
283
|
+
const ctx = createMockContext({ sessionId: 'test-session' });
|
|
284
|
+
|
|
285
|
+
await resolveGitIssue(
|
|
286
|
+
{
|
|
287
|
+
git_issue_id: VALID_GIT_ISSUE_ID,
|
|
288
|
+
auto_resolved: true,
|
|
289
|
+
},
|
|
290
|
+
ctx
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
expect(mockApiClient.resolveGitIssue).toHaveBeenCalledWith(
|
|
294
|
+
VALID_GIT_ISSUE_ID,
|
|
295
|
+
{ resolution_note: undefined, auto_resolved: true },
|
|
296
|
+
'test-session'
|
|
297
|
+
);
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it('should include session_id in API call', async () => {
|
|
301
|
+
mockApiClient.resolveGitIssue.mockResolvedValue({
|
|
302
|
+
ok: true,
|
|
303
|
+
data: { success: true, git_issue_id: VALID_GIT_ISSUE_ID },
|
|
304
|
+
});
|
|
305
|
+
const ctx = createMockContext({ sessionId: 'my-session' });
|
|
306
|
+
|
|
307
|
+
await resolveGitIssue(
|
|
308
|
+
{ git_issue_id: VALID_GIT_ISSUE_ID },
|
|
309
|
+
ctx
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
expect(mockApiClient.resolveGitIssue).toHaveBeenCalledWith(
|
|
313
|
+
VALID_GIT_ISSUE_ID,
|
|
314
|
+
expect.any(Object),
|
|
315
|
+
'my-session'
|
|
316
|
+
);
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it('should return error when API call fails', async () => {
|
|
320
|
+
mockApiClient.resolveGitIssue.mockResolvedValue({
|
|
321
|
+
ok: false,
|
|
322
|
+
error: 'Update failed',
|
|
323
|
+
});
|
|
324
|
+
const ctx = createMockContext();
|
|
325
|
+
|
|
326
|
+
const result = await resolveGitIssue({ git_issue_id: VALID_GIT_ISSUE_ID }, ctx);
|
|
327
|
+
|
|
328
|
+
expect(result.isError).toBe(true);
|
|
329
|
+
expect(result.result).toMatchObject({ error: 'Update failed' });
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
it('should return default error message when API fails without error', async () => {
|
|
333
|
+
mockApiClient.resolveGitIssue.mockResolvedValue({
|
|
334
|
+
ok: false,
|
|
335
|
+
});
|
|
336
|
+
const ctx = createMockContext();
|
|
337
|
+
|
|
338
|
+
const result = await resolveGitIssue({ git_issue_id: VALID_GIT_ISSUE_ID }, ctx);
|
|
339
|
+
|
|
340
|
+
expect(result.isError).toBe(true);
|
|
341
|
+
expect(result.result).toMatchObject({ error: 'Failed to resolve git issue' });
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
// ============================================================================
|
|
346
|
+
// getGitIssues Tests
|
|
347
|
+
// ============================================================================
|
|
348
|
+
|
|
349
|
+
describe('getGitIssues', () => {
|
|
350
|
+
beforeEach(() => vi.clearAllMocks());
|
|
351
|
+
|
|
352
|
+
it('should throw error for missing project_id', async () => {
|
|
353
|
+
const ctx = createMockContext();
|
|
354
|
+
|
|
355
|
+
await expect(getGitIssues({}, ctx)).rejects.toThrow(ValidationError);
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it('should throw error for invalid project_id UUID', async () => {
|
|
359
|
+
const ctx = createMockContext();
|
|
360
|
+
|
|
361
|
+
await expect(
|
|
362
|
+
getGitIssues({ project_id: 'invalid' }, ctx)
|
|
363
|
+
).rejects.toThrow(ValidationError);
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
it('should throw error for invalid status', async () => {
|
|
367
|
+
const ctx = createMockContext();
|
|
368
|
+
|
|
369
|
+
await expect(
|
|
370
|
+
getGitIssues({ project_id: VALID_PROJECT_ID, status: 'invalid_status' }, ctx)
|
|
371
|
+
).rejects.toThrow(ValidationError);
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
it('should throw error for invalid issue_type filter', async () => {
|
|
375
|
+
const ctx = createMockContext();
|
|
376
|
+
|
|
377
|
+
await expect(
|
|
378
|
+
getGitIssues({ project_id: VALID_PROJECT_ID, issue_type: 'invalid_type' }, ctx)
|
|
379
|
+
).rejects.toThrow(ValidationError);
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
it('should return empty list when no git issues', async () => {
|
|
383
|
+
mockApiClient.getGitIssues.mockResolvedValue({
|
|
384
|
+
ok: true,
|
|
385
|
+
data: { git_issues: [] },
|
|
386
|
+
});
|
|
387
|
+
const ctx = createMockContext();
|
|
388
|
+
|
|
389
|
+
const result = await getGitIssues(
|
|
390
|
+
{ project_id: VALID_PROJECT_ID },
|
|
391
|
+
ctx
|
|
392
|
+
);
|
|
393
|
+
|
|
394
|
+
expect(result.result).toMatchObject({
|
|
395
|
+
git_issues: [],
|
|
396
|
+
});
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
it('should return git issues list', async () => {
|
|
400
|
+
const mockGitIssues = [
|
|
401
|
+
{ id: 'gi1', issue_type: 'merge_conflict', branch: 'feature/a', status: 'open', created_at: '2025-01-14T10:00:00Z' },
|
|
402
|
+
{ id: 'gi2', issue_type: 'push_failed', branch: 'feature/b', status: 'open', created_at: '2025-01-14T11:00:00Z' },
|
|
403
|
+
];
|
|
404
|
+
mockApiClient.getGitIssues.mockResolvedValue({
|
|
405
|
+
ok: true,
|
|
406
|
+
data: { git_issues: mockGitIssues },
|
|
407
|
+
});
|
|
408
|
+
const ctx = createMockContext();
|
|
409
|
+
|
|
410
|
+
const result = await getGitIssues(
|
|
411
|
+
{ project_id: VALID_PROJECT_ID },
|
|
412
|
+
ctx
|
|
413
|
+
);
|
|
414
|
+
|
|
415
|
+
expect((result.result as { git_issues: unknown[] }).git_issues).toHaveLength(2);
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
it('should pass status parameter to API (default: open)', async () => {
|
|
419
|
+
mockApiClient.getGitIssues.mockResolvedValue({
|
|
420
|
+
ok: true,
|
|
421
|
+
data: { git_issues: [] },
|
|
422
|
+
});
|
|
423
|
+
const ctx = createMockContext();
|
|
424
|
+
|
|
425
|
+
await getGitIssues(
|
|
426
|
+
{ project_id: VALID_PROJECT_ID },
|
|
427
|
+
ctx
|
|
428
|
+
);
|
|
429
|
+
|
|
430
|
+
expect(mockApiClient.getGitIssues).toHaveBeenCalledWith(
|
|
431
|
+
VALID_PROJECT_ID,
|
|
432
|
+
expect.objectContaining({ status: 'open' })
|
|
433
|
+
);
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
it('should pass custom status to API', async () => {
|
|
437
|
+
mockApiClient.getGitIssues.mockResolvedValue({
|
|
438
|
+
ok: true,
|
|
439
|
+
data: { git_issues: [] },
|
|
440
|
+
});
|
|
441
|
+
const ctx = createMockContext();
|
|
442
|
+
|
|
443
|
+
await getGitIssues(
|
|
444
|
+
{ project_id: VALID_PROJECT_ID, status: 'resolved' },
|
|
445
|
+
ctx
|
|
446
|
+
);
|
|
447
|
+
|
|
448
|
+
expect(mockApiClient.getGitIssues).toHaveBeenCalledWith(
|
|
449
|
+
VALID_PROJECT_ID,
|
|
450
|
+
expect.objectContaining({ status: 'resolved' })
|
|
451
|
+
);
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
it('should pass issue_type filter to API', async () => {
|
|
455
|
+
mockApiClient.getGitIssues.mockResolvedValue({
|
|
456
|
+
ok: true,
|
|
457
|
+
data: { git_issues: [] },
|
|
458
|
+
});
|
|
459
|
+
const ctx = createMockContext();
|
|
460
|
+
|
|
461
|
+
await getGitIssues(
|
|
462
|
+
{ project_id: VALID_PROJECT_ID, issue_type: 'merge_conflict' },
|
|
463
|
+
ctx
|
|
464
|
+
);
|
|
465
|
+
|
|
466
|
+
expect(mockApiClient.getGitIssues).toHaveBeenCalledWith(
|
|
467
|
+
VALID_PROJECT_ID,
|
|
468
|
+
expect.objectContaining({ issue_type: 'merge_conflict' })
|
|
469
|
+
);
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
it('should pass branch filter to API', async () => {
|
|
473
|
+
mockApiClient.getGitIssues.mockResolvedValue({
|
|
474
|
+
ok: true,
|
|
475
|
+
data: { git_issues: [] },
|
|
476
|
+
});
|
|
477
|
+
const ctx = createMockContext();
|
|
478
|
+
|
|
479
|
+
await getGitIssues(
|
|
480
|
+
{ project_id: VALID_PROJECT_ID, branch: 'feature/test' },
|
|
481
|
+
ctx
|
|
482
|
+
);
|
|
483
|
+
|
|
484
|
+
expect(mockApiClient.getGitIssues).toHaveBeenCalledWith(
|
|
485
|
+
VALID_PROJECT_ID,
|
|
486
|
+
expect.objectContaining({ branch: 'feature/test' })
|
|
487
|
+
);
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
it('should pass limit parameter to API (default: 50)', async () => {
|
|
491
|
+
mockApiClient.getGitIssues.mockResolvedValue({
|
|
492
|
+
ok: true,
|
|
493
|
+
data: { git_issues: [] },
|
|
494
|
+
});
|
|
495
|
+
const ctx = createMockContext();
|
|
496
|
+
|
|
497
|
+
await getGitIssues(
|
|
498
|
+
{ project_id: VALID_PROJECT_ID },
|
|
499
|
+
ctx
|
|
500
|
+
);
|
|
501
|
+
|
|
502
|
+
expect(mockApiClient.getGitIssues).toHaveBeenCalledWith(
|
|
503
|
+
VALID_PROJECT_ID,
|
|
504
|
+
expect.objectContaining({ limit: 50, offset: 0 })
|
|
505
|
+
);
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
it('should pass custom limit to API', async () => {
|
|
509
|
+
mockApiClient.getGitIssues.mockResolvedValue({
|
|
510
|
+
ok: true,
|
|
511
|
+
data: { git_issues: [] },
|
|
512
|
+
});
|
|
513
|
+
const ctx = createMockContext();
|
|
514
|
+
|
|
515
|
+
await getGitIssues(
|
|
516
|
+
{ project_id: VALID_PROJECT_ID, limit: 10 },
|
|
517
|
+
ctx
|
|
518
|
+
);
|
|
519
|
+
|
|
520
|
+
expect(mockApiClient.getGitIssues).toHaveBeenCalledWith(
|
|
521
|
+
VALID_PROJECT_ID,
|
|
522
|
+
expect.objectContaining({ limit: 10, offset: 0 })
|
|
523
|
+
);
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
it('should return error when API call fails', async () => {
|
|
527
|
+
mockApiClient.getGitIssues.mockResolvedValue({
|
|
528
|
+
ok: false,
|
|
529
|
+
error: 'Query failed',
|
|
530
|
+
});
|
|
531
|
+
const ctx = createMockContext();
|
|
532
|
+
|
|
533
|
+
const result = await getGitIssues({ project_id: VALID_PROJECT_ID }, ctx);
|
|
534
|
+
|
|
535
|
+
expect(result.isError).toBe(true);
|
|
536
|
+
expect(result.result).toMatchObject({ error: 'Query failed' });
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
it('should return default error message when API fails without error', async () => {
|
|
540
|
+
mockApiClient.getGitIssues.mockResolvedValue({
|
|
541
|
+
ok: false,
|
|
542
|
+
});
|
|
543
|
+
const ctx = createMockContext();
|
|
544
|
+
|
|
545
|
+
const result = await getGitIssues({ project_id: VALID_PROJECT_ID }, ctx);
|
|
546
|
+
|
|
547
|
+
expect(result.isError).toBe(true);
|
|
548
|
+
expect(result.result).toMatchObject({ error: 'Failed to fetch git issues' });
|
|
549
|
+
});
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
// ============================================================================
|
|
553
|
+
// deleteGitIssue Tests
|
|
554
|
+
// ============================================================================
|
|
555
|
+
|
|
556
|
+
describe('deleteGitIssue', () => {
|
|
557
|
+
beforeEach(() => vi.clearAllMocks());
|
|
558
|
+
|
|
559
|
+
it('should throw error for missing git_issue_id', async () => {
|
|
560
|
+
const ctx = createMockContext();
|
|
561
|
+
|
|
562
|
+
await expect(deleteGitIssue({}, ctx)).rejects.toThrow(ValidationError);
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
it('should throw error for invalid git_issue_id UUID', async () => {
|
|
566
|
+
const ctx = createMockContext();
|
|
567
|
+
|
|
568
|
+
await expect(
|
|
569
|
+
deleteGitIssue({ git_issue_id: 'invalid' }, ctx)
|
|
570
|
+
).rejects.toThrow(ValidationError);
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
it('should delete git issue successfully', async () => {
|
|
574
|
+
mockApiClient.deleteGitIssue.mockResolvedValue({
|
|
575
|
+
ok: true,
|
|
576
|
+
data: { success: true },
|
|
577
|
+
});
|
|
578
|
+
const ctx = createMockContext();
|
|
579
|
+
|
|
580
|
+
const result = await deleteGitIssue(
|
|
581
|
+
{ git_issue_id: VALID_GIT_ISSUE_ID },
|
|
582
|
+
ctx
|
|
583
|
+
);
|
|
584
|
+
|
|
585
|
+
expect(result.result).toMatchObject({
|
|
586
|
+
success: true,
|
|
587
|
+
});
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
it('should call API client deleteGitIssue', async () => {
|
|
591
|
+
mockApiClient.deleteGitIssue.mockResolvedValue({
|
|
592
|
+
ok: true,
|
|
593
|
+
data: { success: true },
|
|
594
|
+
});
|
|
595
|
+
const ctx = createMockContext();
|
|
596
|
+
|
|
597
|
+
await deleteGitIssue(
|
|
598
|
+
{ git_issue_id: VALID_GIT_ISSUE_ID },
|
|
599
|
+
ctx
|
|
600
|
+
);
|
|
601
|
+
|
|
602
|
+
expect(mockApiClient.deleteGitIssue).toHaveBeenCalledWith(
|
|
603
|
+
VALID_GIT_ISSUE_ID
|
|
604
|
+
);
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
it('should return error when API call fails', async () => {
|
|
608
|
+
mockApiClient.deleteGitIssue.mockResolvedValue({
|
|
609
|
+
ok: false,
|
|
610
|
+
error: 'Delete failed',
|
|
611
|
+
});
|
|
612
|
+
const ctx = createMockContext();
|
|
613
|
+
|
|
614
|
+
const result = await deleteGitIssue({ git_issue_id: VALID_GIT_ISSUE_ID }, ctx);
|
|
615
|
+
|
|
616
|
+
expect(result.isError).toBe(true);
|
|
617
|
+
expect(result.result).toMatchObject({ error: 'Delete failed' });
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
it('should return default error message when API fails without error', async () => {
|
|
621
|
+
mockApiClient.deleteGitIssue.mockResolvedValue({
|
|
622
|
+
ok: false,
|
|
623
|
+
});
|
|
624
|
+
const ctx = createMockContext();
|
|
625
|
+
|
|
626
|
+
const result = await deleteGitIssue({ git_issue_id: VALID_GIT_ISSUE_ID }, ctx);
|
|
627
|
+
|
|
628
|
+
expect(result.isError).toBe(true);
|
|
629
|
+
expect(result.result).toMatchObject({ error: 'Failed to delete git issue' });
|
|
630
|
+
});
|
|
631
|
+
});
|