@uarlouski/testrail-mcp-server 1.3.0 → 1.4.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 (46) hide show
  1. package/README.md +178 -48
  2. package/dist/client/testrail.js +184 -0
  3. package/{src/index.ts → dist/index.js} +25 -35
  4. package/{src/tools/add_attachment_to_run.ts → dist/tools/add_attachment_to_run.js} +10 -20
  5. package/{src/tools/add_results.ts → dist/tools/add_results.js} +3 -7
  6. package/{src/tools/add_run.ts → dist/tools/add_run.js} +5 -10
  7. package/{src/tools/create_case.ts → dist/tools/create_case.js} +2 -8
  8. package/{src/tools/get_case.ts → dist/tools/get_case.js} +7 -14
  9. package/{src/tools/get_case_fields.ts → dist/tools/get_case_fields.js} +8 -37
  10. package/{src/tools/get_cases.ts → dist/tools/get_cases.js} +18 -22
  11. package/{src/tools/get_projects.ts → dist/tools/get_projects.js} +1 -6
  12. package/{src/tools/get_sections.ts → dist/tools/get_sections.js} +2 -7
  13. package/{src/tools/get_statuses.ts → dist/tools/get_statuses.js} +2 -7
  14. package/{src/tools/get_templates.ts → dist/tools/get_templates.js} +2 -7
  15. package/{src/tools/get_tests.ts → dist/tools/get_tests.js} +5 -10
  16. package/{src/tools/update_case.ts → dist/tools/update_case.js} +3 -8
  17. package/{src/tools/update_cases.ts → dist/tools/update_cases.js} +2 -7
  18. package/dist/types/custom.js +1 -0
  19. package/{src/types/testrail.ts → dist/types/testrail.js} +6 -43
  20. package/{src/utils/mapper.ts → dist/utils/mapper.js} +16 -36
  21. package/{src/utils/sanitizer.ts → dist/utils/sanitizer.js} +4 -8
  22. package/package.json +8 -2
  23. package/.env.example +0 -3
  24. package/.github/workflows/ci.yml +0 -46
  25. package/.github/workflows/publish.yml +0 -35
  26. package/CHANGELOG.md +0 -38
  27. package/jest.config.js +0 -24
  28. package/src/client/testrail.ts +0 -227
  29. package/src/types/custom.ts +0 -19
  30. package/test/client/testrail.test.ts +0 -545
  31. package/test/tools/add_results.test.ts +0 -94
  32. package/test/tools/add_run.test.ts +0 -83
  33. package/test/tools/create_case.test.ts +0 -101
  34. package/test/tools/get_case.test.ts +0 -185
  35. package/test/tools/get_case_fields.test.ts +0 -132
  36. package/test/tools/get_cases.test.ts +0 -104
  37. package/test/tools/get_projects.test.ts +0 -38
  38. package/test/tools/get_sections.test.ts +0 -39
  39. package/test/tools/get_statuses.test.ts +0 -56
  40. package/test/tools/get_templates.test.ts +0 -38
  41. package/test/tools/get_tests.test.ts +0 -47
  42. package/test/tools/update_case.test.ts +0 -102
  43. package/test/tools/update_cases.test.ts +0 -89
  44. package/test/utils/mapper.test.ts +0 -112
  45. package/test/utils/sanitizer.test.ts +0 -143
  46. package/tsconfig.json +0 -24
