import { describe, expect, it, vi } from "vitest"; import { render, screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { StepUpDialog } from "./step-up-dialog"; import { ApiError } from "@/lib/auth/api-client"; function renderDialog(overrides: Partial[0]> = {}) { const onConfirm = vi.fn().mockResolvedValue(undefined); const onOpenChange = vi.fn(); render( , ); return { onConfirm, onOpenChange }; } describe("StepUpDialog", () => { it("asks for the password only without MFA and confirms with it", async () => { const { onConfirm, onOpenChange } = renderDialog(); expect(screen.queryByLabelText("Code aus der Authenticator-App")).not.toBeInTheDocument(); const confirm = screen.getByRole("button", { name: "Verbinden" }); expect(confirm).toBeDisabled(); await userEvent.type(screen.getByLabelText("Dein Passwort"), "geheim"); await userEvent.click(confirm); await waitFor(() => expect(onConfirm).toHaveBeenCalledWith({ password: "geheim" })); await waitFor(() => expect(onOpenChange).toHaveBeenCalledWith(false)); }); it("asks for the six-digit code with MFA and sends both", async () => { const { onConfirm } = renderDialog({ needsMfa: true }); await userEvent.type(screen.getByLabelText("Dein Passwort"), "geheim"); const confirm = screen.getByRole("button", { name: "Verbinden" }); await userEvent.type(screen.getByLabelText("Code aus der Authenticator-App"), "12ab34"); expect(confirm).toBeDisabled(); await userEvent.type(screen.getByLabelText("Code aus der Authenticator-App"), "56"); expect(confirm).toBeEnabled(); await userEvent.click(confirm); await waitFor(() => expect(onConfirm).toHaveBeenCalledWith({ password: "geheim", mfaCode: "123456" }), ); }); it("stays open and shows the server's German reason when the proof is refused", async () => { const onConfirm = vi.fn().mockRejectedValue(new ApiError(401, "Das Passwort ist falsch.")); const { onOpenChange } = renderDialog({ onConfirm }); await userEvent.type(screen.getByLabelText("Dein Passwort"), "falsch"); await userEvent.click(screen.getByRole("button", { name: "Verbinden" })); await waitFor(() => expect(screen.getByRole("alert")).toHaveTextContent("Das Passwort ist falsch."), ); expect(onOpenChange).not.toHaveBeenCalledWith(false); expect(screen.getByLabelText("Dein Passwort")).toHaveValue(""); expect(screen.getByLabelText("Dein Passwort")).toHaveFocus(); }); it("shows the German too-many-attempts text on 429", async () => { renderDialog({ onConfirm: vi.fn().mockRejectedValue(new ApiError(429, "ThrottlerException")) }); await userEvent.type(screen.getByLabelText("Dein Passwort"), "x"); await userEvent.click(screen.getByRole("button", { name: "Verbinden" })); await waitFor(() => expect(screen.getByRole("alert")).toHaveTextContent( "Zu viele Versuche. Bitte in einer Minute noch einmal.", ), ); }); it("says the session expired on a bare 401", async () => { renderDialog({ onConfirm: vi.fn().mockRejectedValue(new ApiError(401, "Unauthorized")) }); await userEvent.type(screen.getByLabelText("Dein Passwort"), "x"); await userEvent.click(screen.getByRole("button", { name: "Verbinden" })); await waitFor(() => expect(screen.getByRole("alert")).toHaveTextContent( "Sitzung abgelaufen. Bitte die Seite neu laden und erneut anmelden.", ), ); }); it("makes the inputs read-only while the action runs", async () => { renderDialog({ onConfirm: vi.fn(() => new Promise(() => {})) }); await userEvent.type(screen.getByLabelText("Dein Passwort"), "geheim"); await userEvent.click(screen.getByRole("button", { name: "Verbinden" })); await waitFor(() => expect(screen.getByLabelText("Dein Passwort")).toHaveAttribute("readonly")); }); it("cannot be closed with Escape while the action runs, and shows the pending label", async () => { const onConfirm = vi.fn(() => new Promise(() => {})); const { onOpenChange } = renderDialog({ onConfirm, pendingLabel: "Wird eingespielt…" }); await userEvent.type(screen.getByLabelText("Dein Passwort"), "geheim"); await userEvent.click(screen.getByRole("button", { name: "Verbinden" })); expect(await screen.findByRole("button", { name: "Wird eingespielt…" })).toBeDisabled(); await userEvent.keyboard("{Escape}"); await userEvent.click(screen.getByRole("button", { name: /Close/ })); expect(onOpenChange).not.toHaveBeenCalled(); expect(screen.getByRole("dialog")).toBeInTheDocument(); }); });