forked from baron/baron-sso
test: raise frontend coverage baselines
This commit is contained in:
161
devfront/src/features/auth/authPages.test.tsx
Normal file
161
devfront/src/features/auth/authPages.test.tsx
Normal file
@@ -0,0 +1,161 @@
|
||||
import { act } from "react";
|
||||
import { createRoot, type Root } from "react-dom/client";
|
||||
import { MemoryRouter, Route, Routes } from "react-router-dom";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import AuthCallbackPage from "./AuthCallbackPage";
|
||||
import AuthGuard from "./AuthGuard";
|
||||
import AuthPage from "./AuthPage";
|
||||
import LoginPage from "./LoginPage";
|
||||
|
||||
const authState = {
|
||||
isAuthenticated: false,
|
||||
isLoading: false,
|
||||
activeNavigator: undefined as string | undefined,
|
||||
error: null as Error | null,
|
||||
user: undefined as
|
||||
| {
|
||||
state?: unknown;
|
||||
}
|
||||
| undefined,
|
||||
signinRedirect: vi.fn(),
|
||||
};
|
||||
|
||||
vi.mock("react-oidc-context", () => ({
|
||||
useAuth: () => authState,
|
||||
}));
|
||||
|
||||
vi.mock("../../lib/auth", () => ({
|
||||
userManager: {
|
||||
signinPopupCallback: vi.fn(async () => undefined),
|
||||
},
|
||||
}));
|
||||
|
||||
const roots: Root[] = [];
|
||||
|
||||
beforeEach(() => {
|
||||
authState.isAuthenticated = false;
|
||||
authState.isLoading = false;
|
||||
authState.activeNavigator = undefined;
|
||||
authState.error = null;
|
||||
authState.user = undefined;
|
||||
authState.signinRedirect.mockReset();
|
||||
authState.signinRedirect.mockResolvedValue(undefined);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
for (const root of roots.splice(0)) {
|
||||
act(() => {
|
||||
root.unmount();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
async function renderWithRouter(
|
||||
element: React.ReactElement,
|
||||
{
|
||||
entry = "/",
|
||||
path = "*",
|
||||
}: {
|
||||
entry?: string;
|
||||
path?: string;
|
||||
} = {},
|
||||
) {
|
||||
const container = document.createElement("div");
|
||||
document.body.appendChild(container);
|
||||
const root = createRoot(container);
|
||||
roots.push(root);
|
||||
|
||||
await act(async () => {
|
||||
root.render(
|
||||
<MemoryRouter initialEntries={[entry]}>
|
||||
<Routes>
|
||||
<Route path={path} element={element}>
|
||||
<Route index element={<div>Protected outlet</div>} />
|
||||
</Route>
|
||||
<Route path="/login" element={<div>Login route</div>} />
|
||||
<Route path="/clients" element={<div>Clients route</div>} />
|
||||
<Route path="/profile" element={<div>Profile route</div>} />
|
||||
</Routes>
|
||||
</MemoryRouter>,
|
||||
);
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
});
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
describe("devfront auth pages", () => {
|
||||
it("renders the static auth planning page", async () => {
|
||||
const container = await renderWithRouter(<AuthPage />);
|
||||
|
||||
expect(container.textContent).toContain("Admin auth guardrails");
|
||||
expect(container.textContent).toContain("Device approval");
|
||||
});
|
||||
|
||||
it("renders login page and starts SSO redirect from the action button", async () => {
|
||||
const container = await renderWithRouter(<LoginPage />, {
|
||||
entry: "/login?returnTo=/profile",
|
||||
path: "/login",
|
||||
});
|
||||
|
||||
expect(container.textContent).toContain("개발자 포털 로그인");
|
||||
|
||||
const loginButton = Array.from(container.querySelectorAll("button")).find(
|
||||
(button) => button.textContent?.includes("SSO 계정으로 로그인"),
|
||||
);
|
||||
await act(async () => {
|
||||
(loginButton as HTMLButtonElement).click();
|
||||
});
|
||||
|
||||
expect(authState.signinRedirect).toHaveBeenCalledWith({
|
||||
state: { returnTo: "/clients" },
|
||||
});
|
||||
});
|
||||
|
||||
it("shows AuthGuard loading, error, redirect, and protected outlet states", async () => {
|
||||
authState.isLoading = true;
|
||||
const loading = await renderWithRouter(<AuthGuard />);
|
||||
expect(loading.textContent).toContain("Loading...");
|
||||
|
||||
authState.isLoading = false;
|
||||
authState.error = new Error("OIDC failed");
|
||||
const error = await renderWithRouter(<AuthGuard />);
|
||||
expect(error.textContent).toContain("Authentication Error");
|
||||
|
||||
const retryButton = error.querySelector("button") as HTMLButtonElement;
|
||||
await act(async () => {
|
||||
retryButton.click();
|
||||
});
|
||||
expect(authState.signinRedirect).toHaveBeenCalled();
|
||||
|
||||
authState.error = null;
|
||||
const redirected = await renderWithRouter(<AuthGuard />);
|
||||
expect(redirected.textContent).toContain("Login route");
|
||||
|
||||
authState.isAuthenticated = true;
|
||||
const protectedPage = await renderWithRouter(<AuthGuard />);
|
||||
expect(protectedPage.textContent).toContain("Protected outlet");
|
||||
});
|
||||
|
||||
it("navigates from callback by auth result and stored return target", async () => {
|
||||
authState.isAuthenticated = true;
|
||||
authState.user = { state: { returnTo: "/profile" } };
|
||||
|
||||
const authenticated = await renderWithRouter(<AuthCallbackPage />, {
|
||||
entry: "/auth/callback",
|
||||
path: "/auth/callback",
|
||||
});
|
||||
expect(authenticated.textContent).toContain("Profile route");
|
||||
|
||||
authState.isAuthenticated = false;
|
||||
authState.error = new Error("callback failed");
|
||||
const failed = await renderWithRouter(<AuthCallbackPage />, {
|
||||
entry: "/auth/callback",
|
||||
path: "/auth/callback",
|
||||
});
|
||||
expect(failed.textContent).toContain("Login route");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user