@uarlouski/testrail-mcp-server 1.1.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.
@@ -0,0 +1,205 @@
1
+ import { jest, describe, test, expect, beforeEach } from '@jest/globals';
2
+ import { getCaseFieldsTool } from '../../src/tools/get_case_fields.js';
3
+ import { TestRailClient } from '../../src/client/testrail.js';
4
+ import { CaseField } from '../../src/types/testrail.js';
5
+
6
+ describe('get_case_fields tool', () => {
7
+ let mockClient: jest.Mocked<TestRailClient>;
8
+ let getCaseFieldsMock: jest.Mock<() => Promise<CaseField[]>>;
9
+
10
+ const mockCaseFields: CaseField[] = [
11
+ {
12
+ id: 1,
13
+ name: 'is_automated',
14
+ system_name: 'custom_is_automated',
15
+ label: 'Is Automated',
16
+ type_id: 5,
17
+ template_ids: [],
18
+ include_all: true,
19
+ is_active: true,
20
+ display_order: 1,
21
+ description: 'Whether test is automated',
22
+ configs: [{ context: { is_global: true, project_ids: null }, options: { is_required: false } }]
23
+ },
24
+ {
25
+ id: 2,
26
+ name: 'automation_priority',
27
+ system_name: 'custom_automation_priority',
28
+ label: 'Automation Priority',
29
+ type_id: 6,
30
+ template_ids: [],
31
+ include_all: true,
32
+ is_active: true,
33
+ display_order: 2,
34
+ description: 'Priority for automation',
35
+ configs: [{ context: { is_global: true, project_ids: null }, options: { items: '1, P0\n2, P1\n3, P2', is_required: true } }]
36
+ },
37
+ {
38
+ id: 3,
39
+ name: 'inactive_field',
40
+ system_name: 'custom_inactive_field',
41
+ label: 'Inactive Field',
42
+ type_id: 1,
43
+ template_ids: [],
44
+ include_all: true,
45
+ is_active: false,
46
+ display_order: 3,
47
+ description: null,
48
+ configs: []
49
+ },
50
+ {
51
+ id: 4,
52
+ name: 'components',
53
+ system_name: 'custom_components',
54
+ label: 'Components',
55
+ type_id: 12,
56
+ template_ids: [],
57
+ include_all: true,
58
+ is_active: true,
59
+ display_order: 4,
60
+ description: 'Test components',
61
+ configs: [{ context: { is_global: true, project_ids: null }, options: { items: '1, UI\n2, API\n3, Database' } }]
62
+ }
63
+ ];
64
+
65
+ beforeEach(() => {
66
+ getCaseFieldsMock = jest.fn<() => Promise<CaseField[]>>().mockResolvedValue(mockCaseFields);
67
+
68
+ mockClient = {
69
+ getCaseFields: getCaseFieldsMock
70
+ } as unknown as jest.Mocked<TestRailClient>;
71
+ });
72
+
73
+ test('exports correct tool definition', () => {
74
+ expect(getCaseFieldsTool.name).toBe('get_case_fields');
75
+ expect(getCaseFieldsTool.description).toContain('field schema');
76
+ expect(getCaseFieldsTool.parameters).toBeDefined();
77
+ });
78
+
79
+ test('handler fetches and returns field schema', async () => {
80
+ const result = await getCaseFieldsTool.handler({}, mockClient);
81
+
82
+ 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
87
+ expect(mockClient.getCaseFields).toHaveBeenCalled();
88
+ });
89
+
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
+ test('includes template_ids for template-specific fields', async () => {
154
+ const templateSpecificFields: CaseField[] = [
155
+ {
156
+ id: 5,
157
+ name: 'steps',
158
+ system_name: 'custom_steps',
159
+ label: 'Steps',
160
+ type_id: 3,
161
+ template_ids: [1, 2],
162
+ include_all: false,
163
+ is_active: true,
164
+ display_order: 5,
165
+ description: 'Test steps',
166
+ configs: []
167
+ }
168
+ ];
169
+
170
+ getCaseFieldsMock.mockResolvedValue(templateSpecificFields);
171
+
172
+ const result = await getCaseFieldsTool.handler({}, mockClient);
173
+ const parsed = JSON.parse(result.content[0].text);
174
+
175
+ const stepsField = parsed.fields.find((f: any) => f.system_name === 'custom_steps');
176
+ expect(stepsField.template_ids).toEqual([1, 2]);
177
+ });
178
+
179
+ test('handles unknown field type gracefully', async () => {
180
+ const unknownTypeFields: CaseField[] = [
181
+ {
182
+ id: 6,
183
+ name: 'unknown_type',
184
+ system_name: 'custom_unknown_type',
185
+ label: 'Unknown Type Field',
186
+ type_id: 999,
187
+ template_ids: [],
188
+ include_all: true,
189
+ is_active: true,
190
+ display_order: 6,
191
+ description: null,
192
+ configs: []
193
+ }
194
+ ];
195
+
196
+ getCaseFieldsMock.mockResolvedValue(unknownTypeFields);
197
+
198
+ const result = await getCaseFieldsTool.handler({}, mockClient);
199
+ const parsed = JSON.parse(result.content[0].text);
200
+
201
+ const unknownField = parsed.fields.find((f: any) => f.system_name === 'custom_unknown_type');
202
+ expect(unknownField.type).toBe('Unknown (999)');
203
+ });
204
+ });
205
+
@@ -0,0 +1,193 @@
1
+ import { jest, describe, test, expect, beforeEach } from '@jest/globals';
2
+ import { getCasesTool } from '../../src/tools/get_cases.js';
3
+ import { TestRailClient } from '../../src/client/testrail.js';
4
+ import { Case } from '../../src/types/testrail.js';
5
+
6
+ describe('get_cases tool', () => {
7
+ let mockClient: jest.Mocked<TestRailClient>;
8
+ let getCasesMock: jest.Mock<(projectId: string, sectionId?: string, filter?: Record<string, string>) => Promise<Case[]>>;
9
+
10
+ const mockCases: Case[] = [
11
+ {
12
+ id: 1, title: 'Login test', section_id: 1, template_id: 1, type_id: 1,
13
+ priority_id: 2, milestone_id: null, refs: null, created_by: 1, created_on: 1700000000,
14
+ updated_by: 1, updated_on: 1700000000, estimate: null, estimate_forecast: null,
15
+ suite_id: 1, display_order: 1, is_deleted: 0, labels: [], custom_automation_status: 1
16
+ },
17
+ {
18
+ id: 2, title: 'Logout test', section_id: 1, template_id: 1, type_id: 1,
19
+ priority_id: 3, milestone_id: null, refs: null, created_by: 1, created_on: 1700000000,
20
+ updated_by: 1, updated_on: 1700000000, estimate: null, estimate_forecast: null,
21
+ suite_id: 1, display_order: 2, is_deleted: 0, labels: [], custom_automation_status: 2
22
+ },
23
+ {
24
+ id: 3, title: 'Registration test', section_id: 2, template_id: 1, type_id: 1,
25
+ priority_id: 1, milestone_id: null, refs: null, created_by: 1, created_on: 1700000000,
26
+ updated_by: 1, updated_on: 1700000000, estimate: null, estimate_forecast: null,
27
+ suite_id: 1, display_order: 3, is_deleted: 0, labels: [], custom_automation_status: 0
28
+ },
29
+ ];
30
+
31
+ beforeEach(() => {
32
+ getCasesMock = jest.fn<(projectId: string, sectionId?: string, filter?: Record<string, string>) => Promise<Case[]>>()
33
+ .mockResolvedValue(mockCases);
34
+
35
+ mockClient = {
36
+ getCases: getCasesMock
37
+ } as unknown as jest.Mocked<TestRailClient>;
38
+ });
39
+
40
+ test('exports correct tool definition', () => {
41
+ expect(getCasesTool.name).toBe('get_cases');
42
+ expect(getCasesTool.description).toContain('cases');
43
+ expect(getCasesTool.parameters).toBeDefined();
44
+ expect(getCasesTool.parameters.project_id).toBeDefined();
45
+ expect(getCasesTool.parameters.section_id).toBeDefined();
46
+ expect(getCasesTool.parameters.filter).toBeDefined();
47
+ expect(getCasesTool.parameters.fields).toBeDefined();
48
+ });
49
+
50
+ test('handler fetches and returns cases', async () => {
51
+ const result = await getCasesTool.handler({ project_id: '1' }, mockClient);
52
+
53
+ expect(result).toBeDefined();
54
+ expect(result.content[0].type).toBe('text');
55
+
56
+ const parsed = JSON.parse(result.content[0].text);
57
+ expect(parsed.cases).toHaveLength(3);
58
+ expect(mockClient.getCases).toHaveBeenCalledWith('1', undefined, undefined);
59
+ });
60
+
61
+ test('passes section_id when provided', async () => {
62
+ await getCasesTool.handler({ project_id: '1', section_id: '2' }, mockClient);
63
+
64
+ expect(mockClient.getCases).toHaveBeenCalledWith('1', '2', undefined);
65
+ });
66
+
67
+ test('passes filter when provided', async () => {
68
+ const filter = { priority_id: '1,2', type_id: '3' };
69
+ await getCasesTool.handler({ project_id: '1', filter }, mockClient);
70
+
71
+ expect(mockClient.getCases).toHaveBeenCalledWith('1', undefined, filter);
72
+ });
73
+
74
+ test('returns correct case structure', async () => {
75
+ const result = await getCasesTool.handler({ project_id: '1' }, mockClient);
76
+ const parsed = JSON.parse(result.content[0].text);
77
+
78
+ expect(parsed.cases[0]).toEqual({
79
+ id: 1,
80
+ title: 'Login test',
81
+ suite_id: 1,
82
+ });
83
+
84
+ expect(parsed.cases[2]).toEqual({
85
+ id: 3,
86
+ title: 'Registration test',
87
+ suite_id: 1,
88
+ });
89
+ });
90
+
91
+ test('includes additional fields when specified', async () => {
92
+ const result = await getCasesTool.handler(
93
+ { project_id: '1', fields: ['priority_id', 'type_id', 'custom_automation_status'] },
94
+ mockClient
95
+ );
96
+ const parsed = JSON.parse(result.content[0].text);
97
+
98
+ expect(parsed.cases[0]).toEqual({
99
+ id: 1,
100
+ title: 'Login test',
101
+ suite_id: 1,
102
+ priority_id: 2,
103
+ type_id: 1,
104
+ custom_automation_status: 1,
105
+ });
106
+
107
+ expect(parsed.cases[1]).toEqual({
108
+ id: 2,
109
+ title: 'Logout test',
110
+ suite_id: 1,
111
+ priority_id: 3,
112
+ type_id: 1,
113
+ custom_automation_status: 2,
114
+ });
115
+ });
116
+
117
+ test('ignores non-existent fields', async () => {
118
+ const result = await getCasesTool.handler(
119
+ { project_id: '1', fields: ['priority_id', 'nonexistent_field'] },
120
+ mockClient
121
+ );
122
+ const parsed = JSON.parse(result.content[0].text);
123
+
124
+ expect(parsed.cases[0]).toEqual({
125
+ id: 1,
126
+ title: 'Login test',
127
+ suite_id: 1,
128
+ priority_id: 2,
129
+ });
130
+ });
131
+
132
+ test('handler returns error on failure', async () => {
133
+ getCasesMock.mockRejectedValue(new Error('API Error'));
134
+
135
+ const result = await getCasesTool.handler({ project_id: '1' }, mockClient);
136
+
137
+ expect(result).toEqual({
138
+ content: [{ type: 'text', text: 'Error: API Error' }],
139
+ isError: true
140
+ });
141
+ });
142
+
143
+ test('filters cases using where clause with single condition', async () => {
144
+ const result = await getCasesTool.handler(
145
+ { project_id: '1', where: { custom_automation_status: 1 } },
146
+ mockClient
147
+ );
148
+ const parsed = JSON.parse(result.content[0].text);
149
+
150
+ expect(parsed.cases).toHaveLength(1);
151
+ expect(parsed.cases[0].id).toBe(1);
152
+ expect(parsed.cases[0].title).toBe('Login test');
153
+ });
154
+
155
+ test('filters cases using where clause with multiple conditions', async () => {
156
+ const result = await getCasesTool.handler(
157
+ { project_id: '1', where: { custom_automation_status: 2, priority_id: 3 } },
158
+ mockClient
159
+ );
160
+ const parsed = JSON.parse(result.content[0].text);
161
+
162
+ expect(parsed.cases).toHaveLength(1);
163
+ expect(parsed.cases[0].id).toBe(2);
164
+ expect(parsed.cases[0].title).toBe('Logout test');
165
+ });
166
+
167
+ test('returns empty array when where clause matches no cases', async () => {
168
+ const result = await getCasesTool.handler(
169
+ { project_id: '1', where: { custom_automation_status: 999 } },
170
+ mockClient
171
+ );
172
+ const parsed = JSON.parse(result.content[0].text);
173
+
174
+ expect(parsed.cases).toHaveLength(0);
175
+ });
176
+
177
+ test('combines where clause with fields parameter', async () => {
178
+ const result = await getCasesTool.handler(
179
+ { project_id: '1', where: { priority_id: 2 }, fields: ['priority_id', 'custom_automation_status'] },
180
+ mockClient
181
+ );
182
+ const parsed = JSON.parse(result.content[0].text);
183
+
184
+ expect(parsed.cases).toHaveLength(1);
185
+ expect(parsed.cases[0]).toEqual({
186
+ id: 1,
187
+ title: 'Login test',
188
+ suite_id: 1,
189
+ priority_id: 2,
190
+ custom_automation_status: 1
191
+ });
192
+ });
193
+ });
@@ -0,0 +1,75 @@
1
+ import { jest, describe, test, expect, beforeEach } from '@jest/globals';
2
+ import { getProjectsTool } from '../../src/tools/get_projects.js';
3
+ import { TestRailClient } from '../../src/client/testrail.js';
4
+ import { Project } from '../../src/types/testrail.js';
5
+
6
+ describe('get_projects tool', () => {
7
+ let mockClient: jest.Mocked<TestRailClient>;
8
+ let getProjectsMock: jest.Mock<() => Promise<Project[]>>;
9
+
10
+ const mockProjects: Project[] = [
11
+ { id: 1, name: 'Project Alpha', announcement: null, is_completed: false, suite_mode: 1 },
12
+ { id: 2, name: 'Project Beta', announcement: 'Active project', is_completed: false, suite_mode: 3 },
13
+ { id: 3, name: 'Old Project', announcement: null, is_completed: true, suite_mode: 1 },
14
+ ];
15
+
16
+ beforeEach(() => {
17
+ getProjectsMock = jest.fn<() => Promise<Project[]>>()
18
+ .mockResolvedValue(mockProjects);
19
+
20
+ mockClient = {
21
+ getProjects: getProjectsMock
22
+ } as unknown as jest.Mocked<TestRailClient>;
23
+ });
24
+
25
+ test('exports correct tool definition', () => {
26
+ expect(getProjectsTool.name).toBe('get_projects');
27
+ expect(getProjectsTool.description).toContain('project');
28
+ expect(getProjectsTool.parameters).toBeDefined();
29
+ });
30
+
31
+ test('handler fetches and returns projects', async () => {
32
+ const result = await getProjectsTool.handler({}, mockClient);
33
+
34
+ expect(result).toBeDefined();
35
+ expect(result.content[0].type).toBe('text');
36
+
37
+ const parsed = JSON.parse(result.content[0].text);
38
+ expect(parsed.projects).toHaveLength(2); // Only active projects
39
+ expect(mockClient.getProjects).toHaveBeenCalled();
40
+ });
41
+
42
+ test('filters out completed projects', async () => {
43
+ const result = await getProjectsTool.handler({}, mockClient);
44
+ const parsed = JSON.parse(result.content[0].text);
45
+
46
+ const completedProject = parsed.projects.find((p: any) => p.name === 'Old Project');
47
+ expect(completedProject).toBeUndefined();
48
+ });
49
+
50
+ test('returns correct project structure', async () => {
51
+ const result = await getProjectsTool.handler({}, mockClient);
52
+ const parsed = JSON.parse(result.content[0].text);
53
+
54
+ expect(parsed.projects[0]).toEqual({
55
+ id: 1,
56
+ name: 'Project Alpha',
57
+ });
58
+
59
+ expect(parsed.projects[1]).toEqual({
60
+ id: 2,
61
+ name: 'Project Beta',
62
+ });
63
+ });
64
+
65
+ test('handler returns error on failure', async () => {
66
+ getProjectsMock.mockRejectedValue(new Error('API Error'));
67
+
68
+ const result = await getProjectsTool.handler({}, mockClient);
69
+
70
+ expect(result).toEqual({
71
+ content: [{ type: 'text', text: 'Error: API Error' }],
72
+ isError: true
73
+ });
74
+ });
75
+ });
@@ -0,0 +1,68 @@
1
+ import { jest, describe, test, expect, beforeEach } from '@jest/globals';
2
+ import { getSectionsTool } from '../../src/tools/get_sections.js';
3
+ import { TestRailClient } from '../../src/client/testrail.js';
4
+ import { Section } from '../../src/types/testrail.js';
5
+
6
+ describe('get_sections tool', () => {
7
+ let mockClient: jest.Mocked<TestRailClient>;
8
+ let getSectionsMock: jest.Mock<(projectId: string) => Promise<Section[]>>;
9
+
10
+ const mockSections: Section[] = [
11
+ { id: 1, name: 'Login', description: null, parent_id: null, depth: 0, display_order: 1, suite_id: 1 },
12
+ { id: 2, name: 'Authentication', description: 'Auth tests', parent_id: 1, depth: 1, display_order: 2, suite_id: 1 },
13
+ { id: 3, name: 'Registration', description: null, parent_id: null, depth: 0, display_order: 3, suite_id: 1 },
14
+ ];
15
+
16
+ beforeEach(() => {
17
+ getSectionsMock = jest.fn<(projectId: string) => Promise<Section[]>>()
18
+ .mockResolvedValue(mockSections);
19
+
20
+ mockClient = {
21
+ getSections: getSectionsMock
22
+ } as unknown as jest.Mocked<TestRailClient>;
23
+ });
24
+
25
+ test('exports correct tool definition', () => {
26
+ expect(getSectionsTool.name).toBe('get_sections');
27
+ expect(getSectionsTool.description).toContain('section');
28
+ expect(getSectionsTool.parameters).toBeDefined();
29
+ expect(getSectionsTool.parameters.project_id).toBeDefined();
30
+ });
31
+
32
+ test('handler fetches and returns sections', async () => {
33
+ const result = await getSectionsTool.handler({ project_id: '1' }, mockClient);
34
+
35
+ expect(result).toBeDefined();
36
+ expect(result.content[0].type).toBe('text');
37
+
38
+ const parsed = JSON.parse(result.content[0].text);
39
+ expect(parsed.sections).toHaveLength(3);
40
+ expect(mockClient.getSections).toHaveBeenCalledWith('1');
41
+ });
42
+
43
+ test('returns correct section structure', async () => {
44
+ const result = await getSectionsTool.handler({ project_id: '1' }, mockClient);
45
+ const parsed = JSON.parse(result.content[0].text);
46
+
47
+ expect(parsed.sections[0]).toEqual({
48
+ id: 1,
49
+ name: 'Login',
50
+ });
51
+
52
+ expect(parsed.sections[1]).toEqual({
53
+ id: 2,
54
+ name: 'Authentication',
55
+ });
56
+ });
57
+
58
+ test('handler returns error on failure', async () => {
59
+ getSectionsMock.mockRejectedValue(new Error('API Error'));
60
+
61
+ const result = await getSectionsTool.handler({ project_id: '1' }, mockClient);
62
+
63
+ expect(result).toEqual({
64
+ content: [{ type: 'text', text: 'Error: API Error' }],
65
+ isError: true
66
+ });
67
+ });
68
+ });
@@ -0,0 +1,67 @@
1
+ import { jest, describe, test, expect, beforeEach } from '@jest/globals';
2
+ import { getTemplatesTool } from '../../src/tools/get_templates.js';
3
+ import { TestRailClient } from '../../src/client/testrail.js';
4
+ import { Template } from '../../src/types/testrail.js';
5
+
6
+ describe('get_templates tool', () => {
7
+ let mockClient: jest.Mocked<TestRailClient>;
8
+ let getTemplatesMock: jest.Mock<(projectId: string) => Promise<Template[]>>;
9
+
10
+ const mockTemplates: Template[] = [
11
+ { id: 1, name: 'Test Case (Text)', is_default: false },
12
+ { id: 2, name: 'Test Case (Steps)', is_default: true },
13
+ { id: 3, name: 'Exploratory Session', is_default: false },
14
+ ];
15
+
16
+ beforeEach(() => {
17
+ getTemplatesMock = jest.fn<(projectId: string) => Promise<Template[]>>().mockResolvedValue(mockTemplates);
18
+
19
+ mockClient = {
20
+ getTemplates: getTemplatesMock
21
+ } as unknown as jest.Mocked<TestRailClient>;
22
+ });
23
+
24
+ test('exports correct tool definition', () => {
25
+ expect(getTemplatesTool.name).toBe('get_templates');
26
+ expect(getTemplatesTool.description).toContain('template');
27
+ expect(getTemplatesTool.parameters).toBeDefined();
28
+ expect(getTemplatesTool.parameters.project_id).toBeDefined();
29
+ });
30
+
31
+ test('handler fetches and returns templates', async () => {
32
+ const result = await getTemplatesTool.handler({ project_id: '1' }, mockClient);
33
+
34
+ expect(result).toBeDefined();
35
+ expect(result.content[0].type).toBe('text');
36
+
37
+ const parsed = JSON.parse(result.content[0].text);
38
+ expect(parsed.templates).toHaveLength(3);
39
+ expect(mockClient.getTemplates).toHaveBeenCalledWith('1');
40
+ });
41
+
42
+ test('returns correct template structure', async () => {
43
+ const result = await getTemplatesTool.handler({ project_id: '1' }, mockClient);
44
+ const parsed = JSON.parse(result.content[0].text);
45
+
46
+ expect(parsed.templates[0]).toEqual({
47
+ id: 1,
48
+ name: 'Test Case (Text)',
49
+ });
50
+
51
+ expect(parsed.templates[1]).toEqual({
52
+ id: 2,
53
+ name: 'Test Case (Steps)',
54
+ });
55
+ });
56
+
57
+ test('handler returns error on failure', async () => {
58
+ getTemplatesMock.mockRejectedValue(new Error('API Error'));
59
+
60
+ const result = await getTemplatesTool.handler({ project_id: '1' }, mockClient);
61
+
62
+ expect(result).toEqual({
63
+ content: [{ type: 'text', text: 'Error: API Error' }],
64
+ isError: true
65
+ });
66
+ });
67
+ });