import { describe, expect, it } from "@suaveplan/testing/runner"; import { loadDictionaries } from "../dicts/load-dictionaries.js"; import { PasswordStrengthError, PWSTRENGTH_CODES, } from "../errors/error-codes.js"; import { StrengthResultSchema } from "../schemas/schemas.js"; import { assess } from "./assess.js"; describe("assess", () => { it("full pipeline returns canonical shape", { timeout: 5000 }, () => { const r = assess("Password1!"); const parsed = StrengthResultSchema.safeParse(r); expect(parsed.success).toBe(true); expect(r.score).toBeLessThanOrEqual(1); }); it("accepts 199-character input", { timeout: 5000 }, () => { const r = assess("a".repeat(199)); expect(r.password.length).toBe(199); }); it("rejects 200-character input", { timeout: 5000 }, () => { expect(() => assess("a".repeat(200))).toThrowError( PasswordStrengthError ); }); it("rejects 200-character input with INPUT_TOO_LONG code", { timeout: 5000, }, () => { try { assess("a".repeat(200)); } catch (e) { expect((e as PasswordStrengthError).code).toBe( PWSTRENGTH_CODES.INPUT_TOO_LONG ); } }); it("userInputs surface as ranked matches", { timeout: 5000 }, () => { const r = assess("hello-acme-corp", { userInputs: ["acme-corp"] }); expect( r.sequence.some( (m) => m.pattern === "dictionary" && m.dictionaryName === "user_inputs" ) ).toBe(true); }); it("userInputs become a ranked dictionary", { timeout: 5000 }, () => { const r = assess("alice@example.com-pwd", { userInputs: ["alice@example.com"], }); const hit = r.sequence.find( (m) => m.pattern === "dictionary" && m.dictionaryName === "user_inputs" ); expect(hit).toBeDefined(); expect(hit?.pattern === "dictionary" && hit.matchedWord).toBe( "alice@example.com" ); }); it("custom dictionaries take precedence", { timeout: 5000 }, () => { const dicts = loadDictionaries({ passwords: new Map([["trustno1", 1]]), }); const r = assess("trustno1", { dictionaries: dicts }); expect(r.score).toBe(0); }); it("rejects malformed AssessOptions", { timeout: 5000 }, () => { expect(() => assess("pwd", { userInputs: 42 as unknown as string[] }) ).toThrowError(PasswordStrengthError); }); it("emits feedback with warning + suggestions", { timeout: 5000 }, () => { const r = assess("password"); expect(r.feedback.warning.length).toBeGreaterThan(0); }); it("returns guesses ≥ 1", { timeout: 5000 }, () => { const r = assess("anything-random-here"); expect(r.guesses).toBeGreaterThanOrEqual(1); }); it("records non-negative calcTimeMs", { timeout: 5000 }, () => { const r = assess("abc"); expect(r.calcTimeMs).toBeGreaterThanOrEqual(0); }); it("accepts custom graphs option", { timeout: 5000 }, () => { const r = assess("hello", { graphs: { qwerty: {} } }); expect(r).toBeDefined(); }); it("accepts custom referenceYear option", { timeout: 5000 }, () => { const r = assess("1985", { referenceYear: 2026 }); expect(r.score).toBeLessThanOrEqual(1); }); it("accepts empty userInputs", { timeout: 5000 }, () => { const r = assess("hello", { userInputs: [] }); expect(r).toBeDefined(); }); it("deduplicates userInputs", { timeout: 5000 }, () => { const r = assess("acme", { userInputs: ["acme", "acme", "acme corp"], }); expect(r).toBeDefined(); }); it("handles empty input", { timeout: 5000 }, () => { const r = assess(""); expect(r.guesses).toBe(1); }); it("accepts custom l33tTable", { timeout: 5000 }, () => { const r = assess("p4ssword", { l33tTable: { a: ["4"] } }); expect(r).toBeDefined(); }); it("accepts feedback resolver via opts.feedback", { timeout: 5000 }, () => { const r = assess("password", { feedback: { resolver: (key) => `RESOLVED:${key}` }, }); expect(r.feedback.warning.startsWith("RESOLVED:")).toBe(true); }); });