@smartbear/mcp 0.24.0 → 0.25.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/dist/bearq/client.js +12 -13
- package/dist/bearq/tool/tasks/chat-with-qa-lead.js +1 -0
- package/dist/bearq/tool/tasks/expand-application-model.js +1 -0
- package/dist/bearq/tool/tasks/get-task-status.js +1 -0
- package/dist/bearq/tool/tasks/get-task.js +1 -0
- package/dist/bearq/tool/tasks/refine-all-draft-tests.js +1 -0
- package/dist/bearq/tool/tasks/refine-test-cases.js +1 -0
- package/dist/bearq/tool/tasks/refine-tests-in-functional-areas.js +1 -0
- package/dist/bearq/tool/tasks/run-regression-tests.js +1 -0
- package/dist/bearq/tool/tasks/run-test-cases.js +1 -0
- package/dist/bearq/tool/tasks/run-tests-in-functional-areas.js +1 -0
- package/dist/bearq/tool/tasks/stop-task.js +1 -0
- package/dist/bearq/tool/tasks/wait-for-task.js +1 -0
- package/dist/bugsnag/client.js +24 -44
- package/dist/bugsnag/tool/error/get-error.js +1 -0
- package/dist/bugsnag/tool/error/list-project-errors.js +1 -0
- package/dist/bugsnag/tool/error/update-error.js +1 -0
- package/dist/bugsnag/tool/event/get-event-details-from-dashboard-url.js +1 -0
- package/dist/bugsnag/tool/event/get-event.js +1 -0
- package/dist/bugsnag/tool/event/list-error-events.js +1 -0
- package/dist/bugsnag/tool/performance/get-network-endpoint-groupings.js +1 -0
- package/dist/bugsnag/tool/performance/get-span-group.js +1 -0
- package/dist/bugsnag/tool/performance/get-trace.js +1 -0
- package/dist/bugsnag/tool/performance/list-span-groups.js +1 -0
- package/dist/bugsnag/tool/performance/list-spans.js +1 -0
- package/dist/bugsnag/tool/performance/list-trace-fields.js +1 -0
- package/dist/bugsnag/tool/performance/set-network-endpoint-groupings.js +1 -0
- package/dist/bugsnag/tool/project/get-current-project.js +1 -0
- package/dist/bugsnag/tool/project/list-project-event-filters.js +1 -0
- package/dist/bugsnag/tool/project/list-projects.js +1 -0
- package/dist/bugsnag/tool/release/get-build.js +1 -0
- package/dist/bugsnag/tool/release/get-release.js +1 -0
- package/dist/bugsnag/tool/release/list-releases.js +1 -0
- package/dist/collaborator/client.js +24 -19
- package/dist/common/client-registry.js +63 -29
- package/dist/common/server.js +57 -1
- package/dist/common/transport-http.js +90 -69
- package/dist/common/transport-stdio.js +29 -24
- package/dist/package.json.js +1 -1
- package/dist/pactflow/client/tools.js +102 -0
- package/dist/pactflow/client.js +30 -43
- package/dist/qmetry/client/tools/automation-tools.js +2 -0
- package/dist/qmetry/client/tools/issue-tools.js +6 -0
- package/dist/qmetry/client/tools/project-tools.js +9 -0
- package/dist/qmetry/client/tools/requirement-tools.js +5 -0
- package/dist/qmetry/client/tools/testcase-tools.js +7 -0
- package/dist/qmetry/client/tools/testsuite-tools.js +11 -0
- package/dist/qmetry/client.js +20 -18
- package/dist/qtm4j/client.js +42 -32
- package/dist/qtm4j/config/constants.js +101 -1
- package/dist/qtm4j/config/field-resolution.types.js +3 -1
- package/dist/qtm4j/http/api-client.js +20 -2
- package/dist/qtm4j/resolver/resolver-registry.js +7 -1
- package/dist/qtm4j/resolver/resolvers/requirement-id-resolver.js +28 -0
- package/dist/qtm4j/resolver/resolvers/test-cycle-uid-resolver.js +28 -0
- package/dist/qtm4j/schema/linked-items.schema.js +95 -0
- package/dist/qtm4j/schema/requirements.schema.js +109 -0
- package/dist/qtm4j/schema/test-cycle.link.schema.js +260 -0
- package/dist/qtm4j/tool/project/get-projects.js +2 -1
- package/dist/qtm4j/tool/project/set-project-context.js +2 -1
- package/dist/qtm4j/tool/requirement/get-linked-testcases.js +93 -0
- package/dist/qtm4j/tool/requirement/link-testcases.js +107 -0
- package/dist/qtm4j/tool/requirement/unlink-testcases.js +97 -0
- package/dist/qtm4j/tool/test-automation/get-automation-history.js +2 -1
- package/dist/qtm4j/tool/test-automation/upload-automation-result.js +2 -1
- package/dist/qtm4j/tool/test-case/create-test-case.js +2 -1
- package/dist/qtm4j/tool/test-case/get-linked-requirements.js +67 -0
- package/dist/qtm4j/tool/test-case/get-test-cases.js +2 -1
- package/dist/qtm4j/tool/test-case/get-test-steps.js +2 -1
- package/dist/qtm4j/tool/test-case/link-requirements.js +124 -0
- package/dist/qtm4j/tool/test-case/unlink-requirements.js +116 -0
- package/dist/qtm4j/tool/test-case/update-test-case.js +2 -1
- package/dist/qtm4j/tool/test-cycle/create-test-cycle.js +2 -1
- package/dist/qtm4j/tool/test-cycle/get-linked-requirements.js +71 -0
- package/dist/qtm4j/tool/test-cycle/link-requirements.js +91 -0
- package/dist/qtm4j/tool/test-cycle/link-testcases.js +118 -0
- package/dist/qtm4j/tool/test-cycle/search-linked-testcases.js +114 -0
- package/dist/qtm4j/tool/test-cycle/search-test-cycle.js +2 -1
- package/dist/qtm4j/tool/test-cycle/unlink-requirements.js +87 -0
- package/dist/qtm4j/tool/test-cycle/unlink-testcases.js +103 -0
- package/dist/qtm4j/tool/test-cycle/update-test-cycle.js +2 -1
- package/dist/reflect/client.js +15 -24
- package/dist/reflect/config/constants.js +0 -2
- package/dist/reflect/tool/recording/add-prompt-step.js +1 -0
- package/dist/reflect/tool/recording/add-segment.js +1 -0
- package/dist/reflect/tool/recording/connect-to-session.js +1 -0
- package/dist/reflect/tool/recording/delete-previous-step.js +1 -0
- package/dist/reflect/tool/recording/get-screenshot.js +1 -0
- package/dist/reflect/tool/suites/cancel-suite-execution.js +1 -0
- package/dist/reflect/tool/suites/execute-suite.js +1 -0
- package/dist/reflect/tool/suites/get-suite-execution-status.js +1 -0
- package/dist/reflect/tool/suites/list-suite-executions.js +1 -0
- package/dist/reflect/tool/suites/list-suites.js +1 -0
- package/dist/reflect/tool/tests/get-test-status.js +1 -0
- package/dist/reflect/tool/tests/list-segments.js +1 -0
- package/dist/reflect/tool/tests/list-tests.js +1 -0
- package/dist/reflect/tool/tests/run-test.js +1 -0
- package/dist/swagger/client/tools.js +23 -0
- package/dist/swagger/client.js +25 -28
- package/dist/zephyr/client.js +14 -21
- package/dist/zephyr/tool/environment/get-environments.js +1 -0
- package/dist/zephyr/tool/folder/create-folder.js +1 -0
- package/dist/zephyr/tool/issue-link/get-test-cases.js +1 -0
- package/dist/zephyr/tool/issue-link/get-test-cycles.js +1 -0
- package/dist/zephyr/tool/issue-link/get-test-executions.js +1 -0
- package/dist/zephyr/tool/priority/get-priorities.js +1 -0
- package/dist/zephyr/tool/project/get-project.js +1 -0
- package/dist/zephyr/tool/project/get-projects.js +1 -0
- package/dist/zephyr/tool/status/get-statuses.js +1 -0
- package/dist/zephyr/tool/test-case/create-issue-link.js +1 -0
- package/dist/zephyr/tool/test-case/create-test-case.js +1 -0
- package/dist/zephyr/tool/test-case/create-test-script.js +1 -0
- package/dist/zephyr/tool/test-case/create-test-steps.js +1 -0
- package/dist/zephyr/tool/test-case/create-web-link.js +1 -0
- package/dist/zephyr/tool/test-case/get-links.js +1 -0
- package/dist/zephyr/tool/test-case/get-test-case.js +1 -0
- package/dist/zephyr/tool/test-case/get-test-cases.js +1 -0
- package/dist/zephyr/tool/test-case/get-test-script.js +1 -0
- package/dist/zephyr/tool/test-case/get-test-steps.js +1 -0
- package/dist/zephyr/tool/test-case/update-test-case.js +1 -0
- package/dist/zephyr/tool/test-cycle/create-issue-link.js +1 -0
- package/dist/zephyr/tool/test-cycle/create-test-cycle.js +1 -0
- package/dist/zephyr/tool/test-cycle/create-web-link.js +1 -0
- package/dist/zephyr/tool/test-cycle/get-links.js +1 -0
- package/dist/zephyr/tool/test-cycle/get-test-cycle.js +1 -0
- package/dist/zephyr/tool/test-cycle/get-test-cycles.js +1 -0
- package/dist/zephyr/tool/test-cycle/update-test-cycle.js +1 -0
- package/dist/zephyr/tool/test-execution/create-issue-link.js +1 -0
- package/dist/zephyr/tool/test-execution/create-test-execution.js +1 -0
- package/dist/zephyr/tool/test-execution/get-test-execution-links.js +1 -0
- package/dist/zephyr/tool/test-execution/get-test-execution.js +1 -0
- package/dist/zephyr/tool/test-execution/get-test-executions.js +1 -0
- package/dist/zephyr/tool/test-execution/get-test-steps.js +1 -0
- package/dist/zephyr/tool/test-execution/update-test-execution.js +1 -0
- package/dist/zephyr/tool/test-execution/update-test-steps.js +1 -0
- package/package.json +1 -1
- package/dist/common/request-context.js +0 -20
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Tool, ToolError } from "../../../common/tools.js";
|
|
2
|
+
import { TOOLSETS, TOOL_NAMES, ENDPOINTS } from "../../config/constants.js";
|
|
3
|
+
import { ResolverKeys } from "../../config/field-resolution.types.js";
|
|
4
|
+
import { GetLinkedRequirementsResponse, GetLinkedRequirementsBody } from "../../schema/linked-items.schema.js";
|
|
5
|
+
class GetLinkedRequirements extends Tool {
|
|
6
|
+
specification = {
|
|
7
|
+
title: TOOL_NAMES.GET_LINKED_REQUIREMENTS.TITLE,
|
|
8
|
+
summary: TOOL_NAMES.GET_LINKED_REQUIREMENTS.SUMMARY,
|
|
9
|
+
readOnly: true,
|
|
10
|
+
idempotent: true,
|
|
11
|
+
toolset: TOOLSETS.TEST_CASES,
|
|
12
|
+
inputSchema: GetLinkedRequirementsBody,
|
|
13
|
+
outputSchema: GetLinkedRequirementsResponse,
|
|
14
|
+
purpose: "Retrieve all Jira requirements linked to a specific test case in QTM4J. The test case's human-readable key (e.g. 'SCRUM-TC-145') is resolved to the internal UID automatically. Returns a paginated list of linked requirements with their Jira metadata (key, summary, status, priority, issue type). PREREQUISITE: set_project_context must be called before this tool.",
|
|
15
|
+
useCases: [
|
|
16
|
+
"Check which Jira stories or bugs a test case covers",
|
|
17
|
+
"Audit requirement traceability for a test case",
|
|
18
|
+
"Retrieve requirement keys to use in other operations",
|
|
19
|
+
"Verify that the correct requirements are linked to a test case before a release"
|
|
20
|
+
],
|
|
21
|
+
examples: [
|
|
22
|
+
{
|
|
23
|
+
description: "Get all requirements linked to a test case",
|
|
24
|
+
parameters: { key: "SCRUM-TC-145" },
|
|
25
|
+
expectedOutput: "Paginated list of linked requirements with Jira metadata"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
description: "Get requirements for a specific version",
|
|
29
|
+
parameters: { key: "SCRUM-TC-85", versionNo: 2, maxResults: 20 },
|
|
30
|
+
expectedOutput: "Requirements linked to version 2 of the test case"
|
|
31
|
+
}
|
|
32
|
+
],
|
|
33
|
+
hints: [
|
|
34
|
+
"PREREQUISITE: set_project_context must be called before this tool. NEVER auto-select a project.",
|
|
35
|
+
"KEY FORMAT: '{PROJECT_KEY}-TC-{number}' — e.g. 'SCRUM-TC-145'.",
|
|
36
|
+
"versionNo defaults to the latest version. Use search_test_cases to find available versions.",
|
|
37
|
+
"Paginate using startAt — increment by maxResults until startAt >= total."
|
|
38
|
+
],
|
|
39
|
+
outputDescription: "Paginated list with total, startAt, maxResults, and data array of linked requirement objects (id, key, summary, status, priority, issueType)."
|
|
40
|
+
};
|
|
41
|
+
handle = async (rawArgs) => {
|
|
42
|
+
const args = GetLinkedRequirementsBody.parse(rawArgs);
|
|
43
|
+
const fieldResolver = this.client.getResolverRegistry();
|
|
44
|
+
const context = fieldResolver.requireProjectContext();
|
|
45
|
+
const uidMap = await fieldResolver.getResolver(ResolverKeys.SearchableField.TEST_CASE_KEY_TO_UID).resolveAndReturn(context.projectId, [args.key]);
|
|
46
|
+
const entry = uidMap[args.key];
|
|
47
|
+
if (!entry) {
|
|
48
|
+
throw new ToolError(
|
|
49
|
+
`Test case '${args.key}' not found in project '${context.projectKey}'. Verify the key using the search_test_cases tool.`
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
const params = {
|
|
53
|
+
maxResults: args.maxResults,
|
|
54
|
+
startAt: args.startAt,
|
|
55
|
+
sort: args.sort,
|
|
56
|
+
tcVersionNo: args.versionNo
|
|
57
|
+
};
|
|
58
|
+
const response = await this.client.getApiClient().get(ENDPOINTS.GET_LINKED_REQUIREMENTS(entry.uid), params);
|
|
59
|
+
return {
|
|
60
|
+
structuredContent: GetLinkedRequirementsResponse.parse(response),
|
|
61
|
+
content: []
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export {
|
|
66
|
+
GetLinkedRequirements
|
|
67
|
+
};
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { Tool } from "../../../common/tools.js";
|
|
2
|
-
import { TOOL_NAMES, RESPONSE_FIELDS, ENDPOINTS } from "../../config/constants.js";
|
|
2
|
+
import { TOOL_NAMES, TOOLSETS, RESPONSE_FIELDS, ENDPOINTS } from "../../config/constants.js";
|
|
3
3
|
import { SearchTestCaseResponse, SearchTestCaseBody } from "../../schema/get-test-case.schema.js";
|
|
4
4
|
class GetTestCases extends Tool {
|
|
5
5
|
specification = {
|
|
6
6
|
title: TOOL_NAMES.SEARCH_TEST_CASES.TITLE,
|
|
7
|
+
toolset: TOOLSETS.TEST_CASES,
|
|
7
8
|
summary: TOOL_NAMES.SEARCH_TEST_CASES.SUMMARY,
|
|
8
9
|
readOnly: true,
|
|
9
10
|
idempotent: true,
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { Tool, ToolError } from "../../../common/tools.js";
|
|
2
|
-
import { TOOL_NAMES, RESPONSE_FIELDS, ENDPOINTS } from "../../config/constants.js";
|
|
2
|
+
import { TOOL_NAMES, TOOLSETS, RESPONSE_FIELDS, ENDPOINTS } from "../../config/constants.js";
|
|
3
3
|
import { ResolverKeys } from "../../config/field-resolution.types.js";
|
|
4
4
|
import { GetTestStepsResponse, GetTestStepsBody } from "../../schema/get-test-steps.schema.js";
|
|
5
5
|
class GetTestSteps extends Tool {
|
|
6
6
|
specification = {
|
|
7
7
|
title: TOOL_NAMES.GET_TEST_STEPS.TITLE,
|
|
8
|
+
toolset: TOOLSETS.TEST_CASES,
|
|
8
9
|
summary: TOOL_NAMES.GET_TEST_STEPS.SUMMARY,
|
|
9
10
|
readOnly: true,
|
|
10
11
|
idempotent: true,
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import zod__default from "zod";
|
|
2
|
+
import { Tool, ToolError } from "../../../common/tools.js";
|
|
3
|
+
import { TOOLSETS, TOOL_NAMES, ENDPOINTS } from "../../config/constants.js";
|
|
4
|
+
import { ResolverKeys } from "../../config/field-resolution.types.js";
|
|
5
|
+
const LinkRequirementsBody = zod__default.object({
|
|
6
|
+
key: zod__default.string().describe(
|
|
7
|
+
"Test case key in '{PROJECT_KEY}-TC-{number}' format (e.g., 'SCRUM-TC-145'). Required."
|
|
8
|
+
),
|
|
9
|
+
versionNo: zod__default.number().int().optional().describe("Test case version number. Defaults to the latest version."),
|
|
10
|
+
requirementKeys: zod__default.array(zod__default.string()).optional().describe(
|
|
11
|
+
"List of requirement keys to link (e.g., ['SCRUM-1', 'SCRUM-2']). Resolved to internal IDs automatically. Provide this OR filter.jql — not both."
|
|
12
|
+
),
|
|
13
|
+
filter: zod__default.object({
|
|
14
|
+
jql: zod__default.string().describe(
|
|
15
|
+
'JQL query to filter requirements to link (e.g., "project = DEMO AND issuetype = Story").'
|
|
16
|
+
)
|
|
17
|
+
}).optional().describe(
|
|
18
|
+
"JQL filter to select requirements to link. Use instead of requirementKeys when filtering by JQL."
|
|
19
|
+
)
|
|
20
|
+
});
|
|
21
|
+
const LinkRequirementsResponse = zod__default.object({
|
|
22
|
+
key: zod__default.string(),
|
|
23
|
+
versionNo: zod__default.number().int(),
|
|
24
|
+
linked: zod__default.literal(true)
|
|
25
|
+
});
|
|
26
|
+
class LinkRequirements extends Tool {
|
|
27
|
+
specification = {
|
|
28
|
+
title: TOOL_NAMES.LINK_REQUIREMENTS.TITLE,
|
|
29
|
+
summary: TOOL_NAMES.LINK_REQUIREMENTS.SUMMARY,
|
|
30
|
+
readOnly: false,
|
|
31
|
+
idempotent: false,
|
|
32
|
+
toolset: TOOLSETS.TEST_CASES,
|
|
33
|
+
inputSchema: LinkRequirementsBody,
|
|
34
|
+
outputSchema: LinkRequirementsResponse,
|
|
35
|
+
purpose: "Link Jira requirements to a test case in QTM4J using the test case's human-readable key. Requirements can be specified by their human-readable keys (e.g. 'SCRUM-1') which are resolved to internal IDs automatically, or via a JQL filter. Requirements that cannot be linked are reported as warnings. PREREQUISITE: set_project_context must be called before this tool.",
|
|
36
|
+
useCases: [
|
|
37
|
+
"Link one or more Jira requirements (stories, bugs, epics) to a test case",
|
|
38
|
+
"Associate requirements with a test case using a JQL filter",
|
|
39
|
+
"Build traceability between requirements and test cases",
|
|
40
|
+
"Link requirements to a specific test case version"
|
|
41
|
+
],
|
|
42
|
+
examples: [
|
|
43
|
+
{
|
|
44
|
+
description: "Link two requirements by key",
|
|
45
|
+
parameters: {
|
|
46
|
+
key: "SCRUM-TC-145",
|
|
47
|
+
requirementKeys: ["SCRUM-1", "SCRUM-2"]
|
|
48
|
+
},
|
|
49
|
+
expectedOutput: "Requirements SCRUM-1 and SCRUM-2 linked to test case"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
description: "Link requirements by JQL filter",
|
|
53
|
+
parameters: {
|
|
54
|
+
key: "SCRUM-TC-145",
|
|
55
|
+
filter: { jql: "project = DEMO AND issuetype = Story" }
|
|
56
|
+
},
|
|
57
|
+
expectedOutput: "Requirements matched by JQL linked to test case"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
description: "Link a requirement to a specific version",
|
|
61
|
+
parameters: {
|
|
62
|
+
key: "SCRUM-TC-85",
|
|
63
|
+
versionNo: 2,
|
|
64
|
+
requirementKeys: ["SCRUM-10"]
|
|
65
|
+
},
|
|
66
|
+
expectedOutput: "Requirement linked to version 2 of test case"
|
|
67
|
+
}
|
|
68
|
+
],
|
|
69
|
+
hints: [
|
|
70
|
+
"PREREQUISITE: set_project_context must be called before this tool. NEVER auto-select a project.",
|
|
71
|
+
"KEY FORMAT: '{PROJECT_KEY}-TC-{number}' — e.g. 'SCRUM-TC-145'.",
|
|
72
|
+
"Requirement keys follow the Jira issue key format: '{PROJECT_KEY}-{number}' (e.g. 'SCRUM-1').",
|
|
73
|
+
"Provide either requirementKeys or filter.jql — not both.",
|
|
74
|
+
"If a requirement key cannot be resolved or linked, it is reported in warnings and other requirements are still linked.",
|
|
75
|
+
"versionNo defaults to the latest version. Use search_test_cases to find available versions if needed."
|
|
76
|
+
],
|
|
77
|
+
outputDescription: "Confirmation with the test case key, version number, and linked: true. Warnings are included if any requirements could not be resolved or linked."
|
|
78
|
+
};
|
|
79
|
+
handle = async (rawArgs) => {
|
|
80
|
+
const args = LinkRequirementsBody.parse(rawArgs);
|
|
81
|
+
const fieldResolver = this.client.getResolverRegistry();
|
|
82
|
+
const context = fieldResolver.requireProjectContext();
|
|
83
|
+
const warnings = [];
|
|
84
|
+
const uidMap = await fieldResolver.getResolver(ResolverKeys.SearchableField.TEST_CASE_KEY_TO_UID).resolveAndReturn(context.projectId, [args.key]);
|
|
85
|
+
const entry = uidMap[args.key];
|
|
86
|
+
if (!entry) {
|
|
87
|
+
throw new ToolError(
|
|
88
|
+
`Test case '${args.key}' not found in project '${context.projectKey}'. Verify the key using the search_test_cases tool.`
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
const versionNo = args.versionNo ?? entry.latestVersion;
|
|
92
|
+
const body = {};
|
|
93
|
+
if (args.requirementKeys?.length) {
|
|
94
|
+
const reqMap = await fieldResolver.getResolver(ResolverKeys.SearchableField.REQUIREMENT_KEY_TO_ID).resolveAndReturn(context.projectId, args.requirementKeys);
|
|
95
|
+
const requirementIds = [];
|
|
96
|
+
for (const reqKey of args.requirementKeys) {
|
|
97
|
+
const resolved = reqMap[reqKey];
|
|
98
|
+
if (resolved) {
|
|
99
|
+
requirementIds.push(Number(resolved.id));
|
|
100
|
+
} else {
|
|
101
|
+
warnings.push(
|
|
102
|
+
`Requirement '${reqKey}' could not be resolved and was skipped.`
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (requirementIds.length > 0) body.requirementIds = requirementIds;
|
|
107
|
+
}
|
|
108
|
+
if (args.filter) {
|
|
109
|
+
body.filter = args.filter;
|
|
110
|
+
}
|
|
111
|
+
await this.client.getApiClient().post(ENDPOINTS.LINK_REQUIREMENTS(entry.uid, versionNo), body);
|
|
112
|
+
return {
|
|
113
|
+
structuredContent: LinkRequirementsResponse.parse({
|
|
114
|
+
key: args.key,
|
|
115
|
+
versionNo,
|
|
116
|
+
linked: true
|
|
117
|
+
}),
|
|
118
|
+
content: warnings.length > 0 ? [{ type: "text", text: `Note: ${warnings.join(" | ")}` }] : []
|
|
119
|
+
};
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
export {
|
|
123
|
+
LinkRequirements
|
|
124
|
+
};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import zod__default from "zod";
|
|
2
|
+
import { Tool, ToolError } from "../../../common/tools.js";
|
|
3
|
+
import { TOOLSETS, TOOL_NAMES, ENDPOINTS } from "../../config/constants.js";
|
|
4
|
+
import { ResolverKeys } from "../../config/field-resolution.types.js";
|
|
5
|
+
const UnlinkRequirementsBody = zod__default.object({
|
|
6
|
+
key: zod__default.string().describe(
|
|
7
|
+
"Test case key in '{PROJECT_KEY}-TC-{number}' format (e.g., 'SCRUM-TC-145'). Required."
|
|
8
|
+
),
|
|
9
|
+
versionNo: zod__default.number().int().optional().describe("Test case version number. Defaults to the latest version."),
|
|
10
|
+
requirementKeys: zod__default.array(zod__default.string()).optional().describe(
|
|
11
|
+
"List of requirement keys to unlink (e.g., ['SCRUM-1', 'SCRUM-2']). Ignored when unLinkAll is true."
|
|
12
|
+
),
|
|
13
|
+
unLinkAll: zod__default.boolean().optional().describe(
|
|
14
|
+
"If true, all linked requirements are unlinked from this test case. Ignores requirementKeys when set."
|
|
15
|
+
)
|
|
16
|
+
});
|
|
17
|
+
const UnlinkRequirementsResponse = zod__default.object({
|
|
18
|
+
key: zod__default.string(),
|
|
19
|
+
versionNo: zod__default.number().int(),
|
|
20
|
+
unlinked: zod__default.literal(true)
|
|
21
|
+
});
|
|
22
|
+
class UnlinkRequirements extends Tool {
|
|
23
|
+
specification = {
|
|
24
|
+
title: TOOL_NAMES.UNLINK_REQUIREMENTS.TITLE,
|
|
25
|
+
summary: TOOL_NAMES.UNLINK_REQUIREMENTS.SUMMARY,
|
|
26
|
+
readOnly: false,
|
|
27
|
+
idempotent: false,
|
|
28
|
+
toolset: TOOLSETS.TEST_CASES,
|
|
29
|
+
inputSchema: UnlinkRequirementsBody,
|
|
30
|
+
outputSchema: UnlinkRequirementsResponse,
|
|
31
|
+
purpose: "Unlink Jira requirements from a test case in QTM4J using the test case's human-readable key. Specify individual requirement keys to unlink (resolved to internal IDs automatically), or set unLinkAll to true to remove all linked requirements in one call. Requirements that cannot be unlinked are reported as warnings. PREREQUISITE: set_project_context must be called before this tool.",
|
|
32
|
+
useCases: [
|
|
33
|
+
"Remove specific requirements from a test case",
|
|
34
|
+
"Unlink all requirements from a test case at once",
|
|
35
|
+
"Clean up stale or incorrect requirement links from a test case",
|
|
36
|
+
"Remove requirement associations from a specific test case version"
|
|
37
|
+
],
|
|
38
|
+
examples: [
|
|
39
|
+
{
|
|
40
|
+
description: "Unlink specific requirements by key",
|
|
41
|
+
parameters: {
|
|
42
|
+
key: "SCRUM-TC-145",
|
|
43
|
+
requirementKeys: ["SCRUM-1", "SCRUM-2"]
|
|
44
|
+
},
|
|
45
|
+
expectedOutput: "Requirements SCRUM-1 and SCRUM-2 unlinked from test case"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
description: "Unlink all requirements from a test case",
|
|
49
|
+
parameters: { key: "SCRUM-TC-145", unLinkAll: true },
|
|
50
|
+
expectedOutput: "All requirements unlinked from test case"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
description: "Unlink a requirement from a specific version",
|
|
54
|
+
parameters: {
|
|
55
|
+
key: "SCRUM-TC-85",
|
|
56
|
+
versionNo: 2,
|
|
57
|
+
requirementKeys: ["SCRUM-10"]
|
|
58
|
+
},
|
|
59
|
+
expectedOutput: "Requirement unlinked from version 2 of test case"
|
|
60
|
+
}
|
|
61
|
+
],
|
|
62
|
+
hints: [
|
|
63
|
+
"PREREQUISITE: set_project_context must be called before this tool. NEVER auto-select a project.",
|
|
64
|
+
"KEY FORMAT: '{PROJECT_KEY}-TC-{number}' — e.g. 'SCRUM-TC-145'.",
|
|
65
|
+
"Requirement keys follow the Jira issue key format: '{PROJECT_KEY}-{number}' (e.g. 'SCRUM-1').",
|
|
66
|
+
"Set unLinkAll to true to remove all requirements in one call — no need to list them individually.",
|
|
67
|
+
"If a requirement key cannot be resolved or unlinked, it is reported in warnings and others are still unlinked.",
|
|
68
|
+
"versionNo defaults to the latest version. Use search_test_cases to find available versions if needed."
|
|
69
|
+
],
|
|
70
|
+
outputDescription: "Confirmation with the test case key, version number, and unlinked: true. Warnings are included if any requirements could not be resolved or unlinked."
|
|
71
|
+
};
|
|
72
|
+
handle = async (rawArgs) => {
|
|
73
|
+
const args = UnlinkRequirementsBody.parse(rawArgs);
|
|
74
|
+
const fieldResolver = this.client.getResolverRegistry();
|
|
75
|
+
const context = fieldResolver.requireProjectContext();
|
|
76
|
+
const warnings = [];
|
|
77
|
+
const uidMap = await fieldResolver.getResolver(ResolverKeys.SearchableField.TEST_CASE_KEY_TO_UID).resolveAndReturn(context.projectId, [args.key]);
|
|
78
|
+
const entry = uidMap[args.key];
|
|
79
|
+
if (!entry) {
|
|
80
|
+
throw new ToolError(
|
|
81
|
+
`Test case '${args.key}' not found in project '${context.projectKey}'. Verify the key using the search_test_cases tool.`
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
const versionNo = args.versionNo ?? entry.latestVersion;
|
|
85
|
+
const body = {};
|
|
86
|
+
if (args.unLinkAll) {
|
|
87
|
+
body.unLinkAll = true;
|
|
88
|
+
} else if (args.requirementKeys?.length) {
|
|
89
|
+
const reqMap = await fieldResolver.getResolver(ResolverKeys.SearchableField.REQUIREMENT_KEY_TO_ID).resolveAndReturn(context.projectId, args.requirementKeys);
|
|
90
|
+
const requirementIds = [];
|
|
91
|
+
for (const reqKey of args.requirementKeys) {
|
|
92
|
+
const resolved = reqMap[reqKey];
|
|
93
|
+
if (resolved) {
|
|
94
|
+
requirementIds.push(Number(resolved.id));
|
|
95
|
+
} else {
|
|
96
|
+
warnings.push(
|
|
97
|
+
`Requirement '${reqKey}' could not be resolved and was skipped.`
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (requirementIds.length > 0) body.requirementIds = requirementIds;
|
|
102
|
+
}
|
|
103
|
+
await this.client.getApiClient().post(ENDPOINTS.UNLINK_REQUIREMENTS(entry.uid, versionNo), body);
|
|
104
|
+
return {
|
|
105
|
+
structuredContent: UnlinkRequirementsResponse.parse({
|
|
106
|
+
key: args.key,
|
|
107
|
+
versionNo,
|
|
108
|
+
unlinked: true
|
|
109
|
+
}),
|
|
110
|
+
content: warnings.length > 0 ? [{ type: "text", text: `Note: ${warnings.join(" | ")}` }] : []
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
export {
|
|
115
|
+
UnlinkRequirements
|
|
116
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Tool, ToolError } from "../../../common/tools.js";
|
|
2
|
-
import { TOOL_NAMES, ENDPOINTS } from "../../config/constants.js";
|
|
2
|
+
import { TOOL_NAMES, TOOLSETS, ENDPOINTS } from "../../config/constants.js";
|
|
3
3
|
import { ResolverKeys, InputField } from "../../config/field-resolution.types.js";
|
|
4
4
|
import { UpdateTestCaseResponse, UpdateTestCaseBody } from "../../schema/update-test-case.schema.js";
|
|
5
5
|
const SIMPLE_FIELD_CONFIG = {
|
|
@@ -33,6 +33,7 @@ async function resolveAddDelete(resolver, inputField, resolverKey, field, contex
|
|
|
33
33
|
class UpdateTestCase extends Tool {
|
|
34
34
|
specification = {
|
|
35
35
|
title: TOOL_NAMES.UPDATE_TEST_CASE.TITLE,
|
|
36
|
+
toolset: TOOLSETS.TEST_CASES,
|
|
36
37
|
summary: TOOL_NAMES.UPDATE_TEST_CASE.SUMMARY,
|
|
37
38
|
readOnly: false,
|
|
38
39
|
idempotent: true,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Tool } from "../../../common/tools.js";
|
|
2
|
-
import { TOOL_NAMES, ENDPOINTS } from "../../config/constants.js";
|
|
2
|
+
import { TOOL_NAMES, TOOLSETS, ENDPOINTS } from "../../config/constants.js";
|
|
3
3
|
import { ResolverKeys, InputField } from "../../config/field-resolution.types.js";
|
|
4
4
|
import { CreateTestCycleResponse, CreateTestCycleBody } from "../../schema/test-cycle.schema.js";
|
|
5
5
|
const FIELD_CONFIG = {
|
|
@@ -13,6 +13,7 @@ class CreateTestCycle extends Tool {
|
|
|
13
13
|
// ─── Tool Specification ────────────────────────────────────────────────────
|
|
14
14
|
specification = {
|
|
15
15
|
title: TOOL_NAMES.CREATE_TEST_CYCLE.TITLE,
|
|
16
|
+
toolset: TOOLSETS.TEST_CYCLES,
|
|
16
17
|
summary: TOOL_NAMES.CREATE_TEST_CYCLE.SUMMARY,
|
|
17
18
|
readOnly: false,
|
|
18
19
|
idempotent: false,
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { Tool, ToolError } from "../../../common/tools.js";
|
|
2
|
+
import { TOOLSETS, TOOL_NAMES, ENDPOINTS } from "../../config/constants.js";
|
|
3
|
+
import { ResolverKeys } from "../../config/field-resolution.types.js";
|
|
4
|
+
import { GetLinkedRequirementsResponse } from "../../schema/linked-items.schema.js";
|
|
5
|
+
import { GetLinkedRequirementsForCycleBody } from "../../schema/test-cycle.link.schema.js";
|
|
6
|
+
class GetLinkedRequirementsForCycle extends Tool {
|
|
7
|
+
specification = {
|
|
8
|
+
title: TOOL_NAMES.GET_LINKED_REQUIREMENTS_FOR_CYCLE.TITLE,
|
|
9
|
+
summary: TOOL_NAMES.GET_LINKED_REQUIREMENTS_FOR_CYCLE.SUMMARY,
|
|
10
|
+
readOnly: true,
|
|
11
|
+
idempotent: true,
|
|
12
|
+
toolset: TOOLSETS.TEST_CYCLES,
|
|
13
|
+
inputSchema: GetLinkedRequirementsForCycleBody,
|
|
14
|
+
outputSchema: GetLinkedRequirementsResponse,
|
|
15
|
+
purpose: "Retrieve all Jira requirements linked to a QTM4J test cycle. The cycle's human-readable key (e.g. 'SCRUM-TR-1') is resolved to the internal UID automatically. Returns a paginated list of linked requirements with their Jira metadata (key, summary, status, priority, issue type). PREREQUISITE: set_project_context must be called before this tool.",
|
|
16
|
+
useCases: [
|
|
17
|
+
"Check which Jira stories or bugs are covered by a test cycle",
|
|
18
|
+
"Audit requirement traceability for a test cycle",
|
|
19
|
+
"Retrieve requirement keys linked to a cycle before a release",
|
|
20
|
+
"Verify correct requirements are linked to a test cycle"
|
|
21
|
+
],
|
|
22
|
+
examples: [
|
|
23
|
+
{
|
|
24
|
+
description: "Get all requirements linked to a test cycle",
|
|
25
|
+
parameters: { cycleKey: "SCRUM-TR-1" },
|
|
26
|
+
expectedOutput: "Paginated list of linked requirements with Jira metadata"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
description: "Get requirements sorted by priority with custom page size",
|
|
30
|
+
parameters: {
|
|
31
|
+
cycleKey: "SCRUM-TR-5",
|
|
32
|
+
sort: "priority:asc",
|
|
33
|
+
maxResults: 20
|
|
34
|
+
},
|
|
35
|
+
expectedOutput: "Requirements linked to cycle sorted by priority ascending"
|
|
36
|
+
}
|
|
37
|
+
],
|
|
38
|
+
hints: [
|
|
39
|
+
"PREREQUISITE: set_project_context must be called before this tool. NEVER auto-select a project.",
|
|
40
|
+
"CYCLE KEY FORMAT: '{PROJECT_KEY}-TR-{id}' — e.g. 'SCRUM-TR-1'. Resolved to internal UID automatically.",
|
|
41
|
+
"Allowed sort fields: key, status, priority. Default sort: 'key:desc'.",
|
|
42
|
+
"Paginate using startAt — increment by maxResults until startAt >= total."
|
|
43
|
+
],
|
|
44
|
+
outputDescription: "Paginated result with total, startAt, maxResults, and data array of linked requirement objects (id, key, summary, status, priority, issueType)."
|
|
45
|
+
};
|
|
46
|
+
handle = async (rawArgs) => {
|
|
47
|
+
const args = GetLinkedRequirementsForCycleBody.parse(rawArgs);
|
|
48
|
+
const fieldResolver = this.client.getResolverRegistry();
|
|
49
|
+
const context = fieldResolver.requireProjectContext();
|
|
50
|
+
const cycleMap = await fieldResolver.getResolver(ResolverKeys.SearchableField.TEST_CYCLE_KEY_TO_UID).resolveAndReturn(context.projectId, [args.cycleKey]);
|
|
51
|
+
const cycleEntry = cycleMap[args.cycleKey];
|
|
52
|
+
if (!cycleEntry) {
|
|
53
|
+
throw new ToolError(
|
|
54
|
+
`Test cycle '${args.cycleKey}' not found in project '${context.projectKey}'. Verify the key is a valid QTM4J test cycle key.`
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
const params = {
|
|
58
|
+
maxResults: args.maxResults,
|
|
59
|
+
startAt: args.startAt,
|
|
60
|
+
sort: args.sort
|
|
61
|
+
};
|
|
62
|
+
const response = await this.client.getApiClient().get(ENDPOINTS.GET_LINKED_REQUIREMENTS_FOR_CYCLE(cycleEntry.uid), params);
|
|
63
|
+
return {
|
|
64
|
+
structuredContent: GetLinkedRequirementsResponse.parse(response),
|
|
65
|
+
content: []
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
export {
|
|
70
|
+
GetLinkedRequirementsForCycle
|
|
71
|
+
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { Tool, ToolError } from "../../../common/tools.js";
|
|
2
|
+
import { TOOLSETS, TOOL_NAMES, ENDPOINTS } from "../../config/constants.js";
|
|
3
|
+
import { ResolverKeys } from "../../config/field-resolution.types.js";
|
|
4
|
+
import { LinkRequirementsToCycleResponse, LinkRequirementsToCycleBody } from "../../schema/test-cycle.link.schema.js";
|
|
5
|
+
class LinkRequirementsToCycle extends Tool {
|
|
6
|
+
specification = {
|
|
7
|
+
title: TOOL_NAMES.LINK_REQUIREMENTS_TO_CYCLE.TITLE,
|
|
8
|
+
summary: TOOL_NAMES.LINK_REQUIREMENTS_TO_CYCLE.SUMMARY,
|
|
9
|
+
readOnly: false,
|
|
10
|
+
idempotent: false,
|
|
11
|
+
toolset: TOOLSETS.TEST_CYCLES,
|
|
12
|
+
inputSchema: LinkRequirementsToCycleBody,
|
|
13
|
+
outputSchema: LinkRequirementsToCycleResponse,
|
|
14
|
+
purpose: "Link Jira requirements to a QTM4J test cycle using the cycle's human-readable key. The cycle key (e.g. 'SCRUM-TR-1') is resolved to the internal UID automatically. Requirements can be specified by their human-readable keys (e.g. 'SCRUM-1') which are resolved to internal IDs automatically, or via a JQL filter. Requirements that cannot be linked are reported as warnings. PREREQUISITE: set_project_context must be called before this tool.",
|
|
15
|
+
useCases: [
|
|
16
|
+
"Link one or more Jira requirements (stories, bugs, epics) to a test cycle",
|
|
17
|
+
"Associate requirements with a test cycle using a JQL filter",
|
|
18
|
+
"Build traceability between requirements and a test cycle",
|
|
19
|
+
"Link all stories from a sprint to a test cycle using JQL"
|
|
20
|
+
],
|
|
21
|
+
examples: [
|
|
22
|
+
{
|
|
23
|
+
description: "Link two requirements by key",
|
|
24
|
+
parameters: {
|
|
25
|
+
cycleKey: "SCRUM-TR-1",
|
|
26
|
+
requirementKeys: ["SCRUM-1", "SCRUM-2"]
|
|
27
|
+
},
|
|
28
|
+
expectedOutput: "Requirements SCRUM-1 and SCRUM-2 linked to test cycle"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
description: "Link requirements by JQL filter",
|
|
32
|
+
parameters: {
|
|
33
|
+
cycleKey: "SCRUM-TR-1",
|
|
34
|
+
filter: { jql: "project = DEMO AND issuetype = Story" }
|
|
35
|
+
},
|
|
36
|
+
expectedOutput: "Requirements matched by JQL linked to test cycle"
|
|
37
|
+
}
|
|
38
|
+
],
|
|
39
|
+
hints: [
|
|
40
|
+
"PREREQUISITE: set_project_context must be called before this tool. NEVER auto-select a project.",
|
|
41
|
+
"CYCLE KEY FORMAT: '{PROJECT_KEY}-TR-{id}' — e.g. 'SCRUM-TR-1'. Resolved to internal UID automatically.",
|
|
42
|
+
"Requirement keys follow Jira issue key format: '{PROJECT_KEY}-{number}' (e.g. 'SCRUM-1').",
|
|
43
|
+
"Provide either requirementKeys or filter.jql — not both.",
|
|
44
|
+
"If a requirement key cannot be resolved or linked, it is reported in warnings and other requirements are still linked."
|
|
45
|
+
],
|
|
46
|
+
outputDescription: "Confirmation with the cycle key and linked: true. Warnings are included if any requirements could not be resolved or linked."
|
|
47
|
+
};
|
|
48
|
+
handle = async (rawArgs) => {
|
|
49
|
+
const args = LinkRequirementsToCycleBody.parse(rawArgs);
|
|
50
|
+
const fieldResolver = this.client.getResolverRegistry();
|
|
51
|
+
const context = fieldResolver.requireProjectContext();
|
|
52
|
+
const warnings = [];
|
|
53
|
+
const cycleMap = await fieldResolver.getResolver(ResolverKeys.SearchableField.TEST_CYCLE_KEY_TO_UID).resolveAndReturn(context.projectId, [args.cycleKey]);
|
|
54
|
+
const cycleEntry = cycleMap[args.cycleKey];
|
|
55
|
+
if (!cycleEntry) {
|
|
56
|
+
throw new ToolError(
|
|
57
|
+
`Test cycle '${args.cycleKey}' not found in project '${context.projectKey}'. Verify the key is a valid QTM4J test cycle key.`
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
const body = {};
|
|
61
|
+
if (args.requirementKeys?.length) {
|
|
62
|
+
const reqMap = await fieldResolver.getResolver(ResolverKeys.SearchableField.REQUIREMENT_KEY_TO_ID).resolveAndReturn(context.projectId, args.requirementKeys);
|
|
63
|
+
const requirementIds = [];
|
|
64
|
+
for (const reqKey of args.requirementKeys) {
|
|
65
|
+
const resolved = reqMap[reqKey];
|
|
66
|
+
if (resolved) {
|
|
67
|
+
requirementIds.push(Number(resolved.id));
|
|
68
|
+
} else {
|
|
69
|
+
warnings.push(
|
|
70
|
+
`Requirement '${reqKey}' could not be resolved and was skipped.`
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (requirementIds.length > 0) body.requirementIds = requirementIds;
|
|
75
|
+
}
|
|
76
|
+
if (args.filter) {
|
|
77
|
+
body.filter = args.filter;
|
|
78
|
+
}
|
|
79
|
+
await this.client.getApiClient().post(ENDPOINTS.LINK_REQUIREMENTS_TO_CYCLE(cycleEntry.uid), body);
|
|
80
|
+
return {
|
|
81
|
+
structuredContent: LinkRequirementsToCycleResponse.parse({
|
|
82
|
+
cycleKey: args.cycleKey,
|
|
83
|
+
linked: true
|
|
84
|
+
}),
|
|
85
|
+
content: warnings.length > 0 ? [{ type: "text", text: `Note: ${warnings.join(" | ")}` }] : []
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
export {
|
|
90
|
+
LinkRequirementsToCycle
|
|
91
|
+
};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { Tool, ToolError } from "../../../common/tools.js";
|
|
2
|
+
import { TOOLSETS, TOOL_NAMES, ENDPOINTS } from "../../config/constants.js";
|
|
3
|
+
import { ResolverKeys } from "../../config/field-resolution.types.js";
|
|
4
|
+
import { TestCycleLinkResponse, LinkTestCasesToCycleBody } from "../../schema/test-cycle.link.schema.js";
|
|
5
|
+
class LinkTestCasesToCycle extends Tool {
|
|
6
|
+
specification = {
|
|
7
|
+
title: TOOL_NAMES.LINK_TESTCASES_TO_CYCLE.TITLE,
|
|
8
|
+
summary: TOOL_NAMES.LINK_TESTCASES_TO_CYCLE.SUMMARY,
|
|
9
|
+
readOnly: false,
|
|
10
|
+
idempotent: false,
|
|
11
|
+
toolset: TOOLSETS.TEST_CYCLES,
|
|
12
|
+
inputSchema: LinkTestCasesToCycleBody,
|
|
13
|
+
outputSchema: TestCycleLinkResponse,
|
|
14
|
+
purpose: "Link test cases to a QTM4J test cycle using the cycle's human-readable key. The cycle key (e.g. 'SCRUM-TR-1') is resolved to the internal UID automatically. Test cases can be specified by their human-readable keys (e.g. 'SCRUM-TC-1'), which are also resolved to internal IDs and latest versions automatically. Alternatively, use a filter object to select test cases by criteria such as status, priority, labels, or folder. The active project ID is injected automatically into the filter. Test cases that cannot be linked are reported in warnings. PREREQUISITE: set_project_context must be called before this tool.",
|
|
15
|
+
useCases: [
|
|
16
|
+
"Add specific test cases to a test cycle by key",
|
|
17
|
+
"Populate a test cycle with test cases matching a filter (e.g., all High priority test cases)",
|
|
18
|
+
"Link test cases from a specific folder to a test cycle",
|
|
19
|
+
"Add test cases to a cycle with a specific assignee or environment"
|
|
20
|
+
],
|
|
21
|
+
examples: [
|
|
22
|
+
{
|
|
23
|
+
description: "Link two test cases by key",
|
|
24
|
+
parameters: {
|
|
25
|
+
cycleKey: "SCRUM-TR-1",
|
|
26
|
+
testCaseKeys: ["SCRUM-TC-10", "SCRUM-TC-11"]
|
|
27
|
+
},
|
|
28
|
+
expectedOutput: "Test cases linked to test cycle"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
description: "Link test cases matching a filter with assignee",
|
|
32
|
+
parameters: {
|
|
33
|
+
cycleKey: "SCRUM-TR-1",
|
|
34
|
+
filter: { priority: ["High"], status: ["To Do"] },
|
|
35
|
+
assignee: "5b10a2844c20165700ede21f",
|
|
36
|
+
startNewExecution: true
|
|
37
|
+
},
|
|
38
|
+
expectedOutput: "Filtered test cases linked to cycle with assignee"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
description: "Link test cases in a folder to a cycle with planned date",
|
|
42
|
+
parameters: {
|
|
43
|
+
cycleKey: "SCRUM-TR-5",
|
|
44
|
+
filter: { folderId: 42, withChild: true },
|
|
45
|
+
executionPlannedDate: "2024-03-31",
|
|
46
|
+
sort: "key:asc"
|
|
47
|
+
},
|
|
48
|
+
expectedOutput: "Folder test cases linked to cycle with planned date"
|
|
49
|
+
}
|
|
50
|
+
],
|
|
51
|
+
hints: [
|
|
52
|
+
"PREREQUISITE: set_project_context must be called before this tool. NEVER auto-select a project.",
|
|
53
|
+
"CYCLE KEY FORMAT: '{PROJECT_KEY}-TR-{id}' — e.g. 'SCRUM-TR-1'. Resolved to internal UID automatically.",
|
|
54
|
+
"TEST CASE KEY FORMAT: '{PROJECT_KEY}-TC-{number}' — e.g. 'SCRUM-TC-145'.",
|
|
55
|
+
"Provide either testCaseKeys or filter — not both.",
|
|
56
|
+
"projectId in filter is auto-filled from the active project context — do not set it manually.",
|
|
57
|
+
"filter.excludeCycleId excludes test cases already in another cycle.",
|
|
58
|
+
"startNewExecution: true creates a fresh execution record for each linked test case.",
|
|
59
|
+
"executionPlannedDate must be in yyyy-MM-dd format (e.g., '2024-03-31').",
|
|
60
|
+
"actualTime must be in HH:mm format (e.g., '02:30').",
|
|
61
|
+
"If a test case key cannot be resolved, it is reported in warnings and others are still linked."
|
|
62
|
+
],
|
|
63
|
+
outputDescription: "Confirmation with the cycle key and linked: true. Warnings included if any test cases could not be resolved or linked."
|
|
64
|
+
};
|
|
65
|
+
handle = async (rawArgs) => {
|
|
66
|
+
const args = LinkTestCasesToCycleBody.parse(rawArgs);
|
|
67
|
+
const fieldResolver = this.client.getResolverRegistry();
|
|
68
|
+
const context = fieldResolver.requireProjectContext();
|
|
69
|
+
const warnings = [];
|
|
70
|
+
const cycleMap = await fieldResolver.getResolver(ResolverKeys.SearchableField.TEST_CYCLE_KEY_TO_UID).resolveAndReturn(context.projectId, [args.cycleKey]);
|
|
71
|
+
const cycleEntry = cycleMap[args.cycleKey];
|
|
72
|
+
if (!cycleEntry) {
|
|
73
|
+
throw new ToolError(
|
|
74
|
+
`Test cycle '${args.cycleKey}' not found in project '${context.projectKey}'. Verify the key is a valid QTM4J test cycle key.`
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
const body = {};
|
|
78
|
+
if (args.testCaseKeys?.length) {
|
|
79
|
+
const uidMap = await fieldResolver.getResolver(ResolverKeys.SearchableField.TEST_CASE_KEY_TO_UID).resolveAndReturn(context.projectId, args.testCaseKeys);
|
|
80
|
+
const testCases = [];
|
|
81
|
+
for (const tcKey of args.testCaseKeys) {
|
|
82
|
+
const resolved = uidMap[tcKey];
|
|
83
|
+
if (resolved) {
|
|
84
|
+
testCases.push({
|
|
85
|
+
id: resolved.uid,
|
|
86
|
+
versionNo: resolved.latestVersion
|
|
87
|
+
});
|
|
88
|
+
} else {
|
|
89
|
+
warnings.push(
|
|
90
|
+
`Test case '${tcKey}' could not be resolved and was skipped.`
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (testCases.length > 0) body.testCases = testCases;
|
|
95
|
+
}
|
|
96
|
+
if (args.filter) {
|
|
97
|
+
body.filter = { ...args.filter, projectId: context.projectId };
|
|
98
|
+
}
|
|
99
|
+
const {
|
|
100
|
+
cycleKey: _cycleKey,
|
|
101
|
+
testCaseKeys: _tcKeys,
|
|
102
|
+
filter: _filter,
|
|
103
|
+
...rest
|
|
104
|
+
} = args;
|
|
105
|
+
Object.assign(body, rest);
|
|
106
|
+
await this.client.getApiClient().post(ENDPOINTS.LINK_TESTCASES_TO_CYCLE(cycleEntry.uid), body);
|
|
107
|
+
return {
|
|
108
|
+
structuredContent: TestCycleLinkResponse.parse({
|
|
109
|
+
cycleKey: args.cycleKey,
|
|
110
|
+
linked: true
|
|
111
|
+
}),
|
|
112
|
+
content: warnings.length > 0 ? [{ type: "text", text: `Note: ${warnings.join(" | ")}` }] : []
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
export {
|
|
117
|
+
LinkTestCasesToCycle
|
|
118
|
+
};
|