Files
aiturk-hermes-ide/web/src/lib/mcp-dashboard-oauth.test.ts
T

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);
});
});