import { describe, expect, it, vi } from "vitest"; import { MIN_PASSPHRASE_LENGTH, checkPassphraseBreached, generateDicewarePassphrase, validateForExport, } from "./cjkPassphrase"; describe("crypto cjkPassphrase", () => { it("generateDicewarePassphrase returns words from the EFF wordlist", () => { const phrase = generateDicewarePassphrase(); const words = phrase.split(" "); expect(words).toHaveLength(6); expect(words.every((word) => /^[a-z]+(?:-[a-z]+)?$/.test(word))).toBe(true); }); it("validateForExport fails when the passphrase is too short", () => { expect( validateForExport({ passphrase: "too short", confirm: "too short", }) ).toMatchObject({ ok: false, error: "PASSPHRASE_TOO_SHORT", }); expect(MIN_PASSPHRASE_LENGTH).toBe(16); }); it("validateForExport fails when zxcvbn rates the passphrase too weak", () => { expect( validateForExport({ passphrase: "aaaaaaaaaaaaaaaa", confirm: "aaaaaaaaaaaaaaaa", }) ).toMatchObject({ ok: false, error: "PASSPHRASE_TOO_WEAK", }); }); it("validateForExport accepts a strong long passphrase", () => { expect( validateForExport({ passphrase: "correct horse battery staple drift waves", confirm: "correct horse battery staple drift waves", }) ).toMatchObject({ ok: true, }); }); it("checkPassphraseBreached only sends the 5-char SHA-1 prefix", async () => { const fetchMock = vi.fn(async () => ({ ok: true, text: async () => "", })); vi.stubGlobal("fetch", fetchMock); await checkPassphraseBreached("correct horse battery staple drift waves"); expect(fetchMock).toHaveBeenCalledTimes(1); const [url] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]; expect(url).toMatch(/\/range\/[A-F0-9]{5}$/); expect(url).not.toContain("correct horse battery"); }); it("checkPassphraseBreached handles network failure without throwing", async () => { vi.stubGlobal("fetch", vi.fn(async () => { throw new Error("network down"); })); await expect( checkPassphraseBreached("correct horse battery staple drift waves") ).resolves.toEqual({ breached: false, apiReachable: false, }); }); });