@uarlouski/testrail-mcp-server 1.3.0 → 1.3.1

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.
Files changed (45) hide show
  1. package/dist/client/testrail.js +167 -0
  2. package/{src/index.ts → dist/index.js} +25 -35
  3. package/{src/tools/add_attachment_to_run.ts → dist/tools/add_attachment_to_run.js} +8 -18
  4. package/{src/tools/add_results.ts → dist/tools/add_results.js} +3 -7
  5. package/{src/tools/add_run.ts → dist/tools/add_run.js} +3 -8
  6. package/{src/tools/create_case.ts → dist/tools/create_case.js} +1 -7
  7. package/{src/tools/get_case.ts → dist/tools/get_case.js} +2 -11
  8. package/{src/tools/get_case_fields.ts → dist/tools/get_case_fields.js} +8 -37
  9. package/{src/tools/get_cases.ts → dist/tools/get_cases.js} +5 -19
  10. package/{src/tools/get_projects.ts → dist/tools/get_projects.js} +1 -6
  11. package/{src/tools/get_sections.ts → dist/tools/get_sections.js} +1 -6
  12. package/{src/tools/get_statuses.ts → dist/tools/get_statuses.js} +2 -7
  13. package/{src/tools/get_templates.ts → dist/tools/get_templates.js} +1 -6
  14. package/{src/tools/get_tests.ts → dist/tools/get_tests.js} +5 -10
  15. package/{src/tools/update_case.ts → dist/tools/update_case.js} +1 -7
  16. package/{src/tools/update_cases.ts → dist/tools/update_cases.js} +1 -6
  17. package/dist/types/custom.js +1 -0
  18. package/{src/types/testrail.ts → dist/types/testrail.js} +2 -42
  19. package/{src/utils/mapper.ts → dist/utils/mapper.js} +16 -36
  20. package/{src/utils/sanitizer.ts → dist/utils/sanitizer.js} +4 -8
  21. package/package.json +8 -2
  22. package/.env.example +0 -3
  23. package/.github/workflows/ci.yml +0 -46
  24. package/.github/workflows/publish.yml +0 -35
  25. package/CHANGELOG.md +0 -38
  26. package/jest.config.js +0 -24
  27. package/src/client/testrail.ts +0 -227
  28. package/src/types/custom.ts +0 -19
  29. package/test/client/testrail.test.ts +0 -545
  30. package/test/tools/add_results.test.ts +0 -94
  31. package/test/tools/add_run.test.ts +0 -83
  32. package/test/tools/create_case.test.ts +0 -101
  33. package/test/tools/get_case.test.ts +0 -185
  34. package/test/tools/get_case_fields.test.ts +0 -132
  35. package/test/tools/get_cases.test.ts +0 -104
  36. package/test/tools/get_projects.test.ts +0 -38
  37. package/test/tools/get_sections.test.ts +0 -39
  38. package/test/tools/get_statuses.test.ts +0 -56
  39. package/test/tools/get_templates.test.ts +0 -38
  40. package/test/tools/get_tests.test.ts +0 -47
  41. package/test/tools/update_case.test.ts +0 -102
  42. package/test/tools/update_cases.test.ts +0 -89
  43. package/test/utils/mapper.test.ts +0 -112
  44. package/test/utils/sanitizer.test.ts +0 -143
  45. package/tsconfig.json +0 -24
