forked from baron/baron-sso
193 lines
5.6 KiB
TypeScript
193 lines
5.6 KiB
TypeScript
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
import { render, screen } from "@testing-library/react";
|
|
import type React from "react";
|
|
import { MemoryRouter, Route, Routes } from "react-router-dom";
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { createI18nMock } from "../../test/i18nMock";
|
|
import AuditLogsPage from "../audit/AuditLogsPage";
|
|
import AuthCallbackPage from "../auth/AuthCallbackPage";
|
|
import AuthGuard from "../auth/AuthGuard";
|
|
|
|
const authState = {
|
|
isAuthenticated: true,
|
|
isLoading: false,
|
|
activeNavigator: undefined as string | undefined,
|
|
error: null as Error | null,
|
|
user: {
|
|
access_token: "access-token",
|
|
state: undefined as unknown,
|
|
},
|
|
};
|
|
|
|
vi.mock("react-oidc-context", () => ({
|
|
useAuth: () => authState,
|
|
}));
|
|
|
|
vi.mock("../../lib/i18n", () => createI18nMock());
|
|
|
|
vi.mock("../../../../common/core/components/audit", () => ({
|
|
AuditLogTable: ({
|
|
logs,
|
|
}: {
|
|
logs: Array<{ user_id: string; event_type: string }>;
|
|
}) => (
|
|
<div>
|
|
{logs.map((log) => (
|
|
<div key={`${log.user_id}-${log.event_type}`}>
|
|
<span>{log.user_id}</span>
|
|
<span>{log.event_type}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
),
|
|
}));
|
|
|
|
vi.mock("../../lib/adminApi", () => ({
|
|
fetchAuditLogs: vi.fn(async () => ({
|
|
items: [
|
|
{
|
|
event_id: "event-1",
|
|
timestamp: "2026-05-01T00:00:00Z",
|
|
user_id: "admin-1",
|
|
event_type: "USER_UPDATE",
|
|
status: "success",
|
|
ip_address: "127.0.0.1",
|
|
user_agent: "Vitest",
|
|
details: JSON.stringify({ action: "USER_UPDATE", actor: "Admin" }),
|
|
},
|
|
{
|
|
event_id: "event-2",
|
|
timestamp: "2026-05-01T01:00:00Z",
|
|
user_id: "admin-2",
|
|
event_type: "LOGIN_FAILED",
|
|
status: "failure",
|
|
ip_address: "127.0.0.2",
|
|
user_agent: "Vitest",
|
|
details: "{}",
|
|
},
|
|
],
|
|
limit: 50,
|
|
})),
|
|
}));
|
|
|
|
function renderWithProviders(ui: React.ReactElement, entry = "/") {
|
|
const queryClient = new QueryClient({
|
|
defaultOptions: {
|
|
queries: { retry: false },
|
|
mutations: { retry: false },
|
|
},
|
|
});
|
|
|
|
return render(
|
|
<QueryClientProvider client={queryClient}>
|
|
<MemoryRouter initialEntries={[entry]}>{ui}</MemoryRouter>
|
|
</QueryClientProvider>,
|
|
);
|
|
}
|
|
|
|
describe("admin audit and auth coverage smoke", () => {
|
|
beforeEach(() => {
|
|
(
|
|
window as Window & typeof globalThis & { _IS_TEST_MODE?: boolean }
|
|
)._IS_TEST_MODE = false;
|
|
authState.isAuthenticated = true;
|
|
authState.isLoading = false;
|
|
authState.activeNavigator = undefined;
|
|
authState.error = null;
|
|
authState.user = {
|
|
access_token: "access-token",
|
|
state: undefined,
|
|
};
|
|
window.localStorage.clear();
|
|
});
|
|
|
|
it("renders audit log table with fetched events", async () => {
|
|
renderWithProviders(<AuditLogsPage />);
|
|
|
|
expect(await screen.findByText("감사 로그")).toBeInTheDocument();
|
|
expect(await screen.findByText("admin-1")).toBeInTheDocument();
|
|
expect(screen.getByText("USER_UPDATE")).toBeInTheDocument();
|
|
});
|
|
|
|
it("renders AuthGuard loading, error, redirect, test, and outlet states", async () => {
|
|
authState.isLoading = true;
|
|
renderWithProviders(
|
|
<Routes>
|
|
<Route path="/secure" element={<AuthGuard />}>
|
|
<Route index element={<div>Secure outlet</div>} />
|
|
</Route>
|
|
</Routes>,
|
|
"/secure",
|
|
);
|
|
expect(screen.getByText("Loading...")).toBeInTheDocument();
|
|
|
|
authState.isLoading = false;
|
|
authState.error = new Error("OIDC failed");
|
|
renderWithProviders(
|
|
<Routes>
|
|
<Route path="/secure" element={<AuthGuard />}>
|
|
<Route index element={<div>Secure outlet</div>} />
|
|
</Route>
|
|
</Routes>,
|
|
"/secure",
|
|
);
|
|
expect(screen.getByText("인증 오류")).toBeInTheDocument();
|
|
|
|
authState.error = null;
|
|
authState.isAuthenticated = false;
|
|
renderWithProviders(
|
|
<Routes>
|
|
<Route path="/secure" element={<AuthGuard />}>
|
|
<Route index element={<div>Secure outlet</div>} />
|
|
</Route>
|
|
<Route path="/login" element={<div>Login outlet</div>} />
|
|
</Routes>,
|
|
"/secure?x=1",
|
|
);
|
|
expect(screen.getByText("Login outlet")).toBeInTheDocument();
|
|
|
|
(
|
|
window as Window & typeof globalThis & { _IS_TEST_MODE?: boolean }
|
|
)._IS_TEST_MODE = true;
|
|
renderWithProviders(
|
|
<Routes>
|
|
<Route path="/secure" element={<AuthGuard />}>
|
|
<Route index element={<div>Secure outlet</div>} />
|
|
</Route>
|
|
</Routes>,
|
|
"/secure",
|
|
);
|
|
expect(screen.getByText("Secure outlet")).toBeInTheDocument();
|
|
});
|
|
|
|
it("stores callback token and navigates by auth result", async () => {
|
|
authState.isAuthenticated = true;
|
|
authState.user = {
|
|
access_token: "callback-token",
|
|
state: { returnTo: "/users" },
|
|
};
|
|
|
|
renderWithProviders(
|
|
<Routes>
|
|
<Route path="/auth/callback" element={<AuthCallbackPage />} />
|
|
<Route path="/users" element={<div>Users outlet</div>} />
|
|
<Route path="/login" element={<div>Login outlet</div>} />
|
|
</Routes>,
|
|
"/auth/callback",
|
|
);
|
|
expect(await screen.findByText("Users outlet")).toBeInTheDocument();
|
|
expect(window.localStorage.getItem("admin_session")).toBe("callback-token");
|
|
|
|
authState.isAuthenticated = false;
|
|
authState.error = new Error("callback failed");
|
|
renderWithProviders(
|
|
<Routes>
|
|
<Route path="/auth/callback" element={<AuthCallbackPage />} />
|
|
<Route path="/login" element={<div>Login outlet</div>} />
|
|
</Routes>,
|
|
"/auth/callback",
|
|
);
|
|
expect(await screen.findByText("Login outlet")).toBeInTheDocument();
|
|
});
|
|
});
|