Skip to content

Commit 045f318

Browse files
authored
Add test plans tool for create test suite (#522)
Adding testplan tool for creating test suite ## GitHub issue number 498, #498 ## **Associated Risks** Adding new test plan tool, no framework level risk ## ✅ **PR Checklist** - [x] **I have read the [contribution guidelines](https://github.com/microsoft/azure-devops-mcp/blob/main/CONTRIBUTING.md)** - [x] **I have read the [code of conduct guidelines](https://github.com/microsoft/azure-devops-mcp/blob/main/CODE_OF_CONDUCT.md)** - [x] Title of the pull request is clear and informative. - [x] 👌 Code hygiene - [x] 🔭 Telemetry added, updated, or N/A - [x] 📄 Documentation added, updated, or N/A - [x] 🛡️ Automated tests added, or N/A ## 🧪 **How did you test it?** In VsCode, <img width="1009" height="955" alt="image" src="https://github.com/user-attachments/assets/2a73581a-c79e-47b3-819b-54ba0a7f0c88" /> <img width="1425" height="1111" alt="image" src="https://github.com/user-attachments/assets/7f6cb3e1-d3ef-4e26-9e69-1763ec118d38" />
1 parent dab714d commit 045f318

File tree

3 files changed

+156
-1
lines changed

3 files changed

+156
-1
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ Interact with these Azure DevOps services:
129129
- **testplan_list_test_plans**: Retrieve a paginated list of test plans from an Azure DevOps project. Allows filtering for active plans and toggling detailed information.
130130
- **testplan_list_test_cases**: Get a list of test cases in the test plan.
131131
- **testplan_show_test_results_from_build_id**: Get a list of test results for a given project and build ID.
132+
- **testplan_create_test_suite**: Creates a new test suite in a test plan.
132133

133134
### 📖 Wiki
134135

src/tools/test-plans.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const Test_Plan_Tools = {
1313
test_results_from_build_id: "testplan_show_test_results_from_build_id",
1414
list_test_cases: "testplan_list_test_cases",
1515
list_test_plans: "testplan_list_test_plans",
16+
create_test_suite: "testplan_create_test_suite",
1617
};
1718

1819
function configureTestPlanTools(server: McpServer, _: () => Promise<string>, connectionProvider: () => Promise<WebApi>) {
@@ -78,6 +79,39 @@ function configureTestPlanTools(server: McpServer, _: () => Promise<string>, con
7879
}
7980
);
8081

82+
/*
83+
Create Test Suite - CREATE
84+
*/
85+
server.tool(
86+
Test_Plan_Tools.create_test_suite,
87+
"Creates a new test suite in a test plan.",
88+
{
89+
project: z.string().describe("Project ID or project name"),
90+
planId: z.number().describe("ID of the test plan that contains the suites"),
91+
parentSuiteId: z.number().describe("ID of the parent suite under which the new suite will be created, if not given by user this can be id of a root suite of the test plan"),
92+
name: z.string().describe("Name of the child test suite"),
93+
},
94+
async ({ project, planId, parentSuiteId, name }) => {
95+
const connection = await connectionProvider();
96+
const testPlanApi = await connection.getTestPlanApi();
97+
98+
const testSuiteToCreate = {
99+
name,
100+
parentSuite: {
101+
id: parentSuiteId,
102+
name: "",
103+
},
104+
suiteType: 2,
105+
};
106+
107+
const createdTestSuite = await testPlanApi.createTestSuite(testSuiteToCreate, project, planId);
108+
109+
return {
110+
content: [{ type: "text", text: JSON.stringify(createdTestSuite, null, 2) }],
111+
};
112+
}
113+
);
114+
81115
/*
82116
Add Test Cases to Suite - ADD
83117
*/

test/src/tools/test-plan.test.ts

Lines changed: 121 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ describe("configureTestPlanTools", () => {
3232
mockTestPlanApi = {
3333
getTestPlans: jest.fn(),
3434
createTestPlan: jest.fn(),
35+
createTestSuite: jest.fn(),
3536
addTestCasesToSuite: jest.fn(),
3637
getTestCaseList: jest.fn(),
3738
} as unknown as ITestPlanApi;
@@ -57,7 +58,14 @@ describe("configureTestPlanTools", () => {
5758
it("registers test plan tools on the server", () => {
5859
configureTestPlanTools(server, tokenProvider, connectionProvider);
5960
expect((server.tool as jest.Mock).mock.calls.map((call) => call[0])).toEqual(
60-
expect.arrayContaining(["testplan_list_test_plans", "testplan_create_test_plan", "testplan_add_test_cases_to_suite", "testplan_list_test_cases", "testplan_show_test_results_from_build_id"])
61+
expect.arrayContaining([
62+
"testplan_list_test_plans",
63+
"testplan_create_test_plan",
64+
"testplan_create_test_suite",
65+
"testplan_add_test_cases_to_suite",
66+
"testplan_list_test_cases",
67+
"testplan_show_test_results_from_build_id",
68+
])
6169
);
6270
});
6371
});
@@ -117,6 +125,118 @@ describe("configureTestPlanTools", () => {
117125
});
118126
});
119127

128+
describe("create_test_suite tool", () => {
129+
it("should call createTestSuite with the correct parameters and return the expected result", async () => {
130+
configureTestPlanTools(server, tokenProvider, connectionProvider);
131+
const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "testplan_create_test_suite");
132+
if (!call) throw new Error("testplan_create_test_suite tool not registered");
133+
const [, , , handler] = call;
134+
135+
(mockTestPlanApi.createTestSuite as jest.Mock).mockResolvedValue({ id: 10, name: "New Test Suite" });
136+
const params = {
137+
project: "proj1",
138+
planId: 1,
139+
parentSuiteId: 5,
140+
name: "New Test Suite",
141+
};
142+
const result = await handler(params);
143+
144+
expect(mockTestPlanApi.createTestSuite).toHaveBeenCalledWith(
145+
{
146+
name: "New Test Suite",
147+
parentSuite: {
148+
id: 5,
149+
name: "",
150+
},
151+
suiteType: 2,
152+
},
153+
"proj1",
154+
1
155+
);
156+
expect(result.content[0].text).toBe(JSON.stringify({ id: 10, name: "New Test Suite" }, null, 2));
157+
});
158+
159+
it("should handle API errors when creating test suite", async () => {
160+
configureTestPlanTools(server, tokenProvider, connectionProvider);
161+
const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "testplan_create_test_suite");
162+
if (!call) throw new Error("testplan_create_test_suite tool not registered");
163+
const [, , , handler] = call;
164+
165+
(mockTestPlanApi.createTestSuite as jest.Mock).mockRejectedValue(new Error("API Error"));
166+
167+
const params = {
168+
project: "proj1",
169+
planId: 1,
170+
parentSuiteId: 5,
171+
name: "Failed Test Suite",
172+
};
173+
174+
await expect(handler(params)).rejects.toThrow("API Error");
175+
});
176+
177+
it("should create test suite with different parent suite IDs", async () => {
178+
configureTestPlanTools(server, tokenProvider, connectionProvider);
179+
const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "testplan_create_test_suite");
180+
if (!call) throw new Error("testplan_create_test_suite tool not registered");
181+
const [, , , handler] = call;
182+
183+
(mockTestPlanApi.createTestSuite as jest.Mock).mockResolvedValue({
184+
id: 15,
185+
name: "Child Test Suite",
186+
parentSuite: { id: 10 },
187+
});
188+
const params = {
189+
project: "proj1",
190+
planId: 2,
191+
parentSuiteId: 10,
192+
name: "Child Test Suite",
193+
};
194+
const result = await handler(params);
195+
196+
expect(mockTestPlanApi.createTestSuite).toHaveBeenCalledWith(
197+
{
198+
name: "Child Test Suite",
199+
parentSuite: {
200+
id: 10,
201+
name: "",
202+
},
203+
suiteType: 2,
204+
},
205+
"proj1",
206+
2
207+
);
208+
expect(result.content[0].text).toBe(
209+
JSON.stringify(
210+
{
211+
id: 15,
212+
name: "Child Test Suite",
213+
parentSuite: { id: 10 },
214+
},
215+
null,
216+
2
217+
)
218+
);
219+
});
220+
221+
it("should handle empty or null response from createTestSuite", async () => {
222+
configureTestPlanTools(server, tokenProvider, connectionProvider);
223+
const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "testplan_create_test_suite");
224+
if (!call) throw new Error("testplan_create_test_suite tool not registered");
225+
const [, , , handler] = call;
226+
227+
(mockTestPlanApi.createTestSuite as jest.Mock).mockResolvedValue(null);
228+
const params = {
229+
project: "proj1",
230+
planId: 1,
231+
parentSuiteId: 5,
232+
name: "Empty Response Suite",
233+
};
234+
const result = await handler(params);
235+
236+
expect(result.content[0].text).toBe(JSON.stringify(null, null, 2));
237+
});
238+
});
239+
120240
describe("list_test_cases tool", () => {
121241
it("should call getTestCaseList with the correct parameters and return the expected result", async () => {
122242
configureTestPlanTools(server, tokenProvider, connectionProvider);

0 commit comments

Comments
 (0)