import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { initPasswordValidation, loadPasswordRules } from "./password_validation"; // The checklist is display-only (the server enforces), but it gates the // submit button and mirrors the ACTIVE server rules — these tests pin the // rules matrix, the fetch fallback behavior, and the submit gating. function buildDom(): void { document.body.innerHTML = `
`; } function setPassword(value: string): void { (document.getElementById("password") as HTMLInputElement).value = value; (document.getElementById("password") as HTMLInputElement).dispatchEvent(new Event("input")); } const submitButton = (): HTMLButtonElement => document.getElementById("register-btn") as HTMLButtonElement; describe("loadPasswordRules", () => { afterEach(() => { vi.unstubAllGlobals(); }); it("adopts fetched rules", async () => { vi.stubGlobal( "fetch", vi.fn( async () => new Response(JSON.stringify({ min_length: 12, require_upper: false }), { status: 200 }), ), ); const rules = await loadPasswordRules(); expect(rules).toEqual({ min_length: 12, require_upper: false, require_lower: true, require_number: true, require_special: true, }); }); it("falls back to defaults when the endpoint fails", async () => { vi.stubGlobal( "fetch", vi.fn(async () => new Response("nope", { status: 500 })), ); const rules = await loadPasswordRules(); expect(rules).toEqual({ min_length: 8, require_upper: true, require_lower: true, require_number: true, require_special: true, }); }); it("falls back to defaults on invalid payloads", async () => { vi.stubGlobal( "fetch", vi.fn(async () => new Response(JSON.stringify({ min_length: -5 }), { status: 200 })), ); const rules = await loadPasswordRules(); expect(rules.min_length).toBe(8); }); }); describe("initPasswordValidation", () => { beforeEach(() => { // Deterministic rules: fetch failure lands on the historical defaults. vi.stubGlobal( "fetch", vi.fn(async () => new Response("nope", { status: 500 })), ); }); afterEach(() => { vi.unstubAllGlobals(); }); // initPasswordValidation debounces through window.setTimeout, which under // jsdom is a distinct timer from the faked global — so these tests wait // out the real 100ms debounce instead of advancing fake clocks. const settleDebounce = (): Promise