generate.test.ts

1.6 KB · raw

1import { describe, expect, it } from "vitest";2import { generatePassword } 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  });4344  it("rejection sampling still yields exact length and only pool chars (audit INFO — modulo bias)", () => {45    // The rejection loop must terminate and never under/over-fill regardless of46    // how many CSPRNG draws land in the discarded high bucket.47    for (let i = 0; i < 50; i++) {48      const pw = generatePassword({ length: 64 });49      expect(pw).toHaveLength(64);50      expect(pw).toMatch(/^[A-Za-z0-9!@#$%^&*]+$/);51    }52  });53});