import { describe, it, expect, vi, beforeEach } from "vitest"; import { render, screen, waitFor, within } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { SecretsView } from "../SecretsView"; type JsonResponse = { ok: boolean; status?: number; body: unknown; }; function mockJsonResponse({ ok, status = ok ? 200 : 400, body }: JsonResponse): Response { return { ok, status, json: vi.fn().mockResolvedValue(body), } as unknown as Response; } describe("SecretsView", () => { beforeEach(() => { vi.clearAllMocks(); }); it("renders Not configured status", async () => { vi.stubGlobal( "fetch", vi .fn() .mockResolvedValueOnce(mockJsonResponse({ ok: true, body: { secrets: [] } })) .mockResolvedValueOnce(mockJsonResponse({ ok: true, body: { configured: false } })), ); render(); expect(await screen.findByText("Not configured")).toBeInTheDocument(); }); it("renders Configured status and clear button", async () => { vi.stubGlobal( "fetch", vi .fn() .mockResolvedValueOnce(mockJsonResponse({ ok: true, body: { secrets: [] } })) .mockResolvedValueOnce(mockJsonResponse({ ok: true, body: { configured: true } })), ); render(); expect(await screen.findByText("Configured")).toBeInTheDocument(); expect(screen.getByRole("button", { name: "Clear" })).toBeInTheDocument(); }); it("submitting matching passphrases issues PUT and re-fetches status", async () => { const fetchMock = vi .fn() .mockResolvedValueOnce(mockJsonResponse({ ok: true, body: { secrets: [] } })) .mockResolvedValueOnce(mockJsonResponse({ ok: true, body: { configured: false } })) .mockResolvedValueOnce(mockJsonResponse({ ok: true, body: { success: true } })) .mockResolvedValueOnce(mockJsonResponse({ ok: true, body: { configured: true } })); vi.stubGlobal("fetch", fetchMock); render(); await screen.findByText("Not configured"); await userEvent.click(screen.getByRole("button", { name: "Set passphrase" })); const dialog = screen.getByRole("dialog", { name: "Set sync passphrase" }); await userEvent.type(within(dialog).getByLabelText("Passphrase"), "shared-pass"); await userEvent.type(within(dialog).getByLabelText("Confirm passphrase"), "shared-pass"); await userEvent.click(within(dialog).getByRole("button", { name: "Set passphrase" })); await waitFor(() => { expect(fetchMock).toHaveBeenCalledWith( "/api/secrets/sync-passphrase", expect.objectContaining({ method: "PUT" }), ); }); expect(await screen.findByText("Configured")).toBeInTheDocument(); }); it("mismatched confirmation disables submit and does not call PUT", async () => { const fetchMock = vi .fn() .mockResolvedValueOnce(mockJsonResponse({ ok: true, body: { secrets: [] } })) .mockResolvedValueOnce(mockJsonResponse({ ok: true, body: { configured: false } })); vi.stubGlobal("fetch", fetchMock); render(); await screen.findByText("Not configured"); await userEvent.click(screen.getByRole("button", { name: "Set passphrase" })); const dialog = screen.getByRole("dialog", { name: "Set sync passphrase" }); await userEvent.type(within(dialog).getByLabelText("Passphrase"), "a"); await userEvent.type(within(dialog).getByLabelText("Confirm passphrase"), "b"); const submitButton = within(dialog).getByRole("button", { name: "Set passphrase" }); expect(submitButton).toBeDisabled(); expect(fetchMock).toHaveBeenCalledTimes(2); }); it("clear button issues DELETE after confirmation", async () => { const fetchMock = vi .fn() .mockResolvedValueOnce(mockJsonResponse({ ok: true, body: { secrets: [] } })) .mockResolvedValueOnce(mockJsonResponse({ ok: true, body: { configured: true } })) .mockResolvedValueOnce(mockJsonResponse({ ok: true, body: { success: true } })) .mockResolvedValueOnce(mockJsonResponse({ ok: true, body: { configured: false } })); vi.stubGlobal("fetch", fetchMock); vi.spyOn(window, "confirm").mockReturnValue(true); render(); await screen.findByText("Configured"); await userEvent.click(screen.getByRole("button", { name: "Clear" })); await waitFor(() => { expect(fetchMock).toHaveBeenCalledWith( "/api/secrets/sync-passphrase", expect.objectContaining({ method: "DELETE" }), ); }); }); it("filters reserved key from main list", async () => { vi.stubGlobal( "fetch", vi .fn() .mockResolvedValueOnce( mockJsonResponse({ ok: true, body: { secrets: [ { id: "1", key: "__sync_passphrase__", scope: "global", description: null, accessPolicy: "deny", envExportable: false, envExportKey: null, lastReadAt: null }, { id: "2", key: "VISIBLE", scope: "project", description: null, accessPolicy: "prompt", envExportable: false, envExportKey: null, lastReadAt: null }, ], }, }), ) .mockResolvedValueOnce(mockJsonResponse({ ok: true, body: { configured: false } })), ); render(); expect(await screen.findByText("VISIBLE")).toBeInTheDocument(); expect(screen.queryByText("__sync_passphrase__")).not.toBeInTheDocument(); }); });