import { GelatoRelay, TaskState } from "@gelatonetwork/relay-sdk" import { BaseRelayParams } from "@gelatonetwork/relay-sdk/dist/lib/types" import relayTransaction from "./relayTransaction" jest.mock("@gelatonetwork/relay-sdk", () => ({ GelatoRelay: jest.fn().mockImplementation(() => ({ sponsoredCall: jest.fn(), getTaskStatus: jest.fn(), })), TaskState: { ExecSuccess: "ExecSuccess", ExecReverted: "ExecReverted", Cancelled: "Cancelled", CheckPending: "CheckPending", }, })) describe("relayTransaction", () => { const taskId = "fakeTaskId" const transactionHash = "fakeTransactionHash" const backoffOptions = { retries: 3, backoffStepMs: 1000 } let relay: GelatoRelay let relayApiKey: string let request: BaseRelayParams beforeAll(() => { relay = new GelatoRelay() relayApiKey = "fakeApiKey" request = { chainId: 1n, data: "0x00", target: "0x" } as BaseRelayParams jest.spyOn(relay, "sponsoredCall").mockResolvedValue({ taskId }) }) describe("when the sponsored call was executed successfully", () => { let result: { transactionHash: string | undefined } beforeAll(async () => { jest .spyOn(relay, "getTaskStatus") // @ts-expect-error We want to mock only used fields in our code. .mockResolvedValueOnce({ taskState: TaskState.CheckPending }) // @ts-expect-error We want to mock only used fields in our code. .mockResolvedValueOnce({ taskState: TaskState.ExecSuccess, transactionHash, }) result = await relayTransaction( relay, relayApiKey, request, backoffOptions, ) }) it("should call sponsored call", () => { expect(relay.sponsoredCall).toHaveBeenCalledWith(request, relayApiKey) }) it("should ask for the task status twice", () => { expect(relay.getTaskStatus).toHaveBeenNthCalledWith(1, taskId) expect(relay.getTaskStatus).toHaveBeenNthCalledWith(2, taskId) }) it("should return transaction hash", () => { expect(result).toEqual({ transactionHash }) }) }) describe.each([ { taskDescription: "reverted", taskState: TaskState.ExecReverted, lastCheckMessage: "Some error", }, { taskDescription: "cancelled", taskState: TaskState.Cancelled, lastCheckMessage: "Transaction was cancelled", }, ])( "when the sponsored call was $taskDescription", ({ taskState, lastCheckMessage }) => { beforeAll(async () => { jest .spyOn(relay, "getTaskStatus") // @ts-expect-error We want to mock only used fields in our code. .mockResolvedValueOnce({ taskState: TaskState.CheckPending }) // @ts-expect-error We want to mock only used fields in our code. .mockResolvedValueOnce({ taskState, transactionHash, lastCheckMessage, }) }) it("should throw an error", async () => { await expect( relayTransaction(relay, relayApiKey, request, backoffOptions), ).rejects.toThrow( `Relayed transaction failed with hash ${transactionHash}; ${lastCheckMessage}`, ) }) }, ) describe("when the task with a given ID is not yet available", () => { const error = new Error( "GelatoRelaySDK/getTaskStatus: Failed with error: Status not found", ) let result: Promise<{ transactionHash: string | undefined }> beforeAll(async () => { jest.useFakeTimers() jest.spyOn(relay, "sponsoredCall").mockResolvedValue({ taskId }) jest .spyOn(relay, "getTaskStatus") // First attempt. The task not funded yet. .mockRejectedValueOnce(error) // Second attempt. The task not funded yet. .mockRejectedValueOnce(error) // Third attempt. The task is available but is still pending. // @ts-expect-error We want to mock only used fields in our code. .mockResolvedValueOnce({ taskState: TaskState.CheckPending }) // Fourth attempt. The task is available and completed successfully. // @ts-expect-error We want to mock only used fields in our code. .mockResolvedValueOnce({ taskState: TaskState.ExecSuccess, transactionHash, }) result = relayTransaction(relay, relayApiKey, request, backoffOptions) await jest.runAllTimersAsync() }) it("should call sponsored call", () => { expect(relay.sponsoredCall).toHaveBeenCalledWith(request, relayApiKey) }) it("should try to get status multiple times", () => { expect(relay.getTaskStatus).toHaveBeenNthCalledWith(1, taskId) expect(relay.getTaskStatus).toHaveBeenNthCalledWith(2, taskId) expect(relay.getTaskStatus).toHaveBeenNthCalledWith(3, taskId) expect(relay.getTaskStatus).toHaveBeenNthCalledWith(4, taskId) }) it("should return transaction hash", async () => { expect(await result).toEqual({ transactionHash }) }) }) })