UNPKG

@ledgerhq/live-common

Version:
393 lines 16.7 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const server_1 = require("./server"); const bignumber_js_1 = __importDefault(require("bignumber.js")); // Mock dependencies jest.mock("@ledgerhq/wallet-api-server", () => ({ RPCHandler: jest.fn(), customWrapper: jest.fn(handler => handler), })); jest.mock("@ledgerhq/cryptoassets", () => ({ findTokenById: jest.fn(), findTokenByAddressInCurrency: jest.fn(), getCryptoCurrencyById: jest.fn(), })); jest.mock("../converters", () => ({ getAccountIdFromWalletAccountId: jest.fn(), getWalletAPITransactionSignFlowInfos: jest.fn(), })); jest.mock("../../bridge", () => ({ getAccountBridge: jest.fn(), })); jest.mock("@ledgerhq/live-env", () => ({ getEnv: jest.fn(), })); // Mock types const mockTracking = { signMessageRequested: jest.fn(), signMessageSuccess: jest.fn(), signMessageFail: jest.fn(), signMessageNoParams: jest.fn(), signMessageUserRefused: jest.fn(), signTransactionRequested: jest.fn(), signTransactionSuccess: jest.fn(), signTransactionFail: jest.fn(), signTransactionNoParams: jest.fn(), signTransactionAndBroadcastNoParams: jest.fn(), broadcastSuccess: jest.fn(), broadcastFail: jest.fn(), broadcastOperationDetailsClick: jest.fn(), }; const mockManifest = { id: "test-manifest", name: "Test App", url: "https://test.app", homepageUrl: "https://test.app", icon: "data:image/png;base64,test", apiVersion: "1.0.0", permissions: [], domains: [], categories: [], platforms: ["desktop", "ios"], manifestVersion: "1.0.0", branch: "stable", currencies: [], visibility: "complete", content: { shortDescription: { en: "Test app" }, description: { en: "Test app description" }, }, }; const mockEthereumCurrency = { type: "CryptoCurrency", id: "ethereum", coinType: 60, name: "Ethereum", managerAppName: "Ethereum", ticker: "ETH", scheme: "ethereum", color: "#0ebdcd", symbol: "Ξ", family: "evm", blockAvgTime: 15, units: [ { name: "ether", code: "ETH", magnitude: 18, }, ], keywords: ["eth", "ethereum"], explorerViews: [], explorerId: "eth", }; const mockTokenCurrency = { type: "TokenCurrency", id: "ethereum/erc20/acre_btc", contractAddress: "0x1234567890123456789012345678901234567890", parentCurrency: mockEthereumCurrency, tokenType: "erc20", name: "ACRE Bitcoin", ticker: "acreBTC", units: [ { name: "ACRE Bitcoin", code: "acreBTC", magnitude: 8, }, ], }; const mockAccount = { type: "Account", id: "js:2:ethereum:0x1234567890123456789012345678901234567890:ethereum", seedIdentifier: "0x1234567890123456789012345678901234567890", derivationMode: "", index: 0, freshAddress: "0x1234567890123456789012345678901234567890", freshAddressPath: "44'/60'/0'/0/0", used: false, blockHeight: 0, creationDate: new Date(), balance: new bignumber_js_1.default(0), spendableBalance: new bignumber_js_1.default(0), operationsCount: 0, operations: [], pendingOperations: [], currency: mockEthereumCurrency, lastSyncDate: new Date(), swapHistory: [], balanceHistoryCache: { HOUR: { latestDate: null, balances: [] }, DAY: { latestDate: null, balances: [] }, WEEK: { latestDate: null, balances: [] }, }, syncHash: "0x00000000", subAccounts: [], nfts: [], }; jest.mock("@ledgerhq/coin-framework/account/index", () => ({ getParentAccount: jest.fn(), getMainAccount: jest.fn(), makeEmptyTokenAccount: jest.fn(), isTokenAccount: jest.fn(), })); describe("ACRE Server Handlers", () => { let mockUiHooks; let serverHandlers; beforeEach(() => { jest.clearAllMocks(); mockUiHooks = { "custom.acre.messageSign": jest.fn().mockImplementation(({ onSuccess }) => { onSuccess("0x1234567890abcdef"); }), "custom.acre.transactionSign": jest.fn().mockImplementation(({ onSuccess }) => { onSuccess({ operation: { hash: "0x1234567890abcdef" }, signature: "0xabcdef1234567890", }); }), "custom.acre.transactionBroadcast": jest.fn(), "custom.acre.registerAccount": jest.fn().mockImplementation(({ onSuccess }) => { onSuccess(); }), }; // Mock the cryptoassets functions globally const { findTokenByAddressInCurrency, getCryptoCurrencyById } = jest.requireMock("@ledgerhq/cryptoassets"); findTokenByAddressInCurrency.mockReturnValue(mockTokenCurrency); getCryptoCurrencyById.mockReturnValue(mockEthereumCurrency); // Mock the account functions const { makeEmptyTokenAccount, getMainAccount, getParentAccount } = jest.requireMock("@ledgerhq/coin-framework/account/index"); makeEmptyTokenAccount.mockReturnValue({ type: "TokenAccount", id: "mock-token-account-id", parentId: "mock-parent-id", token: mockTokenCurrency, balance: new bignumber_js_1.default(0), spendableBalance: new bignumber_js_1.default(0), creationDate: new Date(), operationsCount: 0, operations: [], pendingOperations: [], balanceHistoryCache: { HOUR: { latestDate: null, balances: [] }, DAY: { latestDate: null, balances: [] }, WEEK: { latestDate: null, balances: [] }, }, swapHistory: [], }); getMainAccount.mockReturnValue(mockAccount); getParentAccount.mockReturnValue(undefined); // Mock the converter functions const { getAccountIdFromWalletAccountId, getWalletAPITransactionSignFlowInfos } = jest.requireMock("../converters"); getAccountIdFromWalletAccountId.mockReturnValue("js:2:ethereum:0x1234567890123456789012345678901234567890:ethereum"); getWalletAPITransactionSignFlowInfos.mockReturnValue({ canEditFees: true, liveTx: { family: "evm" }, hasFeesProvided: true, }); // Mock the bridge const { getAccountBridge } = jest.requireMock("../../bridge"); getAccountBridge.mockReturnValue({ broadcast: jest.fn().mockResolvedValue({ hash: "0x1234567890abcdef" }), }); serverHandlers = (0, server_1.handlers)({ accounts: [mockAccount], tracking: mockTracking, manifest: mockManifest, uiHooks: mockUiHooks, }); }); describe("custom.acre.messageSign", () => { it("should handle message signing request successfully", async () => { const mockParams = { accountId: "test-account-id", message: { type: "SignIn", message: "test" }, derivationPath: "0/0", options: { hwAppId: "test-app" }, meta: { test: "data" }, }; const mockSignature = "0x1234567890abcdef"; mockUiHooks["custom.acre.messageSign"].mockImplementation(({ onSuccess }) => { onSuccess(mockSignature); }); const result = await serverHandlers["custom.acre.messageSign"](mockParams); expect(result).toEqual({ hexSignedMessage: "1234567890abcdef", }); expect(mockTracking.signMessageRequested).toHaveBeenCalledWith(mockManifest); expect(mockTracking.signMessageSuccess).toHaveBeenCalledWith(mockManifest); }); it("should handle missing parameters", async () => { const result = await serverHandlers["custom.acre.messageSign"](null); expect(result).toEqual({ hexSignedMessage: "", }); expect(mockTracking.signMessageNoParams).toHaveBeenCalledWith(mockManifest); }); it("should handle account not found", async () => { const mockParams = { accountId: "non-existent-account", message: { type: "SignIn", message: "test" }, derivationPath: "0/0", options: {}, meta: {}, }; // Mock getAccountIdFromWalletAccountId to return null for non-existent account const { getAccountIdFromWalletAccountId } = jest.requireMock("../converters"); getAccountIdFromWalletAccountId.mockReturnValueOnce(null); await expect(serverHandlers["custom.acre.messageSign"](mockParams)).rejects.toThrow("accountId non-existent-account unknown"); }); }); describe("custom.acre.transactionSign", () => { it("should handle transaction signing request successfully", async () => { const mockParams = { accountId: "test-account-id", rawTransaction: "0x1234567890abcdef", options: { hwAppId: "test-app" }, meta: { test: "data" }, tokenCurrency: "ethereum/erc20/test", }; const mockSignedOperation = { operation: { hash: "0x1234567890abcdef" }, signature: "0xabcdef1234567890", }; mockUiHooks["custom.acre.transactionSign"].mockImplementation(({ onSuccess }) => { onSuccess(mockSignedOperation); }); const result = await serverHandlers["custom.acre.transactionSign"](mockParams); expect(result).toEqual({ signedTransactionHex: "307861626364656631323334353637383930", }); expect(mockTracking.signTransactionRequested).toHaveBeenCalledWith(mockManifest); expect(mockTracking.signTransactionSuccess).toHaveBeenCalledWith(mockManifest); }); it("should handle missing parameters", async () => { const result = await serverHandlers["custom.acre.transactionSign"](null); expect(result).toEqual({ signedTransactionHex: "", }); expect(mockTracking.signTransactionNoParams).toHaveBeenCalledWith(mockManifest); }); }); describe("custom.acre.transactionSignAndBroadcast", () => { it("should handle transaction sign and broadcast successfully", async () => { const mockParams = { accountId: "test-account-id", rawTransaction: "0x1234567890abcdef", options: { hwAppId: "test-app" }, meta: { test: "data" }, tokenCurrency: "ethereum/erc20/test", }; const mockSignedOperation = { operation: { hash: "0x1234567890abcdef" }, signature: "0xabcdef1234567890", }; mockUiHooks["custom.acre.transactionSign"].mockImplementation(({ onSuccess }) => { onSuccess(mockSignedOperation); }); const result = await serverHandlers["custom.acre.transactionSignAndBroadcast"](mockParams); expect(result).toEqual({ transactionHash: "0x1234567890abcdef", }); }); it("should handle missing parameters", async () => { const result = await serverHandlers["custom.acre.transactionSignAndBroadcast"](null); expect(result).toEqual({ transactionHash: "", }); expect(mockTracking.signTransactionAndBroadcastNoParams).toHaveBeenCalledWith(mockManifest); }); }); describe("custom.acre.registerYieldBearingEthereumAddress", () => { const mockParams = { ethereumAddress: "0x9876543210987654321098765432109876543210", tokenContractAddress: "0x1234567890123456789012345678901234567890", meta: { test: "data" }, }; it("should register new yield-bearing Ethereum address successfully on mobile", async () => { const result = await serverHandlers["custom.acre.registerYieldBearingEthereumAddress"](mockParams); expect(result.success).toBe(true); expect(result.accountName).toBe("Yield-bearing BTC on ACRE"); expect(result.ethereumAddress).toBe(mockParams.ethereumAddress); expect(result.tokenContractAddress).toBe(mockParams.tokenContractAddress); // Verify UI hook was called with correct parameters expect(mockUiHooks["custom.acre.registerAccount"]).toHaveBeenCalledWith({ parentAccount: expect.objectContaining({ type: "Account", id: expect.stringContaining("0x9876543210987654321098765432109876543210"), }), accountName: "Yield-bearing BTC on ACRE", existingAccounts: [mockAccount], onSuccess: expect.any(Function), onError: expect.any(Function), }); }); it("should register new yield-bearing Ethereum address successfully on desktop", async () => { const desktopServerHandlers = (0, server_1.handlers)({ accounts: [mockAccount], tracking: mockTracking, manifest: mockManifest, uiHooks: mockUiHooks, }); const result = await desktopServerHandlers["custom.acre.registerYieldBearingEthereumAddress"](mockParams); expect(result.success).toBe(true); expect(mockUiHooks["custom.acre.registerAccount"]).toHaveBeenCalledWith({ parentAccount: expect.objectContaining({ type: "Account", id: expect.stringContaining("0x9876543210987654321098765432109876543210"), }), accountName: "Yield-bearing BTC on ACRE", existingAccounts: [mockAccount], onSuccess: expect.any(Function), onError: expect.any(Function), }); }); it("should handle existing account gracefully", async () => { const existingAccount = { ...mockAccount, freshAddress: mockParams.ethereumAddress, }; const serverHandlersWithExisting = (0, server_1.handlers)({ accounts: [existingAccount], tracking: mockTracking, manifest: mockManifest, uiHooks: mockUiHooks, }); const result = await serverHandlersWithExisting["custom.acre.registerYieldBearingEthereumAddress"](mockParams); expect(result.success).toBe(true); expect(result.parentAccountId).toBe(existingAccount.id); expect(result.tokenAccountId).toBe(existingAccount.id); expect(mockUiHooks["custom.acre.registerAccount"]).not.toHaveBeenCalled(); }); it("should handle missing UI hook", async () => { const serverHandlersWithoutHook = (0, server_1.handlers)({ accounts: [mockAccount], tracking: mockTracking, manifest: mockManifest, uiHooks: { ...mockUiHooks, "custom.acre.registerAccount": undefined, }, }); await expect(serverHandlersWithoutHook["custom.acre.registerYieldBearingEthereumAddress"](mockParams)).rejects.toThrow("No account registration UI hook available"); }); it("should validate Ethereum address format", async () => { const invalidParams = { ...mockParams, ethereumAddress: "invalid-address", }; await expect(serverHandlers["custom.acre.registerYieldBearingEthereumAddress"](invalidParams)).rejects.toThrow("Invalid Ethereum address format"); }); it("should require either tokenContractAddress or tokenTicker", async () => { const invalidParams = { ethereumAddress: mockParams.ethereumAddress, meta: mockParams.meta, }; await expect(serverHandlers["custom.acre.registerYieldBearingEthereumAddress"](invalidParams)).rejects.toThrow("Either tokenContractAddress or tokenTicker must be provided"); }); }); }); //# sourceMappingURL=server.test.js.map