@@ -1,83 +0,0 @@
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
- });
@@ -1,101 +0,0 @@
1
- import { jest, describe, test, expect, beforeEach } from '@jest/globals';
2
- import { createCaseTool } from '../../src/tools/create_case.js';
3
- import { TestRailClient } from '../../src/client/testrail.js';
4
- import { Case } from '../../src/types/testrail.js';
5
-
6
- describe('create_case tool', () => {
7
- let mockClient: jest.Mocked<TestRailClient>;
8
- let createCaseMock: jest.Mock<(sectionId: string, fields: Record<string, any>) => Promise<Case>>;
9
-
10
- const mockCreatedCase: Case = {
11
- id: 456,
12
- title: 'New Test Case',
13
- section_id: 1,
14
- template_id: 1,
15
- type_id: 1,
16
- priority_id: 2,
17
- milestone_id: null,
18
- refs: null,
19
- created_by: 1,
20
- created_on: 1700000000,
21
- updated_by: 1,
22
- updated_on: 1700000000,
23
- estimate: null,
24
- estimate_forecast: null,
25
- suite_id: 1,
26
- display_order: 1,
27
- is_deleted: 0,
28
- labels: [],
29
- };
30
-
31
- beforeEach(() => {
32
- createCaseMock = jest.fn<(sectionId: string, fields: Record<string, any>) => Promise<Case>>()
33
- .mockResolvedValue(mockCreatedCase);
34
-
35
- mockClient = {
36
- createCase: createCaseMock
37
- } as unknown as jest.Mocked<TestRailClient>;
38
- });
39
-
40
- test('exports correct tool definition', () => {
41
- expect(createCaseTool.name).toBe('create_case');
42
- expect(createCaseTool.description).toContain('Create');
43
- expect(createCaseTool.parameters).toBeDefined();
44
- expect(createCaseTool.parameters.section_id).toBeDefined();
45
- expect(createCaseTool.parameters.title).toBeDefined();
46
- expect(createCaseTool.parameters.fields).toBeDefined();
47
- });
48
-
49
- test('handler creates case and returns success response', async () => {
50
- const result = await createCaseTool.handler(
51
- { section_id: '1', title: 'New Test Case' },
52
- mockClient
53
- );
54
-
55
- expect(result).toBeDefined();
56
- expect(result.success).toBe(true);
57
- expect(result.case_id).toBe(456);
58
- expect(result.message).toContain('C456');
59
- expect(mockClient.createCase).toHaveBeenCalledWith('1', { title: 'New Test Case' });
60
- });
61
-
62
- test('passes title and optional fields correctly', async () => {
63
- const fields = {
64
- priority_id: 2,
65
- template_id: 1,
66
- custom_automation_priority: 1,
67
- };
68
-
69
- await createCaseTool.handler(
70
- { section_id: '5', title: 'My Test', fields },
71
- mockClient
72
- );
73
-
74
- expect(mockClient.createCase).toHaveBeenCalledWith('5', {
75
- title: 'My Test',
76
- priority_id: 2,
77
- template_id: 1,
78
- custom_automation_priority: 1,
79
- });
80
- });
81
-
82
- test('works without optional fields', async () => {
83
- await createCaseTool.handler(
84
- { section_id: '10', title: 'Simple Test' },
85
- mockClient
86
- );
87
-
88
- expect(mockClient.createCase).toHaveBeenCalledWith('10', { title: 'Simple Test' });
89
- });
90
-
91
- test('handler throws error on failure', async () => {
92
- createCaseMock.mockRejectedValue(new Error('API Error'));
93
-
94
- await expect(
95
- createCaseTool.handler(
96
- { section_id: '1', title: 'Test' },
97
- mockClient
98
- )
99
- ).rejects.toThrow('API Error');
100
- });
101
- });
@@ -1,185 +0,0 @@
1
- import { jest, describe, test, expect, beforeEach } from '@jest/globals';
2
- import { getCaseTool } from '../../src/tools/get_case.js';
3
- import { TestRailClient } from '../../src/client/testrail.js';
4
- import { Case, Section, CaseType, Priority, CaseField } from '../../src/types/testrail.js';
5
-
6
- describe('get_case tool', () => {
7
- let mockClient: jest.Mocked<TestRailClient>;
8
- let getCaseMock: jest.Mock<(id: string) => Promise<Case>>;
9
- let getSectionMock: jest.Mock<(id: string) => Promise<Section>>;
10
- let getCaseTypesMock: jest.Mock<() => Promise<CaseType[]>>;
11
- let getPrioritiesMock: jest.Mock<() => Promise<Priority[]>>;
12
- let getCaseFieldsMock: jest.Mock<() => Promise<CaseField[]>>;
13
-
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, 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
- ];
23
-
24
- beforeEach(() => {
25
- getCaseMock = jest.fn<(id: string) => Promise<Case>>();
26
- getSectionMock = jest.fn<(id: string) => Promise<Section>>();
27
- getCaseTypesMock = jest.fn<() => Promise<CaseType[]>>();
28
- getPrioritiesMock = jest.fn<() => Promise<Priority[]>>();
29
- getCaseFieldsMock = jest.fn<() => Promise<CaseField[]>>().mockResolvedValue(mockCaseFields);
30
-
31
- mockClient = {
32
- getCase: getCaseMock,
33
- getSection: getSectionMock,
34
- getCaseTypes: getCaseTypesMock,
35
- getPriorities: getPrioritiesMock,
36
- getCaseFields: getCaseFieldsMock
37
- } as unknown as jest.Mocked<TestRailClient>;
38
- });
39
-
40
- test('exports correct tool definition', () => {
41
- expect(getCaseTool.name).toBe('get_case');
42
- expect(getCaseTool.description).toBe('Get detailed information about a test case including its custom fields');
43
- expect(getCaseTool.parameters).toBeDefined();
44
- });
45
-
46
- test('handler fetches data and returns transformed response', async () => {
47
- const mockCase: Case = {
48
- id: 123, title: 'My Case', template_id: 1,
49
- section_id: 456,
50
- type_id: 2,
51
- priority_id: 3,
52
- milestone_id: null,
53
- refs: 'JIRA-123',
54
- created_on: 1700000000,
55
- updated_on: 1700000000,
56
- estimate: null,
57
- suite_id: 1,
58
- custom_refs: null,
59
- custom_case_automation_priority: null,
60
- custom_is_automated: null,
61
- custom_reviewer: null,
62
- custom_review_status: null,
63
- custom_testrail_bdd_scenario: null,
64
- custom_override: null,
65
- custom_preconds: null,
66
- custom_steps: null,
67
- custom_expected: null,
68
- custom_steps_separated: null,
69
- custom_mission: null,
70
- custom_goals: null,
71
- custom_postcondition: null,
72
- custom_comments: null,
73
- labels: ['tag1']
74
- };
75
-
76
- const mockSection: Section = {
77
- id: 456, name: 'My Section', description: null, parent_id: null, suite_id: 1
78
- };
79
-
80
- const mockCaseTypes: CaseType[] = [
81
- { id: 1, name: 'Automated', is_default: false },
82
- { id: 2, name: 'Functional', is_default: true }
83
- ];
84
-
85
- const mockPriorities: Priority[] = [
86
- { id: 3, name: 'High', is_default: false }
87
- ];
88
-
89
- getCaseMock.mockResolvedValue(mockCase);
90
- getSectionMock.mockResolvedValue(mockSection);
91
- getCaseTypesMock.mockResolvedValue(mockCaseTypes);
92
- getPrioritiesMock.mockResolvedValue(mockPriorities);
93
-
94
- const result = await getCaseTool.handler({ case_id: 'C123' }, mockClient);
95
-
96
- expect(result).toBeDefined();
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
- });
107
-
108
- expect(mockClient.getCase).toHaveBeenCalledWith('123');
109
- expect(mockClient.getSection).toHaveBeenCalledWith('456');
110
- expect(mockClient.getCaseTypes).toHaveBeenCalled();
111
- expect(mockClient.getPriorities).toHaveBeenCalled();
112
- });
113
-
114
-
115
-
116
- test('handler throws error on failure', async () => {
117
- getCaseMock.mockRejectedValue(new Error('API Error'));
118
-
119
- await expect(
120
- getCaseTool.handler({ case_id: '123' }, mockClient)
121
- ).rejects.toThrow('API Error');
122
- });
123
-
124
- test('handler returns "Unknown" when caseType is not found', async () => {
125
- const mockCase: Case = {
126
- id: 123, title: 'My Case', template_id: 1,
127
- section_id: 456, type_id: 999, priority_id: 3,
128
- milestone_id: null, refs: null, created_on: 1700000000,
129
- updated_on: 1700000000, estimate: null,
130
- suite_id: 1, labels: []
131
- };
132
-
133
- getCaseMock.mockResolvedValue(mockCase);
134
- getSectionMock.mockResolvedValue({ id: 456, name: 'Section', description: null, parent_id: null, suite_id: 1 });
135
- getCaseTypesMock.mockResolvedValue([{ id: 1, name: 'Automated', is_default: false }]);
136
- getPrioritiesMock.mockResolvedValue([{ id: 3, name: 'High', is_default: false }]);
137
-
138
- const result = await getCaseTool.handler({ case_id: '123' }, mockClient);
139
- // result is now a plain object
140
-
141
- expect(result.type).toBe('Unknown');
142
- expect(result.priority).toBe('High');
143
- });
144
-
145
- test('handler returns "Unknown" when priority is not found', async () => {
146
- const mockCase: Case = {
147
- id: 123, title: 'My Case', template_id: 1,
148
- section_id: 456, type_id: 1, priority_id: 999,
149
- milestone_id: null, refs: null, created_on: 1700000000,
150
- updated_on: 1700000000, estimate: null,
151
- suite_id: 1, labels: []
152
- };
153
-
154
- getCaseMock.mockResolvedValue(mockCase);
155
- getSectionMock.mockResolvedValue({ id: 456, name: 'Section', description: null, parent_id: null, suite_id: 1 });
156
- getCaseTypesMock.mockResolvedValue([{ id: 1, name: 'Automated', is_default: false }]);
157
- getPrioritiesMock.mockResolvedValue([{ id: 3, name: 'High', is_default: false }]);
158
-
159
- const result = await getCaseTool.handler({ case_id: '123' }, mockClient);
160
- // result is now a plain object
161
-
162
- expect(result.type).toBe('Automated');
163
- expect(result.priority).toBe('Unknown');
164
- });
165
-
166
- test('handler uses empty array when labels is undefined', async () => {
167
- const mockCase: Case = {
168
- id: 123, title: 'My Case', template_id: 1,
169
- section_id: 456, type_id: 1, priority_id: 3,
170
- milestone_id: null, refs: null, created_on: 1700000000,
171
- updated_on: 1700000000, estimate: null,
172
- suite_id: 1, labels: undefined as any
173
- };
174
-
175
- getCaseMock.mockResolvedValue(mockCase);
176
- getSectionMock.mockResolvedValue({ id: 456, name: 'Section', description: null, parent_id: null, suite_id: 1 });
177
- getCaseTypesMock.mockResolvedValue([{ id: 1, name: 'Automated', is_default: false }]);
178
- getPrioritiesMock.mockResolvedValue([{ id: 3, name: 'High', is_default: false }]);
179
-
180
- const result = await getCaseTool.handler({ case_id: '123' }, mockClient);
181
- // result is now a plain object
182
-
183
- expect(result.labels).toEqual([]);
184
- });
185
- });
@@ -1,132 +0,0 @@
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
- description: 'Whether test is automated',
21
- configs: [{ options: { is_required: false } }]
22
- },
23
- {
24
- id: 2,
25
- name: 'automation_priority',
26
- system_name: 'custom_automation_priority',
27
- label: 'Automation Priority',
28
- type_id: 6,
29
- template_ids: [],
30
- include_all: true,
31
- is_active: true,
32
- description: 'Priority for automation',
33
- configs: [{ options: { items: '1, P0\n2, P1\n3, P2', is_required: true } }]
34
- },
35
- {
36
- id: 3,
37
- name: 'inactive_field',
38
- system_name: 'custom_inactive_field',
39
- label: 'Inactive Field',
40
- type_id: 1,
41
- template_ids: [],
42
- include_all: true,
43
- is_active: false,
44
- description: null,
45
- configs: []
46
- },
47
- {
48
- id: 4,
49
- name: 'components',
50
- system_name: 'custom_components',
51
- label: 'Components',
52
- type_id: 12,
53
- template_ids: [],
54
- include_all: true,
55
- is_active: true,
56
- description: 'Test components',
57
- configs: [{ options: { items: '1, UI\n2, API\n3, Database' } }]
58
- }
59
- ];
60
-
61
- beforeEach(() => {
62
- getCaseFieldsMock = jest.fn<() => Promise<CaseField[]>>().mockResolvedValue(mockCaseFields);
63
-
64
- mockClient = {
65
- getCaseFields: getCaseFieldsMock
66
- } as unknown as jest.Mocked<TestRailClient>;
67
- });
68
-
69
- test('exports correct tool definition', () => {
70
- expect(getCaseFieldsTool.name).toBe('get_case_fields');
71
- expect(getCaseFieldsTool.description).toContain('field schema');
72
- expect(getCaseFieldsTool.parameters).toBeDefined();
73
- });
74
-
75
- test('handler fetches and returns field schema', async () => {
76
- const result = await getCaseFieldsTool.handler({}, mockClient);
77
-
78
- expect(result).toBeDefined();
79
- expect(result.fields).toBeDefined();
80
- expect(result.fields.length).toBeGreaterThan(0);
81
- expect(mockClient.getCaseFields).toHaveBeenCalled();
82
- });
83
-
84
- test('includes template_ids for template-specific fields', async () => {
85
- const templateSpecificFields: CaseField[] = [
86
- {
87
- id: 5,
88
- name: 'steps',
89
- system_name: 'custom_steps',
90
- label: 'Steps',
91
- type_id: 3,
92
- template_ids: [1, 2],
93
- include_all: false,
94
- is_active: true,
95
- description: 'Test steps',
96
- configs: []
97
- }
98
- ];
99
-
100
- getCaseFieldsMock.mockResolvedValue(templateSpecificFields);
101
-
102
- const result = await getCaseFieldsTool.handler({}, mockClient);
103
-
104
- const stepsField = result.fields.find((f: any) => f.system_name === 'custom_steps');
105
- expect(stepsField.template_ids).toEqual([1, 2]);
106
- });
107
-
108
- test('handles unknown field type gracefully', async () => {
109
- const unknownTypeFields: CaseField[] = [
110
- {
111
- id: 6,
112
- name: 'unknown_type',
113
- system_name: 'custom_unknown_type',
114
- label: 'Unknown Type Field',
115
- type_id: 999,
116
- template_ids: [],
117
- include_all: true,
118
- is_active: true,
119
- description: null,
120
- configs: []
121
- }
122
- ];
123
-
124
- getCaseFieldsMock.mockResolvedValue(unknownTypeFields);
125
-
126
- const result = await getCaseFieldsTool.handler({}, mockClient);
127
-
128
- const unknownField = result.fields.find((f: any) => f.system_name === 'custom_unknown_type');
129
- expect(unknownField.type).toBe('Unknown (999)');
130
- });
131
- });
132
-
@@ -1,104 +0,0 @@
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_on: 1700000000,
14
- updated_by: 1, updated_on: 1700000000, estimate: null,
15
- suite_id: 1, 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_on: 1700000000,
20
- updated_by: 1, updated_on: 1700000000, estimate: null,
21
- suite_id: 1, 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_on: 1700000000,
26
- updated_by: 1, updated_on: 1700000000, estimate: null,
27
- suite_id: 1, 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.cases).toHaveLength(3);
55
- expect(mockClient.getCases).toHaveBeenCalledWith('1', undefined, undefined);
56
- });
57
-
58
- test('filters cases using where clause with single condition', async () => {
59
- const result = await getCasesTool.handler(
60
- { project_id: '1', where: { custom_automation_status: 1 } },
61
- mockClient
62
- );
63
-
64
- expect(result.cases).toHaveLength(1);
65
- expect(result.cases[0].id).toBe(1);
66
- expect(result.cases[0].title).toBe('Login test');
67
- });
68
-
69
- test('filters cases using where clause with multiple conditions', async () => {
70
- const result = await getCasesTool.handler(
71
- { project_id: '1', where: { custom_automation_status: 2, priority_id: 3 } },
72
- mockClient
73
- );
74
-
75
- expect(result.cases).toHaveLength(1);
76
- expect(result.cases[0].id).toBe(2);
77
- expect(result.cases[0].title).toBe('Logout test');
78
- });
79
-
80
- test('returns empty array when where clause matches no cases', async () => {
81
- const result = await getCasesTool.handler(
82
- { project_id: '1', where: { custom_automation_status: 999 } },
83
- mockClient
84
- );
85
-
86
- expect(result.cases).toHaveLength(0);
87
- });
88
-
89
- test('combines where clause with fields parameter', async () => {
90
- const result = await getCasesTool.handler(
91
- { project_id: '1', where: { priority_id: 2 }, fields: ['priority_id', 'custom_automation_status'] },
92
- mockClient
93
- );
94
-
95
- expect(result.cases).toHaveLength(1);
96
- expect(result.cases[0]).toEqual({
97
- id: 1,
98
- title: 'Login test',
99
- suite_id: 1,
100
- priority_id: 2,
101
- custom_automation_status: 1
102
- });
103
- });
104
- });
@@ -1,38 +0,0 @@
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', is_completed: false, suite_mode: 1 },
12
- { id: 2, name: 'Project Beta', is_completed: false, suite_mode: 3 },
13
- { id: 3, name: 'Old Project', 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.projects).toHaveLength(2);
36
- expect(mockClient.getProjects).toHaveBeenCalled();
37
- });
38
- });
@@ -1,39 +0,0 @@
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, suite_id: 1 },
12
- { id: 2, name: 'Authentication', description: 'Auth tests', parent_id: 1, suite_id: 1 },
13
- { id: 3, name: 'Registration', description: null, parent_id: null, 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 throws error on failure', async () => {
33
- getSectionsMock.mockRejectedValue(new Error('API Error'));
34
-
35
- await expect(
36
- getSectionsTool.handler({ project_id: '1' }, mockClient)
37
- ).rejects.toThrow('API Error');
38
- });
39
- });