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);
});
});