@uarlouski/testrail-mcp-server 1.2.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -2
- package/dist/client/testrail.js +167 -0
- package/{src/index.ts → dist/index.js} +35 -35
- package/dist/tools/add_attachment_to_run.js +52 -0
- package/dist/tools/add_results.js +24 -0
- package/dist/tools/add_run.js +19 -0
- package/dist/tools/create_case.js +23 -0
- package/{src/tools/get_case.ts → dist/tools/get_case.js} +3 -12
- package/{src/tools/get_case_fields.ts → dist/tools/get_case_fields.js} +8 -37
- package/{src/tools/get_cases.ts → dist/tools/get_cases.js} +10 -24
- package/{src/tools/get_projects.ts → dist/tools/get_projects.js} +3 -10
- package/{src/tools/get_sections.ts → dist/tools/get_sections.js} +4 -11
- package/dist/tools/get_statuses.js +13 -0
- package/dist/tools/get_templates.js +16 -0
- package/dist/tools/get_tests.js +17 -0
- package/{src/tools/update_case.ts → dist/tools/update_case.js} +2 -8
- package/{src/tools/update_cases.ts → dist/tools/update_cases.js} +4 -8
- package/dist/types/custom.js +1 -0
- package/{src/types/testrail.ts → dist/types/testrail.js} +37 -24
- package/{src/utils/mapper.ts → dist/utils/mapper.js} +16 -36
- package/{src/utils/sanitizer.ts → dist/utils/sanitizer.js} +4 -8
- package/package.json +10 -2
- package/.env.example +0 -3
- package/.github/workflows/ci.yml +0 -46
- package/.github/workflows/publish.yml +0 -35
- package/CHANGELOG.md +0 -27
- package/jest.config.js +0 -24
- package/src/client/testrail.ts +0 -161
- package/src/tools/create_case.ts +0 -29
- package/src/tools/get_templates.ts +0 -23
- package/src/types/custom.ts +0 -19
- package/test/client/testrail.test.ts +0 -430
- package/test/tools/create_case.test.ts +0 -101
- package/test/tools/get_case.test.ts +0 -185
- package/test/tools/get_case_fields.test.ts +0 -132
- package/test/tools/get_cases.test.ts +0 -104
- package/test/tools/get_projects.test.ts +0 -38
- package/test/tools/get_sections.test.ts +0 -39
- package/test/tools/get_templates.test.ts +0 -38
- package/test/tools/update_case.test.ts +0 -102
- package/test/tools/update_cases.test.ts +0 -61
- package/test/utils/mapper.test.ts +0 -112
- package/test/utils/sanitizer.test.ts +0 -143
- package/tsconfig.json +0 -24
|
@@ -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 a test case from TestRail by ID');
|
|
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
|
-
});
|
|
@@ -1,38 +0,0 @@
|
|
|
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.templates).toHaveLength(3);
|
|
36
|
-
expect(mockClient.getTemplates).toHaveBeenCalledWith('1');
|
|
37
|
-
});
|
|
38
|
-
});
|