@@ -1,545 +0,0 @@
1
- import { jest, describe, test, expect, beforeEach, afterEach } from '@jest/globals';
2
- import { TestRailClient } from '../../src/client/testrail.js';
3
- import * as fs from 'fs';
4
-
5
- describe('TestRailClient', () => {
6
- let client: TestRailClient;
7
- let fetchMock: any;
8
-
9
- beforeEach(() => {
10
- fetchMock = jest.spyOn(global, 'fetch');
11
- client = new TestRailClient('https://testrail.io/', 'user', 'apikey');
12
- });
13
-
14
- afterEach(() => {
15
- jest.restoreAllMocks();
16
- });
17
-
18
- test('constructor cleans trailing slash from baseUrl', async () => {
19
- const mockData = { id: 1, title: 'Test Case', template_id: 1 };
20
- fetchMock.mockResolvedValue({
21
- ok: true,
22
- json: async () => mockData
23
- });
24
-
25
- await client.getCase('1');
26
-
27
- expect(fetchMock).toHaveBeenCalledWith('https://testrail.io/index.php?/api/v2/get_case/1', expect.any(Object));
28
- });
29
-
30
- test('getCase returns data on success', async () => {
31
- const mockData = { id: 1, title: 'Test Case', template_id: 1 };
32
- fetchMock.mockResolvedValue({
33
- ok: true,
34
- json: async () => mockData
35
- });
36
-
37
- const result = await client.getCase('1');
38
- expect(result).toEqual(mockData);
39
- expect(fetchMock).toHaveBeenCalledWith('https://testrail.io/index.php?/api/v2/get_case/1', expect.objectContaining({
40
- method: 'GET',
41
- headers: expect.objectContaining({
42
- 'Authorization': expect.stringContaining('Basic'),
43
- 'Content-Type': 'application/json'
44
- })
45
- }));
46
- });
47
-
48
- test('getCase throws formatted error on API error', async () => {
49
- const errorResponse = { error: 'Case not found' };
50
- fetchMock.mockResolvedValue({
51
- ok: false,
52
- status: 404,
53
- statusText: 'Not Found',
54
- text: async () => JSON.stringify(errorResponse),
55
- json: async () => errorResponse
56
- });
57
-
58
- await expect(client.getCase('999')).rejects.toThrow(
59
- 'TestRail: 404 Not Found - {"error":"Case not found"}'
60
- );
61
- });
62
-
63
- test('getCase throws error containing raw text when response is not JSON', async () => {
64
- fetchMock.mockResolvedValue({
65
- ok: false,
66
- status: 500,
67
- statusText: 'Internal Server Error',
68
- text: async () => 'Not JSON Content',
69
- json: async () => { throw new Error('Not JSON'); }
70
- });
71
-
72
- await expect(client.getCase('1')).rejects.toThrow(
73
- 'TestRail: 500 Internal Server Error - Not JSON Content'
74
- );
75
- });
76
-
77
- test('getCases returns cases without pagination', async () => {
78
- const mockCases = [{ id: 1, title: 'Case 1' }, { id: 2, title: 'Case 2' }];
79
- fetchMock.mockResolvedValue({
80
- ok: true,
81
- json: async () => ({ cases: mockCases, _links: { next: null } })
82
- });
83
-
84
- const result = await client.getCases('1');
85
- expect(result).toEqual(mockCases);
86
- expect(fetchMock).toHaveBeenCalledWith(
87
- 'https://testrail.io/index.php?/api/v2/get_cases/1',
88
- expect.objectContaining({ method: 'GET' })
89
- );
90
- });
91
-
92
- test('getCases handles response with undefined _links', async () => {
93
- const mockCases = [{ id: 1, title: 'Case 1' }];
94
- fetchMock.mockResolvedValue({
95
- ok: true,
96
- json: async () => ({ cases: mockCases })
97
- });
98
-
99
- const result = await client.getCases('1');
100
- expect(result).toEqual(mockCases);
101
- });
102
-
103
- test('getCases handles pagination', async () => {
104
- const page1Cases = [{ id: 1, title: 'Case 1' }];
105
- const page2Cases = [{ id: 2, title: 'Case 2' }];
106
-
107
- fetchMock
108
- .mockResolvedValueOnce({
109
- ok: true,
110
- json: async () => ({ cases: page1Cases, _links: { next: '/api/v2/get_cases/1&offset=1' } })
111
- })
112
- .mockResolvedValueOnce({
113
- ok: true,
114
- json: async () => ({ cases: page2Cases, _links: { next: null } })
115
- });
116
-
117
- const result = await client.getCases('1');
118
- expect(result).toEqual([...page1Cases, ...page2Cases]);
119
- expect(fetchMock).toHaveBeenCalledTimes(2);
120
- expect(fetchMock).toHaveBeenCalledWith(
121
- 'https://testrail.io/index.php?/api/v2/get_cases/1',
122
- expect.any(Object)
123
- );
124
- expect(fetchMock).toHaveBeenCalledWith(
125
- 'https://testrail.io/index.php?/api/v2/get_cases/1&offset=1',
126
- expect.any(Object)
127
- );
128
- });
129
-
130
- test('getCases with sectionId adds section filter', async () => {
131
- fetchMock.mockResolvedValue({
132
- ok: true,
133
- json: async () => ({ cases: [], _links: { next: null } })
134
- });
135
-
136
- await client.getCases('1', '5');
137
- expect(fetchMock).toHaveBeenCalledWith(
138
- 'https://testrail.io/index.php?/api/v2/get_cases/1&section_id=5',
139
- expect.any(Object)
140
- );
141
- });
142
-
143
- test('getCases with filter adds query params', async () => {
144
- fetchMock.mockResolvedValue({
145
- ok: true,
146
- json: async () => ({ cases: [], _links: { next: null } })
147
- });
148
-
149
- await client.getCases('1', undefined, { type_id: '1', priority_id: '2' });
150
- expect(fetchMock).toHaveBeenCalledWith(
151
- 'https://testrail.io/index.php?/api/v2/get_cases/1&type_id=1&priority_id=2',
152
- expect.any(Object)
153
- );
154
- });
155
-
156
- test('getSection returns data on success', async () => {
157
- const mockData = {
158
- id: 1,
159
- name: 'Section 1',
160
- description: null,
161
- parent_id: null,
162
- depth: 0,
163
- display_order: 1,
164
- suite_id: 1
165
- };
166
- fetchMock.mockResolvedValue({
167
- ok: true,
168
- json: async () => mockData
169
- });
170
-
171
- const result = await client.getSection('1');
172
- expect(result).toEqual(mockData);
173
- expect(fetchMock).toHaveBeenCalledWith(
174
- 'https://testrail.io/index.php?/api/v2/get_section/1',
175
- expect.any(Object)
176
- );
177
- });
178
-
179
- test('getPriorities returns data on success', async () => {
180
- const mockData = [{
181
- id: 1,
182
- is_default: true,
183
- name: 'Low',
184
- priority: 1,
185
- short_name: 'Low'
186
- }];
187
- fetchMock.mockResolvedValue({
188
- ok: true,
189
- json: async () => mockData
190
- });
191
-
192
- const result = await client.getPriorities();
193
- expect(result).toEqual(mockData);
194
- expect(fetchMock).toHaveBeenCalledWith(
195
- 'https://testrail.io/index.php?/api/v2/get_priorities',
196
- expect.any(Object)
197
- );
198
- });
199
-
200
- test('getPriorities caches result', async () => {
201
- const mockData = [{ id: 1, name: 'Low' }];
202
- fetchMock.mockResolvedValue({
203
- ok: true,
204
- json: async () => mockData
205
- });
206
-
207
- await client.getPriorities();
208
- await client.getPriorities();
209
-
210
- expect(fetchMock).toHaveBeenCalledTimes(1);
211
- });
212
-
213
- test('getCaseTypes returns data on success', async () => {
214
- const mockData = [{
215
- id: 1,
216
- is_default: true,
217
- name: 'Automated'
218
- }];
219
- fetchMock.mockResolvedValue({
220
- ok: true,
221
- json: async () => mockData
222
- });
223
-
224
- const result = await client.getCaseTypes();
225
- expect(result).toEqual(mockData);
226
- expect(fetchMock).toHaveBeenCalledWith(
227
- 'https://testrail.io/index.php?/api/v2/get_case_types',
228
- expect.any(Object)
229
- );
230
- });
231
-
232
- test('getCaseFields returns data on success', async () => {
233
- const mockData = [{
234
- id: 1,
235
- name: 'custom_field',
236
- system_name: 'custom_field',
237
- label: 'Custom Field',
238
- type_id: 1
239
- }];
240
- fetchMock.mockResolvedValue({
241
- ok: true,
242
- json: async () => mockData
243
- });
244
-
245
- const result = await client.getCaseFields();
246
- expect(result).toEqual(mockData);
247
- expect(fetchMock).toHaveBeenCalledWith(
248
- 'https://testrail.io/index.php?/api/v2/get_case_fields',
249
- expect.any(Object)
250
- );
251
- });
252
-
253
- test('getCaseFields caches result', async () => {
254
- const mockData = [{ id: 1, name: 'field' }];
255
- fetchMock.mockResolvedValue({
256
- ok: true,
257
- json: async () => mockData
258
- });
259
-
260
- await client.getCaseFields();
261
- await client.getCaseFields();
262
-
263
- expect(fetchMock).toHaveBeenCalledTimes(1);
264
- });
265
-
266
- test('getTemplates returns data on success', async () => {
267
- const mockData = [{ id: 1, name: 'Test Template', is_default: true }];
268
- fetchMock.mockResolvedValue({
269
- ok: true,
270
- json: async () => mockData
271
- });
272
-
273
- const result = await client.getTemplates('1');
274
- expect(result).toEqual(mockData);
275
- expect(fetchMock).toHaveBeenCalledWith(
276
- 'https://testrail.io/index.php?/api/v2/get_templates/1',
277
- expect.any(Object)
278
- );
279
- });
280
-
281
- test('getTemplates caches result per project', async () => {
282
- const mockData1 = [{ id: 1, name: 'Template 1' }];
283
- const mockData2 = [{ id: 2, name: 'Template 2' }];
284
- fetchMock
285
- .mockResolvedValueOnce({
286
- ok: true,
287
- json: async () => mockData1
288
- })
289
- .mockResolvedValueOnce({
290
- ok: true,
291
- json: async () => mockData2
292
- });
293
-
294
- await client.getTemplates('1');
295
- await client.getTemplates('1');
296
- await client.getTemplates('2');
297
-
298
- expect(fetchMock).toHaveBeenCalledTimes(2);
299
- });
300
-
301
- test('updateCase sends POST request', async () => {
302
- const mockData = { id: 1, title: 'Updated Case' };
303
- fetchMock.mockResolvedValue({
304
- ok: true,
305
- json: async () => mockData
306
- });
307
-
308
- const result = await client.updateCase('1', { title: 'Updated Case' });
309
- expect(result).toEqual(mockData);
310
- expect(fetchMock).toHaveBeenCalledWith(
311
- 'https://testrail.io/index.php?/api/v2/update_case/1',
312
- expect.objectContaining({
313
- method: 'POST',
314
- body: JSON.stringify({ title: 'Updated Case' })
315
- })
316
- );
317
- });
318
-
319
- test('updateCases uses passed suiteId', async () => {
320
- const mockUpdated = [{ id: 1, title: 'Updated' }, { id: 2, title: 'Updated' }];
321
-
322
- fetchMock.mockResolvedValue({
323
- ok: true,
324
- json: async () => ({ updated_cases: mockUpdated })
325
- });
326
-
327
- const result = await client.updateCases(10, [1, 2], { title: 'Updated' });
328
- expect(result).toEqual(mockUpdated);
329
- expect(fetchMock).toHaveBeenCalledWith(
330
- 'https://testrail.io/index.php?/api/v2/update_cases/10',
331
- expect.objectContaining({
332
- method: 'POST',
333
- body: JSON.stringify({ case_ids: [1, 2], title: 'Updated' })
334
- })
335
- );
336
- });
337
-
338
- test('createCase sends POST request', async () => {
339
- const mockData = { id: 1, title: 'New Case' };
340
- fetchMock.mockResolvedValue({
341
- ok: true,
342
- json: async () => mockData
343
- });
344
-
345
- const result = await client.createCase('5', { title: 'New Case' });
346
- expect(result).toEqual(mockData);
347
- expect(fetchMock).toHaveBeenCalledWith(
348
- 'https://testrail.io/index.php?/api/v2/add_case/5',
349
- expect.objectContaining({
350
- method: 'POST',
351
- body: JSON.stringify({ title: 'New Case' })
352
- })
353
- );
354
- });
355
-
356
- test('getSections returns sections array', async () => {
357
- const mockSections = [{ id: 1, name: 'Section 1' }, { id: 2, name: 'Section 2' }];
358
- fetchMock.mockResolvedValue({
359
- ok: true,
360
- json: async () => ({ sections: mockSections })
361
- });
362
-
363
- const result = await client.getSections('1');
364
- expect(result).toEqual(mockSections);
365
- expect(fetchMock).toHaveBeenCalledWith(
366
- 'https://testrail.io/index.php?/api/v2/get_sections/1',
367
- expect.any(Object)
368
- );
369
- });
370
-
371
- test('getProjects returns projects array', async () => {
372
- const mockProjects = [{ id: 1, name: 'Project 1' }];
373
- fetchMock.mockResolvedValue({
374
- ok: true,
375
- json: async () => ({ projects: mockProjects })
376
- });
377
-
378
- const result = await client.getProjects();
379
- expect(result).toEqual(mockProjects);
380
- expect(fetchMock).toHaveBeenCalledWith(
381
- 'https://testrail.io/index.php?/api/v2/get_projects',
382
- expect.any(Object)
383
- );
384
- });
385
-
386
- test('getProjects caches result', async () => {
387
- const mockProjects = [{ id: 1, name: 'Project 1' }];
388
- fetchMock.mockResolvedValue({
389
- ok: true,
390
- json: async () => ({ projects: mockProjects })
391
- });
392
-
393
- await client.getProjects();
394
- await client.getProjects();
395
-
396
- expect(fetchMock).toHaveBeenCalledTimes(1);
397
- });
398
-
399
- test('postRequest throws formatted error on API error', async () => {
400
- const errorResponse = { error: 'Invalid field' };
401
- fetchMock.mockResolvedValue({
402
- ok: false,
403
- status: 400,
404
- statusText: 'Bad Request',
405
- text: async () => JSON.stringify(errorResponse),
406
- json: async () => errorResponse
407
- });
408
-
409
- await expect(client.updateCase('1', {})).rejects.toThrow(
410
- 'TestRail: 400 Bad Request - {"error":"Invalid field"}'
411
- );
412
- });
413
-
414
- test('addRun posts fields directly', async () => {
415
- const mockRun = { id: 1, name: 'New Run' };
416
-
417
- fetchMock.mockResolvedValue({
418
- ok: true,
419
- json: async () => mockRun
420
- });
421
-
422
- const result = await client.addRun('1', { name: 'New Run', suite_id: 20 });
423
-
424
- expect(result).toEqual(mockRun);
425
- expect(fetchMock).toHaveBeenCalledWith(
426
- 'https://testrail.io/index.php?/api/v2/add_run/1',
427
- expect.objectContaining({
428
- method: 'POST',
429
- body: JSON.stringify({ name: 'New Run', suite_id: 20 })
430
- })
431
- );
432
- });
433
-
434
- test('getTests handles pagination', async () => {
435
- fetchMock
436
- .mockImplementationOnce(() => Promise.resolve({
437
- ok: true,
438
- json: () => Promise.resolve({
439
- tests: [{ id: 1, case_id: 1, status_id: 1, title: 'Test 1', run_id: 1 }],
440
- _links: { next: '/api/v2/get_tests/1&offset=1' }
441
- }),
442
- } as Response))
443
- .mockImplementationOnce(() => Promise.resolve({
444
- ok: true,
445
- json: () => Promise.resolve({
446
- tests: [{ id: 2, case_id: 2, status_id: 1, title: 'Test 2', run_id: 1 }],
447
- _links: { next: null }
448
- }),
449
- } as Response));
450
-
451
- const tests = await client.getTests(1);
452
- expect(tests).toHaveLength(2);
453
- expect(tests[0].id).toBe(1);
454
- expect(tests[1].id).toBe(2);
455
- expect(fetchMock).toHaveBeenCalledTimes(2);
456
- });
457
-
458
- test('getTests with status_id adds filter', async () => {
459
- fetchMock.mockResolvedValue({
460
- ok: true,
461
- json: async () => ({
462
- tests: [],
463
- _links: { next: null }
464
- })
465
- } as Response);
466
-
467
- await client.getTests(1, [1, 5]);
468
-
469
- expect(fetchMock).toHaveBeenCalledWith(
470
- expect.stringContaining('/get_tests/1&status_id=1,5'),
471
- expect.any(Object)
472
- );
473
- });
474
-
475
- test('getStatuses caches result', async () => {
476
- const mockStatuses = [{ id: 1, name: 'passed', label: 'Passed' }];
477
- fetchMock.mockResolvedValue({
478
- ok: true,
479
- json: async () => mockStatuses
480
- });
481
-
482
- await client.getStatuses();
483
- await client.getStatuses();
484
-
485
- expect(fetchMock).toHaveBeenCalledTimes(1);
486
- });
487
-
488
- test('addResults posts results array', async () => {
489
- const mockResults = [
490
- { id: 1, test_id: 100, status_id: 1, comment: 'Passed', defects: null },
491
- { id: 2, test_id: 101, status_id: 5, comment: 'Failed', defects: 'BUG-123' }
492
- ];
493
-
494
- fetchMock.mockResolvedValue({
495
- ok: true,
496
- json: async () => mockResults
497
- });
498
-
499
- const results = await client.addResults(50, [
500
- { test_id: 100, status_id: 1, comment: 'Passed' },
501
- { test_id: 101, status_id: 5, comment: 'Failed', defects: 'BUG-123' }
502
- ]);
503
-
504
- expect(results).toEqual(mockResults);
505
- expect(fetchMock).toHaveBeenCalledWith(
506
- 'https://testrail.io/index.php?/api/v2/add_results/50',
507
- expect.objectContaining({
508
- method: 'POST',
509
- body: JSON.stringify({
510
- results: [
511
- { test_id: 100, status_id: 1, comment: 'Passed' },
512
- { test_id: 101, status_id: 5, comment: 'Failed', defects: 'BUG-123' }
513
- ]
514
- })
515
- })
516
- );
517
- });
518
-
519
- test('addAttachmentToRun uploads file using FormData', async () => {
520
- const mockAttachment = { attachment_id: 443 };
521
- const mockFileBuffer = Buffer.from('file content');
522
-
523
- jest.spyOn(fs.promises, 'readFile').mockResolvedValue(mockFileBuffer);
524
-
525
- fetchMock.mockResolvedValue({
526
- ok: true,
527
- json: async () => mockAttachment
528
- });
529
-
530
- const result = await client.addAttachmentToRun(100, '/path/to/file.txt', 'file.txt');
531
-
532
- expect(result).toEqual(mockAttachment);
533
- expect(fs.promises.readFile).toHaveBeenCalledWith('/path/to/file.txt');
534
- expect(fetchMock).toHaveBeenCalledWith(
535
- 'https://testrail.io/index.php?/api/v2/add_attachment_to_run/100',
536
- expect.objectContaining({
537
- method: 'POST',
538
- headers: expect.objectContaining({
539
- 'Authorization': expect.stringContaining('Basic')
540
- }),
541
- body: expect.any(FormData)
542
- })
543
- );
544
- });
545
- });
@@ -1,94 +0,0 @@
1
- import { jest, describe, test, expect, beforeEach } from '@jest/globals';
2
- import { addResultsTool } from '../../src/tools/add_results.js';
3
- import { TestRailClient } from '../../src/client/testrail.js';
4
- import { Result } from '../../src/types/testrail.js';
5
-
6
- describe('add_results tool', () => {
7
- let mockClient: jest.Mocked<TestRailClient>;
8
- let addResultsMock: jest.Mock<(runId: number, results: Array<Record<string, any>>) => Promise<Result[]>>;
9
-
10
- const mockResults: Result[] = [
11
- {
12
- id: 1,
13
- test_id: 100,
14
- status_id: 1,
15
- comment: 'Test passed',
16
- defects: null
17
- },
18
- {
19
- id: 2,
20
- test_id: 101,
21
- status_id: 5,
22
- comment: 'Test failed',
23
- defects: 'BUG-123'
24
- }
25
- ];
26
-
27
- beforeEach(() => {
28
- addResultsMock = jest.fn<(runId: number, results: Array<Record<string, any>>) => Promise<Result[]>>().mockResolvedValue(mockResults);
29
-
30
- mockClient = {
31
- addResults: addResultsMock
32
- } as unknown as jest.Mocked<TestRailClient>;
33
- });
34
-
35
- test('exports correct tool definition', () => {
36
- expect(addResultsTool.name).toBe('add_results');
37
- expect(addResultsTool.description).toBeDefined();
38
- expect(addResultsTool.parameters).toBeDefined();
39
- });
40
-
41
- test('handler calls addResults with correct arguments', async () => {
42
- const args = {
43
- run_id: 50,
44
- results: [
45
- {
46
- test_id: 100,
47
- status_id: 1,
48
- comment: 'Test passed'
49
- },
50
- {
51
- test_id: 101,
52
- status_id: 5,
53
- comment: 'Test failed',
54
- defects: 'BUG-123'
55
- }
56
- ]
57
- };
58
-
59
- const result = await addResultsTool.handler(args, mockClient);
60
-
61
- expect(result).toBeDefined();
62
- expect(mockClient.addResults).toHaveBeenCalledWith(50, args.results);
63
-
64
- expect(result.success).toBe(true);
65
- expect(result.added_count).toBe(2);
66
- expect(result.results).toHaveLength(2);
67
- });
68
-
69
- test('handler works with single result', async () => {
70
- const singleResult = [mockResults[0]];
71
- addResultsMock.mockResolvedValue(singleResult);
72
-
73
- const args = {
74
- run_id: 50,
75
- results: [
76
- {
77
- test_id: 100,
78
- status_id: 1,
79
- comment: 'Test passed'
80
- }
81
- ]
82
- };
83
-
84
- const result = await addResultsTool.handler(args, mockClient);
85
-
86
- expect(result.success).toBe(true);
87
- expect(result.added_count).toBe(1);
88
- });
89
-
90
- test('handler returns error on failure', async () => {
91
- addResultsMock.mockRejectedValue(new Error('API Error'));
92
- await expect(addResultsTool.handler({ run_id: 50, results: [] }, mockClient)).rejects.toThrow('API Error');
93
- });
94
- });