import { expect, test } from "@playwright/test"; import { type AuditLog, type Consent, installDevApiMock, makeClient, seedAuth, } from "./helpers/devfront-fixtures"; import { captureEvidence } from "./helpers/evidence"; const appNamePlaceholder = /My Awesome Application|예: 멋진 애플리케이션/i; test.describe("DevFront role report", () => { test.beforeEach(async ({ page }) => { page.on("dialog", async (dialog) => { await dialog.accept(); }); }); test("user can enter and sees empty RP list", async ({ page }, testInfo) => { await seedAuth(page, "user"); await installDevApiMock(page, { clients: [], consents: [] as Consent[], auditLogs: [] as AuditLog[], }); await page.goto("/clients"); await expect( page.getByText(/조회 가능한 RP가 없습니다|No RPs are available/i), ).toBeVisible(); await expect( page.getByRole("heading", { name: /^연동 앱$|^Connected Application$/i, }), ).toBeVisible(); await captureEvidence(page, testInfo, "role-user-empty-rps"); }); test("user sees developer request entry point on overview", async ({ page, }, testInfo) => { await seedAuth(page, "user"); await installDevApiMock(page, { clients: [], consents: [] as Consent[], auditLogs: [] as AuditLog[], auditLogsByCursor: undefined, developerRequests: [], }); await page.goto("/"); await expect( page.getByText( /대시보드는 개발자 권한이 있어야 볼 수 있습니다|개발자 권한 신청을 검토 중입니다./, ), ).toBeVisible(); const requestBtn = page.getByRole("button", { name: /개발자 권한 신청/, }); await expect(requestBtn).toBeVisible(); await requestBtn.click(); await expect(page).toHaveURL(/\/developer-requests$/); await captureEvidence(page, testInfo, "role-user-overview-request-entry"); }); test("user with approved developer request sees overview without CTA", async ({ page, }, testInfo) => { await seedAuth(page, "user"); await installDevApiMock(page, { clients: [], consents: [] as Consent[], auditLogs: [] as AuditLog[], auditLogsByCursor: undefined, developerRequests: [ { id: "req-approved", userId: "playwright-user", userName: "Playwright User", name: "Playwright User", userEmail: "playwright@example.com", organization: "Tenant A", reason: "Need access", status: "approved", createdAt: "2026-05-29T00:00:00.000Z", updatedAt: "2026-05-29T00:00:00.000Z", approvedAt: "2026-05-29T00:10:00.000Z", }, ], }); await page.goto("/"); await expect( page.getByRole("heading", { name: /운영 현황/ }), ).toBeVisible(); await expect( page.getByRole("button", { name: /개발자 권한 신청/ }), ).toHaveCount(0); await captureEvidence(page, testInfo, "role-user-overview-approved"); }); test("rp_admin sees only assigned Gitea app and its logs", async ({ page, }, testInfo) => { await seedAuth(page, "rp_admin"); const state = { clients: [makeClient("gitea-client", { name: "Gitea" })], consents: [] as Consent[], auditLogs: [ { event_id: "evt-rp-1", timestamp: "2026-03-04T01:00:00.000Z", user_id: "rp-admin-user", event_type: "CLIENT_UPDATE", status: "success" as const, ip_address: "127.0.0.1", user_agent: "playwright", details: JSON.stringify({ action: "UPDATE_CLIENT", target_id: "gitea-client", tenant_id: "tenant-a", }), }, ] as AuditLog[], }; await installDevApiMock(page, state); await page.goto("/clients"); await expect( page.getByRole("link", { name: "Gitea", exact: true }), ).toBeVisible(); await expect( page.getByRole("cell", { name: "gitea-client" }), ).toBeVisible(); await captureEvidence(page, testInfo, "role-rp-admin-clients"); await page.goto("/audit-logs"); await expect(page.getByText("UPDATE_CLIENT")).toBeVisible(); await expect(page.getByText("gitea-client")).toBeVisible(); await captureEvidence(page, testInfo, "role-rp-admin-audit"); }); test("tenant_admin can manage tenant apps and see tenant logs", async ({ page, }, testInfo) => { await seedAuth(page, "tenant_admin"); const state = { clients: [ makeClient("tenant-a-app-1", { name: "Tenant A CRM" }), makeClient("tenant-a-app-2", { name: "Tenant A ERP" }), ], consents: [] as Consent[], auditLogs: [] as AuditLog[], auditLogsByCursor: undefined, }; await installDevApiMock(page, state); await page.goto("/clients"); await expect(page.getByText("Tenant A CRM")).toBeVisible(); await expect(page.getByText("Tenant A ERP")).toBeVisible(); await captureEvidence(page, testInfo, "role-tenant-admin-clients"); await page.goto("/clients/tenant-a-app-1/settings"); await page .getByPlaceholder(appNamePlaceholder) .fill("Tenant A CRM Updated"); const updatePromise = page.waitForResponse( (r) => r.url().includes("/api/v1/dev/clients") && r.request().method() === "PUT", ); await page.getByRole("button", { name: /^저장$|^Save$/i }).click(); await updatePromise; await page.goto("/audit-logs"); await expect(page.getByText("UPDATE_CLIENT")).toBeVisible({ timeout: 30000, }); await expect(page.getByText("tenant-a-app-1")).toBeVisible(); await captureEvidence(page, testInfo, "role-tenant-admin-audit"); }); test("super_admin sees all and can generate log entries", async ({ page, }, testInfo) => { await seedAuth(page, "super_admin"); const state = { clients: [ makeClient("tenant-a-app", { name: "Tenant A App" }), makeClient("tenant-b-app", { name: "Tenant B App" }), ], consents: [] as Consent[], auditLogs: [] as AuditLog[], auditLogsByCursor: undefined, }; await installDevApiMock(page, state); await page.goto("/clients"); await expect(page.getByText("Tenant A App")).toBeVisible(); await expect(page.getByText("Tenant B App")).toBeVisible(); await captureEvidence(page, testInfo, "role-super-admin-clients"); await page.goto("/clients/new"); await page .getByPlaceholder(appNamePlaceholder) .fill("Super Admin Created App"); await page .getByPlaceholder(/https:\/\/app\.example\.com\/callback/i) .fill("https://super-admin.example.com/callback"); const createPromise = page.waitForResponse( (r) => r.url().includes("/api/v1/dev/clients") && r.request().method() === "POST", ); await page.getByRole("button", { name: /앱 생성|Create/i }).click(); await createPromise; await expect(page).toHaveURL(/\/clients\/client-\d+\/settings$/); await expect .poll(() => state.auditLogs.some((item) => { try { return JSON.parse(item.details)?.action === "CREATE_CLIENT"; } catch { return false; } }), ) .toBe(true); await page.goto("/audit-logs"); await expect(page.getByText("CREATE_CLIENT")).toBeVisible({ timeout: 30000, }); await captureEvidence(page, testInfo, "role-super-admin-audit"); }); });