policy.test.ts

3.6 KB · raw

1// @vitest-environment jsdom23import { beforeEach, describe, expect, it } from "vitest";4import { detectPasswordPolicy, extractNearbyText } from "./policy";56describe("detectPasswordPolicy", () => {7  beforeEach(() => {8    document.body.innerHTML = "";9  });1011  function setupPasswordField(attrs: Record<string, string> = {}, nearby = ""): HTMLInputElement {12    const wrap = document.createElement("div");13    const input = document.createElement("input");14    input.type = "password";15    Object.entries(attrs).forEach(([k, v]) => input.setAttribute(k, v));16    wrap.appendChild(input);17    if (nearby) {18      const hint = document.createElement("p");19      hint.className = "requirement";20      hint.textContent = nearby;21      wrap.appendChild(hint);22    }23    document.body.appendChild(wrap);24    return input;25  }2627  it("reads maxlength attribute", () => {28    setupPasswordField({ maxlength: "16" });29    expect(detectPasswordPolicy().maxLength).toBe(16);30  });3132  it("reads minLength/maxLength from pattern attribute", () => {33    setupPasswordField({ pattern: ".{8,32}" });34    const p = detectPasswordPolicy();35    expect(p.minLength).toBe(8);36    expect(p.maxLength).toBe(32);37  });3839  it("parses 'at least N' from nearby text", () => {40    setupPasswordField({}, "Password must contain at least 12 characters.");41    expect(detectPasswordPolicy().minLength).toBe(12);42  });4344  it("parses 'cannot exceed N characters'", () => {45    setupPasswordField({}, "Password cannot exceed 40 characters.");46    expect(detectPasswordPolicy().maxLength).toBe(40);47  });4849  it("detects requires/forbids symbols from text", () => {50    setupPasswordField({}, "Must contain a special character and an uppercase letter.");51    const p = detectPasswordPolicy();52    expect(p.requiresSymbols).toBe(true);53    expect(p.requiresUppercase).toBe(true);54  });5556  it("detects no-symbols phrasing", () => {57    setupPasswordField({}, "No special characters allowed in this password.");58    expect(detectPasswordPolicy().forbidsSymbols).toBe(true);59  });6061  it("extracts Oracle-style forbidden character list (space-separated)", () => {62    setupPasswordField(63      {},64      "Password cannot exceed 40 characters, contain the users first name, last name, email address, spaces, or ` ~ < > \\ characters."65    );66    const p = detectPasswordPolicy();67    expect(p.maxLength).toBe(40);68    const forbidden = p.forbiddenChars ?? "";69    for (const c of ["`", "~", "<", ">", "\\"]) {70      expect(forbidden).toContain(c);71    }72  });7374  it("extracts comma-separated forbidden chars", () => {75    setupPasswordField(76      {},77      "Your password must not contain < , > , or \\ symbols."78    );79    const forbidden = detectPasswordPolicy().forbiddenChars ?? "";80    expect(forbidden).toContain("<");81    expect(forbidden).toContain(">");82    expect(forbidden).toContain("\\");83  });8485  it("does not leak commas or periods from descriptive prose", () => {86    setupPasswordField(87      {},88      "Cannot contain your first name, last name, or email address."89    );90    const forbidden = detectPasswordPolicy().forbiddenChars ?? "";91    expect(forbidden).toBe("");92  });9394  it("returns empty policy when there is no password field", () => {95    expect(detectPasswordPolicy()).toEqual({});96  });9798  it("extractNearbyText honors aria-describedby", () => {99    document.body.innerHTML = `100      <input id="pw" type="password" aria-describedby="pw-help">101      <div id="pw-help">At least 12 characters required.</div>102    `;103    const el = document.getElementById("pw") as HTMLInputElement;104    expect(extractNearbyText(el)).toContain("At least 12");105  });106});