@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.
- package/CHANGELOG.md +18 -0
- package/README.md +62 -16
- package/package.json +3 -2
- package/src/client/testrail.ts +111 -56
- package/src/index.ts +32 -2
- package/src/tools/add_attachment_to_run.ts +62 -0
- package/src/tools/add_results.ts +28 -0
- package/src/tools/add_run.ts +24 -0
- package/src/tools/create_case.ts +10 -17
- package/src/tools/get_case.ts +5 -12
- package/src/tools/get_case_fields.ts +4 -11
- package/src/tools/get_cases.ts +9 -16
- package/src/tools/get_projects.ts +7 -27
- package/src/tools/get_sections.ts +7 -27
- package/src/tools/get_statuses.ts +18 -0
- package/src/tools/get_templates.ts +7 -27
- package/src/tools/get_tests.ts +22 -0
- package/src/tools/update_case.ts +7 -14
- package/src/tools/update_cases.ts +10 -16
- package/src/types/custom.ts +1 -16
- package/src/types/testrail.ts +140 -87
- package/src/utils/sanitizer.ts +29 -0
- package/test/client/testrail.test.ts +329 -141
- package/test/tools/add_results.test.ts +94 -0
- package/test/tools/add_run.test.ts +83 -0
- package/test/tools/create_case.test.ts +10 -16
- package/test/tools/get_case.test.ts +47 -62
- package/test/tools/get_case_fields.test.ts +7 -80
- package/test/tools/get_cases.test.ts +19 -108
- package/test/tools/get_projects.test.ts +4 -41
- package/test/tools/get_sections.test.ts +7 -36
- package/test/tools/get_statuses.test.ts +56 -0
- package/test/tools/get_templates.test.ts +1 -30
- package/test/tools/get_tests.test.ts +47 -0
- package/test/tools/update_case.test.ts +10 -16
- package/test/tools/update_cases.test.ts +29 -28
- package/test/utils/mapper.test.ts +13 -13
- package/test/utils/sanitizer.test.ts +85 -1
|
@@ -10,21 +10,21 @@ describe('get_cases tool', () => {
|
|
|
10
10
|
const mockCases: Case[] = [
|
|
11
11
|
{
|
|
12
12
|
id: 1, title: 'Login test', section_id: 1, template_id: 1, type_id: 1,
|
|
13
|
-
priority_id: 2, milestone_id: null, refs: null,
|
|
14
|
-
updated_by: 1, updated_on: 1700000000, estimate: null,
|
|
15
|
-
suite_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
16
|
},
|
|
17
17
|
{
|
|
18
18
|
id: 2, title: 'Logout test', section_id: 1, template_id: 1, type_id: 1,
|
|
19
|
-
priority_id: 3, milestone_id: null, refs: null,
|
|
20
|
-
updated_by: 1, updated_on: 1700000000, estimate: null,
|
|
21
|
-
suite_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
22
|
},
|
|
23
23
|
{
|
|
24
24
|
id: 3, title: 'Registration test', section_id: 2, template_id: 1, type_id: 1,
|
|
25
|
-
priority_id: 1, milestone_id: null, refs: null,
|
|
26
|
-
updated_by: 1, updated_on: 1700000000, estimate: null,
|
|
27
|
-
suite_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
28
|
},
|
|
29
29
|
];
|
|
30
30
|
|
|
@@ -51,105 +51,19 @@ describe('get_cases tool', () => {
|
|
|
51
51
|
const result = await getCasesTool.handler({ project_id: '1' }, mockClient);
|
|
52
52
|
|
|
53
53
|
expect(result).toBeDefined();
|
|
54
|
-
expect(result.
|
|
55
|
-
|
|
56
|
-
const parsed = JSON.parse(result.content[0].text);
|
|
57
|
-
expect(parsed.cases).toHaveLength(3);
|
|
54
|
+
expect(result.cases).toHaveLength(3);
|
|
58
55
|
expect(mockClient.getCases).toHaveBeenCalledWith('1', undefined, undefined);
|
|
59
56
|
});
|
|
60
57
|
|
|
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
58
|
test('filters cases using where clause with single condition', async () => {
|
|
144
59
|
const result = await getCasesTool.handler(
|
|
145
60
|
{ project_id: '1', where: { custom_automation_status: 1 } },
|
|
146
61
|
mockClient
|
|
147
62
|
);
|
|
148
|
-
const parsed = JSON.parse(result.content[0].text);
|
|
149
63
|
|
|
150
|
-
expect(
|
|
151
|
-
expect(
|
|
152
|
-
expect(
|
|
64
|
+
expect(result.cases).toHaveLength(1);
|
|
65
|
+
expect(result.cases[0].id).toBe(1);
|
|
66
|
+
expect(result.cases[0].title).toBe('Login test');
|
|
153
67
|
});
|
|
154
68
|
|
|
155
69
|
test('filters cases using where clause with multiple conditions', async () => {
|
|
@@ -157,11 +71,10 @@ describe('get_cases tool', () => {
|
|
|
157
71
|
{ project_id: '1', where: { custom_automation_status: 2, priority_id: 3 } },
|
|
158
72
|
mockClient
|
|
159
73
|
);
|
|
160
|
-
const parsed = JSON.parse(result.content[0].text);
|
|
161
74
|
|
|
162
|
-
expect(
|
|
163
|
-
expect(
|
|
164
|
-
expect(
|
|
75
|
+
expect(result.cases).toHaveLength(1);
|
|
76
|
+
expect(result.cases[0].id).toBe(2);
|
|
77
|
+
expect(result.cases[0].title).toBe('Logout test');
|
|
165
78
|
});
|
|
166
79
|
|
|
167
80
|
test('returns empty array when where clause matches no cases', async () => {
|
|
@@ -169,9 +82,8 @@ describe('get_cases tool', () => {
|
|
|
169
82
|
{ project_id: '1', where: { custom_automation_status: 999 } },
|
|
170
83
|
mockClient
|
|
171
84
|
);
|
|
172
|
-
const parsed = JSON.parse(result.content[0].text);
|
|
173
85
|
|
|
174
|
-
expect(
|
|
86
|
+
expect(result.cases).toHaveLength(0);
|
|
175
87
|
});
|
|
176
88
|
|
|
177
89
|
test('combines where clause with fields parameter', async () => {
|
|
@@ -179,10 +91,9 @@ describe('get_cases tool', () => {
|
|
|
179
91
|
{ project_id: '1', where: { priority_id: 2 }, fields: ['priority_id', 'custom_automation_status'] },
|
|
180
92
|
mockClient
|
|
181
93
|
);
|
|
182
|
-
const parsed = JSON.parse(result.content[0].text);
|
|
183
94
|
|
|
184
|
-
expect(
|
|
185
|
-
expect(
|
|
95
|
+
expect(result.cases).toHaveLength(1);
|
|
96
|
+
expect(result.cases[0]).toEqual({
|
|
186
97
|
id: 1,
|
|
187
98
|
title: 'Login test',
|
|
188
99
|
suite_id: 1,
|
|
@@ -8,9 +8,9 @@ describe('get_projects tool', () => {
|
|
|
8
8
|
let getProjectsMock: jest.Mock<() => Promise<Project[]>>;
|
|
9
9
|
|
|
10
10
|
const mockProjects: Project[] = [
|
|
11
|
-
{ id: 1, name: 'Project Alpha',
|
|
12
|
-
{ id: 2, name: 'Project Beta',
|
|
13
|
-
{ id: 3, name: 'Old 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
14
|
];
|
|
15
15
|
|
|
16
16
|
beforeEach(() => {
|
|
@@ -32,44 +32,7 @@ describe('get_projects tool', () => {
|
|
|
32
32
|
const result = await getProjectsTool.handler({}, mockClient);
|
|
33
33
|
|
|
34
34
|
expect(result).toBeDefined();
|
|
35
|
-
expect(result.
|
|
36
|
-
|
|
37
|
-
const parsed = JSON.parse(result.content[0].text);
|
|
38
|
-
expect(parsed.projects).toHaveLength(2); // Only active projects
|
|
35
|
+
expect(result.projects).toHaveLength(2);
|
|
39
36
|
expect(mockClient.getProjects).toHaveBeenCalled();
|
|
40
37
|
});
|
|
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
38
|
});
|
|
@@ -8,9 +8,9 @@ describe('get_sections tool', () => {
|
|
|
8
8
|
let getSectionsMock: jest.Mock<(projectId: string) => Promise<Section[]>>;
|
|
9
9
|
|
|
10
10
|
const mockSections: Section[] = [
|
|
11
|
-
{ id: 1, name: 'Login', description: null, parent_id: null,
|
|
12
|
-
{ id: 2, name: 'Authentication', description: 'Auth tests', parent_id: 1,
|
|
13
|
-
{ id: 3, name: 'Registration', description: null, parent_id: null,
|
|
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
14
|
];
|
|
15
15
|
|
|
16
16
|
beforeEach(() => {
|
|
@@ -29,40 +29,11 @@ describe('get_sections tool', () => {
|
|
|
29
29
|
expect(getSectionsTool.parameters.project_id).toBeDefined();
|
|
30
30
|
});
|
|
31
31
|
|
|
32
|
-
test('handler
|
|
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 () => {
|
|
32
|
+
test('handler throws error on failure', async () => {
|
|
59
33
|
getSectionsMock.mockRejectedValue(new Error('API Error'));
|
|
60
34
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
content: [{ type: 'text', text: 'Error: API Error' }],
|
|
65
|
-
isError: true
|
|
66
|
-
});
|
|
35
|
+
await expect(
|
|
36
|
+
getSectionsTool.handler({ project_id: '1' }, mockClient)
|
|
37
|
+
).rejects.toThrow('API Error');
|
|
67
38
|
});
|
|
68
39
|
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { jest, describe, test, expect, beforeEach } from '@jest/globals';
|
|
2
|
+
import { getStatusesTool } from '../../src/tools/get_statuses.js';
|
|
3
|
+
import { TestRailClient } from '../../src/client/testrail.js';
|
|
4
|
+
import { Status } from '../../src/types/testrail.js';
|
|
5
|
+
|
|
6
|
+
describe('get_statuses tool', () => {
|
|
7
|
+
let mockClient: jest.Mocked<TestRailClient>;
|
|
8
|
+
let getStatusesMock: jest.Mock<() => Promise<Status[]>>;
|
|
9
|
+
|
|
10
|
+
const mockStatuses: Status[] = [
|
|
11
|
+
{ id: 1, name: 'passed', label: 'Passed' },
|
|
12
|
+
{ id: 2, name: 'blocked', label: 'Blocked' },
|
|
13
|
+
{ id: 3, name: 'untested', label: 'Untested' },
|
|
14
|
+
{ id: 4, name: 'retest', label: 'Retest' },
|
|
15
|
+
{ id: 5, name: 'failed', label: 'Failed' },
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
getStatusesMock = jest.fn<() => Promise<Status[]>>().mockResolvedValue(mockStatuses);
|
|
20
|
+
|
|
21
|
+
mockClient = {
|
|
22
|
+
getStatuses: getStatusesMock
|
|
23
|
+
} as unknown as jest.Mocked<TestRailClient>;
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test('exports correct tool definition', () => {
|
|
27
|
+
expect(getStatusesTool.name).toBe('get_statuses');
|
|
28
|
+
expect(getStatusesTool.description).toContain('statuses');
|
|
29
|
+
expect(getStatusesTool.parameters).toBeDefined();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('handler fetches and returns statuses', async () => {
|
|
33
|
+
const result = await getStatusesTool.handler({}, mockClient);
|
|
34
|
+
|
|
35
|
+
expect(result).toBeDefined();
|
|
36
|
+
expect(result.statuses).toBeDefined();
|
|
37
|
+
expect(result.statuses).toHaveLength(5);
|
|
38
|
+
expect(mockClient.getStatuses).toHaveBeenCalled();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('returns correct status structure', async () => {
|
|
42
|
+
const result = await getStatusesTool.handler({}, mockClient);
|
|
43
|
+
|
|
44
|
+
expect(result.statuses[0]).toEqual({
|
|
45
|
+
id: 1,
|
|
46
|
+
name: 'passed',
|
|
47
|
+
label: 'Passed'
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('handler returns error on failure', async () => {
|
|
52
|
+
getStatusesMock.mockRejectedValue(new Error('API Error'));
|
|
53
|
+
|
|
54
|
+
await expect(getStatusesTool.handler({}, mockClient)).rejects.toThrow('API Error');
|
|
55
|
+
});
|
|
56
|
+
});
|
|
@@ -32,36 +32,7 @@ describe('get_templates tool', () => {
|
|
|
32
32
|
const result = await getTemplatesTool.handler({ project_id: '1' }, mockClient);
|
|
33
33
|
|
|
34
34
|
expect(result).toBeDefined();
|
|
35
|
-
expect(result.
|
|
36
|
-
|
|
37
|
-
const parsed = JSON.parse(result.content[0].text);
|
|
38
|
-
expect(parsed.templates).toHaveLength(3);
|
|
35
|
+
expect(result.templates).toHaveLength(3);
|
|
39
36
|
expect(mockClient.getTemplates).toHaveBeenCalledWith('1');
|
|
40
37
|
});
|
|
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
38
|
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { jest, describe, test, expect, beforeEach } from '@jest/globals';
|
|
2
|
+
import { getTestsTool } from '../../src/tools/get_tests.js';
|
|
3
|
+
import { TestRailClient } from '../../src/client/testrail.js';
|
|
4
|
+
import { Test } from '../../src/types/testrail.js';
|
|
5
|
+
|
|
6
|
+
describe('get_tests tool', () => {
|
|
7
|
+
let mockClient: jest.Mocked<TestRailClient>;
|
|
8
|
+
let getTestsMock: jest.Mock<(runId: number, statusId?: number[]) => Promise<Test[]>>;
|
|
9
|
+
|
|
10
|
+
const mockTests: Test[] = [
|
|
11
|
+
{ id: 1, case_id: 101, status_id: 1, title: 'Test 1', run_id: 1 },
|
|
12
|
+
{ id: 2, case_id: 102, status_id: 2, title: 'Test 2', run_id: 1 },
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
getTestsMock = jest.fn<(runId: number, statusId?: number[]) => Promise<Test[]>>().mockResolvedValue(mockTests);
|
|
17
|
+
|
|
18
|
+
mockClient = {
|
|
19
|
+
getTests: getTestsMock
|
|
20
|
+
} as unknown as jest.Mocked<TestRailClient>;
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('exports correct tool definition', () => {
|
|
24
|
+
expect(getTestsTool.name).toBe('get_tests');
|
|
25
|
+
expect(getTestsTool.description).toBeDefined();
|
|
26
|
+
expect(getTestsTool.parameters).toBeDefined();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('handler fetches and returns tests', async () => {
|
|
30
|
+
const result = await getTestsTool.handler({ run_id: 1 }, mockClient);
|
|
31
|
+
|
|
32
|
+
expect(result).toBeDefined();
|
|
33
|
+
expect(result.tests).toBeDefined();
|
|
34
|
+
expect(result.tests).toHaveLength(2);
|
|
35
|
+
expect(mockClient.getTests).toHaveBeenCalledWith(1, undefined);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('handler passes status_id filter', async () => {
|
|
39
|
+
const result = await getTestsTool.handler({ run_id: 1, status_id: [1, 5] }, mockClient);
|
|
40
|
+
expect(mockClient.getTests).toHaveBeenCalledWith(1, [1, 5]);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('handler returns error on failure', async () => {
|
|
44
|
+
getTestsMock.mockRejectedValue(new Error('API Error'));
|
|
45
|
+
await expect(getTestsTool.handler({ run_id: 1 }, mockClient)).rejects.toThrow('API Error');
|
|
46
|
+
});
|
|
47
|
+
});
|
|
@@ -52,12 +52,9 @@ describe('update_case tool', () => {
|
|
|
52
52
|
);
|
|
53
53
|
|
|
54
54
|
expect(result).toBeDefined();
|
|
55
|
-
expect(result.
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
expect(parsed.success).toBe(true);
|
|
59
|
-
expect(parsed.case_id).toBe(123);
|
|
60
|
-
expect(parsed.message).toContain('C123');
|
|
55
|
+
expect(result.success).toBe(true);
|
|
56
|
+
expect(result.case_id).toBe(123);
|
|
57
|
+
expect(result.message).toContain('C123');
|
|
61
58
|
expect(mockClient.updateCase).toHaveBeenCalledWith('123', { title: 'Updated Title' });
|
|
62
59
|
});
|
|
63
60
|
|
|
@@ -92,17 +89,14 @@ describe('update_case tool', () => {
|
|
|
92
89
|
expect(mockClient.updateCase).toHaveBeenCalledWith('123', fields);
|
|
93
90
|
});
|
|
94
91
|
|
|
95
|
-
test('handler
|
|
92
|
+
test('handler throws error on failure', async () => {
|
|
96
93
|
updateCaseMock.mockRejectedValue(new Error('API Error'));
|
|
97
94
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
content: [{ type: 'text', text: 'Error: API Error' }],
|
|
105
|
-
isError: true
|
|
106
|
-
});
|
|
95
|
+
await expect(
|
|
96
|
+
updateCaseTool.handler(
|
|
97
|
+
{ case_id: '123', fields: { title: 'Test' } },
|
|
98
|
+
mockClient
|
|
99
|
+
)
|
|
100
|
+
).rejects.toThrow('API Error');
|
|
107
101
|
});
|
|
108
102
|
});
|
|
@@ -10,21 +10,21 @@ describe('update_cases tool', () => {
|
|
|
10
10
|
const mockUpdatedCases: Case[] = [
|
|
11
11
|
{
|
|
12
12
|
id: 1, title: 'Test Case 1', section_id: 1, template_id: 1, type_id: 1,
|
|
13
|
-
priority_id: 3, milestone_id: null, refs: null,
|
|
14
|
-
updated_by: 1, updated_on: 1700000001, estimate: null,
|
|
15
|
-
suite_id: 1,
|
|
13
|
+
priority_id: 3, milestone_id: null, refs: null, created_on: 1700000000,
|
|
14
|
+
updated_by: 1, updated_on: 1700000001, estimate: null,
|
|
15
|
+
suite_id: 1, labels: []
|
|
16
16
|
},
|
|
17
17
|
{
|
|
18
18
|
id: 2, title: 'Test Case 2', section_id: 1, template_id: 1, type_id: 1,
|
|
19
|
-
priority_id: 3, milestone_id: null, refs: null,
|
|
20
|
-
updated_by: 1, updated_on: 1700000001, estimate: null,
|
|
21
|
-
suite_id: 1,
|
|
19
|
+
priority_id: 3, milestone_id: null, refs: null, created_on: 1700000000,
|
|
20
|
+
updated_by: 1, updated_on: 1700000001, estimate: null,
|
|
21
|
+
suite_id: 1, labels: []
|
|
22
22
|
},
|
|
23
23
|
{
|
|
24
24
|
id: 3, title: 'Test Case 3', section_id: 1, template_id: 1, type_id: 1,
|
|
25
|
-
priority_id: 3, milestone_id: null, refs: null,
|
|
26
|
-
updated_by: 1, updated_on: 1700000001, estimate: null,
|
|
27
|
-
suite_id: 1,
|
|
25
|
+
priority_id: 3, milestone_id: null, refs: null, created_on: 1700000000,
|
|
26
|
+
updated_by: 1, updated_on: 1700000001, estimate: null,
|
|
27
|
+
suite_id: 1, labels: []
|
|
28
28
|
},
|
|
29
29
|
];
|
|
30
30
|
|
|
@@ -33,8 +33,12 @@ describe('update_cases tool', () => {
|
|
|
33
33
|
.mockResolvedValue(mockUpdatedCases);
|
|
34
34
|
|
|
35
35
|
mockClient = {
|
|
36
|
-
updateCases: updateCasesMock
|
|
36
|
+
updateCases: updateCasesMock,
|
|
37
|
+
getCase: jest.fn(),
|
|
37
38
|
} as unknown as jest.Mocked<TestRailClient>;
|
|
39
|
+
|
|
40
|
+
// Default mock for getCase to avoid failures in unrelated tests
|
|
41
|
+
(mockClient.getCase as jest.Mock<any>).mockResolvedValue({ suite_id: 1 });
|
|
38
42
|
});
|
|
39
43
|
|
|
40
44
|
test('exports correct tool definition', () => {
|
|
@@ -52,37 +56,34 @@ describe('update_cases tool', () => {
|
|
|
52
56
|
);
|
|
53
57
|
|
|
54
58
|
expect(result).toBeDefined();
|
|
55
|
-
expect(result.
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
expect(
|
|
59
|
-
expect(parsed.updated_count).toBe(3);
|
|
60
|
-
expect(parsed.case_ids).toEqual([1, 2, 3]);
|
|
61
|
-
expect(parsed.message).toContain('3 test cases');
|
|
59
|
+
expect(result.success).toBe(true);
|
|
60
|
+
expect(result.updated_count).toBe(3);
|
|
61
|
+
expect(result.case_ids).toEqual([1, 2, 3]);
|
|
62
|
+
expect(result.message).toContain('3 test cases');
|
|
62
63
|
});
|
|
63
64
|
|
|
64
65
|
test('passes case_ids and fields correctly', async () => {
|
|
65
66
|
const fields = { priority_id: 2, type_id: 1 };
|
|
67
|
+
const mockCase = { suite_id: 5 } as Case;
|
|
68
|
+
(mockClient.getCase as jest.Mock<any>).mockResolvedValue(mockCase);
|
|
66
69
|
|
|
67
70
|
await updateCasesTool.handler(
|
|
68
71
|
{ case_ids: [10, 20, 30], fields },
|
|
69
72
|
mockClient
|
|
70
73
|
);
|
|
71
74
|
|
|
72
|
-
expect(mockClient.
|
|
75
|
+
expect(mockClient.getCase).toHaveBeenCalledWith('10');
|
|
76
|
+
expect(mockClient.updateCases).toHaveBeenCalledWith(5, [10, 20, 30], fields);
|
|
73
77
|
});
|
|
74
78
|
|
|
75
|
-
test('handler
|
|
79
|
+
test('handler throws error on failure', async () => {
|
|
76
80
|
updateCasesMock.mockRejectedValue(new Error('API Error'));
|
|
77
81
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
content: [{ type: 'text', text: 'Error: API Error' }],
|
|
85
|
-
isError: true
|
|
86
|
-
});
|
|
82
|
+
await expect(
|
|
83
|
+
updateCasesTool.handler(
|
|
84
|
+
{ case_ids: [1, 2], fields: { priority_id: 2 } },
|
|
85
|
+
mockClient
|
|
86
|
+
)
|
|
87
|
+
).rejects.toThrow('API Error');
|
|
87
88
|
});
|
|
88
89
|
});
|
|
@@ -6,13 +6,13 @@ describe('processCustomFields', () => {
|
|
|
6
6
|
|
|
7
7
|
// Mock CaseField array simulating TestRail API response
|
|
8
8
|
const mockCaseFields: CaseField[] = [
|
|
9
|
-
{ id: 1, name: 'is_automated', system_name: 'custom_is_automated', label: 'Is Automated', type_id: 1, template_ids: [], include_all: true, is_active: true,
|
|
10
|
-
{ id: 2, name: 'refs', system_name: 'custom_refs', label: 'Defects', type_id: 1, template_ids: [], include_all: true, is_active: true,
|
|
11
|
-
{ 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,
|
|
12
|
-
{ id: 4, name: 'preconds', system_name: 'custom_preconds', label: 'Preconditions', type_id: 1, template_ids: [], include_all: true, is_active: true,
|
|
13
|
-
{ id: 5, name: 'steps', system_name: 'custom_steps', label: 'Steps', type_id: 1, template_ids: [1], include_all: false, is_active: true,
|
|
14
|
-
{ id: 6, name: 'expected', system_name: 'custom_expected', label: 'Expected', type_id: 1, template_ids: [1], include_all: false, is_active: true,
|
|
15
|
-
{ 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,
|
|
9
|
+
{ 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: [] },
|
|
10
|
+
{ id: 2, name: 'refs', system_name: 'custom_refs', label: 'Defects', type_id: 1, template_ids: [], include_all: true, is_active: true, description: null, configs: [] },
|
|
11
|
+
{ 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' } }] },
|
|
12
|
+
{ id: 4, name: 'preconds', system_name: 'custom_preconds', label: 'Preconditions', type_id: 1, template_ids: [], include_all: true, is_active: true, description: null, configs: [] },
|
|
13
|
+
{ 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: [] },
|
|
14
|
+
{ 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: [] },
|
|
15
|
+
{ 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: [] },
|
|
16
16
|
];
|
|
17
17
|
|
|
18
18
|
test('maps custom fields using field metadata', () => {
|
|
@@ -21,7 +21,7 @@ describe('processCustomFields', () => {
|
|
|
21
21
|
section_id: 0, type_id: 0, priority_id: 0,
|
|
22
22
|
custom_is_automated: true,
|
|
23
23
|
custom_refs: 'DEF-123',
|
|
24
|
-
display_order: 1, suite_id: 1,
|
|
24
|
+
display_order: 1, suite_id: 1, created_on: 1, updated_on: 1,
|
|
25
25
|
is_deleted: 0, refs: null, labels: [],
|
|
26
26
|
} as unknown as Case;
|
|
27
27
|
|
|
@@ -35,7 +35,7 @@ describe('processCustomFields', () => {
|
|
|
35
35
|
id: 1, title: 'Foo', template_id: 1,
|
|
36
36
|
section_id: 0, type_id: 0, priority_id: 0,
|
|
37
37
|
custom_case_automation_priority: 2,
|
|
38
|
-
display_order: 1, suite_id: 1,
|
|
38
|
+
display_order: 1, suite_id: 1, created_on: 1, updated_on: 1,
|
|
39
39
|
is_deleted: 0, refs: null, labels: [],
|
|
40
40
|
} as unknown as Case;
|
|
41
41
|
|
|
@@ -50,7 +50,7 @@ describe('processCustomFields', () => {
|
|
|
50
50
|
custom_expected: 'Expectation...',
|
|
51
51
|
custom_steps_separated: [{ content: 'Should not appear' }], // Template 2 field
|
|
52
52
|
section_id: 0, type_id: 0, priority_id: 0,
|
|
53
|
-
display_order: 1, suite_id: 1,
|
|
53
|
+
display_order: 1, suite_id: 1, created_on: 1, updated_on: 1,
|
|
54
54
|
is_deleted: 0, refs: null, labels: [],
|
|
55
55
|
} as unknown as Case;
|
|
56
56
|
|
|
@@ -66,7 +66,7 @@ describe('processCustomFields', () => {
|
|
|
66
66
|
custom_steps: 'Should not appear', // Template 1 field
|
|
67
67
|
custom_steps_separated: [{ content: 'Step 1' }],
|
|
68
68
|
section_id: 0, type_id: 0, priority_id: 0,
|
|
69
|
-
display_order: 1, suite_id: 1,
|
|
69
|
+
display_order: 1, suite_id: 1, created_on: 1, updated_on: 1,
|
|
70
70
|
is_deleted: 0, refs: null, labels: [],
|
|
71
71
|
} as unknown as Case;
|
|
72
72
|
|
|
@@ -80,7 +80,7 @@ describe('processCustomFields', () => {
|
|
|
80
80
|
id: 1, title: 'Foo', template_id: 999,
|
|
81
81
|
custom_unknown_field: 'some value',
|
|
82
82
|
section_id: 0, type_id: 0, priority_id: 0,
|
|
83
|
-
display_order: 1, suite_id: 1,
|
|
83
|
+
display_order: 1, suite_id: 1, created_on: 1, updated_on: 1,
|
|
84
84
|
is_deleted: 0, refs: null, labels: [],
|
|
85
85
|
} as unknown as Case;
|
|
86
86
|
|
|
@@ -101,7 +101,7 @@ describe('processCustomFields', () => {
|
|
|
101
101
|
id: 1, title: 'Foo', template_id: 1,
|
|
102
102
|
section_id: 0, type_id: 0, priority_id: 0,
|
|
103
103
|
custom_case_automation_priority: 999, // Unknown option ID
|
|
104
|
-
display_order: 1, suite_id: 1,
|
|
104
|
+
display_order: 1, suite_id: 1, created_on: 1, updated_on: 1,
|
|
105
105
|
is_deleted: 0, refs: null, labels: [],
|
|
106
106
|
} as unknown as Case;
|
|
107
107
|
|