@uarlouski/testrail-mcp-server 1.1.0 → 1.3.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.
Files changed (38) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +62 -16
  3. package/package.json +3 -2
  4. package/src/client/testrail.ts +111 -56
  5. package/src/index.ts +32 -2
  6. package/src/tools/add_attachment_to_run.ts +62 -0
  7. package/src/tools/add_results.ts +28 -0
  8. package/src/tools/add_run.ts +24 -0
  9. package/src/tools/create_case.ts +10 -17
  10. package/src/tools/get_case.ts +5 -12
  11. package/src/tools/get_case_fields.ts +4 -11
  12. package/src/tools/get_cases.ts +9 -16
  13. package/src/tools/get_projects.ts +7 -27
  14. package/src/tools/get_sections.ts +7 -27
  15. package/src/tools/get_statuses.ts +18 -0
  16. package/src/tools/get_templates.ts +7 -27
  17. package/src/tools/get_tests.ts +22 -0
  18. package/src/tools/update_case.ts +7 -14
  19. package/src/tools/update_cases.ts +10 -16
  20. package/src/types/custom.ts +1 -16
  21. package/src/types/testrail.ts +140 -87
  22. package/src/utils/sanitizer.ts +29 -0
  23. package/test/client/testrail.test.ts +329 -141
  24. package/test/tools/add_results.test.ts +94 -0
  25. package/test/tools/add_run.test.ts +83 -0
  26. package/test/tools/create_case.test.ts +10 -16
  27. package/test/tools/get_case.test.ts +47 -62
  28. package/test/tools/get_case_fields.test.ts +7 -80
  29. package/test/tools/get_cases.test.ts +19 -108
  30. package/test/tools/get_projects.test.ts +4 -41
  31. package/test/tools/get_sections.test.ts +7 -36
  32. package/test/tools/get_statuses.test.ts +56 -0
  33. package/test/tools/get_templates.test.ts +1 -30
  34. package/test/tools/get_tests.test.ts +47 -0
  35. package/test/tools/update_case.test.ts +10 -16
  36. package/test/tools/update_cases.test.ts +29 -28
  37. package/test/utils/mapper.test.ts +13 -13
  38. package/test/utils/sanitizer.test.ts +85 -1
@@ -0,0 +1,94 @@
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
+ });
@@ -0,0 +1,83 @@
1
+ import { jest } from '@jest/globals';
2
+ import { addRunTool } from '../../src/tools/add_run.js';
3
+ import { TestRailClient } from '../../src/client/testrail.js';
4
+
5
+ describe('add_run tool', () => {
6
+ let client: TestRailClient;
7
+ let addRunMock: any;
8
+
9
+ beforeEach(() => {
10
+ addRunMock = jest.fn();
11
+ client = {
12
+ addRun: addRunMock,
13
+ getCase: jest.fn(),
14
+ } as any as TestRailClient;
15
+ });
16
+
17
+ test('should wrap client result including only required fields and forcing include_all to false', async () => {
18
+ const run = {
19
+ id: 1,
20
+ name: 'Test Run',
21
+ description: null,
22
+ suite_id: 10,
23
+ project_id: 1,
24
+ is_completed: false,
25
+ passed_count: 0,
26
+ blocked_count: 0,
27
+ untested_count: 5,
28
+ retest_count: 0,
29
+ failed_count: 0,
30
+ url: 'https://testrail.io/runs/view/1'
31
+ };
32
+ addRunMock.mockResolvedValue(run);
33
+
34
+ const mockCase = { suite_id: 10 };
35
+ (client.getCase as jest.Mock<any>).mockResolvedValue(mockCase);
36
+
37
+ const result = await addRunTool.handler({ project_id: '1', name: 'Test Run', case_ids: [1] }, client);
38
+
39
+ expect(client.getCase).toHaveBeenCalledWith('1');
40
+ expect(addRunMock).toHaveBeenCalledWith('1', { name: 'Test Run', case_ids: [1], suite_id: 10, include_all: false });
41
+ expect(result).toEqual(run);
42
+ });
43
+
44
+ test('should wrap client result handling optional fields', async () => {
45
+ const run = {
46
+ id: 1,
47
+ name: 'Test Run',
48
+ description: 'Description',
49
+ suite_id: 10,
50
+ project_id: 1,
51
+ is_completed: false,
52
+ passed_count: 0,
53
+ blocked_count: 0,
54
+ untested_count: 3,
55
+ retest_count: 0,
56
+ failed_count: 0,
57
+ url: 'https://testrail.io/runs/view/1'
58
+ };
59
+ addRunMock.mockResolvedValue(run);
60
+
61
+ const mockCase = { suite_id: 10 };
62
+ (client.getCase as jest.Mock<any>).mockResolvedValue(mockCase);
63
+
64
+ const args = {
65
+ project_id: '1',
66
+ name: 'Test Run',
67
+ description: 'Description',
68
+ case_ids: [1, 2, 3]
69
+ };
70
+
71
+ const result = await addRunTool.handler(args, client);
72
+
73
+ expect(client.getCase).toHaveBeenCalledWith('1');
74
+ expect(addRunMock).toHaveBeenCalledWith('1', {
75
+ name: 'Test Run',
76
+ description: 'Description',
77
+ case_ids: [1, 2, 3],
78
+ suite_id: 10,
79
+ include_all: false
80
+ });
81
+ expect(result).toEqual(run);
82
+ });
83
+ });
@@ -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
  });
