import { expect, test } from "@playwright/test"; import { getPersistedOidcUser, installDevApiMock, seedAuth, } from "./helpers/devfront-fixtures"; import { captureEvidence } from "./helpers/evidence"; type ClaimScenario = { title: string; role: "super_admin" | "user"; tenantName: string; userMeTenantId: string; userMeCompanyCode: string; profileClaims: Record; expectedProfileAssertions: Record; expectTenantsToBeAbsent?: boolean; }; const claimScenarios: ClaimScenario[] = [ { title: "Server Side App preserves tenant and rp claims", role: "super_admin", tenantName: "Server Side Tenant", userMeTenantId: "tenant-server", userMeCompanyCode: "server-hq", profileClaims: { tenant_id: "tenant-server", companyCode: "server-hq", profile: { names: { name: "서버 앱 사용자", }, emails: ["server@example.com"], }, joined_tenants: ["tenant-server", "tenant-ops"], tenants: { "tenant-server": { department: "Platform", grade: "Lead", }, "tenant-ops": { department: "Operations", grade: "Member", }, }, rp_claims: { approvalLevel: "A", }, metadata: { rp_custom_claims: { "server-app": { approvalLevel: "A", }, }, }, }, expectedProfileAssertions: { tenant_id: "tenant-server", companyCode: "server-hq", joined_tenants: ["tenant-server", "tenant-ops"], rp_claims: { approvalLevel: "A", }, }, }, { title: "PKCE preserves nested profile claims without tenant map expansion", role: "user", tenantName: "PKCE Tenant", userMeTenantId: "tenant-pkce", userMeCompanyCode: "pkce-hq", profileClaims: { tenant_id: "tenant-pkce", companyCode: "pkce-hq", profile: { names: { name: "PKCE 사용자", }, emails: ["pkce@example.com"], }, joined_tenants: ["tenant-pkce"], rp_claims: { features: ["sso", "claims"], }, metadata: { rp_custom_claims: { "pkce-app": { features: ["sso", "claims"], }, }, }, }, expectedProfileAssertions: { tenant_id: "tenant-pkce", companyCode: "pkce-hq", joined_tenants: ["tenant-pkce"], rp_claims: { features: ["sso", "claims"], }, }, expectTenantsToBeAbsent: true, }, { title: "Headless login keeps session claims together with rp claims", role: "super_admin", tenantName: "Headless Tenant", userMeTenantId: "tenant-headless", userMeCompanyCode: "headless-hq", profileClaims: { tenant_id: "tenant-headless", companyCode: "headless-hq", profile: { names: { name: "헤드리스 사용자", }, emails: ["headless@example.com"], }, joined_tenants: ["tenant-headless", "tenant-support"], tenants: { "tenant-headless": { department: "Automation", grade: "Manager", }, "tenant-support": { department: "Support", grade: "Agent", }, }, rp_claims: { approvalLevel: "B", loginMode: "headless", }, sid: "session-headless-1", session_id: "session-headless-1", metadata: { rp_custom_claims: { "headless-app": { approvalLevel: "B", loginMode: "headless", }, }, }, }, expectedProfileAssertions: { tenant_id: "tenant-headless", companyCode: "headless-hq", joined_tenants: ["tenant-headless", "tenant-support"], rp_claims: { approvalLevel: "B", loginMode: "headless", }, sid: "session-headless-1", session_id: "session-headless-1", }, }, ]; test.describe("DevFront login claims", () => { test.afterEach(async ({ page }, testInfo) => { if (testInfo.status === "passed") { await captureEvidence(page, testInfo, testInfo.title); } }); for (const scenario of claimScenarios) { test(scenario.title, async ({ page }) => { await seedAuth(page, { role: scenario.role, profile: scenario.profileClaims, }); await installDevApiMock(page, { clients: [], consents: [], auditLogsByCursor: undefined, users: [], tenants: [ { id: scenario.userMeTenantId, name: scenario.tenantName, slug: scenario.userMeCompanyCode, }, ], }); await page.route("**/api/v1/user/me", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ id: "playwright-user", loginId: "playwright@example.com", email: "playwright@example.com", name: "Playwright User", phoneNumber: "", department: "QA", tenantId: "", tenantName: "", role: scenario.role, createdAt: "2026-06-01T00:00:00.000Z", updatedAt: "2026-06-01T00:00:00.000Z", }), }); }); await page.goto("/profile"); await expect( page.getByRole("heading", { name: "내 정보" }), ).toBeVisible(); const storedUser = await getPersistedOidcUser(page); expect(storedUser).not.toBeNull(); expect(storedUser?.profile).toMatchObject( scenario.expectedProfileAssertions, ); if (scenario.expectTenantsToBeAbsent) { expect(storedUser?.profile).not.toHaveProperty("tenants"); } else { expect(storedUser?.profile).toHaveProperty("tenants"); } await expect( page.getByText(String(scenario.profileClaims.tenant_id)), ).toBeVisible(); await expect(page.getByText(scenario.userMeCompanyCode)).toBeVisible(); await page.getByRole("button", { name: "권한 및 역할" }).click(); await expect( page.getByRole("heading", { name: "시스템 역할" }), ).toBeVisible(); await expect( page.getByText( scenario.role === "super_admin" ? /시스템 관리자|Super Admin/i : /일반 사용자|General User/i, ), ).toBeVisible(); }); } });