Skip to content

Commit 92415c9

Browse files
[Zephyr] TM4J-14631 -Create Test Cycle Issue link tool (#359)
* [Zephyr] TM4J-14631 -Create Test Cycle Issue link tool * [Zephyr] TM4J-14631 - Remove manually added schema from auto-generated file Remove CreateTestCycleIssueLink201Response from rest-api-schemas.ts as this file is auto-generated. The tool already reuses CreateTestCaseIssueLink201Response which has the same structure. * remove output * merge with main * update zephyr integration * update changelog * fix: correct test cycle issue link to reflect empty API response The Zephyr API does not return any object when creating a test cycle issue link. Updated tool examples, tests, and docs accordingly. * remove expectedOutput and structuredContent for no-output API The test cycle issue link API returns no body, so remove expectedOutput from examples, return structuredContent: {} and drop response assertions from tests. * add blank line between imports and class declaration * Resolve comments
1 parent 3c70246 commit 92415c9

5 files changed

Lines changed: 202 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1414
- [Zephyr] Added a tool `create-test-script` for creating Test Script [#328](https://github.com/SmartBear/smartbear-mcp/pull/328)
1515
- [Zephyr] Added a tool `create-test-steps` for creating Test Steps for a Test Case [#353](https://github.com/SmartBear/smartbear-mcp/pull/353)
1616
- [Zephyr] Added a tool `update-test-execution` for updating a test execution [#345](https://github.com/SmartBear/smartbear-mcp/pull/345)
17+
- [Zephyr] Added a tool `create-test-cycle-issue-link` for creating a link between a Jira issue and a Test Cycle [#359](https://github.com/SmartBear/smartbear-mcp/pull/359)
1718
- [Zephyr] Added a tool `get-test-steps` for getting a list of test steps for test case [#355](https://github.com/SmartBear/smartbear-mcp/pull/355)
1819
- [Zephyr] Added a tool `get-test-cases` for fetching test cases linked to a Jira issue [#358](https://github.com/SmartBear/smartbear-mcp/pull/358)
1920
- [Zephyr] Added a tool `create-web-link` for creating a Web link for a Test Cycle [#354](https://github.com/SmartBear/smartbear-mcp/pull/354)

docs/products/SmartBear MCP Server/zephyr-integration.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,15 @@ The following environment variables configure the Zephyr integration:
130130
- **Returns**: The created Test Case Issue Link ID and the API self URL that can be used to delete the link.
131131
- **Use case**: Creates a link between a test case and a Jira Issue.
132132

133+
#### Create Test Cycle Issue Link
134+
135+
- **Purpose**: Creates an Issue link that associates a test cycle with a Jira Issue.
136+
- **Parameters:**
137+
- Test Cycle ID or key (`testCycleIdOrKey`)
138+
- Jira Issue Id (`issueId`)
139+
- **Returns**: A confirmation that the link was successfully created.
140+
- **Use case**: Creates a link between a test cycle and a Jira Issue.
141+
133142
## Test Scripts
134143

135144
### Creation Operations
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import type { RequestHandlerExtra } from "@modelcontextprotocol/sdk/shared/protocol.js";
2+
import type {
3+
ServerNotification,
4+
ServerRequest,
5+
} from "@modelcontextprotocol/sdk/types.js";
6+
import { beforeEach, describe, expect, it, vi } from "vitest";
7+
import { CreateTestCycleIssueLink } from "../../../../../zephyr/tool/test-cycle/create-issue-link";
8+
9+
describe("CreateTestCycleIssueLink", () => {
10+
let mockClient: any;
11+
let instance: CreateTestCycleIssueLink;
12+
13+
const EXTRA_REQUEST_HANDLER: RequestHandlerExtra<
14+
ServerRequest,
15+
ServerNotification
16+
> = {
17+
signal: AbortSignal.timeout(5000),
18+
requestId: "",
19+
sendNotification: (_notification) => {
20+
throw new Error("Function not implemented.");
21+
},
22+
sendRequest: (_request, _resultSchema, _options?) => {
23+
throw new Error("Function not implemented.");
24+
},
25+
};
26+
27+
beforeEach(() => {
28+
mockClient = {
29+
getApiClient: vi.fn().mockReturnValue({
30+
post: vi.fn(),
31+
}),
32+
};
33+
instance = new CreateTestCycleIssueLink(mockClient as any);
34+
});
35+
36+
it("should set specification correctly", () => {
37+
expect(instance.specification.title).toBe("Create Test Cycle Issue Link");
38+
expect(instance.specification.summary).toBe(
39+
"Create a new link between an issue in Jira and a Test Cycle in Zephyr",
40+
);
41+
expect(instance.specification.readOnly).toBe(false);
42+
expect(instance.specification.idempotent).toBe(false);
43+
expect(instance.specification.inputSchema).toBeDefined();
44+
});
45+
46+
it("should call apiClient.post with correct params using test cycle key", async () => {
47+
mockClient.getApiClient().post.mockResolvedValueOnce(undefined);
48+
49+
const args = {
50+
testCycleIdOrKey: "SA-R1",
51+
issueId: 53,
52+
};
53+
54+
await instance.handle(args, EXTRA_REQUEST_HANDLER);
55+
56+
expect(mockClient.getApiClient().post).toHaveBeenCalledWith(
57+
"/testcycles/SA-R1/links/issues",
58+
{
59+
issueId: args.issueId,
60+
},
61+
);
62+
});
63+
64+
it("should call apiClient.post with correct params using test cycle ID", async () => {
65+
mockClient.getApiClient().post.mockResolvedValueOnce(undefined);
66+
67+
const args = {
68+
testCycleIdOrKey: "1001",
69+
issueId: 54,
70+
};
71+
72+
await instance.handle(args, EXTRA_REQUEST_HANDLER);
73+
74+
expect(mockClient.getApiClient().post).toHaveBeenCalledWith(
75+
"/testcycles/1001/links/issues",
76+
{
77+
issueId: args.issueId,
78+
},
79+
);
80+
});
81+
82+
it("should ignore extra parameters not in the schema", async () => {
83+
mockClient.getApiClient().post.mockResolvedValueOnce(undefined);
84+
85+
const args = {
86+
testCycleIdOrKey: "SA-R1",
87+
issueId: 55,
88+
extraField: "should be ignored",
89+
};
90+
91+
await instance.handle(args, EXTRA_REQUEST_HANDLER);
92+
93+
expect(mockClient.getApiClient().post).toHaveBeenCalledWith(
94+
"/testcycles/SA-R1/links/issues",
95+
{
96+
issueId: args.issueId,
97+
},
98+
);
99+
});
100+
101+
it("should handle apiClient.post throwing error", async () => {
102+
mockClient
103+
.getApiClient()
104+
.post.mockRejectedValueOnce(new Error("API error"));
105+
106+
const args = {
107+
testCycleIdOrKey: "SA-R1",
108+
issueId: 53,
109+
};
110+
111+
await expect(instance.handle(args, EXTRA_REQUEST_HANDLER)).rejects.toThrow(
112+
"API error",
113+
);
114+
});
115+
116+
it("should throw validation error if issueId is missing", async () => {
117+
const args = {
118+
testCycleIdOrKey: "SA-R1",
119+
};
120+
121+
await expect(
122+
instance.handle(args, EXTRA_REQUEST_HANDLER),
123+
).rejects.toThrow();
124+
});
125+
126+
it("should throw validation error if testCycleIdOrKey is missing", async () => {
127+
const args = {
128+
issueId: 53,
129+
};
130+
131+
await expect(
132+
instance.handle(args, EXTRA_REQUEST_HANDLER),
133+
).rejects.toThrow();
134+
});
135+
});

src/zephyr/client.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { GetTestCase } from "./tool/test-case/get-test-case";
2222
import { GetTestCases } from "./tool/test-case/get-test-cases";
2323
import { GetTestCaseSteps } from "./tool/test-case/get-test-steps.ts";
2424
import { UpdateTestCase } from "./tool/test-case/update-test-case.ts";
25+
import { CreateTestCycleIssueLink } from "./tool/test-cycle/create-issue-link";
2526
import { CreateTestCycle } from "./tool/test-cycle/create-test-cycle";
2627
import { CreateTestCycleWebLink } from "./tool/test-cycle/create-web-link.ts";
2728
import { GetTestCycle } from "./tool/test-cycle/get-test-cycle";
@@ -96,6 +97,7 @@ export class ZephyrClient implements Client {
9697
new CreateTestCaseWebLink(this),
9798
new CreateTestSteps(this),
9899
new CreateTestCaseIssueLink(this),
100+
new CreateTestCycleIssueLink(this),
99101
new CreateFolder(this),
100102
new CreateTestScript(this),
101103
new UpdateTestExecution(this),
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import type { ToolCallback } from "@modelcontextprotocol/sdk/server/mcp.js";
2+
import type { ZodRawShape } from "zod";
3+
import { Tool } from "../../../common/tools";
4+
import type { ToolParams } from "../../../common/types";
5+
import type { ZephyrClient } from "../../client";
6+
import {
7+
CreateTestCycleIssueLinkBody,
8+
CreateTestCycleIssueLinkParams,
9+
} from "../../common/rest-api-schemas";
10+
11+
export class CreateTestCycleIssueLink extends Tool<ZephyrClient> {
12+
specification: ToolParams = {
13+
title: "Create Test Cycle Issue Link",
14+
summary:
15+
"Create a new link between an issue in Jira and a Test Cycle in Zephyr",
16+
readOnly: false,
17+
idempotent: false,
18+
inputSchema: CreateTestCycleIssueLinkParams.and(
19+
CreateTestCycleIssueLinkBody,
20+
),
21+
examples: [
22+
{
23+
description:
24+
"Create a link between the test cycle with key SA-R1 and the Jira Issue ID 10100",
25+
parameters: {
26+
testCycleIdOrKey: "SA-R1",
27+
issueId: 10100,
28+
},
29+
expectedOutput:
30+
"The link between Test Cycle and Jira issue should be created, but no output is expected.",
31+
},
32+
{
33+
description:
34+
"Create a link between the test cycle with ID 1001 and the Jira issue ID 20200",
35+
parameters: {
36+
testCycleIdOrKey: "1001",
37+
issueId: 20200,
38+
},
39+
expectedOutput:
40+
"The link between Test Cycle and Jira issue should be created, but no output is expected.",
41+
},
42+
],
43+
};
44+
handle: ToolCallback<ZodRawShape> = async (args) => {
45+
const { testCycleIdOrKey } = CreateTestCycleIssueLinkParams.parse(args);
46+
const body = CreateTestCycleIssueLinkBody.parse(args);
47+
await this.client
48+
.getApiClient()
49+
.post(`/testcycles/${testCycleIdOrKey}/links/issues`, body);
50+
return {
51+
structuredContent: {},
52+
content: [],
53+
};
54+
};
55+
}

0 commit comments

Comments
 (0)