generate.test.ts

2.6 KB · raw

1import { describe, expect, it } from "vitest";2import { generatePassword, generateSmartPassword } from "./generate";34describe("generatePassword", () => {5  it("produces a password of the requested length", () => {6    const pw = generatePassword({ length: 32 });7    expect(pw).toHaveLength(32);8  });910  it("omits forbidden characters from the symbol pool", () => {11    const pw = generatePassword({12      length: 200,13      includeSymbols: true,14      forbiddenChars: "^*!@",15    });16    for (const c of ["^", "*", "!", "@"]) {17      expect(pw).not.toContain(c);18    }19  });2021  it("omits forbidden characters from letter/number pools too", () => {22    // Defensive: if a site ever bans a digit, honor it.23    const pw = generatePassword({24      length: 200,25      includeNumbers: true,26      includeSymbols: false,27      includeUppercase: false,28      forbiddenChars: "0",29    });30    expect(pw).not.toContain("0");31  });3233  it("falls back to a non-empty pool when every category is disabled", () => {34    const pw = generatePassword({35      length: 16,36      includeUppercase: false,37      includeLowercase: false,38      includeNumbers: false,39      includeSymbols: false,40    });41    expect(pw).toHaveLength(16);42  });43});4445describe("generateSmartPassword", () => {46  it("clamps to policy.maxLength", () => {47    const pw = generateSmartPassword({ maxLength: 12 });48    expect(pw.length).toBeLessThanOrEqual(12);49  });5051  it("raises to policy.minLength", () => {52    const pw = generateSmartPassword({ minLength: 32 });53    expect(pw.length).toBeGreaterThanOrEqual(32);54  });5556  it("excludes forbidden chars (Oracle-style)", () => {57    const pw = generateSmartPassword({58      maxLength: 40,59      forbiddenChars: "`~<>\\",60    });61    expect(pw.length).toBeLessThanOrEqual(40);62    for (const c of ["`", "~", "<", ">", "\\"]) {63      expect(pw).not.toContain(c);64    }65  });6667  it("honors forbidsSymbols", () => {68    const pw = generateSmartPassword({ forbidsSymbols: true });69    expect(pw).toMatch(/^[A-Za-z0-9]+$/);70  });7172  it("defaults to length 20 when policy is empty", () => {73    const pw = generateSmartPassword({});74    expect(pw).toHaveLength(20);75  });7677  it("rejection sampling still yields exact length and only pool chars (audit INFO — modulo bias)", () => {78    // The rejection loop must terminate and never under/over-fill regardless of79    // how many CSPRNG draws land in the discarded high bucket.80    for (let i = 0; i < 50; i++) {81      const pw = generatePassword({ length: 64 });82      expect(pw).toHaveLength(64);83      expect(pw).toMatch(/^[A-Za-z0-9!@#$%^&*]+$/);84    }85  });86});