@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.
- package/dist/client/testrail.js +167 -0
- package/{src/index.ts → dist/index.js} +25 -35
- package/{src/tools/add_attachment_to_run.ts → dist/tools/add_attachment_to_run.js} +8 -18
- package/{src/tools/add_results.ts → dist/tools/add_results.js} +3 -7
- package/{src/tools/add_run.ts → dist/tools/add_run.js} +3 -8
- package/{src/tools/create_case.ts → dist/tools/create_case.js} +1 -7
- package/{src/tools/get_case.ts → dist/tools/get_case.js} +2 -11
- package/{src/tools/get_case_fields.ts → dist/tools/get_case_fields.js} +8 -37
- package/{src/tools/get_cases.ts → dist/tools/get_cases.js} +5 -19
- package/{src/tools/get_projects.ts → dist/tools/get_projects.js} +1 -6
- package/{src/tools/get_sections.ts → dist/tools/get_sections.js} +1 -6
- package/{src/tools/get_statuses.ts → dist/tools/get_statuses.js} +2 -7
- package/{src/tools/get_templates.ts → dist/tools/get_templates.js} +1 -6
- package/{src/tools/get_tests.ts → dist/tools/get_tests.js} +5 -10
- package/{src/tools/update_case.ts → dist/tools/update_case.js} +1 -7
- package/{src/tools/update_cases.ts → dist/tools/update_cases.js} +1 -6
- package/dist/types/custom.js +1 -0
- package/{src/types/testrail.ts → dist/types/testrail.js} +2 -42
- package/{src/utils/mapper.ts → dist/utils/mapper.js} +16 -36
- package/{src/utils/sanitizer.ts → dist/utils/sanitizer.js} +4 -8
- package/package.json +8 -2
- package/.env.example +0 -3
- package/.github/workflows/ci.yml +0 -46
- package/.github/workflows/publish.yml +0 -35
- package/CHANGELOG.md +0 -38
- package/jest.config.js +0 -24
- package/src/client/testrail.ts +0 -227
- package/src/types/custom.ts +0 -19
- package/test/client/testrail.test.ts +0 -545
- package/test/tools/add_results.test.ts +0 -94
- package/test/tools/add_run.test.ts +0 -83
- package/test/tools/create_case.test.ts +0 -101
- package/test/tools/get_case.test.ts +0 -185
- package/test/tools/get_case_fields.test.ts +0 -132
- package/test/tools/get_cases.test.ts +0 -104
- package/test/tools/get_projects.test.ts +0 -38
- package/test/tools/get_sections.test.ts +0 -39
- package/test/tools/get_statuses.test.ts +0 -56
- package/test/tools/get_templates.test.ts +0 -38
- package/test/tools/get_tests.test.ts +0 -47
- package/test/tools/update_case.test.ts +0 -102
- package/test/tools/update_cases.test.ts +0 -89
- package/test/utils/mapper.test.ts +0 -112
- package/test/utils/sanitizer.test.ts +0 -143
- 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§ion_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
|
-
});
|