134 lines
4.4 KiB
TypeScript
134 lines
4.4 KiB
TypeScript
import { describe, expect, it } from "@suaveplan/testing/runner";
|
|
import { loadDictionaries } from "../dicts/load-dictionaries.js";
|
|
import {
|
|
PasswordStrengthError,
|
|
PWSTRENGTH_CODES,
|
|
} from "../errors/error-codes.js";
|
|
import { StrengthResultSchema } from "../schemas/schemas.js";
|
|
import { assess } from "./assess.js";
|
|
|
|
describe("assess", () => {
|
|
it("full pipeline returns canonical shape", { timeout: 5000 }, () => {
|
|
const r = assess("Password1!");
|
|
const parsed = StrengthResultSchema.safeParse(r);
|
|
expect(parsed.success).toBe(true);
|
|
expect(r.score).toBeLessThanOrEqual(1);
|
|
});
|
|
|
|
it("accepts 199-character input", { timeout: 5000 }, () => {
|
|
const r = assess("a".repeat(199));
|
|
expect(r.password.length).toBe(199);
|
|
});
|
|
|
|
it("rejects 200-character input", { timeout: 5000 }, () => {
|
|
expect(() => assess("a".repeat(200))).toThrowError(
|
|
PasswordStrengthError
|
|
);
|
|
});
|
|
|
|
it("rejects 200-character input with INPUT_TOO_LONG code", {
|
|
timeout: 5000,
|
|
}, () => {
|
|
try {
|
|
assess("a".repeat(200));
|
|
} catch (e) {
|
|
expect((e as PasswordStrengthError).code).toBe(
|
|
PWSTRENGTH_CODES.INPUT_TOO_LONG
|
|
);
|
|
}
|
|
});
|
|
|
|
it("userInputs surface as ranked matches", { timeout: 5000 }, () => {
|
|
const r = assess("hello-acme-corp", { userInputs: ["acme-corp"] });
|
|
expect(
|
|
r.sequence.some(
|
|
(m) =>
|
|
m.pattern === "dictionary" &&
|
|
m.dictionaryName === "user_inputs"
|
|
)
|
|
).toBe(true);
|
|
});
|
|
|
|
it("userInputs become a ranked dictionary", { timeout: 5000 }, () => {
|
|
const r = assess("alice@example.com-pwd", {
|
|
userInputs: ["alice@example.com"],
|
|
});
|
|
const hit = r.sequence.find(
|
|
(m) =>
|
|
m.pattern === "dictionary" && m.dictionaryName === "user_inputs"
|
|
);
|
|
expect(hit).toBeDefined();
|
|
expect(hit?.pattern === "dictionary" && hit.matchedWord).toBe(
|
|
"alice@example.com"
|
|
);
|
|
});
|
|
|
|
it("custom dictionaries take precedence", { timeout: 5000 }, () => {
|
|
const dicts = loadDictionaries({
|
|
passwords: new Map([["trustno1", 1]]),
|
|
});
|
|
const r = assess("trustno1", { dictionaries: dicts });
|
|
expect(r.score).toBe(0);
|
|
});
|
|
|
|
it("rejects malformed AssessOptions", { timeout: 5000 }, () => {
|
|
expect(() =>
|
|
assess("pwd", { userInputs: 42 as unknown as string[] })
|
|
).toThrowError(PasswordStrengthError);
|
|
});
|
|
|
|
it("emits feedback with warning + suggestions", { timeout: 5000 }, () => {
|
|
const r = assess("password");
|
|
expect(r.feedback.warning.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it("returns guesses ≥ 1", { timeout: 5000 }, () => {
|
|
const r = assess("anything-random-here");
|
|
expect(r.guesses).toBeGreaterThanOrEqual(1);
|
|
});
|
|
|
|
it("records non-negative calcTimeMs", { timeout: 5000 }, () => {
|
|
const r = assess("abc");
|
|
expect(r.calcTimeMs).toBeGreaterThanOrEqual(0);
|
|
});
|
|
|
|
it("accepts custom graphs option", { timeout: 5000 }, () => {
|
|
const r = assess("hello", { graphs: { qwerty: {} } });
|
|
expect(r).toBeDefined();
|
|
});
|
|
|
|
it("accepts custom referenceYear option", { timeout: 5000 }, () => {
|
|
const r = assess("1985", { referenceYear: 2026 });
|
|
expect(r.score).toBeLessThanOrEqual(1);
|
|
});
|
|
|
|
it("accepts empty userInputs", { timeout: 5000 }, () => {
|
|
const r = assess("hello", { userInputs: [] });
|
|
expect(r).toBeDefined();
|
|
});
|
|
|
|
it("deduplicates userInputs", { timeout: 5000 }, () => {
|
|
const r = assess("acme", {
|
|
userInputs: ["acme", "acme", "acme corp"],
|
|
});
|
|
expect(r).toBeDefined();
|
|
});
|
|
|
|
it("handles empty input", { timeout: 5000 }, () => {
|
|
const r = assess("");
|
|
expect(r.guesses).toBe(1);
|
|
});
|
|
|
|
it("accepts custom l33tTable", { timeout: 5000 }, () => {
|
|
const r = assess("p4ssword", { l33tTable: { a: ["4"] } });
|
|
expect(r).toBeDefined();
|
|
});
|
|
|
|
it("accepts feedback resolver via opts.feedback", { timeout: 5000 }, () => {
|
|
const r = assess("password", {
|
|
feedback: { resolver: (key) => `RESOLVED:${key}` },
|
|
});
|
|
expect(r.feedback.warning.startsWith("RESOLVED:")).toBe(true);
|
|
});
|
|
});
|