1
0
forked from baron/baron-sso
Files
baron-sso/devfront/src/features/audit/AuditLogsPage.test.tsx
chan fcb246ea9e fix: stabilize tests and refine RBAC model for privileged roles
- Updated devfront to recognize 'rp_admin' and 'tenant_admin' as privileged developer roles.
- Added specific forbidden messages for privileged roles in devfront.
- Improved adminfront Worksmobile test reliability across browsers.
- Updated Makefile to skip userfront tests in environments without Flutter SDK.
- Applied lint and format fixes across adminfront and devfront.
2026-06-04 09:56:02 +09:00

213 lines
5.5 KiB
TypeScript

import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { createRoot, type Root } from "react-dom/client";
import { act } from "react-dom/test-utils";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import AuditLogsPage from "./AuditLogsPage";
const navigateMock = vi.fn();
const fetchMeMock = vi.fn();
const fetchDevAuditLogsMock = vi.fn();
let gateState = {
hasDeveloperAccess: true,
isDeveloperRequestPending: false,
canRequestDeveloperAccess: false,
isLoadingDeveloperAccessGate: false,
};
vi.mock("react-oidc-context", () => ({
useAuth: () => ({
isAuthenticated: true,
isLoading: false,
user: {
access_token: "access-token",
profile: {
role: "super_admin",
tenant_id: "tenant-1",
},
},
}),
}));
vi.mock("react-router-dom", () => ({
useNavigate: () => navigateMock,
}));
vi.mock("../developer-access/developerAccessGate", () => ({
useDeveloperAccessGate: () => gateState,
}));
vi.mock("../../lib/devApi", () => ({
fetchDevAuditLogs: (...args: unknown[]) => fetchDevAuditLogsMock(...args),
}));
vi.mock("../auth/authApi", () => ({
fetchMe: (...args: unknown[]) => fetchMeMock(...args),
}));
vi.mock("../../../../common/core/components/audit", () => ({
AuditLogTable: ({
logs,
onLoadMore,
}: {
logs: Array<{ event_id: string }>;
onLoadMore: () => void;
}) => (
<div>
<div>table:{logs.length}</div>
<button type="button" onClick={onLoadMore}>
Load more
</button>
</div>
),
}));
vi.mock("../../components/common/ForbiddenMessage", () => ({
ForbiddenMessage: ({ resourceToken }: { resourceToken: string }) => (
<div>Forbidden:{resourceToken}</div>
),
}));
const roots: Root[] = [];
afterEach(() => {
for (const root of roots.splice(0)) {
act(() => {
root.unmount();
});
}
vi.clearAllMocks();
document.body.innerHTML = "";
});
beforeEach(() => {
gateState = {
hasDeveloperAccess: true,
isDeveloperRequestPending: false,
canRequestDeveloperAccess: false,
isLoadingDeveloperAccessGate: false,
};
fetchMeMock.mockResolvedValue({
id: "user-1",
role: "super_admin",
});
fetchDevAuditLogsMock.mockResolvedValue({
items: [
{
event_id: "evt-1",
timestamp: "2026-05-28T06:07:18.000Z",
user_id: "user-1",
event_type: "CLIENT_UPDATE",
status: "success",
ip_address: "127.0.0.1",
user_agent: "Vitest",
details: JSON.stringify({
action: "업데이트",
target_id: "client-a",
}),
},
],
limit: 50,
});
});
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 },
},
});
await act(async () => {
root.render(
<QueryClientProvider client={queryClient}>
<AuditLogsPage />
</QueryClientProvider>,
);
});
await act(async () => {
await new Promise((resolve) => setTimeout(resolve, 0));
});
return container;
}
describe("AuditLogsPage", () => {
it("shows the loading gate state", async () => {
gateState = {
hasDeveloperAccess: false,
isDeveloperRequestPending: false,
canRequestDeveloperAccess: false,
isLoadingDeveloperAccessGate: true,
};
const container = await renderPage();
expect(container.textContent).toContain("로딩 중...");
});
it("renders the access request card when access is denied", async () => {
gateState = {
hasDeveloperAccess: false,
isDeveloperRequestPending: false,
canRequestDeveloperAccess: true,
isLoadingDeveloperAccessGate: false,
};
const container = await renderPage();
expect(container.textContent).toContain(
"감사 로그는 개발자 권한이 있어야 볼 수 있습니다.",
);
const button = Array.from(container.querySelectorAll("button")).find(
(item) => item.textContent?.includes("개발자 권한 신청"),
);
expect(button).toBeTruthy();
await act(async () => {
button?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
});
expect(navigateMock).toHaveBeenCalledWith("/developer-requests");
});
it("exports the fetched logs as CSV", async () => {
const createObjectURL = vi
.spyOn(URL, "createObjectURL")
.mockReturnValue("blob:csv");
const revokeObjectURL = vi.spyOn(URL, "revokeObjectURL").mockReturnValue();
const clickSpy = vi
.spyOn(HTMLAnchorElement.prototype, "click")
.mockImplementation(() => {});
const container = await renderPage();
expect(container.textContent).toContain("table:1");
const button = Array.from(container.querySelectorAll("button")).find(
(item) => item.textContent === "CSV 내보내기",
);
expect(button).toBeTruthy();
await act(async () => {
button?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
});
expect(createObjectURL).toHaveBeenCalled();
expect(clickSpy).toHaveBeenCalled();
expect(revokeObjectURL).toHaveBeenCalledWith("blob:csv");
});
it("renders the forbidden state on 403 errors", async () => {
fetchDevAuditLogsMock.mockRejectedValueOnce({
response: { status: 403 },
message: "Forbidden",
});
const container = await renderPage();
expect(container.textContent).toContain("Forbidden:audit");
});
});