Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View File

@@ -0,0 +1,138 @@
import { describe, expect, it } from "@suaveplan/testing/runner";
import type { Rng } from "@suaveplan/types";
import { DIGITS, SYMBOLS } from "../charsets/charsets.js";
import { EFF_LONG_WORDLIST } from "../wordlists/eff-long.js";
import { EFF_SHORT_WORDLIST } from "../wordlists/eff-short.js";
import { generatePassphrase } from "./passphrase.js";
function seededRng(seed: number): Rng {
let s = seed >>> 0;
return {
nextUint32(): number {
s = (s + 0x6d2b79f5) >>> 0;
let t = s;
t = Math.imul(t ^ (t >>> 15), t | 1);
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
return ((t ^ (t >>> 14)) >>> 0) >>> 0;
},
nextFloat(): number {
return this.nextUint32() / 0x1_0000_0000;
},
};
}
describe("generatePassphrase", () => {
it("defaults to EFF Long with length 6", { timeout: 5000 }, () => {
const pp = generatePassphrase({});
const words = pp.split("-");
expect(words.length).toBe(6);
for (const w of words) expect(EFF_LONG_WORDLIST.includes(w)).toBe(true);
});
it("draws from EFF Short when wordlist is short", { timeout: 5000 }, () => {
const pp = generatePassphrase({ wordlist: "short", length: 7 });
const words = pp.split("-");
expect(words.length).toBe(7);
for (const w of words)
expect(EFF_SHORT_WORDLIST.includes(w)).toBe(true);
});
it("accepts inline wordlist arrays meeting the min-size guard", {
timeout: 5000,
}, () => {
const inline = Array.from({ length: 256 }, (_, i) => `w${i}`);
const pp = generatePassphrase({ wordlist: inline, length: 4 });
const words = pp.split("-");
expect(words.length).toBe(4);
for (const w of words) expect(inline.includes(w)).toBe(true);
});
it("rejects inline wordlist arrays below 256 entries", {
timeout: 5000,
}, () => {
expect(() =>
generatePassphrase({ wordlist: ["one", "two"], length: 2 })
).toThrow(/PWGEN\.WORDLIST_TOO_SHORT/);
});
it("capitalize firstChar applies to every word", { timeout: 5000 }, () => {
const pp = generatePassphrase({ length: 4, capitalize: "firstChar" });
const words = pp.split("-");
for (const w of words) {
expect(w.charAt(0)).toBe(w.charAt(0).toUpperCase());
expect(/^[A-Z]/.test(w)).toBe(true);
}
});
it("capitalize randomWord applies to exactly one word", {
timeout: 5000,
}, () => {
const pp = generatePassphrase(
{ length: 4, capitalize: "randomWord" },
seededRng(11)
);
const words = pp.split("-");
const upperCount = words.filter((w) => /^[A-Z]/.test(w)).length;
expect(upperCount).toBe(1);
});
it("addDigits and addSymbols inject the requested counts", {
timeout: 5000,
}, () => {
// Use a separator outside both DIGITS and SYMBOLS so the injected
// counts are not confounded by the separator characters.
const pp = generatePassphrase({
length: 3,
addDigits: 2,
addSymbols: 1,
separator: " ",
});
const digitCount = [...pp].filter((c) => DIGITS.has(c)).length;
const symbolCount = [...pp].filter((c) => SYMBOLS.has(c)).length;
expect(digitCount).toBe(2);
expect(symbolCount).toBe(1);
});
it("uses a custom separator", { timeout: 5000 }, () => {
const pp = generatePassphrase({ length: 4, separator: "." });
expect(pp.split(".").length).toBe(4);
});
it("is deterministic under seeded Rng", { timeout: 5000 }, () => {
const a = generatePassphrase(
{ length: 6, capitalize: "firstChar" },
seededRng(42)
);
const b = generatePassphrase(
{ length: 6, capitalize: "firstChar" },
seededRng(42)
);
expect(a).toBe(b);
});
it("rejects invalid input with PWGEN.INVALID_OPTIONS", {
timeout: 5000,
}, () => {
expect(() => generatePassphrase({ length: 0 })).toThrow(
/PWGEN\.INVALID_OPTIONS/
);
});
it("resolves wordlist via registry name", { timeout: 5000 }, () => {
const pp = generatePassphrase({ wordlist: "eff-short", length: 4 });
const words = pp.split("-");
for (const w of words)
expect(EFF_SHORT_WORDLIST.includes(w)).toBe(true);
});
it("works with no opts argument", { timeout: 5000 }, () => {
const pp = generatePassphrase();
expect(pp.split("-").length).toBe(6);
});
it("applies capitalize none by default", { timeout: 5000 }, () => {
const pp = generatePassphrase({ length: 4 });
const words = pp.split("-");
for (const w of words) expect(/^[a-z]/.test(w)).toBe(true);
});
});