import { expect, test } from "@playwright/test"; test.describe("User Schema Dynamic Form", () => { test.beforeEach(async ({ page }) => { await page.addInitScript(() => { const authority = "http://localhost:5000/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"); // Mock oidc state to prevent redirection if the library checks it 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) => { if (route.request().url().includes("/.well-known/openid-configuration")) { return route.fulfill({ json: { issuer: "http://localhost:5000/oidc", authorization_endpoint: "http://localhost:5000/oidc/auth", token_endpoint: "http://localhost:5000/oidc/token", userinfo_endpoint: "http://localhost:5000/oidc/userinfo", jwks_uri: "http://localhost:5000/oidc/jwks", }, }); } await route.fulfill({ json: { issuer: "http://localhost:5000/oidc" } }); }); await page.route(/.*\/api\/v1\/.*/, async (route) => { const url = route.request().url(); if (url.includes("/user/me")) { console.log("Mocking /user/me"); return route.fulfill({ json: { id: "admin-user", name: "Admin", email: "admin@test.com", role: "super_admin", manageableTenants: [ { id: "t-1", name: "Test Tenant", slug: "test-tenant" }, ], }, }); } if (url.match(/\/admin\/tenants\/t-1$/)) { console.log("Mocking /admin/tenants/t-1"); return route.fulfill({ json: { id: "t-1", name: "Test Tenant", slug: "test-tenant", config: { userSchema: [ { key: "emp_id", label: "Employee ID", required: true, validation: "^E[0-9]{3}$", }, { key: "loginId", label: "Login ID", required: true, isLoginId: true, }, { key: "salary", label: "Salary", adminOnly: true, type: "number", }, ], }, }, }); } if (url.match(/\/admin\/users\/u-1$/)) { console.log("Mocking /admin/users/u-1"); return route.fulfill({ json: { id: "u-1", name: "John Doe", email: "john@test.com", tenantSlug: "test-tenant", tenant: { id: "t-1", name: "Test Tenant", slug: "test-tenant" }, joinedTenants: [ { id: "t-1", name: "Test Tenant", slug: "test-tenant" }, ], metadata: { "t-1": { emp_id: "E123", salary: 1000, loginId: "johndoe" }, }, }, }); } if (url.includes("/password/policy")) { console.log("Mocking /password/policy"); return route.fulfill({ json: { minLength: 12, lowercase: true, uppercase: true, number: true, nonAlphanumeric: true, }, }); } if (url.includes("/rp-history")) { console.log("Mocking /rp-history"); return route.fulfill({ json: [], }); } if (url.match(/\/admin\/tenants(\?.*)?$/)) { console.log("Mocking /admin/tenants"); return route.fulfill({ json: { items: [ { id: "t-1", slug: "test-tenant", name: "Test Tenant", config: { userSchema: [ { key: "emp_id", label: "Employee ID", required: true, validation: "^E[0-9]{3}$", }, { key: "loginId", label: "Login ID", required: true, isLoginId: true, }, { key: "salary", label: "Salary", adminOnly: true, type: "number", }, ], }, }, ], total: 1, }, }); } console.log("Mocking default empty list for:", url); return route.fulfill({ json: { items: [], total: 0 } }); }); }); test("should render custom fields from schema in user detail", async ({ page, }) => { await page.goto("/users/u-1"); // "테넌트 프로필" 탭 클릭 await page .getByRole("tab", { name: /테넌트 프로필|Tenants|Tenant Profile/i }) .click(); // 섹션 헤더 확인 const header = page .getByText(/테넌트별 프로필 관리|Per-tenant Profile/i) .first(); await header.waitFor({ state: "visible" }); // 커스텀 필드 레이블 확인 await expect(page.getByText("Employee ID")).toBeVisible(); // input 값 확인 (id에 t-1.emp_id가 포함됨) const empIdInput = page.locator('input[id*="emp_id"]'); await expect(empIdInput).toHaveValue("E123"); const salaryInput = page.locator('input[id*="salary"]'); await expect(salaryInput).toHaveValue("1000"); await expect(page.getByText(/Admin Only/i).first()).toBeVisible(); }); test("should show regex validation error for custom field", async ({ page, }) => { await page.goto("/users/u-1"); // "테넌트 프로필" 탭 클릭 await page .getByRole("tab", { name: /테넌트 프로필|Tenants|Tenant Profile/i }) .click(); const empIdInput = page.locator('input[id*="emp_id"]'); await empIdInput.waitFor({ state: "visible" }); await empIdInput.fill("invalid"); // Press Enter to trigger form submission and validation await empIdInput.press("Enter"); // 에러 메시지 확인 const errorMsg = page .getByText(/형식이 올바르지 않습니다|Invalid format/i) .first(); await expect(errorMsg).toBeVisible(); }); });