@ledgerhq/coin-casper
Version:
Ledger Casper integration
274 lines • 12.6 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const logs_1 = require("@ledgerhq/logs");
const bignumber_js_1 = __importDefault(require("bignumber.js"));
const live_network_1 = __importDefault(require("@ledgerhq/live-network"));
const casper_js_sdk_1 = require("casper-js-sdk");
const _1 = require(".");
const config_1 = require("../config");
const fixtures_1 = require("../test/fixtures");
const consts_1 = require("../consts");
// Constants
const MOCK_NODE_URL = "https://mock.casper.node";
const MOCK_INDEXER_URL = "https://mock.casper.indexer";
const MOCK_PUBLIC_KEY = fixtures_1.TEST_ADDRESSES.SECP256K1;
const MOCK_PURSE_UREF = "uref-1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef-007";
const MOCK_ACCOUNT_HASH = "account-hash-1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef";
const DEFAULT_LIMIT = 100;
// Mock dependencies
jest.mock("@ledgerhq/logs", () => ({
log: jest.fn(),
}));
jest.mock("@ledgerhq/live-network", () => ({
__esModule: true,
default: jest.fn(),
}));
jest.mock("../config", () => ({
getCoinConfig: jest.fn(),
}));
// Helper functions for creating mocks
const createMockRpcClient = (methodOverrides) => {
const defaultMethods = {
getAccountInfo: jest.fn(),
getStateRootHashLatest: jest.fn(),
getBalanceByStateRootHash: jest.fn(),
getLatestBlock: jest.fn(),
putTransaction: jest.fn(),
};
// Create a combined mock with defaultMethods overridden by methodOverrides
const combinedMock = { ...defaultMethods, ...methodOverrides };
// Mock the RpcClient implementation to return our mock methods
jest.mocked(casper_js_sdk_1.RpcClient).mockImplementation(() => combinedMock);
return combinedMock;
};
const createNetworkMock = (responseData, pageCount = 1, itemCount = responseData.length) => ({
data: {
data: responseData,
pageCount,
itemCount,
pages: [],
},
status: 200,
});
// Mock casper-js-sdk
jest.mock("casper-js-sdk", () => {
const originalModule = jest.requireActual("casper-js-sdk");
return {
...originalModule,
RpcClient: jest.fn(),
HttpHandler: jest.fn(),
AccountIdentifier: jest.fn(),
PublicKey: {
fromHex: jest.fn(),
},
};
});
describe("Casper API Unit Tests", () => {
beforeEach(() => {
jest.clearAllMocks();
// Setup config mock
jest.mocked(config_1.getCoinConfig).mockReturnValue({
infra: {
API_CASPER_NODE_ENDPOINT: MOCK_NODE_URL,
API_CASPER_INDEXER: MOCK_INDEXER_URL,
},
});
});
describe("getCasperNodeRpcClient", () => {
it("should create RPC client with correct URL", () => {
(0, _1.getCasperNodeRpcClient)();
expect(casper_js_sdk_1.HttpHandler).toHaveBeenCalledWith(MOCK_NODE_URL);
expect(casper_js_sdk_1.RpcClient).toHaveBeenCalled();
});
it("should throw error if API base URL is not available", () => {
jest.mocked(config_1.getCoinConfig).mockReturnValueOnce({
infra: {},
});
expect(() => (0, _1.getCasperNodeRpcClient)()).toThrow("API base URL not available");
});
});
describe("fetchAccountStateInfo", () => {
it("should fetch account state info successfully", async () => {
// Mock PublicKey
const mockPublicKeyInstance = { fromHex: () => ({ fromHex: true }) };
jest.mocked(casper_js_sdk_1.PublicKey.fromHex).mockReturnValue(mockPublicKeyInstance);
// Mock AccountIdentifier
const mockAccountIdentifier = {};
jest
.mocked(casper_js_sdk_1.AccountIdentifier)
.mockReturnValue(mockAccountIdentifier);
// Mock RpcClient.getAccountInfo
const mockGetAccountInfo = jest.fn().mockResolvedValue({
account: {
accountHash: { toHex: () => MOCK_ACCOUNT_HASH },
mainPurse: { toPrefixedString: () => MOCK_PURSE_UREF },
},
});
createMockRpcClient({
getAccountInfo: mockGetAccountInfo,
});
const result = await (0, _1.fetchAccountStateInfo)(MOCK_PUBLIC_KEY);
expect(casper_js_sdk_1.PublicKey.fromHex).toHaveBeenCalledWith(MOCK_PUBLIC_KEY);
expect(casper_js_sdk_1.AccountIdentifier).toHaveBeenCalledWith(undefined, mockPublicKeyInstance);
expect(mockGetAccountInfo).toHaveBeenCalledWith(null, mockAccountIdentifier);
expect(result).toEqual({
purseUref: MOCK_PURSE_UREF,
accountHash: MOCK_ACCOUNT_HASH,
});
});
it("should return undefined values when account not found", async () => {
// Mock error with account not found status code
const mockError = new Error("Account not found");
mockError.statusCode = consts_1.NodeErrorCodeAccountNotFound;
createMockRpcClient({
getAccountInfo: jest.fn().mockRejectedValue(mockError),
});
const result = await (0, _1.fetchAccountStateInfo)(MOCK_PUBLIC_KEY);
expect(result).toEqual({
purseUref: undefined,
accountHash: undefined,
});
});
it("should throw error for other error cases", async () => {
const mockError = new Error("General error");
createMockRpcClient({
getAccountInfo: jest.fn().mockRejectedValue(mockError),
});
await expect((0, _1.fetchAccountStateInfo)(MOCK_PUBLIC_KEY)).rejects.toThrow("General error");
});
});
describe("fetchBalance", () => {
it("should fetch balance successfully", async () => {
const mockBalanceValue = "10000000000";
const mockRootHash = "mocked-root-hash";
// Create mock methods with implementations that return expected values
const mockGetStateRootHashLatest = jest.fn().mockResolvedValue({
stateRootHash: { toHex: () => mockRootHash },
});
const mockGetBalanceByStateRootHash = jest.fn().mockResolvedValue({
balanceValue: mockBalanceValue,
});
// Create the mock RPC client with our implementations
const mockMethods = createMockRpcClient({
getStateRootHashLatest: mockGetStateRootHashLatest,
getBalanceByStateRootHash: mockGetBalanceByStateRootHash,
});
const result = await (0, _1.fetchBalance)(MOCK_PURSE_UREF);
expect(mockMethods.getStateRootHashLatest).toHaveBeenCalled();
expect(mockMethods.getBalanceByStateRootHash).toHaveBeenCalledWith(MOCK_PURSE_UREF, mockRootHash);
expect(result).toEqual(new bignumber_js_1.default(mockBalanceValue));
});
it("should throw error when balance fetch fails", async () => {
const mockError = new Error("Failed to fetch balance");
createMockRpcClient({
getStateRootHashLatest: jest.fn().mockRejectedValue(mockError),
});
await expect((0, _1.fetchBalance)(MOCK_PURSE_UREF)).rejects.toThrow("Failed to fetch balance");
expect(logs_1.log).toHaveBeenCalledWith("error", "Failed to fetch balance", mockError);
});
});
describe("fetchBlockHeight", () => {
it("should fetch block height successfully", async () => {
const mockHeight = 12345;
createMockRpcClient({
getLatestBlock: jest.fn().mockResolvedValue({
block: { height: mockHeight },
}),
});
const result = await (0, _1.fetchBlockHeight)();
expect(result).toBe(mockHeight);
});
it("should throw error when block height fetch fails", async () => {
const mockError = new Error("Failed to fetch block height");
createMockRpcClient({
getLatestBlock: jest.fn().mockRejectedValue(mockError),
});
await expect((0, _1.fetchBlockHeight)()).rejects.toThrow("Failed to fetch block height");
expect(logs_1.log).toHaveBeenCalledWith("error", "Failed to fetch block height", mockError);
});
});
describe("fetchTxs", () => {
const createMockTxData = (hash) => ({
deploy_hash: hash,
block_hash: "block-hash-1",
caller_public_key: MOCK_PUBLIC_KEY,
execution_type_id: 1,
cost: "10000",
payment_amount: "100000000",
timestamp: "2023-01-01T12:00:00Z",
status: "success",
args: {
id: {
parsed: 12345,
cl_type: {
Option: "U64",
},
},
amount: {
parsed: "500000000",
cl_type: "U512",
},
target: {
parsed: fixtures_1.TEST_ADDRESSES.RECIPIENT_SECP256K1,
cl_type: "PublicKey",
},
},
amount: "500000000",
});
const mockTxData = [createMockTxData("deploy-hash-1")];
const getExpectedNetworkCall = (page) => ({
method: "GET",
url: `${MOCK_INDEXER_URL}/accounts/${MOCK_PUBLIC_KEY}/ledgerlive-deploys?limit=${DEFAULT_LIMIT}&page=${page}`,
});
it("should fetch transactions successfully (single page)", async () => {
jest.mocked(live_network_1.default).mockResolvedValueOnce(createNetworkMock(mockTxData));
const result = await (0, _1.fetchTxs)(MOCK_PUBLIC_KEY);
expect(live_network_1.default).toHaveBeenCalledWith(getExpectedNetworkCall(1));
expect(result).toEqual(mockTxData);
});
it("should fetch transactions successfully (multiple pages)", async () => {
// Mock first page
jest.mocked(live_network_1.default).mockResolvedValueOnce(createNetworkMock([mockTxData[0]], 2, 2));
// Mock second page
const secondPageTx = createMockTxData("deploy-hash-2");
jest.mocked(live_network_1.default).mockResolvedValueOnce(createNetworkMock([secondPageTx], 2, 2));
const result = await (0, _1.fetchTxs)(MOCK_PUBLIC_KEY);
expect(live_network_1.default).toHaveBeenCalledTimes(2);
expect(live_network_1.default).toHaveBeenNthCalledWith(1, getExpectedNetworkCall(1));
expect(live_network_1.default).toHaveBeenNthCalledWith(2, getExpectedNetworkCall(2));
expect(result).toEqual([mockTxData[0], secondPageTx]);
});
it("should throw error when transactions fetch fails", async () => {
const mockError = new Error("Failed to fetch transactions");
jest.mocked(live_network_1.default).mockRejectedValueOnce(mockError);
await expect((0, _1.fetchTxs)(MOCK_PUBLIC_KEY)).rejects.toThrow("Failed to fetch transactions");
expect(logs_1.log).toHaveBeenCalledWith("error", "Casper indexer error: ", mockError);
});
});
describe("broadcastTx", () => {
it("should broadcast transaction successfully", async () => {
const mockTransaction = {};
const mockTxHash = "0123456789abcdef";
createMockRpcClient({
putTransaction: jest.fn().mockResolvedValue({
transactionHash: { toHex: () => mockTxHash },
}),
});
const result = await (0, _1.broadcastTx)(mockTransaction);
expect(result).toBe(mockTxHash);
});
it("should throw error when broadcast fails", async () => {
const mockTransaction = {};
const mockError = new Error("Failed to broadcast transaction");
createMockRpcClient({
putTransaction: jest.fn().mockRejectedValue(mockError),
});
await expect((0, _1.broadcastTx)(mockTransaction)).rejects.toThrow("Failed to broadcast transaction");
expect(logs_1.log).toHaveBeenCalledWith("error", "Failed to broadcast transaction", mockError);
});
});
});
//# sourceMappingURL=index.test.js.map