1
0
forked from baron/baron-sso
Files
baron-sso/common/core/components/audit/AuditLogTable.test.tsx
2026-06-11 08:55:41 +09:00

153 lines
4.5 KiB
TypeScript

import { createRoot, type Root } from "react-dom/client";
import { act } from "react-dom/test-utils";
import { afterEach, describe, expect, it, vi } from "vitest";
import type { CommonAuditLog } from "../../audit";
import { AuditLogTable } from "./AuditLogTable";
const roots: Root[] = [];
afterEach(() => {
for (const root of roots.splice(0)) {
act(() => {
root.unmount();
});
}
vi.restoreAllMocks();
document.body.innerHTML = "";
});
function renderTable(props: Parameters<typeof AuditLogTable>[0]) {
const container = document.createElement("div");
document.body.appendChild(container);
const root = createRoot(container);
roots.push(root);
act(() => {
root.render(<AuditLogTable {...props} />);
});
return { container };
}
const logs: CommonAuditLog[] = [
{
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",
device_id: "device-1",
details: JSON.stringify({
request_id: "req-1",
method: "POST",
path: "/api/v1/clients",
latency_ms: 120,
tenant_id: "tenant-1",
actor_id: "user-1",
action: "업데이트",
target_id: "client-a",
before: { status: "inactive" },
after: { status: "active" },
}),
},
];
describe("AuditLogTable", () => {
it("renders loading and empty states", () => {
const { container: loadingContainer } = renderTable({
logs: [],
t: (key, fallback) => fallback ?? key,
loading: true,
hasNextPage: false,
isFetchingNextPage: false,
onLoadMore: vi.fn(),
});
expect(loadingContainer.textContent).toContain("Loading audit logs...");
const { container: emptyContainer } = renderTable({
logs: [],
t: (key, fallback) => fallback ?? key,
loading: false,
hasNextPage: false,
isFetchingNextPage: false,
onLoadMore: vi.fn(),
});
expect(emptyContainer.textContent).toContain("No audit logs found.");
expect(emptyContainer.textContent).toContain("End of audit feed");
});
it("renders rows, expands details, copies fields, and loads more", async () => {
const writeText = vi.fn().mockResolvedValue(undefined);
Object.defineProperty(navigator, "clipboard", {
value: { writeText },
configurable: true,
});
const onLoadMore = vi.fn();
const { container } = renderTable({
logs,
t: (key, fallback, vars) => {
let text = fallback ?? key;
for (const [name, value] of Object.entries(vars ?? {})) {
text = text.replaceAll(`{{${name}}}`, String(value));
}
return text;
},
loading: false,
hasNextPage: true,
isFetchingNextPage: false,
onLoadMore,
});
expect(container.textContent).toContain("user-1");
expect(container.textContent).toContain("업데이트");
expect(container.textContent).toContain("client-a");
expect(container.textContent).toContain("success");
const buttons = Array.from(container.querySelectorAll("button"));
const actorCopyButton = buttons.find(
(button) => button.getAttribute("aria-label") === "Copy User ID",
);
const targetCopyButton = buttons.find(
(button) => button.getAttribute("aria-label") === "Copy Client ID",
);
const expandButton = buttons.find(
(button) => !button.getAttribute("aria-label") && !button.textContent,
);
const loadMoreButton = buttons.find(
(button) => button.textContent === "Load more",
);
expect(actorCopyButton).toBeTruthy();
expect(targetCopyButton).toBeTruthy();
expect(expandButton).toBeTruthy();
expect(loadMoreButton).toBeTruthy();
await act(async () => {
actorCopyButton?.dispatchEvent(
new MouseEvent("click", { bubbles: true }),
);
targetCopyButton?.dispatchEvent(
new MouseEvent("click", { bubbles: true }),
);
expandButton?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
});
expect(writeText).toHaveBeenCalledWith("user-1");
expect(writeText).toHaveBeenCalledWith("client-a");
expect(container.textContent).toContain("Request ID · req-1");
expect(container.textContent).toContain("Actor");
expect(container.textContent).toContain("Result");
await act(async () => {
loadMoreButton?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
});
expect(onLoadMore).toHaveBeenCalledTimes(1);
});
});