@ledgerhq/hw-app-btc
Version:
Ledger Hardware Wallet Bitcoin Application API
118 lines (105 loc) • 4.43 kB
text/typescript
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();
});
});
});