import { describe, expect, it } from "vitest"; import { TOTP } from "otpauth"; import { generateTOTP, parseOtpauthURI } from "./totp"; // PCH-01-019: algorithm/digits/period must be threaded through, with RFC // defaults (SHA-1 / 6 / 30) when absent so legacy bare-seed entries are stable. const SECRET = "JBSWY3DPEHPK3PXP"; describe("generateTOTP (PCH-01-019)", () => { it("honours SHA256 / 8-digit / 60s params", () => { const { code, remaining } = generateTOTP(SECRET, "SHA256", 8, 60); const expected = new TOTP({ secret: SECRET, algorithm: "SHA256", digits: 8, period: 60 }).generate(); expect(code).toBe(expected); expect(code.length).toBe(8); expect(remaining).toBeGreaterThan(0); expect(remaining).toBeLessThanOrEqual(60); }); it("legacy bare seed (no params) generates the SHA-1 / 6 / 30 default", () => { const { code, remaining } = generateTOTP(SECRET); const expected = new TOTP({ secret: SECRET, algorithm: "SHA1", digits: 6, period: 30 }).generate(); expect(code).toBe(expected); expect(code.length).toBe(6); expect(remaining).toBeLessThanOrEqual(30); }); it("undefined optional args fall through to defaults", () => { const a = generateTOTP(SECRET, undefined, undefined, undefined); const b = generateTOTP(SECRET); expect(a.code).toBe(b.code); }); }); describe("parseOtpauthURI (PCH-01-019)", () => { it("extracts algorithm, digits, and period", () => { const parsed = parseOtpauthURI( `otpauth://totp/Example:alice?secret=${SECRET}&algorithm=SHA256&digits=8&period=60&issuer=Example`, ); expect(parsed).toMatchObject({ secret: SECRET, algorithm: "SHA256", digits: 8, period: 60, }); }); it("defaults a bare otpauth URI to SHA1 / 6 / 30", () => { const parsed = parseOtpauthURI(`otpauth://totp/Example:alice?secret=${SECRET}&issuer=Example`); expect(parsed).toMatchObject({ secret: SECRET, algorithm: "SHA1", digits: 6, period: 30 }); }); it("returns null for non-otpauth input", () => { expect(parseOtpauthURI("not-a-uri")).toBeNull(); }); });