forked from baron/baron-sso
- 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.
213 lines
5.5 KiB
TypeScript
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");
|
|
});
|
|
});
|