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