import { expect, test } from "@playwright/test"; import { type DevAssignableUser, type AuditLog, type Consent, installDevApiMock, makeClient, seedAuth, } from "./helpers/devfront-fixtures"; import { captureEvidence } from "./helpers/evidence"; test.afterEach(async ({ page }, testInfo) => { if (testInfo.status === "passed") { await captureEvidence(page, testInfo, testInfo.title); } }); test("clients page loads correctly", async ({ page }) => { await seedAuth(page, "super_admin"); await installDevApiMock(page, { clients: [ makeClient("client-playwright", { name: "Playwright Client", createdAt: new Date().toISOString(), redirectUris: ["http://localhost:5174/callback"], }), ], consents: [] as Consent[], auditLogsByCursor: undefined, }); await page.goto("/clients"); await expect(page).toHaveURL(/\/clients$/); // 타이틀 확인 await expect(page).toHaveTitle(/바론 개발자 서비스/); // 페이지 내 주요 텍스트 확인 await expect(page.getByText("연동 앱 목록")).toBeVisible(); // 테이블 헤더 확인 await expect( page.locator("th").filter({ hasText: "애플리케이션" }), ).toBeVisible(); await expect( page.locator("th").filter({ hasText: /클라이언트 ID|Client ID/i }), ).toBeVisible(); }); test("clients page shows recent RP changes", async ({ page }) => { await seedAuth(page, "super_admin"); await installDevApiMock(page, { clients: [ makeClient("client-recent", { name: "Recent RP", }), ], consents: [] as Consent[], auditLogs: [ { event_id: "evt-1", timestamp: "2026-03-03T09:00:00.000Z", user_id: "actor-1", event_type: "CLIENT_RELATION_CREATE", status: "success", ip_address: "127.0.0.1", user_agent: "playwright", details: JSON.stringify({ action: "ADD_RELATION", target_id: "client-recent", relation: "config_editor", subject: "User:user-2", }), }, { event_id: "evt-2", timestamp: "2026-03-03T08:59:00.000Z", user_id: "actor-2", event_type: "CLIENT_ROTATE_SECRET", status: "success", ip_address: "127.0.0.1", user_agent: "playwright", details: JSON.stringify({ action: "ROTATE_SECRET", target_id: "client-recent", }), }, ] as AuditLog[], auditLogsByCursor: undefined, }); await page.goto("/clients"); await expect( page.getByRole("heading", { name: "최근 변경된 앱" }), ).toBeVisible(); await expect(page.getByText("클라이언트 시크릿 재발급")).toBeVisible(); await expect(page.getByText("관계 추가")).toBeVisible(); await expect( page.getByRole("link", { name: "Recent RP", exact: true }).first(), ).toBeVisible(); }); test("clients page shows user-delete relation cleanup in recent changes", async ({ page, }) => { await seedAuth(page, "super_admin"); await installDevApiMock(page, { clients: [ makeClient("client-cleanup", { name: "Cleanup RP", }), ], consents: [] as Consent[], users: [ { id: "cleanup-actor", name: "Cleanup Actor", email: "cleanup.actor@example.com", } satisfies DevAssignableUser, ], auditLogs: [ { event_id: "evt-cleanup-1", timestamp: "2026-03-03T09:00:00.000Z", user_id: "cleanup-actor", event_type: "CLIENT_RELATION_DELETE", status: "success", ip_address: "127.0.0.1", user_agent: "playwright", details: JSON.stringify({ action: "REMOVE_RELATION", target_id: "client-cleanup", relation: "config_editor", subject: "User:deleted-user", before: { relation: "config_editor", subject: "User:deleted-user", }, }), }, ] as AuditLog[], auditLogsByCursor: undefined, }); await page.goto("/clients"); await expect( page.getByRole("heading", { name: "최근 변경된 앱" }), ).toBeVisible(); await expect( page.getByRole("link", { name: "Cleanup RP", exact: true }), ).toBeVisible(); await expect(page.getByText("관계 삭제", { exact: true })).toBeVisible(); await expect(page.getByText(/관계:\s*config_editor/)).toBeVisible(); await expect(page.getByText(/대상:\s*User:deleted-user/)).toBeVisible(); await expect( page.getByText("cleanup-actor", { exact: true }).first(), ).toBeVisible(); }); test("clients page expands recent changes with more button", async ({ page, }) => { await seedAuth(page, "super_admin"); const clients = Array.from({ length: 6 }, (_, index) => makeClient(`client-${index + 1}`, { name: `Recent App ${index + 1}`, }), ); const auditLogs = clients.map((client, index) => ({ event_id: `evt-recent-${index + 1}`, timestamp: `2026-03-03T09:${String(10 - index).padStart(2, "0")}:00.000Z`, user_id: `actor-${index + 1}`, event_type: "CLIENT_CREATE", status: "success" as const, ip_address: "127.0.0.1", user_agent: "playwright", details: JSON.stringify({ action: "CREATE_CLIENT", target_id: client.id, after: { name: client.name, }, }), })); await installDevApiMock(page, { clients, consents: [] as Consent[], auditLogs: auditLogs as AuditLog[], auditLogsByCursor: undefined, }); await page.goto("/clients"); await expect( page.getByRole("heading", { name: "최근 변경된 앱" }), ).toBeVisible(); await expect( page.getByRole("link", { name: "Recent App 1", exact: true }), ).toBeVisible(); await expect( page.getByRole("link", { name: "Recent App 5", exact: true }), ).toBeVisible(); await expect( page.getByRole("link", { name: "Recent App 6", exact: true }), ).not.toBeVisible(); const moreButton = page.getByRole("button", { name: "더 보기" }); await expect(moreButton).toBeVisible(); await moreButton.click(); await expect( page.getByRole("link", { name: "Recent App 6", exact: true }), ).toBeVisible(); await expect(moreButton).toHaveCount(0); });