import { describe, expect, it } from "vitest"; import { parseOtpAuth, serializeOtpAuth } from "../../src/otp/profile"; describe("otpauth profiles", () => { it("parses and serializes a TOTP profile without losing extensions", () => { const parsed = parseOtpAuth( "otpauth://totp/Example:alice%40example.com?secret=JBSWY3DPEHPK3PXP&issuer=Example&algorithm=SHA256&digits=8&period=45&image=ignored", ); expect(parsed.profile).toMatchObject({ kind: "totp", issuer: "Example", account: "alice@example.com", algorithm: "SHA-256", digits: 8, period: 45, }); expect(parsed.profile.extensions.get("image")).toBe("ignored"); expect(serializeOtpAuth(parsed.profile)).toContain("image=ignored"); }); it("requires an HOTP counter and preserves 64-bit values", () => { expect(() => parseOtpAuth("otpauth://hotp/Example?secret=JBSWY3DPEHPK3PXP"), ).toThrow(/counter/iu); const parsed = parseOtpAuth( "otpauth://hotp/Example?secret=JBSWY3DPEHPK3PXP&counter=18446744073709551615", ); expect(parsed.profile.counter).toBe((1n << 64n) - 1n); }); it("rejects duplicate known parameters", () => { expect(() => parseOtpAuth( "otpauth://totp/Example?secret=JBSWY3DPEHPK3PXP&secret=JBSWY3DPEHPK3PXP", ), ).toThrow(/duplicate/iu); }); it("rejects partially numeric parameters and unsafe labels", () => { expect(() => parseOtpAuth( "otpauth://totp/Example?secret=JBSWY3DPEHPK3PXP&digits=6oops", ), ).toThrow(/integer/iu); const parsed = parseOtpAuth( "otpauth://totp/Example?secret=JBSWY3DPEHPK3PXP", ); parsed.profile.account = "unsafe:label"; expect(() => serializeOtpAuth(parsed.profile)).toThrow(/colon/iu); }); it("warns without silently reconciling issuer mismatch", () => { const result = parseOtpAuth( "otpauth://totp/Display:alice?secret=JBSWY3DPEHPK3PXP&issuer=canonical.example", ); expect(result.profile.issuer).toBe("canonical.example"); expect(result.warnings.some(({ code }) => code === "issuer-mismatch")).toBe( true, ); }); });