UNPKG

@ledgerhq/live-common

Version:
177 lines (151 loc) • 6.2 kB
import BigNumber from "bignumber.js"; import { genericGetAccountShape } from "../getAccountShape"; jest.mock("@ledgerhq/coin-framework/account/index", () => ({ encodeAccountId: jest.fn(() => "accId"), })); const mergeOpsMock = jest.fn(); jest.mock("@ledgerhq/coin-framework/bridge/jsHelpers", () => ({ mergeOps: (...args: any[]) => mergeOpsMock(...args), })); const listOperationsMock = jest.fn(); const getBalanceMock = jest.fn(); const lastBlockMock = jest.fn(); const getTokenFromAssetMock = jest.fn(); const chainSpecificGetAccountShapeMock = jest.fn(); jest.mock("../alpaca", () => ({ getAlpacaApi: () => ({ lastBlock: (...a: any[]) => lastBlockMock(...a), getBalance: (...a: any[]) => getBalanceMock(...a), listOperations: (...a: any[]) => listOperationsMock(...a), getTokenFromAsset: (...a: any[]) => getTokenFromAssetMock(...a), getChainSpecificRules: () => ({ getAccountShape: (...a: any[]) => chainSpecificGetAccountShapeMock(...a), }), }), })); const adaptCoreOperationToLiveOperationMock = jest.fn(); const extractBalanceMock = jest.fn(); jest.mock("../utils", () => ({ adaptCoreOperationToLiveOperation: (...a: any[]) => adaptCoreOperationToLiveOperationMock(...a), extractBalance: (...a: any[]) => extractBalanceMock(...a), })); const inferSubOperationsMock = jest.fn(); jest.mock("@ledgerhq/coin-framework/serialization", () => ({ inferSubOperations: (...a: any[]) => inferSubOperationsMock(...a), })); const buildSubAccountsMock = jest.fn(); jest.mock("../buildSubAccounts", () => ({ buildSubAccounts: (...a: any[]) => buildSubAccountsMock(...a), })); // Test matrix for Stellar & XRP const chains = [ { currency: { id: "stellar", name: "Stellar" }, network: "testnet" }, { currency: { id: "ripple", name: "XRP" }, network: "mainnet" }, { currency: { id: "tezos", name: "Tezos" }, network: "mainnet" }, ]; describe("genericGetAccountShape (stellar & xrp)", () => { beforeEach(() => { jest.clearAllMocks(); }); describe.each(chains)("$currency.id", ({ currency, network }) => { test("builds account shape with existing operations, pagination and sub accounts", async () => { const oldOp = { hash: "h1", blockHeight: 10, type: "OPT_IN", extra: { pagingToken: "pt1", assetReference: "ar1", assetOwner: "ow1" }, }; const initialAccount = { operations: [oldOp], blockHeight: 10 }; extractBalanceMock.mockReturnValue({ value: "1000", locked: "300" }); getBalanceMock.mockResolvedValue([ { asset: { type: "native" }, value: "1000", locked: "300" }, { asset: { type: "token", symbol: "TOK1" }, value: "42" }, { asset: { type: "token", symbol: "TOK_IGNORE" }, value: "5" }, ]); getTokenFromAssetMock.mockImplementation(asset => asset.symbol === "TOK1" ? { id: `${currency.id}_token1` } : null, ); const coreOp = { hash: "h2", height: 12 }; listOperationsMock.mockResolvedValue([[coreOp]]); adaptCoreOperationToLiveOperationMock.mockImplementation((_accId, op) => ({ hash: op.hash, type: "IN", blockHeight: 12, extra: { assetReference: "ar2", assetOwner: "ow2" }, })); mergeOpsMock.mockImplementation((oldOps, newOps) => [...newOps, ...oldOps]); buildSubAccountsMock.mockReturnValue([ { id: `${currency.id}_subAcc1`, type: "TokenAccount" }, ]); inferSubOperationsMock.mockImplementation(hash => hash === "h2" ? [{ id: `${currency.id}_subOp1` }] : [], ); lastBlockMock.mockResolvedValue({ height: 123 }); const getShape = genericGetAccountShape(network, currency.id); const result = await getShape( { address: `${currency.id}_addr1`, initialAccount, currency, derivationMode: "", } as any, { paginationConfig: {} as any }, ); expect(chainSpecificGetAccountShapeMock).toHaveBeenCalledWith(`${currency.id}_addr1`); expect(listOperationsMock).toHaveBeenCalledWith(`${currency.id}_addr1`, { minHeight: 11, order: "asc", lastPagingToken: "pt1", }); const assetsBalancePassed = buildSubAccountsMock.mock.calls[0][0].allTokenAssetsBalances; expect(assetsBalancePassed).toEqual([ { asset: { symbol: "TOK1", type: "token" }, value: "42" }, { asset: { symbol: "TOK_IGNORE", type: "token" }, value: "5" }, ]); const assetOpsPassed = buildSubAccountsMock.mock.calls[0][0].operations; expect(assetOpsPassed).toHaveLength(1); expect(assetOpsPassed[0].hash).toBe("h2"); expect(result).toMatchObject({ balance: new BigNumber(1000), spendableBalance: new BigNumber(700), blockHeight: 123, operationsCount: 2, subAccounts: [{ id: `${currency.id}_subAcc1`, type: "TokenAccount" }], operations: [ { hash: "h2", type: "IN", blockHeight: 12, subOperations: [{ id: `${currency.id}_subOp1` }], extra: { assetReference: "ar2", assetOwner: "ow2" }, }, oldOp, ], }); }); test("handles empty operations (no old ops, no new ops) and blockHeight=0", async () => { getBalanceMock.mockResolvedValue([{ asset: { type: "native" }, value: "0", locked: "0" }]); extractBalanceMock.mockReturnValue({ value: "0", locked: "0" }); listOperationsMock.mockResolvedValue([[]]); buildSubAccountsMock.mockReturnValue([]); const getShape = genericGetAccountShape(network, currency.id); const result = await getShape( { address: `${currency.id}_addr2`, initialAccount: undefined, currency, derivationMode: "", } as any, { paginationConfig: {} as any }, ); expect(result).toMatchObject({ operations: [], // Empty array check for `operations` blockHeight: 0, operationsCount: 0, subAccounts: [], // Empty array check for `subAccounts` balance: new BigNumber(0), spendableBalance: new BigNumber(0), }); }); }); });