import { describe, expect, it } from "vitest"; import { generatePassword, generateSmartPassword } from "./generate"; describe("generatePassword", () => { it("produces a password of the requested length", () => { const pw = generatePassword({ length: 32 }); expect(pw).toHaveLength(32); }); it("omits forbidden characters from the symbol pool", () => { const pw = generatePassword({ length: 200, includeSymbols: true, forbiddenChars: "^*!@", }); for (const c of ["^", "*", "!", "@"]) { expect(pw).not.toContain(c); } }); it("omits forbidden characters from letter/number pools too", () => { // Defensive: if a site ever bans a digit, honor it. const pw = generatePassword({ length: 200, includeNumbers: true, includeSymbols: false, includeUppercase: false, forbiddenChars: "0", }); expect(pw).not.toContain("0"); }); it("falls back to a non-empty pool when every category is disabled", () => { const pw = generatePassword({ length: 16, includeUppercase: false, includeLowercase: false, includeNumbers: false, includeSymbols: false, }); expect(pw).toHaveLength(16); }); }); describe("generateSmartPassword", () => { it("clamps to policy.maxLength", () => { const pw = generateSmartPassword({ maxLength: 12 }); expect(pw.length).toBeLessThanOrEqual(12); }); it("raises to policy.minLength", () => { const pw = generateSmartPassword({ minLength: 32 }); expect(pw.length).toBeGreaterThanOrEqual(32); }); it("excludes forbidden chars (Oracle-style)", () => { const pw = generateSmartPassword({ maxLength: 40, forbiddenChars: "`~<>\\", }); expect(pw.length).toBeLessThanOrEqual(40); for (const c of ["`", "~", "<", ">", "\\"]) { expect(pw).not.toContain(c); } }); it("honors forbidsSymbols", () => { const pw = generateSmartPassword({ forbidsSymbols: true }); expect(pw).toMatch(/^[A-Za-z0-9]+$/); }); it("defaults to length 20 when policy is empty", () => { const pw = generateSmartPassword({}); expect(pw).toHaveLength(20); }); it("rejection sampling still yields exact length and only pool chars (audit INFO — modulo bias)", () => { // The rejection loop must terminate and never under/over-fill regardless of // how many CSPRNG draws land in the discarded high bucket. for (let i = 0; i < 50; i++) { const pw = generatePassword({ length: 64 }); expect(pw).toHaveLength(64); expect(pw).toMatch(/^[A-Za-z0-9!@#$%^&*]+$/); } }); });