Skip to content

Commit 4ba5629

Browse files
authored
Improve test coverage (#458)
Improving test coverage with additional tests (generated by copilot). Before <img width="1092" height="1203" alt="image" src="https://github.com/user-attachments/assets/2b0b0515-01a9-476a-b78c-67e08cb84b3c" /> After <img width="982" height="759" alt="image" src="https://github.com/user-attachments/assets/f9c68214-4c73-4047-bf0f-8c562969d156" /> ## GitHub issue number / ## **Associated Risks** / ## ✅ **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?** Executed tests.
1 parent d5dabf6 commit 4ba5629

File tree

3 files changed

+1992
-14
lines changed

3 files changed

+1992
-14
lines changed

test/src/tools/advsec.test.ts

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,198 @@ describe("configureAdvSecTools", () => {
438438
undefined // continuationToken
439439
);
440440
});
441+
442+
it("should handle optional parameters correctly when not provided", async () => {
443+
configureAdvSecTools(server, tokenProvider, connectionProvider);
444+
445+
const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "advsec_get_alerts");
446+
if (!call) throw new Error("advsec_get_alerts tool not registered");
447+
const [, , , handler] = call;
448+
449+
const mockResult: PagedList<Alert> = [];
450+
(mockAlertApi.getAlerts as jest.Mock).mockResolvedValue(mockResult);
451+
452+
// Test with minimal parameters - only required ones
453+
const minimalParams = {
454+
project: "test-project",
455+
repository: "test-repo",
456+
};
457+
458+
await handler(minimalParams);
459+
460+
// When optional parameters aren't provided, they remain undefined
461+
expect(mockAlertApi.getAlerts).toHaveBeenLastCalledWith(
462+
"test-project",
463+
"test-repo",
464+
undefined, // top (optional, no default applied by handler)
465+
undefined, // orderBy (optional, no default applied by handler)
466+
{}, // empty criteria object since no optional filters provided
467+
undefined, // expand
468+
undefined // continuationToken
469+
);
470+
});
471+
472+
it("should include all optional parameters when provided", async () => {
473+
configureAdvSecTools(server, tokenProvider, connectionProvider);
474+
475+
const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "advsec_get_alerts");
476+
if (!call) throw new Error("advsec_get_alerts tool not registered");
477+
const [, , , handler] = call;
478+
479+
const mockResult: PagedList<Alert> = [];
480+
(mockAlertApi.getAlerts as jest.Mock).mockResolvedValue(mockResult);
481+
482+
// Test with all optional parameters provided
483+
const allParamsParams = {
484+
project: "test-project",
485+
repository: "test-repo",
486+
alertType: "dependency",
487+
states: ["active", "dismissed"],
488+
severities: ["high", "medium"],
489+
ruleId: "rule123",
490+
ruleName: "security-rule",
491+
toolName: "CodeQL",
492+
ref: "refs/heads/main",
493+
onlyDefaultBranch: false,
494+
top: 50,
495+
orderBy: "id",
496+
continuationToken: "token123",
497+
};
498+
499+
await handler(allParamsParams);
500+
501+
expect(mockAlertApi.getAlerts).toHaveBeenLastCalledWith(
502+
"test-project",
503+
"test-repo",
504+
50, // top
505+
"id", // orderBy
506+
expect.objectContaining({
507+
alertType: AlertType.Dependency,
508+
states: [State.Active, State.Dismissed],
509+
severities: [Severity.High, Severity.Medium],
510+
ruleId: "rule123",
511+
ruleName: "security-rule",
512+
toolName: "CodeQL",
513+
ref: "refs/heads/main",
514+
onlyDefaultBranch: false,
515+
}),
516+
undefined, // expand
517+
"token123" // continuationToken
518+
);
519+
520+
// Verify all optional fields are included
521+
const lastCall = (mockAlertApi.getAlerts as jest.Mock).mock.calls[0];
522+
const criteria = lastCall[4];
523+
expect(criteria).toHaveProperty("alertType", AlertType.Dependency);
524+
expect(criteria).toHaveProperty("states", [State.Active, State.Dismissed]);
525+
expect(criteria).toHaveProperty("severities", [Severity.High, Severity.Medium]);
526+
expect(criteria).toHaveProperty("ruleId", "rule123");
527+
expect(criteria).toHaveProperty("ruleName", "security-rule");
528+
expect(criteria).toHaveProperty("toolName", "CodeQL");
529+
expect(criteria).toHaveProperty("ref", "refs/heads/main");
530+
expect(criteria).toHaveProperty("onlyDefaultBranch", false);
531+
});
532+
533+
it("should handle onlyDefaultBranch parameter correctly", async () => {
534+
configureAdvSecTools(server, tokenProvider, connectionProvider);
535+
536+
const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "advsec_get_alerts");
537+
if (!call) throw new Error("advsec_get_alerts tool not registered");
538+
const [, , , handler] = call;
539+
540+
const mockResult: PagedList<Alert> = [];
541+
(mockAlertApi.getAlerts as jest.Mock).mockResolvedValue(mockResult);
542+
543+
// Test with onlyDefaultBranch explicitly set to false
544+
const falseParams = {
545+
project: "test-project",
546+
repository: "test-repo",
547+
onlyDefaultBranch: false,
548+
};
549+
550+
await handler(falseParams);
551+
552+
expect(mockAlertApi.getAlerts).toHaveBeenLastCalledWith(
553+
"test-project",
554+
"test-repo",
555+
undefined, // top (not provided)
556+
undefined, // orderBy (not provided)
557+
expect.objectContaining({
558+
onlyDefaultBranch: false,
559+
}),
560+
undefined, // expand
561+
undefined // continuationToken
562+
);
563+
564+
// Test with onlyDefaultBranch explicitly set to true
565+
const trueParams = {
566+
project: "test-project",
567+
repository: "test-repo",
568+
onlyDefaultBranch: true,
569+
};
570+
571+
await handler(trueParams);
572+
573+
expect(mockAlertApi.getAlerts).toHaveBeenLastCalledWith(
574+
"test-project",
575+
"test-repo",
576+
undefined, // top (not provided)
577+
undefined, // orderBy (not provided)
578+
expect.objectContaining({
579+
onlyDefaultBranch: true,
580+
}),
581+
undefined, // expand
582+
undefined // continuationToken
583+
);
584+
});
585+
586+
it("should handle secret alerts without confidenceLevels or validity", async () => {
587+
configureAdvSecTools(server, tokenProvider, connectionProvider);
588+
589+
const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "advsec_get_alerts");
590+
if (!call) throw new Error("advsec_get_alerts tool not registered");
591+
const [, , , handler] = call;
592+
593+
const mockResult: PagedList<Alert> = [];
594+
(mockAlertApi.getAlerts as jest.Mock).mockResolvedValue(mockResult);
595+
596+
// Test secret alert without confidenceLevels or validity explicitly provided
597+
const secretWithoutParams = {
598+
project: "test-project",
599+
repository: "test-repo",
600+
alertType: "secret",
601+
};
602+
603+
await handler(secretWithoutParams);
604+
605+
const lastCall = (mockAlertApi.getAlerts as jest.Mock).mock.calls[0];
606+
const criteria = lastCall[4];
607+
expect(criteria).toHaveProperty("alertType", AlertType.Secret);
608+
// confidenceLevels and validity should not be included if not explicitly provided
609+
expect(criteria).not.toHaveProperty("confidenceLevels");
610+
expect(criteria).not.toHaveProperty("validity");
611+
});
612+
613+
it("should handle non-Error exception types", async () => {
614+
configureAdvSecTools(server, tokenProvider, connectionProvider);
615+
616+
const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "advsec_get_alerts");
617+
if (!call) throw new Error("advsec_get_alerts tool not registered");
618+
const [, , , handler] = call;
619+
620+
// Test with non-Error exception (string)
621+
(mockAlertApi.getAlerts as jest.Mock).mockRejectedValue("String error");
622+
623+
const params = {
624+
project: "test-project",
625+
repository: "test-repo",
626+
};
627+
628+
const result = await handler(params);
629+
630+
expect(result.isError).toBe(true);
631+
expect(result.content[0].text).toContain("Error fetching Advanced Security alerts: Unknown error occurred");
632+
});
441633
});
442634

