import { createWalletPolicy, createProgressCallback, finalizePsbtAndExtract, } from "../../src/signPsbt/signAndFinalize"; import { PsbtV2 } from "@ledgerhq/psbtv2"; import { finalize } from "../../src/newops/psbtFinalizer"; import { extract } from "../../src/newops/psbtExtractor"; jest.mock("../../src/newops/psbtFinalizer", () => ({ finalize: jest.fn(), })); jest.mock("../../src/newops/psbtExtractor", () => ({ extract: jest.fn(), })); describe("signAndFinalize", () => { beforeEach(() => { jest.clearAllMocks(); }); const masterFp = Buffer.from([1, 2, 3, 4]); const accountPath = [0x80000054, 0x80000000, 0x80000000]; const accountXpub = "tpubDCwYjpDhUdPGP5rS3wgNg13mTrrjBuG8V9VpWbyptX6TRPbNoZVXsoVUSkCjmQ8jJycjuDKBb9eataSymXakTTaGifxR6kmVsfFehH1ZgJT"; describe("createWalletPolicy", () => { it("returns a WalletPolicy with descriptor from account type and key from masterFp, path and xpub", () => { const policy = createWalletPolicy(masterFp, accountPath, accountXpub, { getDescriptorTemplate: () => "wpkh(@0)", } as any); expect(policy.descriptorTemplate).toBe("wpkh(@0)"); expect(policy.keys).toHaveLength(1); expect(policy.keys[0]).toContain(accountXpub); expect(policy.keys[0]).toContain(masterFp.toString("hex")); }); }); describe("createProgressCallback", () => { it("invokes onDeviceSignatureRequested once when callback is created", () => { const onDeviceSignatureRequested = jest.fn(); createProgressCallback(2, { onDeviceSignatureRequested }); expect(onDeviceSignatureRequested).toHaveBeenCalledTimes(1); }); it("invokes onDeviceSignatureGranted only on first progress call", () => { const onDeviceSignatureGranted = jest.fn(); const progress = createProgressCallback(1, { onDeviceSignatureGranted }); progress(); expect(onDeviceSignatureGranted).toHaveBeenCalledTimes(1); progress(); progress(); expect(onDeviceSignatureGranted).toHaveBeenCalledTimes(1); }); it("reports progress via onDeviceStreaming with correct total and index", () => { const onDeviceStreaming = jest.fn(); const progress = createProgressCallback(2, { onDeviceStreaming }); progress(); expect(onDeviceStreaming).toHaveBeenLastCalledWith({ total: 4, index: 0, progress: 1 / 4, }); progress(); expect(onDeviceStreaming).toHaveBeenLastCalledWith({ total: 4, index: 1, progress: 2 / 4, }); }); it("does not call onDeviceStreaming when option is omitted", () => { const progress = createProgressCallback(1, {}); progress(); progress(); // No throw, and we can't assert on internal state; we've exercised the branch expect(() => progress()).not.toThrow(); }); it("should not call onDeviceStreaming when inputCount is 0", () => { const onDeviceStreaming = jest.fn(); const progress = createProgressCallback(0, { onDeviceStreaming }); progress(); progress(); expect(onDeviceStreaming).not.toHaveBeenCalled(); }); it("should not call onDeviceStreaming when inputCount is negative", () => { const onDeviceStreaming = jest.fn(); const progress = createProgressCallback(-1, { onDeviceStreaming }); progress(); expect(onDeviceStreaming).not.toHaveBeenCalled(); }); }); describe("finalizePsbtAndExtract", () => { it("finalizes and returns psbt + tx when shouldFinalize is true", () => { const psbt = { serialize: () => Buffer.from("010203", "hex") } as unknown as PsbtV2; jest.mocked(extract).mockReturnValue(Buffer.from("deadbeef", "hex")); const result = finalizePsbtAndExtract(psbt, true); expect(finalize).toHaveBeenCalledWith(psbt); expect(extract).toHaveBeenCalledWith(psbt); expect(result.psbt.toString("hex")).toBe("010203"); expect(result.tx).toBe("deadbeef"); }); it("does not finalize and returns only psbt when shouldFinalize is false", () => { const psbt = { serialize: () => Buffer.from("aabb", "hex") } as unknown as PsbtV2; const result = finalizePsbtAndExtract(psbt, false); expect(finalize).not.toHaveBeenCalled(); expect(extract).not.toHaveBeenCalled(); expect(result.psbt.toString("hex")).toBe("aabb"); expect(result.tx).toBeUndefined(); }); }); });