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 { fetchOrySSOTSystemStatus, flushIdentityCache, } 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 })), fetchOrySSOTSystemStatus: vi.fn(async () => ({ userProjection: { name: "kratos_users", status: "ready", ready: true, lastSyncedAt: "2026-05-11T03:00:00Z", updatedAt: "2026-05-11T03:00:10Z", projectedUsers: 152, }, identityCache: { status: "ready", redisReady: true, observedCount: 151, lastRefreshedAt: "2026-05-11T03:00:00Z", updatedAt: "2026-05-11T03:00:10Z", keyCount: 153, }, })), flushIdentityCache: vi.fn(async () => ({ status: "success", flushedKeys: 153, 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 Ory SSOT and Redis identity cache status for super_admin", async () => { renderPage(); expect(await screen.findByText("Ory SSOT 시스템")).toBeInTheDocument(); expect( await screen.findByText( "Kratos 원장과 Redis identity cache 상태를 분리해서 확인합니다.", ), ).toBeInTheDocument(); expect(await screen.findByText("Redis identity cache")).toBeInTheDocument(); expect(screen.getAllByText("준비됨").length).toBeGreaterThan(0); expect(screen.getByText("관측 identity")).toBeInTheDocument(); expect(screen.getByText("152")).toBeInTheDocument(); expect(screen.getByText("151")).toBeInTheDocument(); expect(fetchOrySSOTSystemStatus).toHaveBeenCalled(); }); it("flushes only the Redis identity cache for super_admin", async () => { renderPage(); await screen.findByText("Ory SSOT 시스템"); expect(screen.queryByRole("button", { name: /재동기화/ })).toBeNull(); expect( screen.queryByRole("button", { name: /초기화 후 재구축/ }), ).toBeNull(); fireEvent.click(screen.getByRole("button", { name: /Redis cache flush/ })); await waitFor(() => { expect(flushIdentityCache).toHaveBeenCalledTimes(1); }); }); it("blocks non-super admins", async () => { currentRole = "tenant_admin"; renderPage(); expect(await screen.findByText("접근 권한이 없습니다")).toBeInTheDocument(); expect(screen.queryByText("Ory SSOT 시스템")).not.toBeInTheDocument(); expect(fetchOrySSOTSystemStatus).not.toHaveBeenCalled(); }); it("renders localized labels in English", async () => { window.localStorage.setItem("locale", "en"); renderPage(); expect(await screen.findByText("Ory SSOT System")).toBeInTheDocument(); expect( await screen.findByText( "Review Kratos source-of-truth and Redis identity cache status separately.", ), ).toBeInTheDocument(); expect(screen.getByText("Redis cache flush")).toBeInTheDocument(); expect((await screen.findAllByText("ready")).length).toBeGreaterThan(0); }); });