Skip to content

Commit 6e5dcfb

Browse files
committed
sdk: added unit tests for axelar utils
1 parent 96619a6 commit 6e5dcfb

File tree

1 file changed

+318
-0
lines changed

1 file changed

+318
-0
lines changed

evm/ts/__tests__/axelar.test.ts

Lines changed: 318 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,318 @@
1+
import {
2+
getAxelarApiUrl,
3+
getAxelarChain,
4+
parseGMPStatus,
5+
parseGMPError,
6+
getAxelarGasFee,
7+
getAxelarTransactionStatus,
8+
GMPStatus,
9+
} from "../src/axelar.js";
10+
11+
describe("Axelar Utility Functions", () => {
12+
describe("getAxelarApiUrl", () => {
13+
it("should return mainnet API URL for Mainnet network", () => {
14+
const url = getAxelarApiUrl("Mainnet");
15+
expect(url).toBe("https://api.axelarscan.io");
16+
});
17+
18+
it("should return testnet API URL for Testnet network", () => {
19+
const url = getAxelarApiUrl("Testnet");
20+
expect(url).toBe("https://testnet.api.axelarscan.io");
21+
});
22+
});
23+
24+
describe("getAxelarChain", () => {
25+
it("should return axelar chain name for Ethereum", () => {
26+
const chain = getAxelarChain("Ethereum");
27+
expect(chain).toBe("ethereum");
28+
});
29+
30+
it("should return axelar chain name for Monad", () => {
31+
const chain = getAxelarChain("Monad");
32+
expect(chain).toBe("monad");
33+
});
34+
35+
it("should return axelar chain name for Sepolia", () => {
36+
const chain = getAxelarChain("Sepolia");
37+
expect(chain).toBe("ethereum-sepolia");
38+
});
39+
40+
it("should throw error for unsupported chain", () => {
41+
expect(() => getAxelarChain("Solana" as any)).toThrow(
42+
"Unsupported axelar chain: Solana"
43+
);
44+
});
45+
});
46+
47+
describe("parseGMPStatus", () => {
48+
it("should parse error status", () => {
49+
const response = { status: "error", error: { message: "some error" } };
50+
const status = parseGMPStatus(response);
51+
expect(status).toBe(GMPStatus.DEST_EXECUTE_ERROR);
52+
});
53+
54+
it("should parse executed status", () => {
55+
const response = { status: "executed" };
56+
const status = parseGMPStatus(response);
57+
expect(status).toBe(GMPStatus.DEST_EXECUTED);
58+
});
59+
60+
it("should parse called status", () => {
61+
const response = { status: "called" };
62+
const status = parseGMPStatus(response);
63+
expect(status).toBe(GMPStatus.SRC_GATEWAY_CALLED);
64+
});
65+
66+
it("should parse executing status", () => {
67+
const response = { status: "executing" };
68+
const status = parseGMPStatus(response);
69+
expect(status).toBe(GMPStatus.DEST_EXECUTING);
70+
});
71+
});
72+
73+
describe("parseGMPError", () => {
74+
it("should parse error from response", () => {
75+
const response = {
76+
error: {
77+
error: { message: "execution failed" },
78+
sourceTransactionHash: "0xabc123",
79+
chain: "ethereum",
80+
},
81+
};
82+
const error = parseGMPError(response);
83+
expect(error).toEqual({
84+
message: "execution failed",
85+
txHash: "0xabc123",
86+
chain: "ethereum",
87+
});
88+
});
89+
90+
it("should parse insufficient fee error", () => {
91+
const response = {
92+
is_insufficient_fee: true,
93+
call: {
94+
transaction: { hash: "0xdef456" },
95+
chain: "monad",
96+
},
97+
};
98+
const error = parseGMPError(response);
99+
expect(error).toEqual({
100+
message: "Insufficient gas",
101+
txHash: "0xdef456",
102+
chain: "monad",
103+
});
104+
});
105+
106+
it("should return undefined when no error", () => {
107+
const response = { status: "executed" };
108+
const error = parseGMPError(response);
109+
expect(error).toBeUndefined();
110+
});
111+
});
112+
113+
describe("getAxelarGasFee", () => {
114+
const originalFetch = global.fetch;
115+
let mockFetch: jest.Mock;
116+
117+
beforeEach(() => {
118+
mockFetch = jest.fn() as jest.Mock;
119+
global.fetch = mockFetch as any;
120+
});
121+
122+
afterEach(() => {
123+
global.fetch = originalFetch;
124+
});
125+
126+
it("should fetch and return gas fee", async () => {
127+
const mockResponse = {
128+
ok: true,
129+
json: async () => "1000000000000000",
130+
};
131+
mockFetch.mockResolvedValue(mockResponse);
132+
133+
const fee = await getAxelarGasFee("Testnet", "Sepolia", "Monad", 500000n);
134+
expect(fee).toBe(1000000000000000n);
135+
expect(global.fetch).toHaveBeenCalledWith(
136+
"https://testnet.api.axelarscan.io/gmp/estimateGasFee",
137+
expect.objectContaining({
138+
method: "POST",
139+
headers: { "Content-Type": "application/json" },
140+
body: JSON.stringify({
141+
sourceChain: "ethereum-sepolia",
142+
destinationChain: "monad",
143+
gasMultiplier: "auto",
144+
gasLimit: "500000",
145+
}),
146+
})
147+
);
148+
});
149+
150+
it("should return minimum fee of 1 when API returns 0", async () => {
151+
const mockResponse = {
152+
ok: true,
153+
json: async () => "0",
154+
};
155+
mockFetch.mockResolvedValue(mockResponse);
156+
157+
const fee = await getAxelarGasFee("Testnet", "Sepolia", "Monad", 500000n);
158+
expect(fee).toBe(1n);
159+
});
160+
161+
it("should throw error when API call fails", async () => {
162+
const mockResponse = {
163+
ok: false,
164+
status: 500,
165+
text: async () => "Internal server error",
166+
};
167+
mockFetch.mockResolvedValue(mockResponse);
168+
169+
await expect(
170+
getAxelarGasFee("Testnet", "Sepolia", "Monad", 500000n)
171+
).rejects.toThrow("Failed to estimate gas fee: 500");
172+
});
173+
174+
it("should throw error when request times out", async () => {
175+
mockFetch.mockImplementation(() => {
176+
return new Promise((_, reject) => {
177+
setTimeout(() => reject(new Error("Timeout")), 100);
178+
});
179+
});
180+
181+
await expect(
182+
getAxelarGasFee("Testnet", "Sepolia", "Monad", 500000n, 50)
183+
).rejects.toThrow("Timeout");
184+
});
185+
186+
it("should use mainnet API for Mainnet network", async () => {
187+
const mockResponse = {
188+
ok: true,
189+
json: async () => "2000000000000000",
190+
};
191+
mockFetch.mockResolvedValue(mockResponse);
192+
193+
await getAxelarGasFee("Mainnet", "Ethereum", "Monad", 500000n);
194+
expect(global.fetch).toHaveBeenCalledWith(
195+
"https://api.axelarscan.io/gmp/estimateGasFee",
196+
expect.any(Object)
197+
);
198+
});
199+
});
200+
201+
describe("getAxelarTransactionStatus", () => {
202+
const originalFetch = global.fetch;
203+
let mockFetch: jest.Mock;
204+
205+
beforeEach(() => {
206+
mockFetch = jest.fn() as jest.Mock;
207+
global.fetch = mockFetch as any;
208+
});
209+
210+
afterEach(() => {
211+
global.fetch = originalFetch;
212+
});
213+
214+
it("should fetch and return transaction status", async () => {
215+
const mockResponse = {
216+
ok: true,
217+
json: async () => ({
218+
data: [
219+
{
220+
status: "executed",
221+
},
222+
],
223+
}),
224+
};
225+
mockFetch.mockResolvedValue(mockResponse);
226+
227+
const result = await getAxelarTransactionStatus(
228+
"Testnet",
229+
"Sepolia",
230+
"0xabc123"
231+
);
232+
expect(result.status).toBe(GMPStatus.DEST_EXECUTED);
233+
expect(result.error).toBeUndefined();
234+
expect(global.fetch).toHaveBeenCalledWith(
235+
"https://testnet.api.axelarscan.io/gmp/searchGMP",
236+
expect.objectContaining({
237+
method: "POST",
238+
headers: { "Content-Type": "application/json" },
239+
body: JSON.stringify({
240+
sourceChain: "ethereum-sepolia",
241+
txHash: "0xabc123",
242+
}),
243+
})
244+
);
245+
});
246+
247+
it("should return status with error when present", async () => {
248+
const mockResponse = {
249+
ok: true,
250+
json: async () => ({
251+
data: [
252+
{
253+
status: "error",
254+
error: {
255+
error: { message: "execution reverted" },
256+
sourceTransactionHash: "0xabc123",
257+
chain: "ethereum",
258+
},
259+
},
260+
],
261+
}),
262+
};
263+
mockFetch.mockResolvedValue(mockResponse);
264+
265+
const result = await getAxelarTransactionStatus(
266+
"Testnet",
267+
"Sepolia",
268+
"0xabc123"
269+
);
270+
expect(result.status).toBe(GMPStatus.DEST_EXECUTE_ERROR);
271+
expect(result.error).toEqual({
272+
message: "execution reverted",
273+
txHash: "0xabc123",
274+
chain: "ethereum",
275+
});
276+
});
277+
278+
it("should throw error when no transaction details found", async () => {
279+
const mockResponse = {
280+
ok: true,
281+
json: async () => ({
282+
data: [],
283+
}),
284+
};
285+
mockFetch.mockResolvedValue(mockResponse);
286+
287+
await expect(
288+
getAxelarTransactionStatus("Testnet", "Sepolia", "0xabc123")
289+
).rejects.toThrow("No transaction details found");
290+
});
291+
292+
it("should throw error when API call fails", async () => {
293+
const mockResponse = {
294+
ok: false,
295+
status: 500,
296+
text: async () => "Internal server error",
297+
};
298+
mockFetch.mockResolvedValue(mockResponse);
299+
300+
await expect(
301+
getAxelarTransactionStatus("Testnet", "Sepolia", "0xabc123")
302+
).rejects.toThrow("Failed to get transaction status: 500");
303+
});
304+
305+
it("should throw error when request times out", async () => {
306+
mockFetch.mockImplementation(
307+
() =>
308+
new Promise((_, reject) => {
309+
setTimeout(() => reject(new Error("Timeout")), 100);
310+
})
311+
);
312+
313+
await expect(
314+
getAxelarTransactionStatus("Testnet", "Sepolia", "0xabc123", 50)
315+
).rejects.toThrow();
316+
});
317+
});
318+
});

0 commit comments

Comments
 (0)