98 lines
3.1 KiB
TypeScript
98 lines
3.1 KiB
TypeScript
import { describe, expect, it } from "@suaveplan/testing/runner";
|
|
import { assess } from "./assess/assess.js";
|
|
import { dictionaryMatch } from "./matchers/dictionary/dictionary.js";
|
|
import type { Match } from "./matchers/types.js";
|
|
import { estimateGuesses } from "./scoring/estimate-guesses.js";
|
|
|
|
/**
|
|
* Wheeler 2016 corpus agreement tests. We do not vendor the upstream test
|
|
* vector JSON (GPLv2 half of the dual licence); instead we assemble a curated
|
|
* set of canonical inputs and verify the per-pattern guess estimates agree
|
|
* within ≤1 bit log10 against the closed-form formulae documented in
|
|
* Wheeler §4.
|
|
*/
|
|
describe("Wheeler corpus agrees to <=1 bit log10", () => {
|
|
const dicts = { passwords: new Map([["password", 1]]) };
|
|
|
|
const cases: { match: Match; password: string; expectedLog10: number }[] = [
|
|
{
|
|
match: {
|
|
pattern: "dictionary",
|
|
i: 0,
|
|
j: 7,
|
|
token: "password",
|
|
matchedWord: "password",
|
|
rank: 1,
|
|
dictionaryName: "passwords",
|
|
reversed: false,
|
|
l33t: false,
|
|
},
|
|
password: "password",
|
|
expectedLog10: 0, // log10(1) = 0
|
|
},
|
|
{
|
|
match: {
|
|
pattern: "sequence",
|
|
i: 0,
|
|
j: 3,
|
|
token: "abcd",
|
|
sequenceName: "lower",
|
|
sequenceSpace: 26,
|
|
ascending: true,
|
|
},
|
|
password: "abcd",
|
|
// 4 (base) * 4 (length) * 1 = 16 → log10 ≈ 1.20
|
|
expectedLog10: 1.2,
|
|
},
|
|
{
|
|
match: {
|
|
pattern: "date",
|
|
i: 0,
|
|
j: 9,
|
|
token: "11/22/1985",
|
|
year: 1985,
|
|
month: 11,
|
|
day: 22,
|
|
separator: "/",
|
|
},
|
|
password: "11/22/1985",
|
|
// 365 * 41 * 4 = 59860 → log10 ≈ 4.78
|
|
expectedLog10: 4.78,
|
|
},
|
|
{
|
|
match: {
|
|
pattern: "regex",
|
|
i: 0,
|
|
j: 3,
|
|
token: "1985",
|
|
regexName: "recent_year",
|
|
regexMatch: ["1985"] as unknown as RegExpMatchArray,
|
|
},
|
|
password: "1985",
|
|
// |1985 - 2026| = 41 → log10 ≈ 1.61
|
|
expectedLog10: 1.61,
|
|
},
|
|
];
|
|
|
|
for (const c of cases) {
|
|
it(`${c.match.pattern} / ${c.match.token}`, { timeout: 5000 }, () => {
|
|
const g = estimateGuesses(c.match, c.password, {
|
|
referenceYear: 2026,
|
|
});
|
|
const log10 = Math.log10(g);
|
|
expect(Math.abs(log10 - c.expectedLog10)).toBeLessThanOrEqual(1);
|
|
});
|
|
}
|
|
|
|
it("full assess agrees with corpus reference within bucket", {
|
|
timeout: 5000,
|
|
}, () => {
|
|
const r = assess("password", {
|
|
dictionaries: { passwords: dicts.passwords },
|
|
});
|
|
expect(r.score).toBe(0);
|
|
const matches = dictionaryMatch("password", dicts);
|
|
expect(matches.length).toBeGreaterThanOrEqual(1);
|
|
});
|
|
});
|