147 lines
4.5 KiB
TypeScript
147 lines
4.5 KiB
TypeScript
import { describe, expect, it, vi } from "vitest";
|
|
import { completeMcpDashboardOAuth } from "./mcp-dashboard-oauth";
|
|
|
|
describe("completeMcpDashboardOAuth", () => {
|
|
it("opens the authorization URL in the dashboard browser and polls to approval", async () => {
|
|
const authWindow = { location: { href: "" }, opener: {} } as unknown as Window;
|
|
const open = vi.fn().mockReturnValue(authWindow);
|
|
const start = vi.fn().mockResolvedValue({
|
|
flow_id: "flow-1",
|
|
server_name: "reports",
|
|
status: "authorization_required",
|
|
authorization_url: "https://idp.example/authorize",
|
|
error: null,
|
|
});
|
|
const status = vi
|
|
.fn()
|
|
.mockResolvedValueOnce({
|
|
flow_id: "flow-1",
|
|
server_name: "reports",
|
|
status: "authorization_required",
|
|
authorization_url: "https://idp.example/authorize",
|
|
error: null,
|
|
tools: [],
|
|
})
|
|
.mockResolvedValueOnce({
|
|
flow_id: "flow-1",
|
|
server_name: "reports",
|
|
status: "approved",
|
|
authorization_url: "https://idp.example/authorize",
|
|
error: null,
|
|
tools: [{ name: "list_reports", description: "List reports" }],
|
|
});
|
|
|
|
const result = await completeMcpDashboardOAuth({
|
|
serverName: "reports",
|
|
start,
|
|
status,
|
|
open,
|
|
sleep: async () => {},
|
|
});
|
|
|
|
expect(open).toHaveBeenCalledWith(
|
|
"about:blank",
|
|
"_blank",
|
|
);
|
|
expect(authWindow.opener).toBeNull();
|
|
expect(authWindow.location.href).toBe("https://idp.example/authorize");
|
|
expect(status).toHaveBeenCalledTimes(2);
|
|
expect(result.status).toBe("approved");
|
|
});
|
|
|
|
it("surfaces a terminal OAuth error", async () => {
|
|
const close = vi.fn();
|
|
await expect(
|
|
completeMcpDashboardOAuth({
|
|
serverName: "reports",
|
|
start: async () => ({
|
|
flow_id: "flow-2",
|
|
server_name: "reports",
|
|
status: "error",
|
|
authorization_url: null,
|
|
error: "registration denied",
|
|
}),
|
|
status: vi.fn(),
|
|
open: vi.fn().mockReturnValue({ location: { href: "" }, close }),
|
|
sleep: async () => {},
|
|
}),
|
|
).rejects.toThrow("registration denied");
|
|
expect(close).toHaveBeenCalledOnce();
|
|
});
|
|
|
|
it("fails before starting when the browser blocks the popup", async () => {
|
|
const start = vi.fn();
|
|
await expect(
|
|
completeMcpDashboardOAuth({
|
|
serverName: "reports",
|
|
start,
|
|
status: vi.fn(),
|
|
open: vi.fn().mockReturnValue(null),
|
|
}),
|
|
).rejects.toThrow("popup was blocked");
|
|
expect(start).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("fails when the authorization window closes before approval", async () => {
|
|
const authWindow = { location: { href: "" }, opener: {}, closed: false } as unknown as Window;
|
|
const status = vi.fn().mockImplementation(async () => {
|
|
Object.defineProperty(authWindow, "closed", { value: true });
|
|
return {
|
|
flow_id: "flow-closed",
|
|
server_name: "reports",
|
|
status: "authorization_required",
|
|
authorization_url: "https://idp.example/authorize",
|
|
error: null,
|
|
};
|
|
});
|
|
|
|
await expect(
|
|
completeMcpDashboardOAuth({
|
|
serverName: "reports",
|
|
start: async () => ({
|
|
flow_id: "flow-closed",
|
|
server_name: "reports",
|
|
status: "authorization_required",
|
|
authorization_url: "https://idp.example/authorize",
|
|
error: null,
|
|
}),
|
|
status,
|
|
open: vi.fn().mockReturnValue(authWindow),
|
|
sleep: async () => {},
|
|
}),
|
|
).rejects.toThrow("authorization window was closed");
|
|
});
|
|
|
|
it("retries a transient status failure", async () => {
|
|
const authWindow = { location: { href: "" }, opener: {}, closed: false } as unknown as Window;
|
|
const status = vi
|
|
.fn()
|
|
.mockRejectedValueOnce(new Error("temporary network failure"))
|
|
.mockResolvedValueOnce({
|
|
flow_id: "flow-retry",
|
|
server_name: "reports",
|
|
status: "approved",
|
|
authorization_url: "https://idp.example/authorize",
|
|
error: null,
|
|
tools: [],
|
|
});
|
|
|
|
const result = await completeMcpDashboardOAuth({
|
|
serverName: "reports",
|
|
start: async () => ({
|
|
flow_id: "flow-retry",
|
|
server_name: "reports",
|
|
status: "authorization_required",
|
|
authorization_url: "https://idp.example/authorize",
|
|
error: null,
|
|
}),
|
|
status,
|
|
open: vi.fn().mockReturnValue(authWindow),
|
|
sleep: async () => {},
|
|
});
|
|
|
|
expect(result.status).toBe("approved");
|
|
expect(status).toHaveBeenCalledTimes(2);
|
|
});
|
|
});
|