@@ -12,13 +12,13 @@ describe('get_case tool', () => {
12
12
  let getCaseFieldsMock: jest.Mock<() => Promise<CaseField[]>>;
13
13
 
14
14
  const mockCaseFields: CaseField[] = [
15
- { id: 1, name: 'is_automated', system_name: 'custom_is_automated', label: 'Is Automated', type_id: 1, template_ids: [], include_all: true, is_active: true, display_order: 1, description: null, configs: [] },
16
- { id: 2, name: 'refs', system_name: 'custom_refs', label: 'Defects', type_id: 1, template_ids: [], include_all: true, is_active: true, display_order: 2, description: null, configs: [] },
17
- { id: 3, name: 'case_automation_priority', system_name: 'custom_case_automation_priority', label: 'Automation Priority', type_id: 6, template_ids: [], include_all: true, is_active: true, display_order: 3, description: null, configs: [{ context: { is_global: true, project_ids: null }, options: { items: '2, P0\n3, P1\n4, P2\n5, P3\n6, P4' } }] },
18
- { id: 4, name: 'preconds', system_name: 'custom_preconds', label: 'Preconditions', type_id: 1, template_ids: [], include_all: true, is_active: true, display_order: 4, description: null, configs: [] },
19
- { id: 5, name: 'steps', system_name: 'custom_steps', label: 'Steps', type_id: 1, template_ids: [1], include_all: false, is_active: true, display_order: 5, description: null, configs: [] },
20
- { id: 6, name: 'expected', system_name: 'custom_expected', label: 'Expected', type_id: 1, template_ids: [1], include_all: false, is_active: true, display_order: 6, description: null, configs: [] },
21
- { id: 7, name: 'steps_separated', system_name: 'custom_steps_separated', label: 'Steps Separated', type_id: 1, template_ids: [2], include_all: false, is_active: true, display_order: 7, description: null, configs: [] },
15
+ { id: 1, name: 'is_automated', system_name: 'custom_is_automated', label: 'Is Automated', type_id: 1, template_ids: [], include_all: true, is_active: true, description: null, configs: [] },
16
+ { id: 2, name: 'refs', system_name: 'custom_refs', label: 'Defects', type_id: 1, template_ids: [], include_all: true, is_active: true, description: null, configs: [] },
17
+ { id: 3, name: 'case_automation_priority', system_name: 'custom_case_automation_priority', label: 'Automation Priority', type_id: 6, template_ids: [], include_all: true, is_active: true, description: null, configs: [{ options: { items: '2, P0\n3, P1\n4, P2\n5, P3\n6, P4' } }] },
18
+ { id: 4, name: 'preconds', system_name: 'custom_preconds', label: 'Preconditions', type_id: 1, template_ids: [], include_all: true, is_active: true, description: null, configs: [] },
19
+ { id: 5, name: 'steps', system_name: 'custom_steps', label: 'Steps', type_id: 1, template_ids: [1], include_all: false, is_active: true, description: null, configs: [] },
20
+ { id: 6, name: 'expected', system_name: 'custom_expected', label: 'Expected', type_id: 1, template_ids: [1], include_all: false, is_active: true, description: null, configs: [] },
21
+ { id: 7, name: 'steps_separated', system_name: 'custom_steps_separated', label: 'Steps Separated', type_id: 1, template_ids: [2], include_all: false, is_active: true, description: null, configs: [] },
22
22
  ];
23
23
 
24
24
  beforeEach(() => {
@@ -39,7 +39,7 @@ describe('get_case tool', () => {
39
39
 
40
40
  test('exports correct tool definition', () => {
41
41
  expect(getCaseTool.name).toBe('get_case');
42
- expect(getCaseTool.description).toBe('Get a test case from TestRail by ID');
42
+ expect(getCaseTool.description).toBe('Get detailed information about a test case including its custom fields');
43
43
  expect(getCaseTool.parameters).toBeDefined();
44
44
  });
45
45
 
@@ -51,15 +51,10 @@ describe('get_case tool', () => {
51
51
  priority_id: 3,
52
52
  milestone_id: null,
53
53
  refs: 'JIRA-123',
54
- created_by: 1,
55
54
  created_on: 1700000000,
56
- updated_by: 1,
57
55
  updated_on: 1700000000,
58
56
  estimate: null,
59
- estimate_forecast: null,
60
57
  suite_id: 1,
61
- display_order: 1,
62
- is_deleted: 0,
63
58
  custom_refs: null,
64
59
  custom_case_automation_priority: null,
65
60
  custom_is_automated: null,
@@ -79,7 +74,7 @@ describe('get_case tool', () => {
79
74
  };
80
75
 
81
76
  const mockSection: Section = {
82
- id: 456, name: 'My Section', description: null, parent_id: null, depth: 0, display_order: 1, suite_id: 1
77
+ id: 456, name: 'My Section', description: null, parent_id: null, suite_id: 1
83
78
  };
84
79
 
85
80
  const mockCaseTypes: CaseType[] = [
@@ -88,7 +83,7 @@ describe('get_case tool', () => {
88
83
  ];
89
84
 
90
85
  const mockPriorities: Priority[] = [
91
- { id: 3, name: 'High', priority: 3, short_name: 'High', is_default: false }
86
+ { id: 3, name: 'High', is_default: false }
92
87
  ];
93
88
 
94
89
  getCaseMock.mockResolvedValue(mockCase);
@@ -99,23 +94,16 @@ describe('get_case tool', () => {
99
94
  const result = await getCaseTool.handler({ case_id: 'C123' }, mockClient);
100
95
 
101
96
  expect(result).toBeDefined();
102
- if ('content' in result) {
103
- expect(result.content[0].type).toBe('text');
104
- const parsed = JSON.parse(result.content[0].text);
105
-
106
- expect(parsed).toEqual({
107
- id: 123,
108
- title: 'My Case',
109
- section: 'My Section',
110
- type: 'Functional',
111
- priority: 'High',
112
- labels: ['tag1'],
113
- references: 'JIRA-123',
114
- updated_on: 1700000000
115
- });
116
- } else {
117
- throw new Error('Result should have content');
118
- }
97
+ expect(result).toEqual({
98
+ id: 123,
99
+ title: 'My Case',
100
+ section: 'My Section',
101
+ type: 'Functional',
102
+ priority: 'High',
103
+ labels: ['tag1'],
104
+ references: 'JIRA-123',
105
+ updated_on: 1700000000
106
+ });
119
107
 
120
108
  expect(mockClient.getCase).toHaveBeenCalledWith('123');
121
109
  expect(mockClient.getSection).toHaveBeenCalledWith('456');
@@ -125,76 +113,73 @@ describe('get_case tool', () => {
125
113
 
126
114
 
127
115
 
128
- test('handler returns error on failure', async () => {
116
+ test('handler throws error on failure', async () => {
129
117
  getCaseMock.mockRejectedValue(new Error('API Error'));
130
118
 
131
- const result = await getCaseTool.handler({ case_id: '123' }, mockClient);
132
-
133
- expect(result).toEqual({
134
- content: [{ type: 'text', text: 'Error: API Error' }],
135
- isError: true
136
- });
119
+ await expect(
120
+ getCaseTool.handler({ case_id: '123' }, mockClient)
121
+ ).rejects.toThrow('API Error');
137
122
  });
138
123
 
139
124
  test('handler returns "Unknown" when caseType is not found', async () => {
140
125
  const mockCase: Case = {
141
126
  id: 123, title: 'My Case', template_id: 1,
142
127
  section_id: 456, type_id: 999, priority_id: 3,
143
- milestone_id: null, refs: null, created_by: 1, created_on: 1700000000,
144
- updated_by: 1, updated_on: 1700000000, estimate: null, estimate_forecast: null,
145
- suite_id: 1, display_order: 1, is_deleted: 0, labels: []
128
+ milestone_id: null, refs: null, created_on: 1700000000,
129
+ updated_on: 1700000000, estimate: null,
130
+ suite_id: 1, labels: []
146
131
  };
147
132
 
148
133
  getCaseMock.mockResolvedValue(mockCase);
149
- getSectionMock.mockResolvedValue({ id: 456, name: 'Section', description: null, parent_id: null, depth: 0, display_order: 1, suite_id: 1 });
134
+ getSectionMock.mockResolvedValue({ id: 456, name: 'Section', description: null, parent_id: null, suite_id: 1 });
150
135
  getCaseTypesMock.mockResolvedValue([{ id: 1, name: 'Automated', is_default: false }]);
151
- getPrioritiesMock.mockResolvedValue([{ id: 3, name: 'High', priority: 3, short_name: 'High', is_default: false }]);
136
+ getPrioritiesMock.mockResolvedValue([{ id: 3, name: 'High', is_default: false }]);
152
137
 
153
138
  const result = await getCaseTool.handler({ case_id: '123' }, mockClient);
154
- const parsed = JSON.parse(result.content[0].text);
139
+ // result is now a plain object
155
140
 
156
- expect(parsed.type).toBe('Unknown');
157
- expect(parsed.priority).toBe('High');
141
+ expect(result.type).toBe('Unknown');
142
+ expect(result.priority).toBe('High');
158
143
  });
159
144
 
160
145
  test('handler returns "Unknown" when priority is not found', async () => {
161
146
  const mockCase: Case = {
162
147
  id: 123, title: 'My Case', template_id: 1,
163
148
  section_id: 456, type_id: 1, priority_id: 999,
164
- milestone_id: null, refs: null, created_by: 1, created_on: 1700000000,
165
- updated_by: 1, updated_on: 1700000000, estimate: null, estimate_forecast: null,
166
- suite_id: 1, display_order: 1, is_deleted: 0, labels: []
149
+ milestone_id: null, refs: null, created_on: 1700000000,
150
+ updated_on: 1700000000, estimate: null,
151
+ suite_id: 1, labels: []
167
152
  };
168
153
 
169
154
  getCaseMock.mockResolvedValue(mockCase);
170
- getSectionMock.mockResolvedValue({ id: 456, name: 'Section', description: null, parent_id: null, depth: 0, display_order: 1, suite_id: 1 });
155
+ getSectionMock.mockResolvedValue({ id: 456, name: 'Section', description: null, parent_id: null, suite_id: 1 });
171
156
  getCaseTypesMock.mockResolvedValue([{ id: 1, name: 'Automated', is_default: false }]);
172
- getPrioritiesMock.mockResolvedValue([{ id: 3, name: 'High', priority: 3, short_name: 'High', is_default: false }]);
157
+ getPrioritiesMock.mockResolvedValue([{ id: 3, name: 'High', is_default: false }]);
173
158
 
174
159
  const result = await getCaseTool.handler({ case_id: '123' }, mockClient);
175
- const parsed = JSON.parse(result.content[0].text);
160
+ // result is now a plain object
176
161
 
177
- expect(parsed.type).toBe('Automated');
178
- expect(parsed.priority).toBe('Unknown');
162
+ expect(result.type).toBe('Automated');
163
+ expect(result.priority).toBe('Unknown');
179
164
  });
180
165
 
181
166
  test('handler uses empty array when labels is undefined', async () => {
182
167
  const mockCase: Case = {
183
168
  id: 123, title: 'My Case', template_id: 1,
184
169
  section_id: 456, type_id: 1, priority_id: 3,
185
- milestone_id: null, refs: null, created_by: 1, created_on: 1700000000,
186
- updated_by: 1, updated_on: 1700000000, estimate: null, estimate_forecast: null,
187
- suite_id: 1, display_order: 1, is_deleted: 0, labels: undefined as any
170
+ milestone_id: null, refs: null, created_on: 1700000000,
171
+ updated_on: 1700000000, estimate: null,
172
+ suite_id: 1, labels: undefined as any
188
173
  };
189
174
 
190
175
  getCaseMock.mockResolvedValue(mockCase);
191
- getSectionMock.mockResolvedValue({ id: 456, name: 'Section', description: null, parent_id: null, depth: 0, display_order: 1, suite_id: 1 });
176
+ getSectionMock.mockResolvedValue({ id: 456, name: 'Section', description: null, parent_id: null, suite_id: 1 });
192
177
  getCaseTypesMock.mockResolvedValue([{ id: 1, name: 'Automated', is_default: false }]);
193
- getPrioritiesMock.mockResolvedValue([{ id: 3, name: 'High', priority: 3, short_name: 'High', is_default: false }]);
178
+ getPrioritiesMock.mockResolvedValue([{ id: 3, name: 'High', is_default: false }]);
194
179
 
195
180
  const result = await getCaseTool.handler({ case_id: '123' }, mockClient);
196
- const parsed = JSON.parse(result.content[0].text);
181
+ // result is now a plain object
197
182
 
198
- expect(parsed.labels).toEqual([]);
183
+ expect(result.labels).toEqual([]);
199
184
  });
200
185
  });
@@ -17,9 +17,8 @@ describe('get_case_fields tool', () => {
17
17
  template_ids: [],
18
18
  include_all: true,
19
19
  is_active: true,
20
- display_order: 1,
21
20
  description: 'Whether test is automated',
22
- configs: [{ context: { is_global: true, project_ids: null }, options: { is_required: false } }]
21
+ configs: [{ options: { is_required: false } }]
23
22
  },
24
23
  {
25
24
  id: 2,
@@ -30,9 +29,8 @@ describe('get_case_fields tool', () => {
30
29
  template_ids: [],
31
30
  include_all: true,
32
31
  is_active: true,
33
- display_order: 2,
34
32
  description: 'Priority for automation',
35
- configs: [{ context: { is_global: true, project_ids: null }, options: { items: '1, P0\n2, P1\n3, P2', is_required: true } }]
33
+ configs: [{ options: { items: '1, P0\n2, P1\n3, P2', is_required: true } }]
36
34
  },
37
35
  {
38
36
  id: 3,
@@ -43,7 +41,6 @@ describe('get_case_fields tool', () => {
43
41
  template_ids: [],
44
42
  include_all: true,
45
43
  is_active: false,
46
- display_order: 3,
47
44
  description: null,
48
45
  configs: []
49
46
  },
@@ -56,9 +53,8 @@ describe('get_case_fields tool', () => {
56
53
  template_ids: [],
57
54
  include_all: true,
58
55
  is_active: true,
59
- display_order: 4,
60
56
  description: 'Test components',
61
- configs: [{ context: { is_global: true, project_ids: null }, options: { items: '1, UI\n2, API\n3, Database' } }]
57
+ configs: [{ options: { items: '1, UI\n2, API\n3, Database' } }]
62
58
  }
63
59
  ];
64
60
 
@@ -80,76 +76,11 @@ describe('get_case_fields tool', () => {
80
76
  const result = await getCaseFieldsTool.handler({}, mockClient);
81
77
 
82
78
  expect(result).toBeDefined();
83
- expect(result.content[0].type).toBe('text');
84
-
85
- const parsed = JSON.parse(result.content[0].text);
86
- expect(parsed.fields).toHaveLength(11); // 8 system fields + 3 active custom fields
79
+ expect(result.fields).toBeDefined();
80
+ expect(result.fields.length).toBeGreaterThan(0);
87
81
  expect(mockClient.getCaseFields).toHaveBeenCalled();
88
82
  });
89
83
 
90
- test('returns only essential schema fields', async () => {
91
- const result = await getCaseFieldsTool.handler({}, mockClient);
92
- const parsed = JSON.parse(result.content[0].text);
93
-
94
- const field = parsed.fields[0];
95
- expect(Object.keys(field).sort()).toEqual(['is_required', 'label', 'system_name', 'type']);
96
- });
97
-
98
- test('returns correct field types', async () => {
99
- const result = await getCaseFieldsTool.handler({}, mockClient);
100
- const parsed = JSON.parse(result.content[0].text);
101
-
102
- const checkboxField = parsed.fields.find((f: any) => f.system_name === 'custom_is_automated');
103
- expect(checkboxField.type).toBe('Checkbox');
104
-
105
- const dropdownField = parsed.fields.find((f: any) => f.system_name === 'custom_automation_priority');
106
- expect(dropdownField.type).toBe('Dropdown');
107
-
108
- const multiSelectField = parsed.fields.find((f: any) => f.system_name === 'custom_components');
109
- expect(multiSelectField.type).toBe('Multi-select');
110
- });
111
-
112
- test('parses dropdown options correctly', async () => {
113
- const result = await getCaseFieldsTool.handler({}, mockClient);
114
- const parsed = JSON.parse(result.content[0].text);
115
-
116
- const dropdownField = parsed.fields.find((f: any) => f.system_name === 'custom_automation_priority');
117
- expect(dropdownField.options).toEqual(['P0', 'P1', 'P2']);
118
-
119
- const multiSelectField = parsed.fields.find((f: any) => f.system_name === 'custom_components');
120
- expect(multiSelectField.options).toEqual(['UI', 'API', 'Database']);
121
- });
122
-
123
- test('identifies required fields', async () => {
124
- const result = await getCaseFieldsTool.handler({}, mockClient);
125
- const parsed = JSON.parse(result.content[0].text);
126
-
127
- const optionalField = parsed.fields.find((f: any) => f.system_name === 'custom_is_automated');
128
- expect(optionalField.is_required).toBe(false);
129
-
130
- const requiredField = parsed.fields.find((f: any) => f.system_name === 'custom_automation_priority');
131
- expect(requiredField.is_required).toBe(true);
132
- });
133
-
134
- test('filters out inactive fields', async () => {
135
- const result = await getCaseFieldsTool.handler({}, mockClient);
136
- const parsed = JSON.parse(result.content[0].text);
137
-
138
- const inactiveField = parsed.fields.find((f: any) => f.system_name === 'custom_inactive_field');
139
- expect(inactiveField).toBeUndefined();
140
- });
141
-
142
- test('handler returns error on failure', async () => {
143
- getCaseFieldsMock.mockRejectedValue(new Error('API Error'));
144
-
145
- const result = await getCaseFieldsTool.handler({}, mockClient);
146
-
147
- expect(result).toEqual({
148
- content: [{ type: 'text', text: 'Error: API Error' }],
149
- isError: true
150
- });
151
- });
152
-
153
84
  test('includes template_ids for template-specific fields', async () => {
154
85
  const templateSpecificFields: CaseField[] = [
155
86
  {
@@ -161,7 +92,6 @@ describe('get_case_fields tool', () => {
161
92
  template_ids: [1, 2],
162
93
  include_all: false,
163
94
  is_active: true,
164
- display_order: 5,
165
95
  description: 'Test steps',
166
96
  configs: []
167
97
  }
@@ -170,9 +100,8 @@ describe('get_case_fields tool', () => {
170
100
  getCaseFieldsMock.mockResolvedValue(templateSpecificFields);
171
101
 
172
102
  const result = await getCaseFieldsTool.handler({}, mockClient);
173
- const parsed = JSON.parse(result.content[0].text);
174
103
 
175
- const stepsField = parsed.fields.find((f: any) => f.system_name === 'custom_steps');
104
+ const stepsField = result.fields.find((f: any) => f.system_name === 'custom_steps');
176
105
  expect(stepsField.template_ids).toEqual([1, 2]);
177
106
  });
178
107
 
@@ -187,7 +116,6 @@ describe('get_case_fields tool', () => {
187
116
  template_ids: [],
188
117
  include_all: true,
189
118
  is_active: true,
190
- display_order: 6,
191
119
  description: null,
192
120
  configs: []
193
121
  }
@@ -196,9 +124,8 @@ describe('get_case_fields tool', () => {
196
124
  getCaseFieldsMock.mockResolvedValue(unknownTypeFields);
197
125
 
198
126
  const result = await getCaseFieldsTool.handler({}, mockClient);
199
- const parsed = JSON.parse(result.content[0].text);
200
127
 
201
- const unknownField = parsed.fields.find((f: any) => f.system_name === 'custom_unknown_type');
128
+ const unknownField = result.fields.find((f: any) => f.system_name === 'custom_unknown_type');
202
129
  expect(unknownField.type).toBe('Unknown (999)');
203
130
  });
204
131
  });