@vibescope/mcp-server 0.2.1 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -38
- package/dist/api-client.d.ts +187 -0
- package/dist/api-client.js +53 -1
- package/dist/handlers/blockers.js +9 -8
- package/dist/handlers/bodies-of-work.js +14 -14
- package/dist/handlers/connectors.d.ts +45 -0
- package/dist/handlers/connectors.js +183 -0
- package/dist/handlers/cost.d.ts +10 -0
- package/dist/handlers/cost.js +54 -0
- package/dist/handlers/decisions.js +3 -3
- package/dist/handlers/deployment.js +35 -19
- package/dist/handlers/discovery.d.ts +7 -0
- package/dist/handlers/discovery.js +61 -2
- package/dist/handlers/fallback.js +5 -4
- package/dist/handlers/file-checkouts.d.ts +2 -0
- package/dist/handlers/file-checkouts.js +38 -6
- package/dist/handlers/findings.js +13 -12
- package/dist/handlers/git-issues.js +4 -4
- package/dist/handlers/ideas.js +5 -5
- package/dist/handlers/index.d.ts +1 -0
- package/dist/handlers/index.js +3 -0
- package/dist/handlers/milestones.js +5 -5
- package/dist/handlers/organizations.js +13 -13
- package/dist/handlers/progress.js +2 -2
- package/dist/handlers/project.js +6 -6
- package/dist/handlers/requests.js +3 -3
- package/dist/handlers/session.js +28 -9
- package/dist/handlers/sprints.js +17 -17
- package/dist/handlers/tasks.d.ts +2 -0
- package/dist/handlers/tasks.js +78 -20
- package/dist/handlers/types.d.ts +64 -2
- package/dist/handlers/types.js +48 -1
- package/dist/handlers/validation.js +3 -3
- package/dist/index.js +7 -2716
- package/dist/token-tracking.d.ts +74 -0
- package/dist/token-tracking.js +122 -0
- package/dist/tools.js +298 -9
- package/dist/utils.d.ts +5 -0
- package/dist/utils.js +17 -0
- package/docs/TOOLS.md +2053 -0
- package/package.json +4 -1
- package/scripts/generate-docs.ts +212 -0
- package/src/api-client.test.ts +723 -0
- package/src/api-client.ts +236 -1
- package/src/handlers/__test-setup__.ts +9 -0
- package/src/handlers/blockers.test.ts +31 -19
- package/src/handlers/blockers.ts +9 -8
- package/src/handlers/bodies-of-work.test.ts +55 -32
- package/src/handlers/bodies-of-work.ts +14 -14
- package/src/handlers/connectors.test.ts +834 -0
- package/src/handlers/connectors.ts +229 -0
- package/src/handlers/cost.ts +66 -0
- package/src/handlers/decisions.test.ts +34 -25
- package/src/handlers/decisions.ts +3 -3
- package/src/handlers/deployment.ts +39 -19
- package/src/handlers/discovery.ts +61 -2
- package/src/handlers/fallback.test.ts +26 -22
- package/src/handlers/fallback.ts +5 -4
- package/src/handlers/file-checkouts.test.ts +242 -49
- package/src/handlers/file-checkouts.ts +44 -6
- package/src/handlers/findings.test.ts +38 -24
- package/src/handlers/findings.ts +13 -12
- package/src/handlers/git-issues.test.ts +51 -43
- package/src/handlers/git-issues.ts +4 -4
- package/src/handlers/ideas.test.ts +28 -23
- package/src/handlers/ideas.ts +5 -5
- package/src/handlers/index.ts +3 -0
- package/src/handlers/milestones.test.ts +33 -28
- package/src/handlers/milestones.ts +5 -5
- package/src/handlers/organizations.test.ts +104 -83
- package/src/handlers/organizations.ts +13 -13
- package/src/handlers/progress.test.ts +20 -14
- package/src/handlers/progress.ts +2 -2
- package/src/handlers/project.test.ts +34 -27
- package/src/handlers/project.ts +6 -6
- package/src/handlers/requests.test.ts +27 -18
- package/src/handlers/requests.ts +3 -3
- package/src/handlers/session.test.ts +47 -0
- package/src/handlers/session.ts +26 -9
- package/src/handlers/sprints.test.ts +71 -50
- package/src/handlers/sprints.ts +17 -17
- package/src/handlers/tasks.test.ts +77 -15
- package/src/handlers/tasks.ts +90 -21
- package/src/handlers/tool-categories.test.ts +66 -0
- package/src/handlers/types.ts +81 -2
- package/src/handlers/validation.test.ts +78 -45
- package/src/handlers/validation.ts +3 -3
- package/src/index.ts +12 -2732
- package/src/token-tracking.test.ts +453 -0
- package/src/token-tracking.ts +164 -0
- package/src/tools.ts +298 -9
- package/src/utils.test.ts +2 -2
- package/src/utils.ts +17 -0
|
@@ -0,0 +1,834 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
getConnectors,
|
|
4
|
+
getConnector,
|
|
5
|
+
addConnector,
|
|
6
|
+
updateConnector,
|
|
7
|
+
deleteConnector,
|
|
8
|
+
testConnector,
|
|
9
|
+
getConnectorEvents,
|
|
10
|
+
} from './connectors.js';
|
|
11
|
+
import { ValidationError } from '../validators.js';
|
|
12
|
+
import { createMockContext } from './__test-utils__.js';
|
|
13
|
+
import { mockApiClient } from './__test-setup__.js';
|
|
14
|
+
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// getConnectors Tests
|
|
17
|
+
// ============================================================================
|
|
18
|
+
|
|
19
|
+
describe('getConnectors', () => {
|
|
20
|
+
beforeEach(() => {});
|
|
21
|
+
|
|
22
|
+
it('should throw error for missing project_id', async () => {
|
|
23
|
+
const ctx = createMockContext();
|
|
24
|
+
|
|
25
|
+
await expect(getConnectors({}, ctx)).rejects.toThrow(ValidationError);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should throw error for invalid project_id UUID', async () => {
|
|
29
|
+
const ctx = createMockContext();
|
|
30
|
+
|
|
31
|
+
await expect(
|
|
32
|
+
getConnectors({ project_id: 'invalid' }, ctx)
|
|
33
|
+
).rejects.toThrow(ValidationError);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should return connectors successfully', async () => {
|
|
37
|
+
const mockConnectors = [
|
|
38
|
+
{ id: 'conn-1', name: 'Slack Notifications', type: 'slack' },
|
|
39
|
+
{ id: 'conn-2', name: 'Webhook', type: 'webhook' },
|
|
40
|
+
];
|
|
41
|
+
mockApiClient.getConnectors.mockResolvedValue({
|
|
42
|
+
ok: true,
|
|
43
|
+
data: { connectors: mockConnectors, total: 2 },
|
|
44
|
+
});
|
|
45
|
+
const ctx = createMockContext();
|
|
46
|
+
|
|
47
|
+
const result = await getConnectors(
|
|
48
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
49
|
+
ctx
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
expect(result.result).toMatchObject({
|
|
53
|
+
connectors: mockConnectors,
|
|
54
|
+
total: 2,
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should filter by type', async () => {
|
|
59
|
+
mockApiClient.getConnectors.mockResolvedValue({
|
|
60
|
+
ok: true,
|
|
61
|
+
data: { connectors: [], total: 0 },
|
|
62
|
+
});
|
|
63
|
+
const ctx = createMockContext();
|
|
64
|
+
|
|
65
|
+
await getConnectors(
|
|
66
|
+
{
|
|
67
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
68
|
+
type: 'slack',
|
|
69
|
+
},
|
|
70
|
+
ctx
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
expect(mockApiClient.getConnectors).toHaveBeenCalledWith(
|
|
74
|
+
'123e4567-e89b-12d3-a456-426614174000',
|
|
75
|
+
expect.objectContaining({ type: 'slack' })
|
|
76
|
+
);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should filter by status', async () => {
|
|
80
|
+
mockApiClient.getConnectors.mockResolvedValue({
|
|
81
|
+
ok: true,
|
|
82
|
+
data: { connectors: [], total: 0 },
|
|
83
|
+
});
|
|
84
|
+
const ctx = createMockContext();
|
|
85
|
+
|
|
86
|
+
await getConnectors(
|
|
87
|
+
{
|
|
88
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
89
|
+
status: 'active',
|
|
90
|
+
},
|
|
91
|
+
ctx
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
expect(mockApiClient.getConnectors).toHaveBeenCalledWith(
|
|
95
|
+
'123e4567-e89b-12d3-a456-426614174000',
|
|
96
|
+
expect.objectContaining({ status: 'active' })
|
|
97
|
+
);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should throw error for invalid type', async () => {
|
|
101
|
+
const ctx = createMockContext();
|
|
102
|
+
|
|
103
|
+
await expect(
|
|
104
|
+
getConnectors(
|
|
105
|
+
{
|
|
106
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
107
|
+
type: 'invalid_type',
|
|
108
|
+
},
|
|
109
|
+
ctx
|
|
110
|
+
)
|
|
111
|
+
).rejects.toThrow(ValidationError);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('should throw error for invalid status', async () => {
|
|
115
|
+
const ctx = createMockContext();
|
|
116
|
+
|
|
117
|
+
await expect(
|
|
118
|
+
getConnectors(
|
|
119
|
+
{
|
|
120
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
121
|
+
status: 'invalid_status',
|
|
122
|
+
},
|
|
123
|
+
ctx
|
|
124
|
+
)
|
|
125
|
+
).rejects.toThrow(ValidationError);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should handle pagination with limit and offset', async () => {
|
|
129
|
+
mockApiClient.getConnectors.mockResolvedValue({
|
|
130
|
+
ok: true,
|
|
131
|
+
data: { connectors: [], total: 100 },
|
|
132
|
+
});
|
|
133
|
+
const ctx = createMockContext();
|
|
134
|
+
|
|
135
|
+
await getConnectors(
|
|
136
|
+
{
|
|
137
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
138
|
+
limit: 10,
|
|
139
|
+
offset: 20,
|
|
140
|
+
},
|
|
141
|
+
ctx
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
expect(mockApiClient.getConnectors).toHaveBeenCalledWith(
|
|
145
|
+
'123e4567-e89b-12d3-a456-426614174000',
|
|
146
|
+
expect.objectContaining({ limit: 10, offset: 20 })
|
|
147
|
+
);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('should handle API error', async () => {
|
|
151
|
+
mockApiClient.getConnectors.mockResolvedValue({
|
|
152
|
+
ok: false,
|
|
153
|
+
error: 'Database error',
|
|
154
|
+
});
|
|
155
|
+
const ctx = createMockContext();
|
|
156
|
+
|
|
157
|
+
const result = await getConnectors(
|
|
158
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
159
|
+
ctx
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
expect(result.result.error).toBe('Database error');
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// ============================================================================
|
|
167
|
+
// getConnector Tests
|
|
168
|
+
// ============================================================================
|
|
169
|
+
|
|
170
|
+
describe('getConnector', () => {
|
|
171
|
+
beforeEach(() => {});
|
|
172
|
+
|
|
173
|
+
it('should throw error for missing connector_id', async () => {
|
|
174
|
+
const ctx = createMockContext();
|
|
175
|
+
|
|
176
|
+
await expect(getConnector({}, ctx)).rejects.toThrow(ValidationError);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('should throw error for invalid connector_id UUID', async () => {
|
|
180
|
+
const ctx = createMockContext();
|
|
181
|
+
|
|
182
|
+
await expect(
|
|
183
|
+
getConnector({ connector_id: 'invalid' }, ctx)
|
|
184
|
+
).rejects.toThrow(ValidationError);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('should return connector details', async () => {
|
|
188
|
+
const mockConnector = {
|
|
189
|
+
id: 'conn-1',
|
|
190
|
+
name: 'Slack Bot',
|
|
191
|
+
type: 'slack',
|
|
192
|
+
config: { webhook_url: 'https://hooks.slack.com/...' },
|
|
193
|
+
events: { task_completed: true, blocker_added: true },
|
|
194
|
+
};
|
|
195
|
+
mockApiClient.getConnector.mockResolvedValue({
|
|
196
|
+
ok: true,
|
|
197
|
+
data: mockConnector,
|
|
198
|
+
});
|
|
199
|
+
const ctx = createMockContext();
|
|
200
|
+
|
|
201
|
+
const result = await getConnector(
|
|
202
|
+
{ connector_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
203
|
+
ctx
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
expect(result.result).toMatchObject(mockConnector);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('should handle API error', async () => {
|
|
210
|
+
mockApiClient.getConnector.mockResolvedValue({
|
|
211
|
+
ok: false,
|
|
212
|
+
error: 'Connector not found',
|
|
213
|
+
});
|
|
214
|
+
const ctx = createMockContext();
|
|
215
|
+
|
|
216
|
+
const result = await getConnector(
|
|
217
|
+
{ connector_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
218
|
+
ctx
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
expect(result.result.error).toBe('Connector not found');
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// ============================================================================
|
|
226
|
+
// addConnector Tests
|
|
227
|
+
// ============================================================================
|
|
228
|
+
|
|
229
|
+
describe('addConnector', () => {
|
|
230
|
+
beforeEach(() => {});
|
|
231
|
+
|
|
232
|
+
it('should throw error for missing project_id', async () => {
|
|
233
|
+
const ctx = createMockContext();
|
|
234
|
+
|
|
235
|
+
await expect(
|
|
236
|
+
addConnector({ name: 'Test', type: 'webhook' }, ctx)
|
|
237
|
+
).rejects.toThrow(ValidationError);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('should throw error for missing name', async () => {
|
|
241
|
+
const ctx = createMockContext();
|
|
242
|
+
|
|
243
|
+
await expect(
|
|
244
|
+
addConnector(
|
|
245
|
+
{
|
|
246
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
247
|
+
type: 'webhook',
|
|
248
|
+
},
|
|
249
|
+
ctx
|
|
250
|
+
)
|
|
251
|
+
).rejects.toThrow(ValidationError);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it('should throw error for missing type', async () => {
|
|
255
|
+
const ctx = createMockContext();
|
|
256
|
+
|
|
257
|
+
await expect(
|
|
258
|
+
addConnector(
|
|
259
|
+
{
|
|
260
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
261
|
+
name: 'Test',
|
|
262
|
+
},
|
|
263
|
+
ctx
|
|
264
|
+
)
|
|
265
|
+
).rejects.toThrow(ValidationError);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it('should throw error for invalid type', async () => {
|
|
269
|
+
const ctx = createMockContext();
|
|
270
|
+
|
|
271
|
+
await expect(
|
|
272
|
+
addConnector(
|
|
273
|
+
{
|
|
274
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
275
|
+
name: 'Test',
|
|
276
|
+
type: 'invalid_type',
|
|
277
|
+
},
|
|
278
|
+
ctx
|
|
279
|
+
)
|
|
280
|
+
).rejects.toThrow(ValidationError);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it('should create connector with minimal params', async () => {
|
|
284
|
+
mockApiClient.addConnector.mockResolvedValue({
|
|
285
|
+
ok: true,
|
|
286
|
+
data: { connector_id: 'conn-new' },
|
|
287
|
+
});
|
|
288
|
+
const ctx = createMockContext();
|
|
289
|
+
|
|
290
|
+
const result = await addConnector(
|
|
291
|
+
{
|
|
292
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
293
|
+
name: 'My Webhook',
|
|
294
|
+
type: 'webhook',
|
|
295
|
+
},
|
|
296
|
+
ctx
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
expect(result.result).toMatchObject({
|
|
300
|
+
connector_id: 'conn-new',
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it('should create connector with all params', async () => {
|
|
305
|
+
mockApiClient.addConnector.mockResolvedValue({
|
|
306
|
+
ok: true,
|
|
307
|
+
data: { connector_id: 'conn-new' },
|
|
308
|
+
});
|
|
309
|
+
const ctx = createMockContext();
|
|
310
|
+
|
|
311
|
+
await addConnector(
|
|
312
|
+
{
|
|
313
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
314
|
+
name: 'Slack Alerts',
|
|
315
|
+
type: 'slack',
|
|
316
|
+
description: 'Send alerts to Slack',
|
|
317
|
+
config: { webhook_url: 'https://hooks.slack.com/...' },
|
|
318
|
+
events: { task_completed: true, deployment_success: true },
|
|
319
|
+
},
|
|
320
|
+
ctx
|
|
321
|
+
);
|
|
322
|
+
|
|
323
|
+
expect(mockApiClient.addConnector).toHaveBeenCalledWith(
|
|
324
|
+
'123e4567-e89b-12d3-a456-426614174000',
|
|
325
|
+
expect.objectContaining({
|
|
326
|
+
name: 'Slack Alerts',
|
|
327
|
+
type: 'slack',
|
|
328
|
+
description: 'Send alerts to Slack',
|
|
329
|
+
config: { webhook_url: 'https://hooks.slack.com/...' },
|
|
330
|
+
events: { task_completed: true, deployment_success: true },
|
|
331
|
+
})
|
|
332
|
+
);
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
it('should accept all valid connector types', async () => {
|
|
336
|
+
const ctx = createMockContext();
|
|
337
|
+
const validTypes = ['webhook', 'slack', 'discord', 'github', 'custom'];
|
|
338
|
+
|
|
339
|
+
for (const type of validTypes) {
|
|
340
|
+
mockApiClient.addConnector.mockResolvedValue({
|
|
341
|
+
ok: true,
|
|
342
|
+
data: { connector_id: `conn-${type}` },
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
const result = await addConnector(
|
|
346
|
+
{
|
|
347
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
348
|
+
name: `Test ${type}`,
|
|
349
|
+
type,
|
|
350
|
+
},
|
|
351
|
+
ctx
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
expect(result.result.connector_id).toBe(`conn-${type}`);
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it('should handle API error', async () => {
|
|
359
|
+
mockApiClient.addConnector.mockResolvedValue({
|
|
360
|
+
ok: false,
|
|
361
|
+
error: 'Project not found',
|
|
362
|
+
});
|
|
363
|
+
const ctx = createMockContext();
|
|
364
|
+
|
|
365
|
+
const result = await addConnector(
|
|
366
|
+
{
|
|
367
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
368
|
+
name: 'Test',
|
|
369
|
+
type: 'webhook',
|
|
370
|
+
},
|
|
371
|
+
ctx
|
|
372
|
+
);
|
|
373
|
+
|
|
374
|
+
expect(result.result.error).toBe('Project not found');
|
|
375
|
+
});
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
// ============================================================================
|
|
379
|
+
// updateConnector Tests
|
|
380
|
+
// ============================================================================
|
|
381
|
+
|
|
382
|
+
describe('updateConnector', () => {
|
|
383
|
+
beforeEach(() => {});
|
|
384
|
+
|
|
385
|
+
it('should throw error for missing connector_id', async () => {
|
|
386
|
+
const ctx = createMockContext();
|
|
387
|
+
|
|
388
|
+
await expect(
|
|
389
|
+
updateConnector({ name: 'Updated Name' }, ctx)
|
|
390
|
+
).rejects.toThrow(ValidationError);
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
it('should throw error for invalid connector_id UUID', async () => {
|
|
394
|
+
const ctx = createMockContext();
|
|
395
|
+
|
|
396
|
+
await expect(
|
|
397
|
+
updateConnector({ connector_id: 'invalid', name: 'Updated' }, ctx)
|
|
398
|
+
).rejects.toThrow(ValidationError);
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
it('should update connector name', async () => {
|
|
402
|
+
mockApiClient.updateConnector.mockResolvedValue({
|
|
403
|
+
ok: true,
|
|
404
|
+
data: { updated: true },
|
|
405
|
+
});
|
|
406
|
+
const ctx = createMockContext();
|
|
407
|
+
|
|
408
|
+
const result = await updateConnector(
|
|
409
|
+
{
|
|
410
|
+
connector_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
411
|
+
name: 'New Name',
|
|
412
|
+
},
|
|
413
|
+
ctx
|
|
414
|
+
);
|
|
415
|
+
|
|
416
|
+
expect(result.result).toMatchObject({ updated: true });
|
|
417
|
+
expect(mockApiClient.updateConnector).toHaveBeenCalledWith(
|
|
418
|
+
'123e4567-e89b-12d3-a456-426614174000',
|
|
419
|
+
expect.objectContaining({ name: 'New Name' })
|
|
420
|
+
);
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
it('should update connector config', async () => {
|
|
424
|
+
mockApiClient.updateConnector.mockResolvedValue({
|
|
425
|
+
ok: true,
|
|
426
|
+
data: { updated: true },
|
|
427
|
+
});
|
|
428
|
+
const ctx = createMockContext();
|
|
429
|
+
|
|
430
|
+
await updateConnector(
|
|
431
|
+
{
|
|
432
|
+
connector_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
433
|
+
config: { webhook_url: 'https://new-url.com' },
|
|
434
|
+
},
|
|
435
|
+
ctx
|
|
436
|
+
);
|
|
437
|
+
|
|
438
|
+
expect(mockApiClient.updateConnector).toHaveBeenCalledWith(
|
|
439
|
+
'123e4567-e89b-12d3-a456-426614174000',
|
|
440
|
+
expect.objectContaining({
|
|
441
|
+
config: { webhook_url: 'https://new-url.com' },
|
|
442
|
+
})
|
|
443
|
+
);
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
it('should update connector events', async () => {
|
|
447
|
+
mockApiClient.updateConnector.mockResolvedValue({
|
|
448
|
+
ok: true,
|
|
449
|
+
data: { updated: true },
|
|
450
|
+
});
|
|
451
|
+
const ctx = createMockContext();
|
|
452
|
+
|
|
453
|
+
await updateConnector(
|
|
454
|
+
{
|
|
455
|
+
connector_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
456
|
+
events: { task_completed: false, blocker_added: true },
|
|
457
|
+
},
|
|
458
|
+
ctx
|
|
459
|
+
);
|
|
460
|
+
|
|
461
|
+
expect(mockApiClient.updateConnector).toHaveBeenCalledWith(
|
|
462
|
+
'123e4567-e89b-12d3-a456-426614174000',
|
|
463
|
+
expect.objectContaining({
|
|
464
|
+
events: { task_completed: false, blocker_added: true },
|
|
465
|
+
})
|
|
466
|
+
);
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
it('should update connector status to active', async () => {
|
|
470
|
+
mockApiClient.updateConnector.mockResolvedValue({
|
|
471
|
+
ok: true,
|
|
472
|
+
data: { updated: true },
|
|
473
|
+
});
|
|
474
|
+
const ctx = createMockContext();
|
|
475
|
+
|
|
476
|
+
await updateConnector(
|
|
477
|
+
{
|
|
478
|
+
connector_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
479
|
+
status: 'active',
|
|
480
|
+
},
|
|
481
|
+
ctx
|
|
482
|
+
);
|
|
483
|
+
|
|
484
|
+
expect(mockApiClient.updateConnector).toHaveBeenCalledWith(
|
|
485
|
+
'123e4567-e89b-12d3-a456-426614174000',
|
|
486
|
+
expect.objectContaining({ status: 'active' })
|
|
487
|
+
);
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
it('should update connector status to disabled', async () => {
|
|
491
|
+
mockApiClient.updateConnector.mockResolvedValue({
|
|
492
|
+
ok: true,
|
|
493
|
+
data: { updated: true },
|
|
494
|
+
});
|
|
495
|
+
const ctx = createMockContext();
|
|
496
|
+
|
|
497
|
+
await updateConnector(
|
|
498
|
+
{
|
|
499
|
+
connector_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
500
|
+
status: 'disabled',
|
|
501
|
+
},
|
|
502
|
+
ctx
|
|
503
|
+
);
|
|
504
|
+
|
|
505
|
+
expect(mockApiClient.updateConnector).toHaveBeenCalledWith(
|
|
506
|
+
'123e4567-e89b-12d3-a456-426614174000',
|
|
507
|
+
expect.objectContaining({ status: 'disabled' })
|
|
508
|
+
);
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
it('should throw error for invalid status', async () => {
|
|
512
|
+
const ctx = createMockContext();
|
|
513
|
+
|
|
514
|
+
await expect(
|
|
515
|
+
updateConnector(
|
|
516
|
+
{
|
|
517
|
+
connector_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
518
|
+
status: 'invalid_status',
|
|
519
|
+
},
|
|
520
|
+
ctx
|
|
521
|
+
)
|
|
522
|
+
).rejects.toThrow(ValidationError);
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
it('should handle API error', async () => {
|
|
526
|
+
mockApiClient.updateConnector.mockResolvedValue({
|
|
527
|
+
ok: false,
|
|
528
|
+
error: 'Connector not found',
|
|
529
|
+
});
|
|
530
|
+
const ctx = createMockContext();
|
|
531
|
+
|
|
532
|
+
const result = await updateConnector(
|
|
533
|
+
{
|
|
534
|
+
connector_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
535
|
+
name: 'Updated',
|
|
536
|
+
},
|
|
537
|
+
ctx
|
|
538
|
+
);
|
|
539
|
+
|
|
540
|
+
expect(result.result.error).toBe('Connector not found');
|
|
541
|
+
});
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
// ============================================================================
|
|
545
|
+
// deleteConnector Tests
|
|
546
|
+
// ============================================================================
|
|
547
|
+
|
|
548
|
+
describe('deleteConnector', () => {
|
|
549
|
+
beforeEach(() => {});
|
|
550
|
+
|
|
551
|
+
it('should throw error for missing connector_id', async () => {
|
|
552
|
+
const ctx = createMockContext();
|
|
553
|
+
|
|
554
|
+
await expect(deleteConnector({}, ctx)).rejects.toThrow(ValidationError);
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
it('should throw error for invalid connector_id UUID', async () => {
|
|
558
|
+
const ctx = createMockContext();
|
|
559
|
+
|
|
560
|
+
await expect(
|
|
561
|
+
deleteConnector({ connector_id: 'invalid' }, ctx)
|
|
562
|
+
).rejects.toThrow(ValidationError);
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
it('should delete connector successfully', async () => {
|
|
566
|
+
mockApiClient.deleteConnector.mockResolvedValue({
|
|
567
|
+
ok: true,
|
|
568
|
+
data: { deleted: true },
|
|
569
|
+
});
|
|
570
|
+
const ctx = createMockContext();
|
|
571
|
+
|
|
572
|
+
const result = await deleteConnector(
|
|
573
|
+
{ connector_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
574
|
+
ctx
|
|
575
|
+
);
|
|
576
|
+
|
|
577
|
+
expect(result.result).toMatchObject({ deleted: true });
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
it('should handle API error', async () => {
|
|
581
|
+
mockApiClient.deleteConnector.mockResolvedValue({
|
|
582
|
+
ok: false,
|
|
583
|
+
error: 'Connector not found',
|
|
584
|
+
});
|
|
585
|
+
const ctx = createMockContext();
|
|
586
|
+
|
|
587
|
+
const result = await deleteConnector(
|
|
588
|
+
{ connector_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
589
|
+
ctx
|
|
590
|
+
);
|
|
591
|
+
|
|
592
|
+
expect(result.result.error).toBe('Connector not found');
|
|
593
|
+
});
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
// ============================================================================
|
|
597
|
+
// testConnector Tests
|
|
598
|
+
// ============================================================================
|
|
599
|
+
|
|
600
|
+
describe('testConnector', () => {
|
|
601
|
+
beforeEach(() => {});
|
|
602
|
+
|
|
603
|
+
it('should throw error for missing connector_id', async () => {
|
|
604
|
+
const ctx = createMockContext();
|
|
605
|
+
|
|
606
|
+
await expect(testConnector({}, ctx)).rejects.toThrow(ValidationError);
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
it('should throw error for invalid connector_id UUID', async () => {
|
|
610
|
+
const ctx = createMockContext();
|
|
611
|
+
|
|
612
|
+
await expect(
|
|
613
|
+
testConnector({ connector_id: 'invalid' }, ctx)
|
|
614
|
+
).rejects.toThrow(ValidationError);
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
it('should test connector successfully', async () => {
|
|
618
|
+
mockApiClient.testConnector.mockResolvedValue({
|
|
619
|
+
ok: true,
|
|
620
|
+
data: { success: true, message: 'Test event sent' },
|
|
621
|
+
});
|
|
622
|
+
const ctx = createMockContext();
|
|
623
|
+
|
|
624
|
+
const result = await testConnector(
|
|
625
|
+
{ connector_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
626
|
+
ctx
|
|
627
|
+
);
|
|
628
|
+
|
|
629
|
+
expect(result.result).toMatchObject({
|
|
630
|
+
success: true,
|
|
631
|
+
message: 'Test event sent',
|
|
632
|
+
});
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
it('should handle test failure', async () => {
|
|
636
|
+
mockApiClient.testConnector.mockResolvedValue({
|
|
637
|
+
ok: true,
|
|
638
|
+
data: { success: false, error: 'Webhook returned 404' },
|
|
639
|
+
});
|
|
640
|
+
const ctx = createMockContext();
|
|
641
|
+
|
|
642
|
+
const result = await testConnector(
|
|
643
|
+
{ connector_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
644
|
+
ctx
|
|
645
|
+
);
|
|
646
|
+
|
|
647
|
+
expect(result.result).toMatchObject({
|
|
648
|
+
success: false,
|
|
649
|
+
error: 'Webhook returned 404',
|
|
650
|
+
});
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
it('should handle API error', async () => {
|
|
654
|
+
mockApiClient.testConnector.mockResolvedValue({
|
|
655
|
+
ok: false,
|
|
656
|
+
error: 'Connector not found',
|
|
657
|
+
});
|
|
658
|
+
const ctx = createMockContext();
|
|
659
|
+
|
|
660
|
+
const result = await testConnector(
|
|
661
|
+
{ connector_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
662
|
+
ctx
|
|
663
|
+
);
|
|
664
|
+
|
|
665
|
+
expect(result.result.error).toBe('Connector not found');
|
|
666
|
+
});
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
// ============================================================================
|
|
670
|
+
// getConnectorEvents Tests
|
|
671
|
+
// ============================================================================
|
|
672
|
+
|
|
673
|
+
describe('getConnectorEvents', () => {
|
|
674
|
+
beforeEach(() => {});
|
|
675
|
+
|
|
676
|
+
it('should throw error when neither connector_id nor project_id provided', async () => {
|
|
677
|
+
const ctx = createMockContext();
|
|
678
|
+
|
|
679
|
+
const result = await getConnectorEvents({}, ctx);
|
|
680
|
+
|
|
681
|
+
expect(result.result.error).toBe(
|
|
682
|
+
'Either connector_id or project_id is required'
|
|
683
|
+
);
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
it('should throw error for invalid connector_id UUID', async () => {
|
|
687
|
+
const ctx = createMockContext();
|
|
688
|
+
|
|
689
|
+
await expect(
|
|
690
|
+
getConnectorEvents({ connector_id: 'invalid' }, ctx)
|
|
691
|
+
).rejects.toThrow(ValidationError);
|
|
692
|
+
});
|
|
693
|
+
|
|
694
|
+
it('should throw error for invalid project_id UUID', async () => {
|
|
695
|
+
const ctx = createMockContext();
|
|
696
|
+
|
|
697
|
+
await expect(
|
|
698
|
+
getConnectorEvents({ project_id: 'invalid' }, ctx)
|
|
699
|
+
).rejects.toThrow(ValidationError);
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
it('should get events by connector_id', async () => {
|
|
703
|
+
const mockEvents = [
|
|
704
|
+
{ id: 'evt-1', event_type: 'task_completed', status: 'sent' },
|
|
705
|
+
{ id: 'evt-2', event_type: 'blocker_added', status: 'failed' },
|
|
706
|
+
];
|
|
707
|
+
mockApiClient.getConnectorEvents.mockResolvedValue({
|
|
708
|
+
ok: true,
|
|
709
|
+
data: { events: mockEvents, total: 2 },
|
|
710
|
+
});
|
|
711
|
+
const ctx = createMockContext();
|
|
712
|
+
|
|
713
|
+
const result = await getConnectorEvents(
|
|
714
|
+
{ connector_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
715
|
+
ctx
|
|
716
|
+
);
|
|
717
|
+
|
|
718
|
+
expect(result.result).toMatchObject({
|
|
719
|
+
events: mockEvents,
|
|
720
|
+
total: 2,
|
|
721
|
+
});
|
|
722
|
+
});
|
|
723
|
+
|
|
724
|
+
it('should get events by project_id', async () => {
|
|
725
|
+
mockApiClient.getConnectorEvents.mockResolvedValue({
|
|
726
|
+
ok: true,
|
|
727
|
+
data: { events: [], total: 0 },
|
|
728
|
+
});
|
|
729
|
+
const ctx = createMockContext();
|
|
730
|
+
|
|
731
|
+
await getConnectorEvents(
|
|
732
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
733
|
+
ctx
|
|
734
|
+
);
|
|
735
|
+
|
|
736
|
+
expect(mockApiClient.getConnectorEvents).toHaveBeenCalledWith(
|
|
737
|
+
expect.objectContaining({
|
|
738
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
739
|
+
})
|
|
740
|
+
);
|
|
741
|
+
});
|
|
742
|
+
|
|
743
|
+
it('should filter by status', async () => {
|
|
744
|
+
mockApiClient.getConnectorEvents.mockResolvedValue({
|
|
745
|
+
ok: true,
|
|
746
|
+
data: { events: [], total: 0 },
|
|
747
|
+
});
|
|
748
|
+
const ctx = createMockContext();
|
|
749
|
+
|
|
750
|
+
await getConnectorEvents(
|
|
751
|
+
{
|
|
752
|
+
connector_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
753
|
+
status: 'failed',
|
|
754
|
+
},
|
|
755
|
+
ctx
|
|
756
|
+
);
|
|
757
|
+
|
|
758
|
+
expect(mockApiClient.getConnectorEvents).toHaveBeenCalledWith(
|
|
759
|
+
expect.objectContaining({ status: 'failed' })
|
|
760
|
+
);
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
it('should accept all valid event statuses', async () => {
|
|
764
|
+
const ctx = createMockContext();
|
|
765
|
+
const validStatuses = ['pending', 'sent', 'failed', 'retrying'];
|
|
766
|
+
|
|
767
|
+
for (const status of validStatuses) {
|
|
768
|
+
mockApiClient.getConnectorEvents.mockResolvedValue({
|
|
769
|
+
ok: true,
|
|
770
|
+
data: { events: [], total: 0 },
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
const result = await getConnectorEvents(
|
|
774
|
+
{
|
|
775
|
+
connector_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
776
|
+
status,
|
|
777
|
+
},
|
|
778
|
+
ctx
|
|
779
|
+
);
|
|
780
|
+
|
|
781
|
+
expect(result.result.events).toBeDefined();
|
|
782
|
+
}
|
|
783
|
+
});
|
|
784
|
+
|
|
785
|
+
it('should throw error for invalid status', async () => {
|
|
786
|
+
const ctx = createMockContext();
|
|
787
|
+
|
|
788
|
+
await expect(
|
|
789
|
+
getConnectorEvents(
|
|
790
|
+
{
|
|
791
|
+
connector_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
792
|
+
status: 'invalid_status',
|
|
793
|
+
},
|
|
794
|
+
ctx
|
|
795
|
+
)
|
|
796
|
+
).rejects.toThrow(ValidationError);
|
|
797
|
+
});
|
|
798
|
+
|
|
799
|
+
it('should handle pagination', async () => {
|
|
800
|
+
mockApiClient.getConnectorEvents.mockResolvedValue({
|
|
801
|
+
ok: true,
|
|
802
|
+
data: { events: [], total: 100 },
|
|
803
|
+
});
|
|
804
|
+
const ctx = createMockContext();
|
|
805
|
+
|
|
806
|
+
await getConnectorEvents(
|
|
807
|
+
{
|
|
808
|
+
connector_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
809
|
+
limit: 25,
|
|
810
|
+
offset: 50,
|
|
811
|
+
},
|
|
812
|
+
ctx
|
|
813
|
+
);
|
|
814
|
+
|
|
815
|
+
expect(mockApiClient.getConnectorEvents).toHaveBeenCalledWith(
|
|
816
|
+
expect.objectContaining({ limit: 25, offset: 50 })
|
|
817
|
+
);
|
|
818
|
+
});
|
|
819
|
+
|
|
820
|
+
it('should handle API error', async () => {
|
|
821
|
+
mockApiClient.getConnectorEvents.mockResolvedValue({
|
|
822
|
+
ok: false,
|
|
823
|
+
error: 'Database error',
|
|
824
|
+
});
|
|
825
|
+
const ctx = createMockContext();
|
|
826
|
+
|
|
827
|
+
const result = await getConnectorEvents(
|
|
828
|
+
{ connector_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
829
|
+
ctx
|
|
830
|
+
);
|
|
831
|
+
|
|
832
|
+
expect(result.result.error).toBe('Database error');
|
|
833
|
+
});
|
|
834
|
+
});
|