targetprocess-mcp-server 2.5.1 → 2.5.3
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 +14 -1
- package/build/handlers/add_test_case_step_by_id.js +17 -0
- package/build/handlers/delete_test_case_step_by_id.js +19 -0
- package/build/handlers/get_bug_workflows.js +33 -0
- package/build/handlers/get_process_workflows.js +23 -0
- package/build/handlers/get_processes.js +23 -0
- package/build/handlers/get_relation_types.js +12 -0
- package/build/handlers/get_release_bugs.js +2 -2
- package/build/handlers/get_test_case_by_id.js +37 -0
- package/build/handlers/get_test_plan_by_id.js +36 -0
- package/build/handlers/get_test_plan_test_cases_by_id.js +43 -0
- package/build/handlers/get_test_plan_test_cases_with_steps_by_id.js +49 -0
- package/build/handlers/get_user_story_workflows.js +33 -0
- package/build/handlers/get_version.js +5 -0
- package/build/handlers/update_test_case_by_id.js +22 -0
- package/build/handlers/update_test_case_step_by_id.js +35 -0
- package/build/index.js +120 -141
- package/build/tp.js +156 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -109,6 +109,14 @@ Cards — Write
|
|
|
109
109
|
Test Case Workflows
|
|
110
110
|
- `write_test_cases` — Fetch a card (UserStory, Bug, or Feature) by ID and trigger the full test case writing workflow: Claude analyzes the card, generates detailed test cases covering happy path, edge cases, and error scenarios, creates a linked test plan via `create_test_plan`, then calls `add_test_cases_to_test_plan`. Each test case description contains Preconditions and Test Type as HTML; steps are passed as a structured array (resourceId, optional resourceType)
|
|
111
111
|
- `add_test_cases_to_test_plan` — Add pre-generated test cases to an existing test plan. Each test case has a `name`, an HTML `description` (Preconditions and Test Type only), and a `steps` array of `{ description, result }` objects — steps are created via the TP test step API rather than embedded in the description (testPlanId, testCases array of {name, description, steps})
|
|
112
|
+
- `get_test_plan_by_id` — Get a test plan by ID, including name, plain-text description, state, project, and linked card (id)
|
|
113
|
+
- `get_test_plan_test_cases_by_id` — Get test cases for a test plan by ID, including cases from nested child test plans/containers; returns id, name, plain-text description, and containing test plan metadata, no steps (id)
|
|
114
|
+
- `get_test_plan_test_cases_with_steps_by_id` — Get test cases directly on a test plan by ID, each with its steps (id)
|
|
115
|
+
- `get_test_case_by_id` — Get a single test case by ID, including plain-text description and its steps (id)
|
|
116
|
+
- `update_test_case_by_id` — Update a test case's name and/or description (id, optional name, optional description)
|
|
117
|
+
- `add_test_case_step_by_id` — Add a new step to a test case (testCaseId, description, result)
|
|
118
|
+
- `update_test_case_step_by_id` — Update a test step's description and/or result (id, optional description, optional result)
|
|
119
|
+
- `delete_test_case_step_by_id` — Delete a test step by ID (id)
|
|
112
120
|
|
|
113
121
|
Processes & Workflows
|
|
114
122
|
- `get_processes` — Get all Targetprocess processes (no params needed)
|
|
@@ -219,7 +227,7 @@ npx vitest # watch mode
|
|
|
219
227
|
|
|
220
228
|
### Coverage
|
|
221
229
|
|
|
222
|
-
**
|
|
230
|
+
**53 of 64 tools (83%) are covered by unit tests.**
|
|
223
231
|
|
|
224
232
|
| Test file | Handlers covered |
|
|
225
233
|
|---|---|
|
|
@@ -236,3 +244,8 @@ npx vitest # watch mode
|
|
|
236
244
|
| `creation_tools.test.ts` | `create_bug`, `create_user_story`, `create_feature`, `create_task`, `update_bug`, `update_user_story_state` |
|
|
237
245
|
| `my_work_tools.test.ts` | `get_in_progress_tasks_and_bugs`, `list_my_user_stories`, `list_my_bugs`, `log_time`, `get_my_time_logs` |
|
|
238
246
|
| `entity_tools.test.ts` | `get_feature_user_stories`, `get_user_story_bugs`, `get_card_current_status` |
|
|
247
|
+
| `relation_tools.test.ts` | `get_card_relations`, `create_card_relation`, `delete_card_relation` |
|
|
248
|
+
| `epic_tools.test.ts` | `create_epic`, `get_epic_content`, `update_epic`, `get_epic_features` |
|
|
249
|
+
| `test_plan_tools.test.ts` | `get_test_plan_by_id`, `get_test_plan_test_cases_by_id`, `get_test_plan_test_cases_with_steps_by_id` |
|
|
250
|
+
| `test_case_tools.test.ts` | `get_test_case_by_id`, `update_test_case_by_id`, `add_test_case_step_by_id`, `update_test_case_step_by_id`, `delete_test_case_step_by_id` |
|
|
251
|
+
| `workflow_tools.test.ts` | `get_processes`, `get_process_workflows`, `get_bug_workflows`, `get_user_story_workflows`, `get_relation_types`, `get_version` |
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export async function handleAddTestCaseStepById(tp, params) {
|
|
2
|
+
const testStepResponse = await tp.addTestStep(params.testCaseId, {
|
|
3
|
+
description: params.description,
|
|
4
|
+
result: params.result,
|
|
5
|
+
});
|
|
6
|
+
if (!testStepResponse) {
|
|
7
|
+
return {
|
|
8
|
+
content: [{
|
|
9
|
+
type: 'text',
|
|
10
|
+
text: `Failed to add test step to test case id: ${params.testCaseId}\n JSON: ${JSON.stringify(testStepResponse, null, 2)}`
|
|
11
|
+
}],
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
return {
|
|
15
|
+
content: [{ type: 'text', text: JSON.stringify(testStepResponse) }],
|
|
16
|
+
};
|
|
17
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export async function handleDeleteTestCaseStepById(tp, id) {
|
|
2
|
+
const result = await tp.deleteTestStep(id);
|
|
3
|
+
if (!result.ok) {
|
|
4
|
+
return {
|
|
5
|
+
content: [{
|
|
6
|
+
type: 'text',
|
|
7
|
+
text: `Failed to delete test step id: ${id}\n` +
|
|
8
|
+
`HTTP status: ${result.status}\n` +
|
|
9
|
+
`Response body: ${result.body}`
|
|
10
|
+
}],
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
return {
|
|
14
|
+
content: [{
|
|
15
|
+
type: 'text',
|
|
16
|
+
text: JSON.stringify({ deleted: true, testStepId: Number(id), testStep: result.data })
|
|
17
|
+
}],
|
|
18
|
+
};
|
|
19
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export async function handleGetBugWorkflows(tp) {
|
|
2
|
+
const response = await tp.getBugWorkflows();
|
|
3
|
+
if (!response) {
|
|
4
|
+
return {
|
|
5
|
+
content: [{
|
|
6
|
+
type: 'text',
|
|
7
|
+
text: `Failed to get bug entity statuses, JSON: ${JSON.stringify(response, null, 2)}`
|
|
8
|
+
}],
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
const items = response.items || [];
|
|
12
|
+
if (items.length === 0) {
|
|
13
|
+
return {
|
|
14
|
+
content: [{
|
|
15
|
+
type: 'text',
|
|
16
|
+
text: `No status data found for workflows`
|
|
17
|
+
}],
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
const workflows = items.map((w) => ({
|
|
21
|
+
id: w.id,
|
|
22
|
+
name: w.name,
|
|
23
|
+
processId: w.process,
|
|
24
|
+
entityType: w.entityType,
|
|
25
|
+
entityStates: w.entityStates.map((es) => ({
|
|
26
|
+
id: es.id,
|
|
27
|
+
name: es.name,
|
|
28
|
+
})),
|
|
29
|
+
}));
|
|
30
|
+
return {
|
|
31
|
+
content: [{ type: 'text', text: JSON.stringify(workflows) }],
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export async function handleGetProcessWorkflows(tp, processId) {
|
|
2
|
+
const response = await tp.getProcessWorkflows({ processId });
|
|
3
|
+
if (!response) {
|
|
4
|
+
return {
|
|
5
|
+
content: [{
|
|
6
|
+
type: 'text',
|
|
7
|
+
text: `Failed to get process workflows, JSON: ${JSON.stringify(response, null, 2)}`
|
|
8
|
+
}],
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
const items = response.items || [];
|
|
12
|
+
if (items.length === 0) {
|
|
13
|
+
return {
|
|
14
|
+
content: [{
|
|
15
|
+
type: 'text',
|
|
16
|
+
text: `No process workflows found`,
|
|
17
|
+
}],
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
content: [{ type: 'text', text: JSON.stringify(items) }],
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export async function handleGetProcesses(tp) {
|
|
2
|
+
const response = await tp.getProcesses();
|
|
3
|
+
if (!response) {
|
|
4
|
+
return {
|
|
5
|
+
content: [{
|
|
6
|
+
type: 'text',
|
|
7
|
+
text: `Failed to get processes, JSON: ${JSON.stringify(response, null, 2)}`
|
|
8
|
+
}],
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
const items = response.items || [];
|
|
12
|
+
if (items.length === 0) {
|
|
13
|
+
return {
|
|
14
|
+
content: [{
|
|
15
|
+
type: 'text',
|
|
16
|
+
text: `No processes found`,
|
|
17
|
+
}],
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
content: [{ type: 'text', text: JSON.stringify(items) }],
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export async function handleGetRelationTypes(tp) {
|
|
2
|
+
const response = await tp.getRelationTypes();
|
|
3
|
+
if (!response) {
|
|
4
|
+
return {
|
|
5
|
+
content: [{ type: 'text', text: `Failed to get relation types` }],
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
const items = (response.Items || []).map((t) => ({ id: t.Id, name: t.Name }));
|
|
9
|
+
return {
|
|
10
|
+
content: [{ type: 'text', text: JSON.stringify(items) }],
|
|
11
|
+
};
|
|
12
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export async function handleGetReleaseBugs(tp, name, results) {
|
|
2
|
-
const release = await tp.getReleaseBugs({ name, results });
|
|
1
|
+
export async function handleGetReleaseBugs(tp, name, results, withDescription) {
|
|
2
|
+
const release = await tp.getReleaseBugs({ name, results, withDescription });
|
|
3
3
|
if (!release) {
|
|
4
4
|
return {
|
|
5
5
|
content: [{
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { JSDOM } from 'jsdom';
|
|
2
|
+
export async function handleGetTestCaseById(tp, id) {
|
|
3
|
+
const testCase = await tp.getTestCase(id);
|
|
4
|
+
if (!testCase) {
|
|
5
|
+
return {
|
|
6
|
+
content: [{
|
|
7
|
+
type: 'text',
|
|
8
|
+
text: `Failed to get test case id: ${id}\n JSON: ${JSON.stringify(testCase, null, 2)}`
|
|
9
|
+
}],
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
let description = '';
|
|
13
|
+
if (testCase.Description) {
|
|
14
|
+
try {
|
|
15
|
+
const dom = new JSDOM(`<html><body><div id="content">${testCase.Description}</div></body></html>`);
|
|
16
|
+
description = dom.window.document.getElementById('content')?.textContent || '';
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
console.error('Error parsing test case description:', error);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const testCaseSteps = await tp.getTestCaseSteps(String(testCase.Id));
|
|
23
|
+
const result = {
|
|
24
|
+
id: testCase.Id,
|
|
25
|
+
name: testCase.Name,
|
|
26
|
+
description,
|
|
27
|
+
testPlan: testCase.TestPlans?.Items?.[0]?.Name ?? testCase.LinkedTestPlan?.Name,
|
|
28
|
+
steps: testCaseSteps?.Items?.map((step) => ({
|
|
29
|
+
description: step.Description,
|
|
30
|
+
result: step.Result,
|
|
31
|
+
runOrder: step.RunOrder,
|
|
32
|
+
})) || [],
|
|
33
|
+
};
|
|
34
|
+
return {
|
|
35
|
+
content: [{ type: 'text', text: JSON.stringify(result) }],
|
|
36
|
+
};
|
|
37
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { JSDOM } from 'jsdom';
|
|
2
|
+
export async function handleGetTestPlanById(tp, id) {
|
|
3
|
+
const testPlan = await tp.getTestPlan(id);
|
|
4
|
+
if (!testPlan) {
|
|
5
|
+
return {
|
|
6
|
+
content: [{
|
|
7
|
+
type: "text",
|
|
8
|
+
text: `Failed to get test plan id: ${id}\n JSON: ${JSON.stringify(testPlan, null, 2)}`
|
|
9
|
+
}],
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
let description = '';
|
|
13
|
+
if (testPlan.Description) {
|
|
14
|
+
try {
|
|
15
|
+
const dom = new JSDOM(`<html><body><div id="content">${testPlan.Description}</div></body></html>`);
|
|
16
|
+
description = dom.window.document.getElementById('content')?.textContent || '';
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
console.error('Error parsing test plan description:', error);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const result = {
|
|
23
|
+
id: testPlan.Id,
|
|
24
|
+
name: testPlan.Name,
|
|
25
|
+
description,
|
|
26
|
+
entityState: testPlan.EntityState?.Name,
|
|
27
|
+
project: testPlan.Project?.Name,
|
|
28
|
+
linkedUserStory: testPlan.LinkedUserStory?.Name,
|
|
29
|
+
linkedAssignable: testPlan.LinkedAssignable?.Name,
|
|
30
|
+
createDate: testPlan.CreateDate,
|
|
31
|
+
modifyDate: testPlan.ModifyDate,
|
|
32
|
+
};
|
|
33
|
+
return {
|
|
34
|
+
content: [{ type: 'text', text: JSON.stringify(result) }],
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { JSDOM } from 'jsdom';
|
|
2
|
+
export async function handleGetTestPlanTestCasesById(tp, id) {
|
|
3
|
+
const response = await tp.getIndirectTestPlanTestCases(id);
|
|
4
|
+
if (!response) {
|
|
5
|
+
return {
|
|
6
|
+
content: [{
|
|
7
|
+
type: 'text',
|
|
8
|
+
text: `Failed to get test cases for test plan id: ${id}\n JSON: ${JSON.stringify(response, null, 2)}`
|
|
9
|
+
}],
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
const items = response.Items || [];
|
|
13
|
+
if (items.length === 0) {
|
|
14
|
+
return {
|
|
15
|
+
content: [{
|
|
16
|
+
type: 'text',
|
|
17
|
+
text: `No test cases found for test plan id: ${id}`,
|
|
18
|
+
}],
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
const testCases = items.map((item) => {
|
|
22
|
+
let description = '';
|
|
23
|
+
if (item.Description) {
|
|
24
|
+
try {
|
|
25
|
+
const dom = new JSDOM(`<html><body><div id="content">${item.Description}</div></body></html>`);
|
|
26
|
+
description = dom.window.document.getElementById('content')?.textContent || '';
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
console.error('Error parsing test case description:', error);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
id: item.Id,
|
|
34
|
+
name: item.Name,
|
|
35
|
+
description,
|
|
36
|
+
testPlanId: item.TestPlanId,
|
|
37
|
+
testPlanName: item.TestPlanName,
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
return {
|
|
41
|
+
content: [{ type: 'text', text: JSON.stringify(testCases) }],
|
|
42
|
+
};
|
|
43
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { JSDOM } from 'jsdom';
|
|
2
|
+
export async function handleGetTestPlanTestCasesWithStepsById(tp, id) {
|
|
3
|
+
const response = await tp.getTestPlanTestCases(id);
|
|
4
|
+
if (!response) {
|
|
5
|
+
return {
|
|
6
|
+
content: [{
|
|
7
|
+
type: 'text',
|
|
8
|
+
text: `Failed to get test cases for test plan id: ${id}\n JSON: ${JSON.stringify(response, null, 2)}`
|
|
9
|
+
}],
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
const items = response.Items || [];
|
|
13
|
+
if (items.length === 0) {
|
|
14
|
+
return {
|
|
15
|
+
content: [{
|
|
16
|
+
type: 'text',
|
|
17
|
+
text: `No test cases found for test plan id: ${id}`,
|
|
18
|
+
}],
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
const testCasesData = await Promise.all(items.map(async (item) => {
|
|
22
|
+
let description = '';
|
|
23
|
+
if (item.Description) {
|
|
24
|
+
try {
|
|
25
|
+
const dom = new JSDOM(`<html><body><div id="content">${item.Description}</div></body></html>`);
|
|
26
|
+
description = dom.window.document.getElementById('content')?.textContent || '';
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
console.error('Error parsing test case description:', error);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const testCaseSteps = await tp.getTestCaseSteps(String(item.Id));
|
|
33
|
+
return {
|
|
34
|
+
testCaseId: item.Id,
|
|
35
|
+
testCaseName: item.Name,
|
|
36
|
+
testCaseDescription: description,
|
|
37
|
+
testPlanId: item.TestPlanId,
|
|
38
|
+
testPlanName: item.TestPlanName,
|
|
39
|
+
testCaseSteps: testCaseSteps?.Items?.map((step) => ({
|
|
40
|
+
description: step.Description,
|
|
41
|
+
result: step.Result,
|
|
42
|
+
runOrder: step.RunOrder,
|
|
43
|
+
})) || [],
|
|
44
|
+
};
|
|
45
|
+
}));
|
|
46
|
+
return {
|
|
47
|
+
content: [{ type: 'text', text: JSON.stringify(testCasesData) }],
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export async function handleGetUserStoryWorkflows(tp) {
|
|
2
|
+
const response = await tp.getUserStoryWorkflowsWithSubStates();
|
|
3
|
+
if (!response) {
|
|
4
|
+
return {
|
|
5
|
+
content: [{
|
|
6
|
+
type: 'text',
|
|
7
|
+
text: `Failed to get user story entity statuses, JSON: ${JSON.stringify(response, null, 2)}`
|
|
8
|
+
}],
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
const items = response.items || [];
|
|
12
|
+
if (items.length === 0) {
|
|
13
|
+
return {
|
|
14
|
+
content: [{
|
|
15
|
+
type: 'text',
|
|
16
|
+
text: `No status data found for workflows`
|
|
17
|
+
}],
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
const userStoryWorkflows = items.filter((w) => w.entityType.name === "UserStory");
|
|
21
|
+
const workflows = userStoryWorkflows.map((w) => ({
|
|
22
|
+
id: w.id,
|
|
23
|
+
processId: w.workflow.process.id,
|
|
24
|
+
entityType: w.entityType.name,
|
|
25
|
+
entityStates: w.subEntityStates.map((es) => ({
|
|
26
|
+
id: es.id,
|
|
27
|
+
name: es.name,
|
|
28
|
+
})),
|
|
29
|
+
}));
|
|
30
|
+
return {
|
|
31
|
+
content: [{ type: 'text', text: JSON.stringify(workflows) }],
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export async function handleUpdateTestCaseById(tp, params) {
|
|
2
|
+
if (params.name === undefined && params.description === undefined) {
|
|
3
|
+
return {
|
|
4
|
+
content: [{
|
|
5
|
+
type: 'text',
|
|
6
|
+
text: `Nothing to update for test case id: ${params.id}`
|
|
7
|
+
}],
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
const testCaseResponse = await tp.updateTestCase(params);
|
|
11
|
+
if (!testCaseResponse) {
|
|
12
|
+
return {
|
|
13
|
+
content: [{
|
|
14
|
+
type: 'text',
|
|
15
|
+
text: `Failed to update test case id: ${params.id}\n JSON: ${JSON.stringify(testCaseResponse, null, 2)}`
|
|
16
|
+
}],
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
20
|
+
content: [{ type: 'text', text: JSON.stringify(testCaseResponse) }],
|
|
21
|
+
};
|
|
22
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export async function handleUpdateTestCaseStepById(tp, params) {
|
|
2
|
+
if (params.description === undefined && params.result === undefined) {
|
|
3
|
+
return {
|
|
4
|
+
content: [{
|
|
5
|
+
type: 'text',
|
|
6
|
+
text: `Nothing to update for test step id: ${params.id}`
|
|
7
|
+
}],
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
const existingTestStep = await tp.getTestStep(params.id);
|
|
11
|
+
if (!existingTestStep) {
|
|
12
|
+
return {
|
|
13
|
+
content: [{
|
|
14
|
+
type: 'text',
|
|
15
|
+
text: `Failed to get test step id: ${params.id}\n JSON: ${JSON.stringify(existingTestStep, null, 2)}`
|
|
16
|
+
}],
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
const testStepResponse = await tp.updateTestStep({
|
|
20
|
+
id: params.id,
|
|
21
|
+
description: params.description ?? existingTestStep.Description,
|
|
22
|
+
result: params.result ?? existingTestStep.Result,
|
|
23
|
+
});
|
|
24
|
+
if (!testStepResponse) {
|
|
25
|
+
return {
|
|
26
|
+
content: [{
|
|
27
|
+
type: 'text',
|
|
28
|
+
text: `Failed to update test step id: ${params.id}\n JSON: ${JSON.stringify(testStepResponse, null, 2)}`
|
|
29
|
+
}],
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
content: [{ type: 'text', text: JSON.stringify(testStepResponse) }],
|
|
34
|
+
};
|
|
35
|
+
}
|
package/build/index.js
CHANGED
|
@@ -44,6 +44,20 @@ import { handleUpdateUserStorySubState } from "./handlers/update_user_story_sub_
|
|
|
44
44
|
import { handleGetCardRelations } from "./handlers/get_card_relations.js";
|
|
45
45
|
import { handleCreateCardRelation } from "./handlers/create_card_relation.js";
|
|
46
46
|
import { handleDeleteCardRelation } from "./handlers/delete_card_relation.js";
|
|
47
|
+
import { handleGetTestPlanById } from "./handlers/get_test_plan_by_id.js";
|
|
48
|
+
import { handleGetTestPlanTestCasesById } from "./handlers/get_test_plan_test_cases_by_id.js";
|
|
49
|
+
import { handleGetTestPlanTestCasesWithStepsById } from "./handlers/get_test_plan_test_cases_with_steps_by_id.js";
|
|
50
|
+
import { handleGetTestCaseById } from "./handlers/get_test_case_by_id.js";
|
|
51
|
+
import { handleUpdateTestCaseById } from "./handlers/update_test_case_by_id.js";
|
|
52
|
+
import { handleAddTestCaseStepById } from "./handlers/add_test_case_step_by_id.js";
|
|
53
|
+
import { handleUpdateTestCaseStepById } from "./handlers/update_test_case_step_by_id.js";
|
|
54
|
+
import { handleDeleteTestCaseStepById } from "./handlers/delete_test_case_step_by_id.js";
|
|
55
|
+
import { handleGetProcessWorkflows } from "./handlers/get_process_workflows.js";
|
|
56
|
+
import { handleGetProcesses } from "./handlers/get_processes.js";
|
|
57
|
+
import { handleGetBugWorkflows } from "./handlers/get_bug_workflows.js";
|
|
58
|
+
import { handleGetUserStoryWorkflows } from "./handlers/get_user_story_workflows.js";
|
|
59
|
+
import { handleGetRelationTypes } from "./handlers/get_relation_types.js";
|
|
60
|
+
import { handleGetVersion } from "./handlers/get_version.js";
|
|
47
61
|
const server = new McpServer({
|
|
48
62
|
name: "tp",
|
|
49
63
|
version: "1.0.0"
|
|
@@ -95,11 +109,13 @@ server.registerTool('get_release_bugs', {
|
|
|
95
109
|
name: z.string()
|
|
96
110
|
.describe('Release name'),
|
|
97
111
|
results: z.number()
|
|
98
|
-
.default(
|
|
112
|
+
.default(300)
|
|
99
113
|
.optional()
|
|
100
114
|
.describe('Number of results to return, default is 100'),
|
|
115
|
+
withDescription: z.boolean()
|
|
116
|
+
.describe('Include description in the response'),
|
|
101
117
|
},
|
|
102
|
-
}, async ({ name, results }) => handleGetReleaseBugs(tp, name, results));
|
|
118
|
+
}, async ({ name, results, withDescription }) => handleGetReleaseBugs(tp, name, results, withDescription));
|
|
103
119
|
server.registerTool('get_release_features', {
|
|
104
120
|
title: 'Get release features',
|
|
105
121
|
description: 'Get release features',
|
|
@@ -1147,139 +1163,19 @@ server.registerTool('get_process_workflows', {
|
|
|
1147
1163
|
processId: z.string()
|
|
1148
1164
|
.describe('Process ID (e.g. 145636)'),
|
|
1149
1165
|
},
|
|
1150
|
-
}, async ({ processId }) =>
|
|
1151
|
-
const response = await tp.getProcessWorkflows({ processId });
|
|
1152
|
-
if (!response) {
|
|
1153
|
-
return {
|
|
1154
|
-
content: [{
|
|
1155
|
-
type: 'text',
|
|
1156
|
-
text: `Failed to get process workflows, JSON: ${JSON.stringify(response, null, 2)}`
|
|
1157
|
-
}],
|
|
1158
|
-
};
|
|
1159
|
-
}
|
|
1160
|
-
const items = response.items || [];
|
|
1161
|
-
if (items.length === 0) {
|
|
1162
|
-
return {
|
|
1163
|
-
content: [{
|
|
1164
|
-
type: 'text',
|
|
1165
|
-
text: `No process workflows found`,
|
|
1166
|
-
}],
|
|
1167
|
-
};
|
|
1168
|
-
}
|
|
1169
|
-
return {
|
|
1170
|
-
content: [{
|
|
1171
|
-
type: 'text',
|
|
1172
|
-
text: JSON.stringify(items)
|
|
1173
|
-
}],
|
|
1174
|
-
};
|
|
1175
|
-
});
|
|
1166
|
+
}, async ({ processId }) => handleGetProcessWorkflows(tp, processId));
|
|
1176
1167
|
server.registerTool('get_processes', {
|
|
1177
1168
|
title: 'Get processes',
|
|
1178
1169
|
description: 'Get all Targetprocess processes',
|
|
1179
|
-
}, async (
|
|
1180
|
-
const response = await tp.getProcesses();
|
|
1181
|
-
if (!response) {
|
|
1182
|
-
return {
|
|
1183
|
-
content: [{
|
|
1184
|
-
type: 'text',
|
|
1185
|
-
text: `Failed to get processes, JSON: ${JSON.stringify(response, null, 2)}`
|
|
1186
|
-
}],
|
|
1187
|
-
};
|
|
1188
|
-
}
|
|
1189
|
-
const items = response.items || [];
|
|
1190
|
-
if (items.length === 0) {
|
|
1191
|
-
return {
|
|
1192
|
-
content: [{
|
|
1193
|
-
type: 'text',
|
|
1194
|
-
text: `No processes found`,
|
|
1195
|
-
}],
|
|
1196
|
-
};
|
|
1197
|
-
}
|
|
1198
|
-
return {
|
|
1199
|
-
content: [{
|
|
1200
|
-
type: 'text',
|
|
1201
|
-
text: JSON.stringify(items)
|
|
1202
|
-
}],
|
|
1203
|
-
};
|
|
1204
|
-
});
|
|
1170
|
+
}, async () => handleGetProcesses(tp));
|
|
1205
1171
|
server.registerTool('get_bug_workflows', {
|
|
1206
1172
|
title: 'Get bug workflows',
|
|
1207
1173
|
description: 'Get all Targetprocess bug workflows',
|
|
1208
|
-
}, async (
|
|
1209
|
-
const response = await tp.getBugWorkflows();
|
|
1210
|
-
if (!response) {
|
|
1211
|
-
return {
|
|
1212
|
-
content: [{
|
|
1213
|
-
type: 'text',
|
|
1214
|
-
text: `Failed to get bug entity statuses, JSON: ${JSON.stringify(response, null, 2)}`
|
|
1215
|
-
}],
|
|
1216
|
-
};
|
|
1217
|
-
}
|
|
1218
|
-
const items = response.items || [];
|
|
1219
|
-
if (items.length === 0) {
|
|
1220
|
-
return {
|
|
1221
|
-
content: [{
|
|
1222
|
-
type: 'text',
|
|
1223
|
-
text: `No status data found for workflows`
|
|
1224
|
-
}],
|
|
1225
|
-
};
|
|
1226
|
-
}
|
|
1227
|
-
const workflows = items.map((w) => ({
|
|
1228
|
-
id: w.id,
|
|
1229
|
-
name: w.name,
|
|
1230
|
-
processId: w.process,
|
|
1231
|
-
entityType: w.entityType,
|
|
1232
|
-
entityStates: w.entityStates.map((es) => ({
|
|
1233
|
-
id: es.id,
|
|
1234
|
-
name: es.name,
|
|
1235
|
-
})),
|
|
1236
|
-
}));
|
|
1237
|
-
return {
|
|
1238
|
-
content: [{
|
|
1239
|
-
type: 'text',
|
|
1240
|
-
text: JSON.stringify(workflows)
|
|
1241
|
-
}],
|
|
1242
|
-
};
|
|
1243
|
-
});
|
|
1174
|
+
}, async () => handleGetBugWorkflows(tp));
|
|
1244
1175
|
server.registerTool('get_user_story_workflows', {
|
|
1245
1176
|
title: 'Get User Story workflows',
|
|
1246
1177
|
description: 'Get all Targetprocess user story workflows, with sub-states',
|
|
1247
|
-
}, async (
|
|
1248
|
-
const response = await tp.getUserStoryWorkflowsWithSubStates();
|
|
1249
|
-
if (!response) {
|
|
1250
|
-
return {
|
|
1251
|
-
content: [{
|
|
1252
|
-
type: 'text',
|
|
1253
|
-
text: `Failed to get user story entity statuses, JSON: ${JSON.stringify(response, null, 2)}`
|
|
1254
|
-
}],
|
|
1255
|
-
};
|
|
1256
|
-
}
|
|
1257
|
-
const items = response.items || [];
|
|
1258
|
-
if (items.length === 0) {
|
|
1259
|
-
return {
|
|
1260
|
-
content: [{
|
|
1261
|
-
type: 'text',
|
|
1262
|
-
text: `No status data found for workflows`
|
|
1263
|
-
}],
|
|
1264
|
-
};
|
|
1265
|
-
}
|
|
1266
|
-
const userStoryWorkflows = items.filter((w) => w.entityType.name === "UserStory");
|
|
1267
|
-
const workflows = userStoryWorkflows.map((w) => ({
|
|
1268
|
-
id: w.id,
|
|
1269
|
-
processId: w.workflow.process.id,
|
|
1270
|
-
entityType: w.entityType.name,
|
|
1271
|
-
entityStates: w.subEntityStates.map((es) => ({
|
|
1272
|
-
id: es.id,
|
|
1273
|
-
name: es.name,
|
|
1274
|
-
})),
|
|
1275
|
-
}));
|
|
1276
|
-
return {
|
|
1277
|
-
content: [{
|
|
1278
|
-
type: 'text',
|
|
1279
|
-
text: JSON.stringify(workflows)
|
|
1280
|
-
}],
|
|
1281
|
-
};
|
|
1282
|
-
});
|
|
1178
|
+
}, async () => handleGetUserStoryWorkflows(tp));
|
|
1283
1179
|
server.registerTool('get_card_current_status', {
|
|
1284
1180
|
title: 'Get card status',
|
|
1285
1181
|
description: 'Get the EntityState, TeamState, and assigned teams for a TP card (UserStory, Bug, or Feature) by ID',
|
|
@@ -1310,18 +1206,7 @@ server.registerTool('get_card_relations', {
|
|
|
1310
1206
|
server.registerTool('get_relation_types', {
|
|
1311
1207
|
title: 'Get relation types',
|
|
1312
1208
|
description: 'Get all relation types available in this Targetprocess instance (id + name). Use this to find the correct relationType name for "create_card_relation".',
|
|
1313
|
-
}, async () =>
|
|
1314
|
-
const response = await tp.getRelationTypes();
|
|
1315
|
-
if (!response) {
|
|
1316
|
-
return {
|
|
1317
|
-
content: [{ type: 'text', text: `Failed to get relation types` }],
|
|
1318
|
-
};
|
|
1319
|
-
}
|
|
1320
|
-
const items = (response.Items || []).map((t) => ({ id: t.Id, name: t.Name }));
|
|
1321
|
-
return {
|
|
1322
|
-
content: [{ type: 'text', text: JSON.stringify(items) }],
|
|
1323
|
-
};
|
|
1324
|
-
});
|
|
1209
|
+
}, async () => handleGetRelationTypes(tp));
|
|
1325
1210
|
server.registerTool('create_card_relation', {
|
|
1326
1211
|
title: 'Create a relation between two cards',
|
|
1327
1212
|
description: `Create a relation between two TP cards (UserStory, Bug, Feature, etc.).
|
|
@@ -1457,9 +1342,103 @@ server.registerTool('get_version', {
|
|
|
1457
1342
|
title: 'Get server version',
|
|
1458
1343
|
description: 'Returns the current version of the MCP server from package.json.',
|
|
1459
1344
|
inputSchema: {},
|
|
1460
|
-
}, async () => (
|
|
1461
|
-
|
|
1462
|
-
|
|
1345
|
+
}, async () => handleGetVersion(version));
|
|
1346
|
+
server.registerTool('get_test_plan_by_id', {
|
|
1347
|
+
title: 'Get test plan by ID',
|
|
1348
|
+
description: 'Get a Targetprocess Test Plan by its ID, including name, plain-text description, state, and linked card.',
|
|
1349
|
+
inputSchema: {
|
|
1350
|
+
id: z.string()
|
|
1351
|
+
.min(5)
|
|
1352
|
+
.max(6)
|
|
1353
|
+
.describe('Test plan ID (e.g. 145789)'),
|
|
1354
|
+
},
|
|
1355
|
+
}, async ({ id }) => handleGetTestPlanById(tp, id));
|
|
1356
|
+
server.registerTool('get_test_plan_test_cases_by_id', {
|
|
1357
|
+
title: 'Get test plan test cases by ID',
|
|
1358
|
+
description: 'Get test cases belonging to a Targetprocess Test Plan by plan ID, including cases in nested child test plans/containers. Returns id, name, plain-text description, and containing test plan metadata (no steps).',
|
|
1359
|
+
inputSchema: {
|
|
1360
|
+
id: z.string()
|
|
1361
|
+
.min(5)
|
|
1362
|
+
.max(6)
|
|
1363
|
+
.describe('Test plan ID (e.g. 145789)'),
|
|
1364
|
+
},
|
|
1365
|
+
}, async ({ id }) => handleGetTestPlanTestCasesById(tp, id));
|
|
1366
|
+
server.registerTool('get_test_plan_test_cases_with_steps_by_id', {
|
|
1367
|
+
title: 'Get test plan test cases with steps by ID',
|
|
1368
|
+
description: 'Get test cases belonging to a Targetprocess Test Plan by plan ID, including nested child test plans/containers and each test case steps.',
|
|
1369
|
+
inputSchema: {
|
|
1370
|
+
id: z.string()
|
|
1371
|
+
.min(5)
|
|
1372
|
+
.max(6)
|
|
1373
|
+
.describe('Test plan ID (e.g. 145789)'),
|
|
1374
|
+
},
|
|
1375
|
+
}, async ({ id }) => handleGetTestPlanTestCasesWithStepsById(tp, id));
|
|
1376
|
+
server.registerTool('get_test_case_by_id', {
|
|
1377
|
+
title: 'Get test case by ID',
|
|
1378
|
+
description: 'Get a single Targetprocess Test Case by its ID, including plain-text description and its steps.',
|
|
1379
|
+
inputSchema: {
|
|
1380
|
+
id: z.string()
|
|
1381
|
+
.min(5)
|
|
1382
|
+
.max(6)
|
|
1383
|
+
.describe('Test case ID (e.g. 145789)'),
|
|
1384
|
+
},
|
|
1385
|
+
}, async ({ id }) => handleGetTestCaseById(tp, id));
|
|
1386
|
+
server.registerTool('update_test_case_by_id', {
|
|
1387
|
+
title: 'Update test case by ID',
|
|
1388
|
+
description: 'Update a Targetprocess Test Case by its ID. Supports name and description only.',
|
|
1389
|
+
inputSchema: {
|
|
1390
|
+
id: z.string()
|
|
1391
|
+
.min(5)
|
|
1392
|
+
.max(6)
|
|
1393
|
+
.describe('Test case ID (e.g. 145789)'),
|
|
1394
|
+
name: z.string()
|
|
1395
|
+
.optional()
|
|
1396
|
+
.describe('Updated test case name'),
|
|
1397
|
+
description: z.string()
|
|
1398
|
+
.optional()
|
|
1399
|
+
.describe('Updated test case description (format as HTML or plain text)'),
|
|
1400
|
+
},
|
|
1401
|
+
}, async ({ id, name, description }) => handleUpdateTestCaseById(tp, { id, name, description }));
|
|
1402
|
+
server.registerTool('add_test_case_step_by_id', {
|
|
1403
|
+
title: 'Add test case step by test case ID',
|
|
1404
|
+
description: 'Add a new step to a Targetprocess Test Case. Despite tool name consistency, this takes testCaseId, not a step ID.',
|
|
1405
|
+
inputSchema: {
|
|
1406
|
+
testCaseId: z.string()
|
|
1407
|
+
.min(5)
|
|
1408
|
+
.max(6)
|
|
1409
|
+
.describe('Test case ID to append the step to (e.g. 145789)'),
|
|
1410
|
+
description: z.string()
|
|
1411
|
+
.describe('Step action text'),
|
|
1412
|
+
result: z.string()
|
|
1413
|
+
.describe('Expected result for this step'),
|
|
1414
|
+
},
|
|
1415
|
+
}, async ({ testCaseId, description, result }) => handleAddTestCaseStepById(tp, { testCaseId, description, result }));
|
|
1416
|
+
server.registerTool('update_test_case_step_by_id', {
|
|
1417
|
+
title: 'Update test case step by ID',
|
|
1418
|
+
description: 'Update a Targetprocess Test Step by its ID. Supports description and result only.',
|
|
1419
|
+
inputSchema: {
|
|
1420
|
+
id: z.string()
|
|
1421
|
+
.min(5)
|
|
1422
|
+
.max(6)
|
|
1423
|
+
.describe('Test step ID (e.g. 145789)'),
|
|
1424
|
+
description: z.string()
|
|
1425
|
+
.optional()
|
|
1426
|
+
.describe('Updated step action text'),
|
|
1427
|
+
result: z.string()
|
|
1428
|
+
.optional()
|
|
1429
|
+
.describe('Updated expected result for this step'),
|
|
1430
|
+
},
|
|
1431
|
+
}, async ({ id, description, result }) => handleUpdateTestCaseStepById(tp, { id, description, result }));
|
|
1432
|
+
server.registerTool('delete_test_case_step_by_id', {
|
|
1433
|
+
title: 'Delete test case step by ID',
|
|
1434
|
+
description: 'Delete a Targetprocess Test Step by its ID.',
|
|
1435
|
+
inputSchema: {
|
|
1436
|
+
id: z.string()
|
|
1437
|
+
.min(5)
|
|
1438
|
+
.max(6)
|
|
1439
|
+
.describe('Test step ID (e.g. 145789)'),
|
|
1440
|
+
},
|
|
1441
|
+
}, async ({ id }) => handleDeleteTestCaseStepById(tp, id));
|
|
1463
1442
|
async function main() {
|
|
1464
1443
|
const transport = new StdioServerTransport();
|
|
1465
1444
|
await server.connect(transport);
|
package/build/tp.js
CHANGED
|
@@ -45,6 +45,7 @@ export class TpClient {
|
|
|
45
45
|
async get(params) {
|
|
46
46
|
params.param["access_token"] = this.token;
|
|
47
47
|
let _url = this.params(params);
|
|
48
|
+
console.error(JSON.stringify({ "TP_GET_URL": _url }));
|
|
48
49
|
try {
|
|
49
50
|
const response = await fetch(_url, {
|
|
50
51
|
method: "GET",
|
|
@@ -130,6 +131,35 @@ export class TpClient {
|
|
|
130
131
|
return { ok: false, status: 0, body: String(error) };
|
|
131
132
|
}
|
|
132
133
|
}
|
|
134
|
+
async getAllOrNull(params) {
|
|
135
|
+
const allItems = [];
|
|
136
|
+
let skip = 0;
|
|
137
|
+
const take = 100;
|
|
138
|
+
while (true) {
|
|
139
|
+
const page = await this.get({
|
|
140
|
+
...params,
|
|
141
|
+
param: {
|
|
142
|
+
...params.param,
|
|
143
|
+
take,
|
|
144
|
+
skip,
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
if (!page)
|
|
148
|
+
return null;
|
|
149
|
+
if (!page?.Items?.length)
|
|
150
|
+
break;
|
|
151
|
+
allItems.push(...page.Items);
|
|
152
|
+
if (!page.Next)
|
|
153
|
+
break;
|
|
154
|
+
skip += take;
|
|
155
|
+
}
|
|
156
|
+
return allItems;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
160
|
+
* TP
|
|
161
|
+
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
162
|
+
*/
|
|
133
163
|
async getUserStory(userStoryId) {
|
|
134
164
|
const response = await this.get({
|
|
135
165
|
pathParam: ["userStories", userStoryId],
|
|
@@ -517,7 +547,7 @@ export class TpClient {
|
|
|
517
547
|
param: {
|
|
518
548
|
"where": `Description contains '${text}' and EntityState.Name eq 'Done'`,
|
|
519
549
|
"format": "json",
|
|
520
|
-
"take": "
|
|
550
|
+
"take": "100",
|
|
521
551
|
},
|
|
522
552
|
});
|
|
523
553
|
}
|
|
@@ -530,7 +560,7 @@ export class TpClient {
|
|
|
530
560
|
},
|
|
531
561
|
});
|
|
532
562
|
}
|
|
533
|
-
async getReleaseUserStories({ name, results =
|
|
563
|
+
async getReleaseUserStories({ name, results = 100, withDescription = false }) {
|
|
534
564
|
const includeFilter = withDescription ? "[Name, Description, Id]" : "[Name, Id]";
|
|
535
565
|
return this.get({
|
|
536
566
|
pathParam: ["UserStories"],
|
|
@@ -542,7 +572,7 @@ export class TpClient {
|
|
|
542
572
|
}
|
|
543
573
|
});
|
|
544
574
|
}
|
|
545
|
-
async getReleaseOpenUserStories({ name, results =
|
|
575
|
+
async getReleaseOpenUserStories({ name, results = 300, withDescription = false }) {
|
|
546
576
|
const includeFilter = withDescription ? "[Name, Description, Id]" : "[Name, Id]";
|
|
547
577
|
return this.get({
|
|
548
578
|
pathParam: ["UserStories"],
|
|
@@ -554,7 +584,7 @@ export class TpClient {
|
|
|
554
584
|
}
|
|
555
585
|
});
|
|
556
586
|
}
|
|
557
|
-
async getReleaseOpenBugs({ name, results =
|
|
587
|
+
async getReleaseOpenBugs({ name, results = 300, withDescription = false }) {
|
|
558
588
|
const includeFilter = withDescription ? "[Name, Description, Id]" : "[Name, Id]";
|
|
559
589
|
return this.get({
|
|
560
590
|
pathParam: ["Bugs"],
|
|
@@ -566,8 +596,8 @@ export class TpClient {
|
|
|
566
596
|
}
|
|
567
597
|
});
|
|
568
598
|
}
|
|
569
|
-
async getReleaseBugs({ name, results =
|
|
570
|
-
const includeFilter = withDescription ? "[Name, Description, Id]" : "[Name, Id]";
|
|
599
|
+
async getReleaseBugs({ name, results = 500, withDescription = false }) {
|
|
600
|
+
const includeFilter = withDescription ? "[Name, Description, Id, Creator, Owner, Team]" : "[Name, Id]";
|
|
571
601
|
return this.get({
|
|
572
602
|
pathParam: ["Bugs"],
|
|
573
603
|
param: {
|
|
@@ -578,7 +608,7 @@ export class TpClient {
|
|
|
578
608
|
}
|
|
579
609
|
});
|
|
580
610
|
}
|
|
581
|
-
async getReleaseFeatures({ name, results =
|
|
611
|
+
async getReleaseFeatures({ name, results = 100, withDescription = false }) {
|
|
582
612
|
const includeFilter = withDescription ? "[Name, Description, Id]" : "[Name, Id]";
|
|
583
613
|
return this.get({
|
|
584
614
|
pathParam: ["Features"],
|
|
@@ -643,6 +673,79 @@ export class TpClient {
|
|
|
643
673
|
},
|
|
644
674
|
});
|
|
645
675
|
}
|
|
676
|
+
toTestPlanNode(testPlan, fallbackId) {
|
|
677
|
+
const id = testPlan?.Id ?? fallbackId;
|
|
678
|
+
if (id === undefined || id === null)
|
|
679
|
+
return null;
|
|
680
|
+
const numericId = Number(id);
|
|
681
|
+
return {
|
|
682
|
+
id: String(id),
|
|
683
|
+
numericId: Number.isNaN(numericId) ? 0 : numericId,
|
|
684
|
+
name: testPlan?.Name,
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
async getDirectTestPlanTestCaseItems(testPlan) {
|
|
688
|
+
const items = await this.getAllOrNull({
|
|
689
|
+
pathParam: ["testPlans", testPlan.id, "testcases"],
|
|
690
|
+
param: { "format": "json" },
|
|
691
|
+
});
|
|
692
|
+
if (!items)
|
|
693
|
+
return null;
|
|
694
|
+
return items.map((item) => ({
|
|
695
|
+
...item,
|
|
696
|
+
TestPlanId: testPlan.numericId,
|
|
697
|
+
TestPlanName: testPlan.name || item.LinkedTestPlan?.Name,
|
|
698
|
+
}));
|
|
699
|
+
}
|
|
700
|
+
async getChildTestPlanNodes(testPlanId) {
|
|
701
|
+
const items = await this.getAllOrNull({
|
|
702
|
+
pathParam: ["testPlans"],
|
|
703
|
+
param: {
|
|
704
|
+
"format": "json",
|
|
705
|
+
"where": `ParentTestPlans.Id eq ${testPlanId}`,
|
|
706
|
+
"include": "[Id,Name,ParentTestPlans[Id,Name]]",
|
|
707
|
+
},
|
|
708
|
+
});
|
|
709
|
+
if (!items)
|
|
710
|
+
return null;
|
|
711
|
+
return items
|
|
712
|
+
.map((item) => this.toTestPlanNode(item))
|
|
713
|
+
.filter((item) => Boolean(item));
|
|
714
|
+
}
|
|
715
|
+
async getIndirectTestPlanTestCases(testPlanId) {
|
|
716
|
+
const rootTestPlan = await this.getTestPlan(testPlanId);
|
|
717
|
+
const rootTestPlanNode = this.toTestPlanNode(rootTestPlan, testPlanId);
|
|
718
|
+
if (!rootTestPlanNode)
|
|
719
|
+
return null;
|
|
720
|
+
const queue = [rootTestPlanNode];
|
|
721
|
+
const visitedPlanIds = new Set();
|
|
722
|
+
const seenTestCaseIds = new Set();
|
|
723
|
+
const testCases = [];
|
|
724
|
+
while (queue.length > 0) {
|
|
725
|
+
const testPlan = queue.shift();
|
|
726
|
+
if (visitedPlanIds.has(testPlan.id))
|
|
727
|
+
continue;
|
|
728
|
+
visitedPlanIds.add(testPlan.id);
|
|
729
|
+
const directTestCases = await this.getDirectTestPlanTestCaseItems(testPlan);
|
|
730
|
+
if (!directTestCases)
|
|
731
|
+
return null;
|
|
732
|
+
for (const testCase of directTestCases) {
|
|
733
|
+
const testCaseId = String(testCase.Id);
|
|
734
|
+
if (seenTestCaseIds.has(testCaseId))
|
|
735
|
+
continue;
|
|
736
|
+
seenTestCaseIds.add(testCaseId);
|
|
737
|
+
testCases.push(testCase);
|
|
738
|
+
}
|
|
739
|
+
const childTestPlans = await this.getChildTestPlanNodes(testPlan.id);
|
|
740
|
+
if (!childTestPlans)
|
|
741
|
+
return null;
|
|
742
|
+
for (const childTestPlan of childTestPlans) {
|
|
743
|
+
if (!visitedPlanIds.has(childTestPlan.id))
|
|
744
|
+
queue.push(childTestPlan);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
return { Next: "", Items: testCases };
|
|
748
|
+
}
|
|
646
749
|
async getTestPlanTestCases(testPlanId) {
|
|
647
750
|
return this.get({
|
|
648
751
|
pathParam: ["testPlans", testPlanId, "testcases"],
|
|
@@ -655,6 +758,46 @@ export class TpClient {
|
|
|
655
758
|
param: { "format": "json" },
|
|
656
759
|
});
|
|
657
760
|
}
|
|
761
|
+
async getTestCase(testCaseId) {
|
|
762
|
+
return this.get({
|
|
763
|
+
pathParam: ["testCases", testCaseId],
|
|
764
|
+
param: { "format": "json" },
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
async updateTestCase({ id, name, description }) {
|
|
768
|
+
const testCase = { "Id": id };
|
|
769
|
+
if (name !== undefined)
|
|
770
|
+
testCase["Name"] = name;
|
|
771
|
+
if (description !== undefined)
|
|
772
|
+
testCase["Description"] = description;
|
|
773
|
+
return this.post({
|
|
774
|
+
pathParam: ["testCases"],
|
|
775
|
+
param: { "format": "json" },
|
|
776
|
+
}, testCase);
|
|
777
|
+
}
|
|
778
|
+
async getTestStep(testStepId) {
|
|
779
|
+
return this.get({
|
|
780
|
+
pathParam: ["testSteps", testStepId],
|
|
781
|
+
param: { "format": "json" },
|
|
782
|
+
});
|
|
783
|
+
}
|
|
784
|
+
async updateTestStep({ id, description, result }) {
|
|
785
|
+
const testStep = { "Id": id };
|
|
786
|
+
if (description !== undefined)
|
|
787
|
+
testStep["Description"] = description;
|
|
788
|
+
if (result !== undefined)
|
|
789
|
+
testStep["Result"] = result;
|
|
790
|
+
return this.post({
|
|
791
|
+
pathParam: ["testSteps"],
|
|
792
|
+
param: { "format": "json" },
|
|
793
|
+
}, testStep);
|
|
794
|
+
}
|
|
795
|
+
async deleteTestStep(testStepId) {
|
|
796
|
+
return this.del({
|
|
797
|
+
pathParam: ["testSteps", testStepId],
|
|
798
|
+
param: { "format": "json" },
|
|
799
|
+
});
|
|
800
|
+
}
|
|
658
801
|
async getProjects() {
|
|
659
802
|
return this.get({
|
|
660
803
|
pathParam: ["Projects"],
|
|
@@ -942,6 +1085,12 @@ export class TpClient {
|
|
|
942
1085
|
param: { "format": "json" },
|
|
943
1086
|
});
|
|
944
1087
|
}
|
|
1088
|
+
async getTestPlan(testPlanId) {
|
|
1089
|
+
return this.get({
|
|
1090
|
+
pathParam: ["testPlans", testPlanId],
|
|
1091
|
+
param: { "format": "json" },
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
945
1094
|
async addAttachedFile(generalId, source) {
|
|
946
1095
|
let blob;
|
|
947
1096
|
let fileName;
|