Strengthen setup password validation

- Add backend Pydantic password complexity validation for setup
- Update frontend setup page with password rule feedback and strength indicator
- Add/adjust setup API tests for password validation
- Document setup password requirements
- Fix frontend test type annotation issue
This commit is contained in:
2026-04-20 19:23:12 +02:00
parent cc8c71906f
commit e593498de5
7 changed files with 241 additions and 22 deletions

View File

@@ -1,5 +1,6 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { MemoryRouter, Routes, Route } from "react-router-dom";
import { FluentProvider, webLightTheme } from "@fluentui/react-components";
import { SetupPage } from "../SetupPage";
@@ -10,9 +11,10 @@ vi.mock("../../api/setup", () => ({
submitSetup: vi.fn(),
}));
import { getSetupStatus } from "../../api/setup";
import { getSetupStatus, submitSetup } from "../../api/setup";
const mockedGetSetupStatus = vi.mocked(getSetupStatus);
const mockedSubmitSetup = vi.mocked(submitSetup);
function renderPage() {
return render(
@@ -59,6 +61,45 @@ describe("SetupPage", () => {
expect(screen.queryByRole("progressbar")).not.toBeInTheDocument();
});
it("displays password complexity feedback while the user types", async () => {
mockedGetSetupStatus.mockResolvedValue({ completed: false });
renderPage();
await waitFor(() => {
expect(
screen.getByRole("heading", { name: /bangui setup/i }),
).toBeInTheDocument();
});
const user = userEvent.setup();
const passwordInput = screen.getByLabelText(/master password/i);
await user.type(passwordInput, "Short1");
expect(screen.getByText(/2 of 4 rules satisfied/i)).toBeInTheDocument();
expect(screen.getByText(/at least 8 characters/i)).toBeInTheDocument();
expect(screen.getByText(/at least one uppercase letter/i)).toBeInTheDocument();
expect(screen.getByText(/at least one special character/i)).toBeInTheDocument();
});
it("does not submit the form when the password is too weak", async () => {
mockedGetSetupStatus.mockResolvedValue({ completed: false });
mockedSubmitSetup.mockResolvedValue({ message: "Setup completed successfully. Please log in." });
renderPage();
await waitFor(() => {
expect(
screen.getByRole("heading", { name: /bangui setup/i }),
).toBeInTheDocument();
});
const user = userEvent.setup();
await user.type(screen.getByLabelText(/master password/i), "Short1");
await user.type(screen.getByLabelText(/confirm password/i), "Short1");
await user.click(screen.getByRole("button", { name: /complete setup/i }));
expect(mockedSubmitSetup).not.toHaveBeenCalled();
});
it("redirects to /login when setup is already complete", async () => {
mockedGetSetupStatus.mockResolvedValue({ completed: true });
renderPage();