443635
describe("advsec_get_alert_details tool", () => {
@@ -487,6 +679,44 @@ describe("configureAdvSecTools", () => {
487679
expect(result.isError).toBeUndefined();
488680
});
489681

682+
it("should fetch specific alert details with ref parameter", async () => {
683+
configureAdvSecTools(server, tokenProvider, connectionProvider);
684+
685+
const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "advsec_get_alert_details");
686+
if (!call) throw new Error("advsec_get_alert_details tool not registered");
687+
const [, , , handler] = call;
688+
689+
const mockResult: Alert = {
690+
alertId: 1,
691+
state: State.Active,
692+
severity: Severity.High,
693+
alertType: AlertType.Code,
694+
title: "Test security alert",
695+
};
696+
697+
(mockAlertApi.getAlert as jest.Mock).mockResolvedValue(mockResult);
698+
699+
const params = {
700+
project: "test-project",
701+
repository: "test-repo",
702+
alertId: 1,
703+
ref: "refs/heads/feature-branch",
704+
};
705+
706+
const result = await handler(params);
707+
708+
expect(mockAlertApi.getAlert).toHaveBeenCalledWith(
709+
"test-project",
710+
1,
711+
"test-repo",
712+
"refs/heads/feature-branch", // ref
713+
undefined // expand
714+
);
715+
716+
expect(result.content[0].text).toBe(JSON.stringify(mockResult, null, 2));
717+
expect(result.isError).toBeUndefined();
718+
});
719+
490720
it("should handle API errors correctly", async () => {
491721
configureAdvSecTools(server, tokenProvider, connectionProvider);
492722

@@ -510,6 +740,28 @@ describe("configureAdvSecTools", () => {
510740
expect(result.content[0].text).toContain("Error fetching alert details: Alert not found");
511741
});
512742

743+
it("should handle non-Error exception types", async () => {
744+
configureAdvSecTools(server, tokenProvider, connectionProvider);
745+
746+
const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "advsec_get_alert_details");
747+
if (!call) throw new Error("advsec_get_alert_details tool not registered");
748+
const [, , , handler] = call;
749+
750+
// Test with non-Error exception (string)
751+
(mockAlertApi.getAlert as jest.Mock).mockRejectedValue("String error");
752+
753+
const params = {
754+
project: "test-project",
755+
repository: "test-repo",
756+
alertId: 999,
757+
};
758+
759+
const result = await handler(params);
760+
761+
expect(result.isError).toBe(true);
762+
expect(result.content[0].text).toContain("Error fetching alert details: Unknown error occurred");
763+
});
764+
513765
it("should handle null API results correctly", async () => {
514766
configureAdvSecTools(server, tokenProvider, connectionProvider);
515767

0 commit comments

Comments
 (0)