Initial Commit
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user