@uarlouski/testrail-mcp-server 1.1.0 → 1.2.0

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.
@@ -1,103 +1,98 @@
1
- import { jest, describe, test, expect, beforeEach } from '@jest/globals';
1
+ import { jest, describe, test, expect, beforeEach, afterEach } from '@jest/globals';
2
2
  import { TestRailClient } from '../../src/client/testrail.js';
3
- import axios, { AxiosInstance, AxiosError } from 'axios';
4
3
 
5
4
  describe('TestRailClient', () => {
6
5
  let client: TestRailClient;
7
- let mockAxiosInstance: any;
6
+ let fetchMock: any;
8
7
 
9
8
  beforeEach(() => {
10
- // Create a simple object mock for AxiosInstance to avoid complex mocking types
11
- mockAxiosInstance = {
12
- get: jest.fn(),
13
- post: jest.fn(),
14
- defaults: { headers: { common: {} } },
15
- interceptors: { request: { use: jest.fn() }, response: { use: jest.fn() } }
16
- };
17
-
18
- // Mock axios.create to return our mock instance - using type casting to bypass TS warnings
19
- jest.spyOn(axios, 'create').mockReturnValue(mockAxiosInstance);
20
-
21
- // Pass the mock instance directly to constructor
9
+ fetchMock = jest.spyOn(global, 'fetch');
22
10
  client = new TestRailClient('https://testrail.io/', 'user', 'apikey');
23
11
  });
24
12
 
13
+ afterEach(() => {
14
+ jest.restoreAllMocks();
15
+ });
16
+
25
17
  test('constructor cleans trailing slash from baseUrl', async () => {
26
18
  const mockData = { id: 1, title: 'Test Case', template_id: 1 };
27
- mockAxiosInstance.get.mockResolvedValue({ data: mockData });
19
+ fetchMock.mockResolvedValue({
20
+ ok: true,
21
+ json: async () => mockData
22
+ });
28
23
 
29
24
  await client.getCase('1');
30
25
 
31
- // Verify axios.create was called with cleaned URL (no trailing slash)
32
- expect(axios.create).toHaveBeenCalledWith(expect.objectContaining({
33
- baseURL: 'https://testrail.io'
34
- }));
35
- });
36
-
37
- test('validateStatus returns true for 2xx status codes', () => {
38
- // Get the config passed to axios.create
39
- const createCall = (axios.create as jest.Mock).mock.calls[0][0] as { validateStatus: (status: number) => boolean };
40
- const validateStatus = createCall.validateStatus;
41
-
42
- expect(validateStatus(200)).toBe(true);
43
- expect(validateStatus(201)).toBe(true);
44
- expect(validateStatus(299)).toBe(true);
45
- });
46
-
47
- test('validateStatus returns false for non-2xx status codes', () => {
48
- // Get the config passed to axios.create
49
- const createCall = (axios.create as jest.Mock).mock.calls[0][0] as { validateStatus: (status: number) => boolean };
50
- const validateStatus = createCall.validateStatus;
51
-
52
- expect(validateStatus(199)).toBe(false);
53
- expect(validateStatus(300)).toBe(false);
54
- expect(validateStatus(400)).toBe(false);
55
- expect(validateStatus(500)).toBe(false);
26
+ expect(fetchMock).toHaveBeenCalledWith('https://testrail.io/index.php?/api/v2/get_case/1', expect.any(Object));
56
27
  });
57
28
 
58
29
  test('getCase returns data on success', async () => {
59
30
  const mockData = { id: 1, title: 'Test Case', template_id: 1 };
60
- mockAxiosInstance.get.mockResolvedValue({ data: mockData });
31
+ fetchMock.mockResolvedValue({
32
+ ok: true,
33
+ json: async () => mockData
34
+ });
61
35
 
62
36
  const result = await client.getCase('1');
63
37
  expect(result).toEqual(mockData);
64
- expect(mockAxiosInstance.get).toHaveBeenCalledWith('/index.php?/api/v2/get_case/1');
38
+ expect(fetchMock).toHaveBeenCalledWith('https://testrail.io/index.php?/api/v2/get_case/1', expect.objectContaining({
39
+ method: 'GET',
40
+ headers: expect.objectContaining({
41
+ 'Authorization': expect.stringContaining('Basic'),
42
+ 'Content-Type': 'application/json'
43
+ })
44
+ }));
65
45
  });
66
46
 
67
47
  test('getCase throws formatted error on API error', async () => {
68
- const mockError = {
69
- message: 'Request failed',
70
- response: {
71
- status: 404,
72
- statusText: 'Not Found',
73
- data: { error: 'Case not found' }
74
- },
75
- isAxiosError: true,
76
- toJSON: () => ({})
77
- };
78
-
79
- mockAxiosInstance.get.mockRejectedValue(mockError);
48
+ const errorResponse = { error: 'Case not found' };
49
+ fetchMock.mockResolvedValue({
50
+ ok: false,
51
+ status: 404,
52
+ statusText: 'Not Found',
53
+ text: async () => JSON.stringify(errorResponse),
54
+ json: async () => errorResponse
55
+ });
80
56
 
81
57
  await expect(client.getCase('999')).rejects.toThrow(
82
- 'TestRail API error: 404 Not Found - {"error":"Case not found"}'
58
+ 'TestRail: 404 Not Found - {"error":"Case not found"}'
59
+ );
60
+ });
61
+
62
+ test('getCase throws error containing raw text when response is not JSON', async () => {
63
+ fetchMock.mockResolvedValue({
64
+ ok: false,
65
+ status: 500,
66
+ statusText: 'Internal Server Error',
67
+ text: async () => 'Not JSON Content',
68
+ json: async () => { throw new Error('Not JSON'); }
69
+ });
70
+
71
+ await expect(client.getCase('1')).rejects.toThrow(
72
+ 'TestRail: 500 Internal Server Error - Not JSON Content'
83
73
  );
84
74
  });
85
75
 
86
76
  test('getCases returns cases without pagination', async () => {
87
77
  const mockCases = [{ id: 1, title: 'Case 1' }, { id: 2, title: 'Case 2' }];
88
- mockAxiosInstance.get.mockResolvedValue({
89
- data: { cases: mockCases, _links: { next: null } }
78
+ fetchMock.mockResolvedValue({
79
+ ok: true,
80
+ json: async () => ({ cases: mockCases, _links: { next: null } })
90
81
  });
91
82
 
92
83
  const result = await client.getCases('1');
93
84
  expect(result).toEqual(mockCases);
94
- expect(mockAxiosInstance.get).toHaveBeenCalledWith('/index.php?/api/v2/get_cases/1');
85
+ expect(fetchMock).toHaveBeenCalledWith(
86
+ 'https://testrail.io/index.php?/api/v2/get_cases/1',
87
+ expect.objectContaining({ method: 'GET' })
88
+ );
95
89
  });
96
90
 
97
91
  test('getCases handles response with undefined _links', async () => {
98
92
  const mockCases = [{ id: 1, title: 'Case 1' }];
99
- mockAxiosInstance.get.mockResolvedValue({
100
- data: { cases: mockCases }
93
+ fetchMock.mockResolvedValue({
94
+ ok: true,
95
+ json: async () => ({ cases: mockCases })
101
96
  });
102
97
 
103
98
  const result = await client.getCases('1');
@@ -108,36 +103,52 @@ describe('TestRailClient', () => {
108
103
  const page1Cases = [{ id: 1, title: 'Case 1' }];
109
104
  const page2Cases = [{ id: 2, title: 'Case 2' }];
110
105
 
111
- mockAxiosInstance.get
106
+ fetchMock
112
107
  .mockResolvedValueOnce({
113
- data: { cases: page1Cases, _links: { next: '/api/v2/get_cases/1&offset=1' } }
108
+ ok: true,
109
+ json: async () => ({ cases: page1Cases, _links: { next: '/api/v2/get_cases/1&offset=1' } })
114
110
  })
115
111
  .mockResolvedValueOnce({
116
- data: { cases: page2Cases, _links: { next: null } }
112
+ ok: true,
113
+ json: async () => ({ cases: page2Cases, _links: { next: null } })
117
114
  });
118
115
 
119
116
  const result = await client.getCases('1');
120
117
  expect(result).toEqual([...page1Cases, ...page2Cases]);
121
- expect(mockAxiosInstance.get).toHaveBeenCalledTimes(2);
118
+ expect(fetchMock).toHaveBeenCalledTimes(2);
119
+ expect(fetchMock).toHaveBeenCalledWith(
120
+ 'https://testrail.io/index.php?/api/v2/get_cases/1',
121
+ expect.any(Object)
122
+ );
123
+ expect(fetchMock).toHaveBeenCalledWith(
124
+ 'https://testrail.io/index.php?/api/v2/get_cases/1&offset=1',
125
+ expect.any(Object)
126
+ );
122
127
  });
123
128
 
124
129
  test('getCases with sectionId adds section filter', async () => {
125
- mockAxiosInstance.get.mockResolvedValue({
126
- data: { cases: [], _links: { next: null } }
130
+ fetchMock.mockResolvedValue({
131
+ ok: true,
132
+ json: async () => ({ cases: [], _links: { next: null } })
127
133
  });
128
134
 
129
135
  await client.getCases('1', '5');
130
- expect(mockAxiosInstance.get).toHaveBeenCalledWith('/index.php?/api/v2/get_cases/1&section_id=5');
136
+ expect(fetchMock).toHaveBeenCalledWith(
137
+ 'https://testrail.io/index.php?/api/v2/get_cases/1&section_id=5',
138
+ expect.any(Object)
139
+ );
131
140
  });
132
141
 
133
142
  test('getCases with filter adds query params', async () => {
134
- mockAxiosInstance.get.mockResolvedValue({
135
- data: { cases: [], _links: { next: null } }
143
+ fetchMock.mockResolvedValue({
144
+ ok: true,
145
+ json: async () => ({ cases: [], _links: { next: null } })
136
146
  });
137
147
 
138
148
  await client.getCases('1', undefined, { type_id: '1', priority_id: '2' });
139
- expect(mockAxiosInstance.get).toHaveBeenCalledWith(
140
- '/index.php?/api/v2/get_cases/1&type_id=1&priority_id=2'
149
+ expect(fetchMock).toHaveBeenCalledWith(
150
+ 'https://testrail.io/index.php?/api/v2/get_cases/1&type_id=1&priority_id=2',
151
+ expect.any(Object)
141
152
  );
142
153
  });
143
154
 
@@ -151,11 +162,17 @@ describe('TestRailClient', () => {
151
162
  display_order: 1,
152
163
  suite_id: 1
153
164
  };
154
- mockAxiosInstance.get.mockResolvedValue({ data: mockData });
165
+ fetchMock.mockResolvedValue({
166
+ ok: true,
167
+ json: async () => mockData
168
+ });
155
169
 
156
170
  const result = await client.getSection('1');
157
171
  expect(result).toEqual(mockData);
158
- expect(mockAxiosInstance.get).toHaveBeenCalledWith('/index.php?/api/v2/get_section/1');
172
+ expect(fetchMock).toHaveBeenCalledWith(
173
+ 'https://testrail.io/index.php?/api/v2/get_section/1',
174
+ expect.any(Object)
175
+ );
159
176
  });
160
177
 
161
178
  test('getPriorities returns data on success', async () => {
@@ -166,21 +183,30 @@ describe('TestRailClient', () => {
166
183
  priority: 1,
167
184
  short_name: 'Low'
168
185
  }];
169
- mockAxiosInstance.get.mockResolvedValue({ data: mockData });
186
+ fetchMock.mockResolvedValue({
187
+ ok: true,
188
+ json: async () => mockData
189
+ });
170
190
 
171
191
  const result = await client.getPriorities();
172
192
  expect(result).toEqual(mockData);
173
- expect(mockAxiosInstance.get).toHaveBeenCalledWith('/index.php?/api/v2/get_priorities');
193
+ expect(fetchMock).toHaveBeenCalledWith(
194
+ 'https://testrail.io/index.php?/api/v2/get_priorities',
195
+ expect.any(Object)
196
+ );
174
197
  });
175
198
 
176
199
  test('getPriorities caches result', async () => {
177
200
  const mockData = [{ id: 1, name: 'Low' }];
178
- mockAxiosInstance.get.mockResolvedValue({ data: mockData });
201
+ fetchMock.mockResolvedValue({
202
+ ok: true,
203
+ json: async () => mockData
204
+ });
179
205
 
180
206
  await client.getPriorities();
181
207
  await client.getPriorities();
182
208
 
183
- expect(mockAxiosInstance.get).toHaveBeenCalledTimes(1);
209
+ expect(fetchMock).toHaveBeenCalledTimes(1);
184
210
  });
185
211
 
186
212
  test('getCaseTypes returns data on success', async () => {
@@ -189,11 +215,17 @@ describe('TestRailClient', () => {
189
215
  is_default: true,
190
216
  name: 'Automated'
191
217
  }];
192
- mockAxiosInstance.get.mockResolvedValue({ data: mockData });
218
+ fetchMock.mockResolvedValue({
219
+ ok: true,
220
+ json: async () => mockData
221
+ });
193
222
 
194
223
  const result = await client.getCaseTypes();
195
224
  expect(result).toEqual(mockData);
196
- expect(mockAxiosInstance.get).toHaveBeenCalledWith('/index.php?/api/v2/get_case_types');
225
+ expect(fetchMock).toHaveBeenCalledWith(
226
+ 'https://testrail.io/index.php?/api/v2/get_case_types',
227
+ expect.any(Object)
228
+ );
197
229
  });
198
230
 
199
231
  test('getCaseFields returns data on success', async () => {
@@ -204,55 +236,82 @@ describe('TestRailClient', () => {
204
236
  label: 'Custom Field',
205
237
  type_id: 1
206
238
  }];
207
- mockAxiosInstance.get.mockResolvedValue({ data: mockData });
239
+ fetchMock.mockResolvedValue({
240
+ ok: true,
241
+ json: async () => mockData
242
+ });
208
243
 
209
244
  const result = await client.getCaseFields();
210
245
  expect(result).toEqual(mockData);
211
- expect(mockAxiosInstance.get).toHaveBeenCalledWith('/index.php?/api/v2/get_case_fields');
246
+ expect(fetchMock).toHaveBeenCalledWith(
247
+ 'https://testrail.io/index.php?/api/v2/get_case_fields',
248
+ expect.any(Object)
249
+ );
212
250
  });
213
251
 
214
252
  test('getCaseFields caches result', async () => {
215
253
  const mockData = [{ id: 1, name: 'field' }];
216
- mockAxiosInstance.get.mockResolvedValue({ data: mockData });
254
+ fetchMock.mockResolvedValue({
255
+ ok: true,
256
+ json: async () => mockData
257
+ });
217
258
 
218
259
  await client.getCaseFields();
219
260
  await client.getCaseFields();
220
261
 
221
- expect(mockAxiosInstance.get).toHaveBeenCalledTimes(1);
262
+ expect(fetchMock).toHaveBeenCalledTimes(1);
222
263
  });
223
264
 
224
265
  test('getTemplates returns data on success', async () => {
225
266
  const mockData = [{ id: 1, name: 'Test Template', is_default: true }];
226
- mockAxiosInstance.get.mockResolvedValue({ data: mockData });
267
+ fetchMock.mockResolvedValue({
268
+ ok: true,
269
+ json: async () => mockData
270
+ });
227
271
 
228
272
  const result = await client.getTemplates('1');
229
273
  expect(result).toEqual(mockData);
230
- expect(mockAxiosInstance.get).toHaveBeenCalledWith('/index.php?/api/v2/get_templates/1');
274
+ expect(fetchMock).toHaveBeenCalledWith(
275
+ 'https://testrail.io/index.php?/api/v2/get_templates/1',
276
+ expect.any(Object)
277
+ );
231
278
  });
232
279
 
233
280
  test('getTemplates caches result per project', async () => {
234
281
  const mockData1 = [{ id: 1, name: 'Template 1' }];
235
282
  const mockData2 = [{ id: 2, name: 'Template 2' }];
236
- mockAxiosInstance.get
237
- .mockResolvedValueOnce({ data: mockData1 })
238
- .mockResolvedValueOnce({ data: mockData2 });
283
+ fetchMock
284
+ .mockResolvedValueOnce({
285
+ ok: true,
286
+ json: async () => mockData1
287
+ })
288
+ .mockResolvedValueOnce({
289
+ ok: true,
290
+ json: async () => mockData2
291
+ });
239
292
 
240
293
  await client.getTemplates('1');
241
294
  await client.getTemplates('1');
242
295
  await client.getTemplates('2');
243
296
 
244
- expect(mockAxiosInstance.get).toHaveBeenCalledTimes(2);
297
+ expect(fetchMock).toHaveBeenCalledTimes(2);
245
298
  });
246
299
 
247
300
  test('updateCase sends POST request', async () => {
248
301
  const mockData = { id: 1, title: 'Updated Case' };
249
- mockAxiosInstance.post.mockResolvedValue({ data: mockData });
302
+ fetchMock.mockResolvedValue({
303
+ ok: true,
304
+ json: async () => mockData
305
+ });
250
306
 
251
307
  const result = await client.updateCase('1', { title: 'Updated Case' });
252
308
  expect(result).toEqual(mockData);
253
- expect(mockAxiosInstance.post).toHaveBeenCalledWith(
254
- '/index.php?/api/v2/update_case/1',
255
- { title: 'Updated Case' }
309
+ expect(fetchMock).toHaveBeenCalledWith(
310
+ 'https://testrail.io/index.php?/api/v2/update_case/1',
311
+ expect.objectContaining({
312
+ method: 'POST',
313
+ body: JSON.stringify({ title: 'Updated Case' })
314
+ })
256
315
  );
257
316
  });
258
317
 
@@ -260,98 +319,112 @@ describe('TestRailClient', () => {
260
319
  const mockCase = { id: 1, suite_id: 10 };
261
320
  const mockUpdated = [{ id: 1, title: 'Updated' }, { id: 2, title: 'Updated' }];
262
321
 
263
- mockAxiosInstance.get.mockResolvedValue({ data: mockCase });
264
- mockAxiosInstance.post.mockResolvedValue({ data: { updated_cases: mockUpdated } });
322
+ fetchMock
323
+ .mockResolvedValueOnce({
324
+ ok: true,
325
+ json: async () => mockCase
326
+ })
327
+ .mockResolvedValueOnce({
328
+ ok: true,
329
+ json: async () => ({ updated_cases: mockUpdated })
330
+ });
265
331
 
266
332
  const result = await client.updateCases([1, 2], { title: 'Updated' });
267
333
  expect(result).toEqual(mockUpdated);
268
- expect(mockAxiosInstance.get).toHaveBeenCalledWith('/index.php?/api/v2/get_case/1');
269
- expect(mockAxiosInstance.post).toHaveBeenCalledWith(
270
- '/index.php?/api/v2/update_cases/10',
271
- { case_ids: [1, 2], title: 'Updated' }
334
+ expect(fetchMock).toHaveBeenCalledWith(
335
+ 'https://testrail.io/index.php?/api/v2/get_case/1',
336
+ expect.any(Object)
337
+ );
338
+ expect(fetchMock).toHaveBeenCalledWith(
339
+ 'https://testrail.io/index.php?/api/v2/update_cases/10',
340
+ expect.objectContaining({
341
+ method: 'POST',
342
+ body: JSON.stringify({ case_ids: [1, 2], title: 'Updated' })
343
+ })
272
344
  );
273
345
  });
274
346
 
275
347
  test('createCase sends POST request', async () => {
276
348
  const mockData = { id: 1, title: 'New Case' };
277
- mockAxiosInstance.post.mockResolvedValue({ data: mockData });
349
+ fetchMock.mockResolvedValue({
350
+ ok: true,
351
+ json: async () => mockData
352
+ });
278
353
 
279
354
  const result = await client.createCase('5', { title: 'New Case' });
280
355
  expect(result).toEqual(mockData);
281
- expect(mockAxiosInstance.post).toHaveBeenCalledWith(
282
- '/index.php?/api/v2/add_case/5',
283
- { title: 'New Case' }
356
+ expect(fetchMock).toHaveBeenCalledWith(
357
+ 'https://testrail.io/index.php?/api/v2/add_case/5',
358
+ expect.objectContaining({
359
+ method: 'POST',
360
+ body: JSON.stringify({ title: 'New Case' })
361
+ })
284
362
  );
285
363
  });
286
364
 
287
365
  test('getSections returns sections array', async () => {
288
366
  const mockSections = [{ id: 1, name: 'Section 1' }, { id: 2, name: 'Section 2' }];
289
- mockAxiosInstance.get.mockResolvedValue({ data: { sections: mockSections } });
367
+ fetchMock.mockResolvedValue({
368
+ ok: true,
369
+ json: async () => ({ sections: mockSections })
370
+ });
290
371
 
291
372
  const result = await client.getSections('1');
292
373
  expect(result).toEqual(mockSections);
293
- expect(mockAxiosInstance.get).toHaveBeenCalledWith('/index.php?/api/v2/get_sections/1');
374
+ expect(fetchMock).toHaveBeenCalledWith(
375
+ 'https://testrail.io/index.php?/api/v2/get_sections/1',
376
+ expect.any(Object)
377
+ );
294
378
  });
295
379
 
296
380
  test('getProjects returns projects array', async () => {
297
381
  const mockProjects = [{ id: 1, name: 'Project 1' }];
298
- mockAxiosInstance.get.mockResolvedValue({ data: { projects: mockProjects } });
382
+ fetchMock.mockResolvedValue({
383
+ ok: true,
384
+ json: async () => ({ projects: mockProjects })
385
+ });
299
386
 
300
387
  const result = await client.getProjects();
301
388
  expect(result).toEqual(mockProjects);
302
- expect(mockAxiosInstance.get).toHaveBeenCalledWith('/index.php?/api/v2/get_projects');
389
+ expect(fetchMock).toHaveBeenCalledWith(
390
+ 'https://testrail.io/index.php?/api/v2/get_projects',
391
+ expect.any(Object)
392
+ );
303
393
  });
304
394
 
305
395
  test('getProjects caches result', async () => {
306
396
  const mockProjects = [{ id: 1, name: 'Project 1' }];
307
- mockAxiosInstance.get.mockResolvedValue({ data: { projects: mockProjects } });
397
+ fetchMock.mockResolvedValue({
398
+ ok: true,
399
+ json: async () => ({ projects: mockProjects })
400
+ });
308
401
 
309
402
  await client.getProjects();
310
403
  await client.getProjects();
311
404
 
312
- expect(mockAxiosInstance.get).toHaveBeenCalledTimes(1);
405
+ expect(fetchMock).toHaveBeenCalledTimes(1);
313
406
  });
314
407
 
315
408
  test('postRequest throws formatted error on API error', async () => {
316
- const mockError = {
317
- message: 'Request failed',
318
- response: {
319
- status: 400,
320
- statusText: 'Bad Request',
321
- data: { error: 'Invalid field' }
322
- },
323
- isAxiosError: true,
324
- toJSON: () => ({})
325
- };
326
-
327
- mockAxiosInstance.post.mockRejectedValue(mockError);
409
+ const errorResponse = { error: 'Invalid field' };
410
+ fetchMock.mockResolvedValue({
411
+ ok: false,
412
+ status: 400,
413
+ statusText: 'Bad Request',
414
+ text: async () => JSON.stringify(errorResponse),
415
+ json: async () => errorResponse
416
+ });
328
417
 
329
418
  await expect(client.updateCase('1', {})).rejects.toThrow(
330
- 'TestRail API error: 400 Bad Request - {"error":"Invalid field"}'
331
- );
332
- });
333
-
334
- test('handleError formats error when no response received', async () => {
335
- const mockError = {
336
- message: 'Network Error',
337
- request: {},
338
- isAxiosError: true,
339
- toJSON: () => ({})
340
- };
341
-
342
- mockAxiosInstance.get.mockRejectedValue(mockError);
343
-
344
- await expect(client.getCase('1')).rejects.toThrow(
345
- 'TestRail API error: No response received. Network Error'
419
+ 'TestRail: 400 Bad Request - {"error":"Invalid field"}'
346
420
  );
347
421
  });
348
422
 
349
- test('handleError returns generic message for non-axios errors', async () => {
350
- const mockError = new Error('Unknown error');
351
- mockAxiosInstance.get.mockRejectedValue(mockError);
423
+ test('handleError formats error when network fails', async () => {
424
+ fetchMock.mockRejectedValue(new Error('Network Error'));
352
425
 
353
426
  await expect(client.getCase('1')).rejects.toThrow(
354
- 'TestRail API error: Unknown error'
427
+ 'Network Error'
355
428
  );
356
429
  });
357
430
  });
@@ -53,12 +53,9 @@ describe('create_case tool', () => {
53
53
  );
54
54
 
55
55
  expect(result).toBeDefined();
56
- expect(result.content[0].type).toBe('text');
57
-
58
- const parsed = JSON.parse(result.content[0].text);
59
- expect(parsed.success).toBe(true);
60
- expect(parsed.case_id).toBe(456);
61
- expect(parsed.message).toContain('C456');
56
+ expect(result.success).toBe(true);
57
+ expect(result.case_id).toBe(456);
58
+ expect(result.message).toContain('C456');
62
59
  expect(mockClient.createCase).toHaveBeenCalledWith('1', { title: 'New Test Case' });
63
60
  });
64
61
 
@@ -91,17 +88,14 @@ describe('create_case tool', () => {
91
88
  expect(mockClient.createCase).toHaveBeenCalledWith('10', { title: 'Simple Test' });
92
89
  });
93
90
 
94
- test('handler returns error on failure', async () => {
91
+ test('handler throws error on failure', async () => {
95
92
  createCaseMock.mockRejectedValue(new Error('API Error'));
96
93
 
97
- const result = await createCaseTool.handler(
98
- { section_id: '1', title: 'Test' },
99
- mockClient
100
- );
101
-
102
- expect(result).toEqual({
103
- content: [{ type: 'text', text: 'Error: API Error' }],
104
- isError: true
105
- });
94
+ await expect(
95
+ createCaseTool.handler(
96
+ { section_id: '1', title: 'Test' },
97
+ mockClient
98
+ )
99
+ ).rejects.toThrow('API Error');
106
100
  });
107
101
  });