import { expect, test } from "@playwright/test"; test.describe("보안 및 접근 제어: 시스템 관리자 vs 일반 사용자", () => { const setupAuth = async (page, role: string) => { // 1. Inject initial state and mock tokens await page.addInitScript( ({ role }) => { window.localStorage.setItem("locale", "ko"); window.localStorage.setItem("admin_session", "fake-token"); ( window as Window & typeof globalThis & { _IS_TEST_MODE?: boolean } )._IS_TEST_MODE = true; const auth = "https://ssologin.hmac.kr/oidc"; const client_id = "adminfront"; const key = `oidc.user:${auth}:${client_id}`; const authData = { access_token: "fake-token", token_type: "Bearer", profile: { sub: "test-user", name: "테스트 사용자", role: role }, expires_at: Math.floor(Date.now() / 1000) + 36000, }; window.localStorage.setItem(key, JSON.stringify(authData)); window.localStorage.setItem("X-Mock-Role-Enabled", "true"); window.localStorage.setItem("X-Mock-Role", role); }, { role }, ); // 2. OIDC Configuration Mocking await page.route(/.*\/oidc\/.*/, async (route) => { const url = route.request().url(); if (url.includes("openid-configuration")) { await route.fulfill({ json: { issuer: "https://ssologin.hmac.kr/oidc", authorization_endpoint: "https://ssologin.hmac.kr/oidc/auth", token_endpoint: "https://ssologin.hmac.kr/oidc/token", jwks_uri: "https://ssologin.hmac.kr/oidc/jwks", userinfo_endpoint: "https://ssologin.hmac.kr/oidc/userinfo", end_session_endpoint: "https://ssologin.hmac.kr/oidc/session/end", response_types_supported: ["code", "id_token"], subject_types_supported: ["public"], id_token_signing_alg_values_supported: ["RS256"], }, headers: { "Access-Control-Allow-Origin": "*" }, }); } else { await route.fulfill({ status: 200, json: {}, headers: { "Access-Control-Allow-Origin": "*" }, }); } }); // 3. API Mocking await page.route(/.*\/user\/me/, async (route) => { await route.fulfill({ json: { id: "test-user", name: "테스트 사용자", role: role, manageableTenants: [], }, headers: { "Access-Control-Allow-Origin": "*" }, }); }); // Mock generic API responses await page.route(/.*\/api\/v1\/.*/, async (route) => { const url = route.request().url(); if (url.includes("/tenants")) { await route.fulfill({ json: { items: [ { id: "t1", name: "테넌트 1", slug: "t1", status: "active", type: "COMPANY", }, ], total: 1, }, headers: { "Access-Control-Allow-Origin": "*" }, }); } else if (url.includes("/users")) { await route.fulfill({ json: { items: [ { id: "u1", name: "사용자 1", email: "u1@example.com", role: "user", status: "active", createdAt: new Date().toISOString(), }, ], total: 1, }, headers: { "Access-Control-Allow-Origin": "*" }, }); } else if (url.includes("/password-policy")) { await route.fulfill({ json: { minLength: 8, minCharacterTypes: 2 }, headers: { "Access-Control-Allow-Origin": "*" }, }); } else { await route.fulfill({ json: { items: [], total: 0 }, headers: { "Access-Control-Allow-Origin": "*" }, }); } }); }; test.describe("시스템 관리자 (Super Admin) 권한", () => { test.beforeEach(async ({ page }) => { await setupAuth(page, "super_admin"); await page.goto("/"); await expect(page.locator("aside")).toBeVisible({ timeout: 10000 }); }); test("모든 행정 메뉴가 보여야 함", async ({ page }) => { await expect(page.locator('a[href="/tenants"]')).toBeVisible(); await expect(page.locator('a[href="/api-keys"]')).toBeVisible(); await expect(page.locator('a[href="/audit-logs"]')).toBeVisible(); await expect( page.locator('a[href="/system/projections/users"]'), ).toBeVisible(); await expect( page.locator('a[href="/system/data-integrity"]'), ).toBeVisible(); }); test("테넌트 관리 기능에 접근 가능해야 함", async ({ page }) => { await page.goto("/tenants"); // "테넌트 추가" 버튼 확인 await expect( page.getByRole("link", { name: /테넌트 추가/i }), ).toBeVisible(); // "데이터 관리" 드롭다운 확인 await expect(page.getByTestId("tenant-data-mgmt-btn")).toBeVisible(); }); test("사용자 관리에서 역할 변경이 가능해야 함", async ({ page }) => { await page.goto("/users"); // 테이블 내 역할 선택 버튼 확인 (RoleSwitcher/Select) // i18n에 따라 "일반 사용자" 또는 "Tenant Member" 등으로 표시될 수 있음 const roleSelect = page.locator('button[id^="radix-"]').first(); await expect(roleSelect).toBeEnabled(); }); test("사용자 상세에서 '저장하기' 및 '삭제' 버튼이 보여야 함", async ({ page, }) => { await page.goto("/users/u1"); await expect( page.getByRole("button", { name: /저장하기/i }), ).toBeVisible(); await expect( page.getByRole("button", { name: /사용자 삭제/i }), ).toBeVisible(); }); }); test.describe("일반 사용자 (Tenant Member) 제한", () => { test.beforeEach(async ({ page }) => { await setupAuth(page, "user"); await page.goto("/"); await expect(page.locator("aside")).toBeVisible({ timeout: 10000 }); }); test("행정 메뉴가 보이지 않아야 함", async ({ page }) => { await expect(page.locator('a[href="/tenants"]')).not.toBeVisible(); await expect(page.locator('a[href="/api-keys"]')).not.toBeVisible(); await expect(page.locator('a[href="/audit-logs"]')).not.toBeVisible(); await expect( page.locator('a[href="/system/projections/users"]'), ).not.toBeVisible(); await expect( page.locator('a[href="/system/data-integrity"]'), ).not.toBeVisible(); await expect(page.locator('a[href="/users"]')).not.toBeVisible(); }); test("테넌트 페이지 직접 접근 시 차단되어야 함", async ({ page }) => { await page.goto("/tenants"); await expect(page.getByText(/접근 권한이 없습니다/i)).toBeVisible(); }); test("사용자 관리 페이지 직접 접근 시 차단되어야 함", async ({ page }) => { await page.goto("/users"); await expect( page.getByText(/이 작업을 수행할 권한이 없습니다/i), ).toBeVisible(); }); test("사용자 상세 페이지 직접 접근 시 차단되어야 함 (본인 제외)", async ({ page, }) => { await page.goto("/users/other-user"); await expect( page.getByText(/이 작업을 수행할 권한이 없습니다/i), ).toBeVisible(); }); test("사용자 생성 페이지 직접 접근 시 차단되어야 함", async ({ page }) => { await page.goto("/users/new"); await expect( page.getByText(/이 작업을 수행할 권한이 없습니다/i), ).toBeVisible(); }); }); });