The sha256Hex helper used window.crypto.subtle.digest(), which is only available in a secure context (HTTPS / localhost). In the HTTP Docker environment crypto.subtle is undefined, causing a TypeError before any request is sent — the setup and login forms both silently failed with 'An unexpected error occurred'. Fix: pass raw passwords directly to the API. The backend already applies bcrypt, which is sufficient. No stored hashes need migration because setup never completed successfully in the HTTP environment. * frontend/src/pages/SetupPage.tsx — remove sha256Hex call * frontend/src/api/auth.ts — remove sha256Hex call * frontend/src/pages/__tests__/SetupPage.test.tsx — drop crypto mock * frontend/src/utils/crypto.ts — deleted (no remaining callers)
84 lines
2.8 KiB
TypeScript
84 lines
2.8 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
import { render, screen, waitFor } from "@testing-library/react";
|
|
import { MemoryRouter, Routes, Route } from "react-router-dom";
|
|
import { FluentProvider, webLightTheme } from "@fluentui/react-components";
|
|
import { SetupPage } from "../SetupPage";
|
|
|
|
// Mock the setup API so tests never hit a real network.
|
|
vi.mock("../../api/setup", () => ({
|
|
getSetupStatus: vi.fn(),
|
|
submitSetup: vi.fn(),
|
|
}));
|
|
|
|
import { getSetupStatus } from "../../api/setup";
|
|
|
|
const mockedGetSetupStatus = vi.mocked(getSetupStatus);
|
|
|
|
function renderPage() {
|
|
return render(
|
|
<FluentProvider theme={webLightTheme}>
|
|
<MemoryRouter initialEntries={["/setup"]}>
|
|
<Routes>
|
|
<Route path="/setup" element={<SetupPage />} />
|
|
<Route
|
|
path="/login"
|
|
element={<div data-testid="login-page">Login</div>}
|
|
/>
|
|
</Routes>
|
|
</MemoryRouter>
|
|
</FluentProvider>,
|
|
);
|
|
}
|
|
|
|
describe("SetupPage", () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it("shows a full-screen spinner while the setup status check is in flight", () => {
|
|
// getSetupStatus never resolves — spinner should be visible immediately.
|
|
mockedGetSetupStatus.mockReturnValue(new Promise(() => {}));
|
|
renderPage();
|
|
expect(screen.getByRole("progressbar")).toBeInTheDocument();
|
|
// Form should NOT be visible yet.
|
|
expect(
|
|
screen.queryByRole("heading", { name: /bangui setup/i }),
|
|
).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("renders the setup form once the status check resolves (not complete)", async () => {
|
|
// Task 0.4: form must not flash before the check resolves.
|
|
mockedGetSetupStatus.mockResolvedValue({ completed: false });
|
|
renderPage();
|
|
await waitFor(() => {
|
|
expect(
|
|
screen.getByRole("heading", { name: /bangui setup/i }),
|
|
).toBeInTheDocument();
|
|
});
|
|
// Spinner should be gone.
|
|
expect(screen.queryByRole("progressbar")).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("redirects to /login when setup is already complete", async () => {
|
|
mockedGetSetupStatus.mockResolvedValue({ completed: true });
|
|
renderPage();
|
|
await waitFor(() => {
|
|
expect(screen.getByTestId("login-page")).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it("renders the form and logs a warning when the status check fails", async () => {
|
|
// Task 0.4: catch block must log a warning and keep the form visible.
|
|
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
mockedGetSetupStatus.mockRejectedValue(new Error("Connection refused"));
|
|
renderPage();
|
|
await waitFor(() => {
|
|
expect(
|
|
screen.getByRole("heading", { name: /bangui setup/i }),
|
|
).toBeInTheDocument();
|
|
});
|
|
expect(warnSpy).toHaveBeenCalledOnce();
|
|
warnSpy.mockRestore();
|
|
});
|
|
});
|