import { describe, expect, it } from "vitest"; import { exportCsv, importCsv, importOtpAuthList, importPlainPskc, } from "../../src/otp/migration"; import { utf8ToBytes } from "../../src/crypto/encoding"; import type { OtpProfile } from "../../src/otp/profile"; const profile: OtpProfile = { kind: "totp", account: 'alice,"admin"', issuer: "Example", secret: utf8ToBytes("12345678901234567890"), algorithm: "SHA-256", digits: 8, period: 45, counter: 0n, extensions: new Map(), }; describe("OTP migrations", () => { it("round-trips the documented CSV including quoting", () => { const result = importCsv(exportCsv([profile])); expect(result.profiles[0]).toMatchObject({ account: profile.account, issuer: "Example", algorithm: "SHA-256", digits: 8, period: 45, }); expect([...result.profiles[0]!.secret]).toEqual([...profile.secret]); }); it("neutralizes spreadsheet formulas without changing a round trip", () => { const dangerous = { ...profile, account: '=HYPERLINK("https://bad")' }; const csv = exportCsv([dangerous]); expect(csv).toContain("'=HYPERLINK"); expect(importCsv(csv).profiles[0]!.account).toBe(dangerous.account); }); it("reports the failing line in URI lists", () => { expect(() => importOtpAuthList("otpauth://totp/Good?secret=JBSWY3DPEHPK3PXP\nnope"), ).toThrow(/Line 2/iu); }); it("imports plain-secret PSKC and refuses encrypted keys", () => { const xml = `ExampleMTIzNDU2Nzg5MDEyMzQ1Njc4OTA=30OTP`; expect(importPlainPskc(xml).profiles[0]).toMatchObject({ kind: "totp", account: "alice", issuer: "Example", digits: 6, }); expect(() => importPlainPskc( xml.replace( "MTIzNDU2Nzg5MDEyMzQ1Njc4OTA=", "", ), ), ).toThrow(/encrypted/iu); }); });