@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
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { jest, describe, test, expect, beforeEach } from '@jest/globals';
|
|
2
|
+
import { addResultsTool } from '../../src/tools/add_results.js';
|
|
3
|
+
import { TestRailClient } from '../../src/client/testrail.js';
|
|
4
|
+
import { Result } from '../../src/types/testrail.js';
|
|
5
|
+
|
|
6
|
+
describe('add_results tool', () => {
|
|
7
|
+
let mockClient: jest.Mocked<TestRailClient>;
|
|
8
|
+
let addResultsMock: jest.Mock<(runId: number, results: Array<Record<string, any>>) => Promise<Result[]>>;
|
|
9
|
+
|
|
10
|
+
const mockResults: Result[] = [
|
|
11
|
+
{
|
|
12
|
+
id: 1,
|
|
13
|
+
test_id: 100,
|
|
14
|
+
status_id: 1,
|
|
15
|
+
comment: 'Test passed',
|
|
16
|
+
defects: null
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
id: 2,
|
|
20
|
+
test_id: 101,
|
|
21
|
+
status_id: 5,
|
|
22
|
+
comment: 'Test failed',
|
|
23
|
+
defects: 'BUG-123'
|
|
24
|
+
}
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
addResultsMock = jest.fn<(runId: number, results: Array<Record<string, any>>) => Promise<Result[]>>().mockResolvedValue(mockResults);
|
|
29
|
+
|
|
30
|
+
mockClient = {
|
|
31
|
+
addResults: addResultsMock
|
|
32
|
+
} as unknown as jest.Mocked<TestRailClient>;
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('exports correct tool definition', () => {
|
|
36
|
+
expect(addResultsTool.name).toBe('add_results');
|
|
37
|
+
expect(addResultsTool.description).toBeDefined();
|
|
38
|
+
expect(addResultsTool.parameters).toBeDefined();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('handler calls addResults with correct arguments', async () => {
|
|
42
|
+
const args = {
|
|
43
|
+
run_id: 50,
|
|
44
|
+
results: [
|
|
45
|
+
{
|
|
46
|
+
test_id: 100,
|
|
47
|
+
status_id: 1,
|
|
48
|
+
comment: 'Test passed'
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
test_id: 101,
|
|
52
|
+
status_id: 5,
|
|
53
|
+
comment: 'Test failed',
|
|
54
|
+
defects: 'BUG-123'
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const result = await addResultsTool.handler(args, mockClient);
|
|
60
|
+
|
|
61
|
+
expect(result).toBeDefined();
|
|
62
|
+
expect(mockClient.addResults).toHaveBeenCalledWith(50, args.results);
|
|
63
|
+
|
|
64
|
+
expect(result.success).toBe(true);
|
|
65
|
+
expect(result.added_count).toBe(2);
|
|
66
|
+
expect(result.results).toHaveLength(2);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('handler works with single result', async () => {
|
|
70
|
+
const singleResult = [mockResults[0]];
|
|
71
|
+
addResultsMock.mockResolvedValue(singleResult);
|
|
72
|
+
|
|
73
|
+
const args = {
|
|
74
|
+
run_id: 50,
|
|
75
|
+
results: [
|
|
76
|
+
{
|
|
77
|
+
test_id: 100,
|
|
78
|
+
status_id: 1,
|
|
79
|
+
comment: 'Test passed'
|
|
80
|
+
}
|
|
81
|
+
]
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const result = await addResultsTool.handler(args, mockClient);
|
|
85
|
+
|
|
86
|
+
expect(result.success).toBe(true);
|
|
87
|
+
expect(result.added_count).toBe(1);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('handler returns error on failure', async () => {
|
|
91
|
+
addResultsMock.mockRejectedValue(new Error('API Error'));
|
|
92
|
+
await expect(addResultsTool.handler({ run_id: 50, results: [] }, mockClient)).rejects.toThrow('API Error');
|
|
93
|
+
});
|
|
94
|
+
});
|
|
@@ -0,0 +1,83 @@
|
|
|
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
|
+
});
|
|
@@ -53,12 +53,9 @@ describe('create_case tool', () => {
|
|
|
53
53
|
);
|
|
54
54
|
|
|
55
55
|
expect(result).toBeDefined();
|
|
56
|
-
expect(result.
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
expect(parsed.success).toBe(true);
|
|
60
|
-
expect(parsed.case_id).toBe(456);
|
|
61
|
-
expect(parsed.message).toContain('C456');
|
|
56
|
+
expect(result.success).toBe(true);
|
|
57
|
+
expect(result.case_id).toBe(456);
|
|
58
|
+
expect(result.message).toContain('C456');
|
|
62
59
|
expect(mockClient.createCase).toHaveBeenCalledWith('1', { title: 'New Test Case' });
|
|
63
60
|
});
|
|
64
61
|
|
|
@@ -91,17 +88,14 @@ describe('create_case tool', () => {
|
|
|
91
88
|
expect(mockClient.createCase).toHaveBeenCalledWith('10', { title: 'Simple Test' });
|
|
92
89
|
});
|
|
93
90
|
|
|
94
|
-
test('handler
|
|
91
|
+
test('handler throws error on failure', async () => {
|
|
95
92
|
createCaseMock.mockRejectedValue(new Error('API Error'));
|
|
96
93
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
content: [{ type: 'text', text: 'Error: API Error' }],
|
|
104
|
-
isError: true
|
|
105
|
-
});
|
|
94
|
+
await expect(
|
|
95
|
+
createCaseTool.handler(
|
|
96
|
+
{ section_id: '1', title: 'Test' },
|
|
97
|
+
mockClient
|
|
98
|
+
)
|
|
99
|
+
).rejects.toThrow('API Error');
|
|
106
100
|
});
|
|
107
101
|
});
|
|
@@ -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(() => {
|
|
@@ -39,7 +39,7 @@ describe('get_case tool', () => {
|
|
|
39
39
|
|
|
40
40
|
test('exports correct tool definition', () => {
|
|
41
41
|
expect(getCaseTool.name).toBe('get_case');
|
|
42
|
-
expect(getCaseTool.description).toBe('Get a test case
|
|
42
|
+
expect(getCaseTool.description).toBe('Get detailed information about a test case including its custom fields');
|
|
43
43
|
expect(getCaseTool.parameters).toBeDefined();
|
|
44
44
|
});
|
|
45
45
|
|
|
@@ -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
|
});
|