import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { render, screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { MemoryRouter } from "react-router-dom"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { fetchApiKeys, rotateApiKeySecret, updateApiKeyScopes, } from "../../lib/adminApi"; import ApiKeyListPage from "./ApiKeyListPage"; vi.mock("../../lib/i18n", () => ({ t: (_key: string, fallback?: string) => fallback ?? "", })); vi.mock("../../lib/adminApi", () => ({ fetchApiKeys: vi.fn(async () => ({ items: [ { id: "api-key-id", name: "org-context-client", client_id: "client-id-stable", scopes: ["audit:read"], status: "active", createdAt: "2026-05-13T00:00:00Z", }, ], total: 1, })), deleteApiKey: vi.fn(async () => undefined), updateApiKeyScopes: vi.fn(async () => ({ id: "api-key-id", name: "org-context-client", client_id: "client-id-stable", scopes: ["audit:read", "org-context:read"], status: "active", createdAt: "2026-05-13T00:00:00Z", })), rotateApiKeySecret: vi.fn(async () => ({ apiKey: { id: "api-key-id", name: "org-context-client", client_id: "client-id-stable", scopes: ["audit:read"], status: "active", createdAt: "2026-05-13T00:00:00Z", }, clientSecret: "rotated-secret", })), })); function renderPage() { const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false }, mutations: { retry: false }, }, }); return render( , ); } describe("ApiKeyListPage", () => { beforeEach(() => { vi.clearAllMocks(); vi.spyOn(window, "confirm").mockReturnValue(true); }); it("updates scopes without changing client_id", async () => { const user = userEvent.setup({ delay: null }); renderPage(); expect(await screen.findByText("client-id-stable")).toBeInTheDocument(); await user.click(screen.getByRole("button", { name: /권한 수정/ })); await user.click(screen.getByRole("button", { name: /조직 Context 조회/ })); await user.click(screen.getByRole("button", { name: /권한 저장/ })); await waitFor(() => { expect(updateApiKeyScopes).toHaveBeenCalledWith("api-key-id", { scopes: expect.arrayContaining(["audit:read", "org-context:read"]), }); }); }, 15_000); it("rotates only the secret and shows the one-time secret", async () => { const user = userEvent.setup(); renderPage(); expect(await screen.findByText("client-id-stable")).toBeInTheDocument(); await user.click(screen.getByRole("button", { name: /Secret 재발급/ })); await waitFor(() => { expect(rotateApiKeySecret).toHaveBeenCalledWith("api-key-id"); }); expect( await screen.findByDisplayValue("rotated-secret"), ).toBeInTheDocument(); expect(fetchApiKeys).toHaveBeenCalled(); }); it("refresh button refetches the list without navigation", async () => { const user = userEvent.setup(); renderPage(); await screen.findByText("client-id-stable"); const refreshButton = screen.getByRole("button", { name: /새로고침/ }); expect(refreshButton).toHaveAttribute("type", "button"); await user.click(refreshButton); await waitFor(() => { expect(fetchApiKeys).toHaveBeenCalledTimes(2); }); }); });