@ledgerhq/coin-canton
Version:
205 lines (171 loc) • 5.92 kB
text/typescript
import coinConfig from "../../config";
import { isGatewayEnabled } from "../../network/gateway";
import { getOperations } from "../../network/gateway";
import { createMockCantonCurrency, createMockCoinConfigValue } from "../../test/fixtures";
import type { OperationView } from "../../types/gateway";
import { listOperations } from "./listOperations";
jest.mock("../../network/gateway", () => ({
getOperations: jest.fn(),
isGatewayEnabled: jest.fn(() => true),
}));
jest.mock("../../config", () => ({
__esModule: true,
default: {
getCoinConfig: jest.fn(),
},
}));
const mockCurrency = createMockCantonCurrency();
const mockedGetOperations = jest.mocked(getOperations);
const mockedGetCoinConfig = jest.mocked(coinConfig.getCoinConfig);
const mockedIsGatewayEnabled = jest.mocked(isGatewayEnabled);
const feeAssetNative = {
type: "native" as const,
instrumentAdmin: "admin",
instrumentId: "Amulet",
};
function createMockOperationView(overrides: Partial<OperationView> = {}): OperationView {
return {
uid: "uid-1",
transaction_hash: "0xtxhash",
transaction_timestamp: "2024-01-01T00:00:00.000Z",
status: "Success",
type: "Send",
senders: ["alice-party"],
recipients: ["bob-party"],
transfers: [
{
address: "addr",
type: "Send",
value: "100",
details: {},
asset: "asset-ref",
},
],
block: {
height: 10,
time: "2024-01-01T00:00:00.000Z",
hash: "blockhash",
},
fee: {
value: "1",
asset: feeAssetNative,
details: {},
},
asset: { type: "native", instrumentAdmin: "admin", instrumentId: "Amulet" },
details: {},
...overrides,
};
}
/** Omits optional fields (exactOptionalPropertyTypes forbids passing `undefined` in overrides). */
function mockOperationViewWithoutAsset(): OperationView {
const { asset: _a, ...rest } = createMockOperationView();
return rest;
}
function mockOperationViewWithoutTransfers(): OperationView {
const { transfers: _t, ...rest } = createMockOperationView();
return rest;
}
describe("listOperations", () => {
beforeEach(() => {
jest.clearAllMocks();
mockedIsGatewayEnabled.mockReturnValue(true);
mockedGetCoinConfig.mockReturnValue(createMockCoinConfigValue());
});
it("should throw when gateway is disabled in config", async () => {
mockedIsGatewayEnabled.mockReturnValue(false);
await expect(listOperations(mockCurrency, "alice-party", 0)).rejects.toThrow("Not implemented");
expect(mockedGetOperations).not.toHaveBeenCalled();
});
it("maps gateway native asset to AssetInfo with native instrument id (Amulet)", async () => {
mockedGetOperations.mockResolvedValue({
next: 0,
operations: [
createMockOperationView({
asset: { type: "native", instrumentAdmin: "admin", instrumentId: "Amulet" },
}),
],
});
const page = await listOperations(mockCurrency, "alice-party", 0);
expect(page.items).toHaveLength(1);
expect(page.items[0].asset).toEqual({ type: "Amulet" });
});
it("maps token asset to AssetInfo with assetReference and assetOwner", async () => {
mockedGetOperations.mockResolvedValue({
next: 0,
operations: [
createMockOperationView({
asset: {
type: "token",
instrumentAdmin: "token-admin",
instrumentId: "token-id",
},
}),
],
});
const page = await listOperations(mockCurrency, "alice-party", 0);
expect(page.items[0].asset).toEqual({
type: "token",
assetReference: "token-id",
assetOwner: "token-admin",
});
});
it("treats missing asset as native instrument (Amulet)", async () => {
mockedGetOperations.mockResolvedValue({
next: 0,
operations: [mockOperationViewWithoutAsset()],
});
const page = await listOperations(mockCurrency, "alice-party", 0);
expect(page.items[0].asset).toEqual({ type: "Amulet" });
});
it("uses empty string when block hash is missing", async () => {
mockedGetOperations.mockResolvedValue({
next: 0,
operations: [
createMockOperationView({
block: { height: 10, time: "2024-01-01T00:00:00.000Z" },
}),
],
});
const page = await listOperations(mockCurrency, "alice-party", 0);
expect(page.items[0].tx.hash).toBe("0xtxhash");
expect(page.items[0].tx.block.hash).toBe("");
});
it("returns OUT when partyId is among senders", async () => {
mockedGetOperations.mockResolvedValue({
next: 0,
operations: [createMockOperationView()],
});
const page = await listOperations(mockCurrency, "alice-party", 0);
expect(page.items[0].type).toBe("OUT");
});
it("returns IN when partyId is not among senders", async () => {
mockedGetOperations.mockResolvedValue({
next: 0,
operations: [createMockOperationView()],
});
const page = await listOperations(mockCurrency, "other-party", 0);
expect(page.items[0].type).toBe("IN");
});
it("skips operations that are not Send or lack transfers", async () => {
mockedGetOperations.mockResolvedValue({
next: 0,
operations: [
createMockOperationView({ type: "Receive" }),
createMockOperationView({ transfers: [] }),
mockOperationViewWithoutTransfers(),
],
});
const page = await listOperations(mockCurrency, "alice-party", 0);
expect(page.items).toHaveLength(0);
});
it("passes pagination options to getOperations", async () => {
mockedGetOperations.mockResolvedValue({ next: 5, operations: [] });
const page = await listOperations(mockCurrency, "alice-party", 3, "10", 20);
expect(mockedGetOperations).toHaveBeenCalledWith(mockCurrency, "alice-party", {
cursor: 10,
minOffset: 3,
limit: 20,
});
expect(page.next).toBe("5");
});
});