forked from baron/baron-sso
180 lines
5.3 KiB
TypeScript
180 lines
5.3 KiB
TypeScript
import { act } from "react-dom/test-utils";
|
|
import { createRoot, type Root } from "react-dom/client";
|
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { ClientFederationPage } from "./ClientFederationPage";
|
|
|
|
let params: { id?: string } = { id: "client-a" };
|
|
const listIdpConfigsMock = vi.fn();
|
|
const createIdpConfigMock = vi.fn();
|
|
|
|
vi.mock("react-router-dom", async () => {
|
|
const actual =
|
|
await vi.importActual<typeof import("react-router-dom")>(
|
|
"react-router-dom",
|
|
);
|
|
return {
|
|
...actual,
|
|
useParams: () => params,
|
|
};
|
|
});
|
|
|
|
vi.mock("../../../lib/devApi", () => ({
|
|
listIdpConfigsForClient: (clientId: string) => listIdpConfigsMock(clientId),
|
|
createIdpConfigForClient: (payload: unknown) => createIdpConfigMock(payload),
|
|
}));
|
|
|
|
vi.mock("../../../lib/i18n", () => ({
|
|
t: (key: string, fallback?: string, vars?: Record<string, unknown>) => {
|
|
let text = fallback ?? key;
|
|
for (const [name, value] of Object.entries(vars ?? {})) {
|
|
text = text.replaceAll(`{{${name}}}`, String(value));
|
|
}
|
|
return text;
|
|
},
|
|
}));
|
|
|
|
const roots: Root[] = [];
|
|
|
|
afterEach(() => {
|
|
for (const root of roots.splice(0)) {
|
|
act(() => {
|
|
root.unmount();
|
|
});
|
|
}
|
|
vi.clearAllMocks();
|
|
document.body.innerHTML = "";
|
|
});
|
|
|
|
beforeEach(() => {
|
|
params = { id: "client-a" };
|
|
listIdpConfigsMock.mockResolvedValue([
|
|
{
|
|
id: "idp-1",
|
|
client_id: "client-a",
|
|
provider_type: "oidc",
|
|
display_name: "Workspace OIDC",
|
|
status: "active",
|
|
issuer_url: "https://accounts.example",
|
|
oidc_client_id: "oidc-client",
|
|
scopes: "openid email profile",
|
|
createdAt: "2026-05-01T00:00:00Z",
|
|
updatedAt: "2026-05-01T00:00:00Z",
|
|
},
|
|
]);
|
|
createIdpConfigMock.mockResolvedValue({
|
|
id: "idp-2",
|
|
client_id: "client-a",
|
|
provider_type: "oidc",
|
|
display_name: "New Provider",
|
|
status: "active",
|
|
createdAt: "2026-05-02T00:00:00Z",
|
|
updatedAt: "2026-05-02T00:00:00Z",
|
|
});
|
|
});
|
|
|
|
async function setInputValue(input: HTMLInputElement, value: string) {
|
|
const descriptor = Object.getOwnPropertyDescriptor(
|
|
HTMLInputElement.prototype,
|
|
"value",
|
|
);
|
|
descriptor?.set?.call(input, value);
|
|
input.dispatchEvent(new Event("input", { bubbles: true }));
|
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
}
|
|
|
|
async function renderPage() {
|
|
const container = document.createElement("div");
|
|
document.body.appendChild(container);
|
|
const root = createRoot(container);
|
|
roots.push(root);
|
|
const queryClient = new QueryClient({
|
|
defaultOptions: {
|
|
queries: { retry: false },
|
|
mutations: { retry: false },
|
|
},
|
|
});
|
|
|
|
await act(async () => {
|
|
root.render(
|
|
<QueryClientProvider client={queryClient}>
|
|
<ClientFederationPage />
|
|
</QueryClientProvider>,
|
|
);
|
|
});
|
|
|
|
await act(async () => {
|
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
});
|
|
|
|
return container;
|
|
}
|
|
|
|
describe("ClientFederationPage", () => {
|
|
it("shows a missing client id message when no route param exists", async () => {
|
|
params = {};
|
|
const container = await renderPage();
|
|
expect(container.textContent).toContain("Client ID is missing");
|
|
});
|
|
|
|
it("opens the create modal and submits a new IdP config", async () => {
|
|
const container = await renderPage();
|
|
expect(container.textContent).toContain("Workspace OIDC");
|
|
|
|
const addButton = Array.from(container.querySelectorAll("button")).find(
|
|
(button) => button.textContent === "Add Provider",
|
|
);
|
|
expect(addButton).toBeTruthy();
|
|
|
|
await act(async () => {
|
|
addButton?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
|
});
|
|
|
|
expect(container.textContent).toContain("Add Identity Provider");
|
|
|
|
const displayName = container.querySelector(
|
|
'input[name="display_name"]',
|
|
) as HTMLInputElement | null;
|
|
const issuerUrl = container.querySelector(
|
|
'input[name="issuer_url"]',
|
|
) as HTMLInputElement | null;
|
|
const clientId = container.querySelector(
|
|
'input[name="oidc_client_id"]',
|
|
) as HTMLInputElement | null;
|
|
const clientSecret = container.querySelector(
|
|
'input[name="oidc_client_secret"]',
|
|
) as HTMLInputElement | null;
|
|
|
|
if (!displayName || !issuerUrl || !clientId || !clientSecret) {
|
|
throw new Error("Expected federation form inputs to be rendered");
|
|
}
|
|
|
|
await act(async () => {
|
|
await setInputValue(displayName, "New Provider");
|
|
await setInputValue(issuerUrl, "https://login.example");
|
|
await setInputValue(clientId, "client-oidc");
|
|
await setInputValue(clientSecret, "secret-value");
|
|
});
|
|
|
|
const submitButton = Array.from(container.querySelectorAll("button")).find(
|
|
(button) => button.textContent === "Save Configuration",
|
|
);
|
|
expect(submitButton).toBeTruthy();
|
|
|
|
await act(async () => {
|
|
submitButton?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
});
|
|
|
|
expect(createIdpConfigMock).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
client_id: "client-a",
|
|
display_name: "New Provider",
|
|
issuer_url: "https://login.example",
|
|
oidc_client_id: "client-oidc",
|
|
oidc_client_secret: "secret-value",
|
|
}),
|
|
);
|
|
});
|
|
});
|