forked from baron/baron-sso
153 lines
4.5 KiB
TypeScript
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);
|
|
});
|
|
});
|