import { expect, test } from "@playwright/test"; test.describe("Audit Logs Management", () => { test.beforeEach(async ({ page }) => { page.on("console", (msg) => console.log(`[PAGE] ${msg.text()}`)); await page.addInitScript(() => { const authority = `${window.location.origin}/oidc`; const client_id = "adminfront"; const key = `oidc.user:${authority}:${client_id}`; const authData = { id_token: "fake-id-token", access_token: "fake-token", token_type: "Bearer", scope: "openid profile email", profile: { sub: "admin-user", name: "Admin", email: "admin@test.com", role: "super_admin", }, expires_at: Math.floor(Date.now() / 1000) + 36000, }; window.localStorage.setItem(key, JSON.stringify(authData)); window.localStorage.setItem("admin_session", "fake-token"); window.localStorage.setItem("locale", "ko"); window.localStorage.setItem("oidc.state", "dummy"); ( window as Window & typeof globalThis & { _IS_TEST_MODE?: boolean } )._IS_TEST_MODE = true; }); await page.route("**/oidc/**", async (route) => { const url = route.request().url(); if (url.includes("/.well-known/openid-configuration")) { const origin = new URL(url).origin; return route.fulfill({ json: { issuer: `${origin}/oidc`, authorization_endpoint: `${origin}/oidc/auth`, token_endpoint: `${origin}/oidc/token`, userinfo_endpoint: `${origin}/oidc/userinfo`, jwks_uri: `${origin}/oidc/jwks`, }, }); } const origin = new URL(url).origin; await route.fulfill({ json: { issuer: `${origin}/oidc` } }); }); await page.route("**/v1/audit*", async (route) => { const url = route.request().url(); const urlObj = new URL(url); const cursor = urlObj.searchParams.get("cursor"); const offset = cursor ? 20 : 0; console.log(`[mock] Audit logs request: ${url} (offset: ${offset})`); return route.fulfill({ json: { items: generateMockLogs(20, offset), next_cursor: offset === 0 ? "fake-cursor" : null, total: 40, }, headers: { "Access-Control-Allow-Origin": "*" }, }); }); await page.route("**/user/me", async (route) => { return route.fulfill({ json: { id: "admin-user", name: "Admin", role: "super_admin", manageableTenants: [], }, headers: { "Access-Control-Allow-Origin": "*" }, }); }); }); const generateMockLogs = (count: number, offset = 0) => { return Array.from({ length: count }, (_, i) => { const id = offset + i; return { event_id: `evt-${id}`, timestamp: new Date(Date.now() - id * 10000).toISOString(), user_id: id % 2 === 0 ? "user-even" : "user-odd", event_type: "API_REQUEST", status: id % 5 === 0 ? "failure" : "success", ip_address: "192.168.1.1", user_agent: "Playwright", details: JSON.stringify({ action: id % 3 === 0 ? "CREATE_TENANT" : "ROTATE_SECRET", method: "POST", path: `/v1/admin/tenants`, }), }; }); }; test("should load initial logs and display correctly", async ({ page }) => { console.log("[test] Navigating to /audit-logs"); await page.goto("/audit-logs"); // Check header - this should be visible immediately now await expect(page.getByText(/감사 로그|Audit Logs/i).first()).toBeVisible({ timeout: 10000, }); // Ensure we are not stuck in a global loading state (AppLayout) await expect(page.locator(".animate-spin")).not.toBeVisible({ timeout: 10000, }); // Check for audit page specific error const errorEl = page.getByTestId("audit-error"); if (await errorEl.isVisible()) { const errorText = await errorEl.innerText(); throw new Error(`Audit log page showed error: ${errorText}`); } // Wait for loading to finish await expect(page.getByTestId("audit-loading")).not.toBeVisible({ timeout: 15000, }); // Wait for the table row to appear await expect(page.locator("tbody tr")).toHaveCount(20, { timeout: 15000 }); // Check specific data visible in the row await expect(page.locator("tbody")).toContainText("user-even"); await expect(page.locator("tbody")).toContainText("CREATE_TENANT"); }); test("should load more logs on scroll (infinite scroll)", async ({ page, }) => { await page.goto("/audit-logs"); await expect(page.locator(".animate-spin")).not.toBeVisible({ timeout: 10000, }); await expect(page.getByTestId("audit-loading")).not.toBeVisible({ timeout: 15000, }); await expect(page.locator("tbody tr")).toHaveCount(20, { timeout: 15000 }); const loadMoreBtn = page.getByRole("button", { name: /더 보기|Load more/i, }); await expect(loadMoreBtn).toBeVisible({ timeout: 10000 }); await expect(loadMoreBtn).toBeEnabled(); await loadMoreBtn.click(); // Wait for the next page to load (should reach 40) await expect(page.locator("tbody tr")).toHaveCount(40, { timeout: 15000 }); await expect(page.locator("tbody")).toContainText("user-even"); }); test("should filter logs by Action and User ID locally", async ({ page }) => { await page.goto("/audit-logs"); await expect(page.locator(".animate-spin")).not.toBeVisible({ timeout: 10000, }); await expect(page.getByTestId("audit-loading")).not.toBeVisible({ timeout: 15000, }); await expect(page.locator("tbody tr")).toHaveCount(20, { timeout: 15000 }); // Search by User ID const userIdInput = page.getByTestId("audit-search-user-id"); await userIdInput.fill("user-even"); // Wait for deferred value to apply await expect(page.locator("tbody tr")).toHaveCount(10, { timeout: 15000 }); await expect(page.locator("tbody")).not.toContainText("user-odd"); // Clear User ID await userIdInput.clear(); await expect(page.locator("tbody tr")).toHaveCount(20, { timeout: 15000 }); // Search by Action const actionInput = page.getByTestId("audit-search-action"); await actionInput.fill("ROTATE_SECRET"); // Check that we only see ROTATE_SECRET (20 - 7 = 13) await expect(page.locator("tbody tr")).toHaveCount(13, { timeout: 15000 }); await expect(page.locator("tbody")).not.toContainText("CREATE_TENANT"); }); test("should filter logs by Status locally", async ({ page }) => { await page.goto("/audit-logs"); await expect(page.locator(".animate-spin")).not.toBeVisible({ timeout: 10000, }); await expect(page.getByTestId("audit-loading")).not.toBeVisible({ timeout: 15000, }); await expect(page.locator("tbody tr")).toHaveCount(20, { timeout: 15000 }); // Select "Failure" status await page.getByTestId("audit-filter-status").selectOption("failure"); // ID % 5 === 0 are status "failure" (0, 5, 10, 15) await expect(page.locator("tbody tr")).toHaveCount(4, { timeout: 15000 }); // Select "Success" status await page.getByTestId("audit-filter-status").selectOption("success"); await expect(page.locator("tbody tr")).toHaveCount(16, { timeout: 15000 }); }); });