import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { fireEvent, render, screen, waitFor } from "@testing-library/react"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { fetchUserProjectionStatus, reconcileUserProjection, resetUserProjection, } from "../../lib/adminApi"; import { createI18nMock } from "../../test/i18nMock"; import UserProjectionPage from "./UserProjectionPage"; vi.mock("../../lib/i18n", () => createI18nMock()); let currentRole = "super_admin"; vi.mock("../../lib/adminApi", () => ({ fetchMe: vi.fn(async () => ({ role: currentRole })), fetchUserProjectionStatus: vi.fn(async () => ({ name: "kratos_users", status: "ready", ready: true, lastSyncedAt: "2026-05-11T03:00:00Z", updatedAt: "2026-05-11T03:00:10Z", projectedUsers: 152, })), reconcileUserProjection: vi.fn(async () => ({ status: "success", syncedUsers: 152, updatedAt: "2026-05-11T03:01:00Z", })), resetUserProjection: vi.fn(async () => ({ status: "success", syncedUsers: 152, updatedAt: "2026-05-11T03:02:00Z", })), })); function renderPage() { const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false }, mutations: { retry: false }, }, }); return render( , ); } describe("UserProjectionPage", () => { beforeEach(() => { currentRole = "super_admin"; vi.clearAllMocks(); vi.spyOn(window, "confirm").mockReturnValue(true); window.localStorage.setItem("locale", "ko"); }); it("renders projection status for super_admin", async () => { renderPage(); expect(await screen.findByText("사용자 동기화 관리")).toBeInTheDocument(); expect( await screen.findByText( "Kratos 사용자 read model을 확인하고 동기화 상태를 갱신합니다.", ), ).toBeInTheDocument(); expect(await screen.findByText("Kratos 사용자 동기화")).toBeInTheDocument(); expect(screen.getByText("준비됨")).toBeInTheDocument(); expect(screen.getByText("152")).toBeInTheDocument(); expect(fetchUserProjectionStatus).toHaveBeenCalled(); }); it("runs reconcile and reset actions for super_admin", async () => { renderPage(); await screen.findByText("사용자 동기화 관리"); fireEvent.click(screen.getByRole("button", { name: /재동기화/ })); await waitFor(() => { expect(reconcileUserProjection).toHaveBeenCalledTimes(1); }); fireEvent.click(screen.getByRole("button", { name: /초기화 후 재구축/ })); await waitFor(() => { expect(resetUserProjection).toHaveBeenCalledTimes(1); }); }); it("blocks non-super admins", async () => { currentRole = "tenant_admin"; renderPage(); expect(await screen.findByText("접근 권한이 없습니다")).toBeInTheDocument(); expect(screen.queryByText("사용자 동기화 관리")).not.toBeInTheDocument(); expect(fetchUserProjectionStatus).not.toHaveBeenCalled(); }); it("renders localized labels in English", async () => { window.localStorage.setItem("locale", "en"); renderPage(); expect( await screen.findByText("User Projection Management"), ).toBeInTheDocument(); expect( await screen.findByText("Review and sync the Kratos user read model."), ).toBeInTheDocument(); expect(screen.getByText("Re-sync")).toBeInTheDocument(); expect(await screen.findByText("ready")).toBeInTheDocument(); }); });