Skip to content

Commit 769e3bc

Browse files
committed
test(agentkit): add comprehensive tests for applyGasMultiplier and retryWithExponentialBackoff
1 parent e44e0e4 commit 769e3bc

1 file changed

Lines changed: 142 additions & 1 deletion

File tree

typescript/agentkit/src/utils.test.ts

Lines changed: 142 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { encodeFunctionData } from "viem";
22
import { EvmWalletProvider } from "./wallet-providers";
3-
import { approve } from "./utils";
3+
import { approve, applyGasMultiplier, retryWithExponentialBackoff } from "./utils";
44

55
const MOCK_TOKEN_ADDRESS = "0x1234567890123456789012345678901234567890";
66
const MOCK_SPENDER_ADDRESS = "0x9876543210987654321098765432109876543210";
@@ -67,4 +67,145 @@ describe("utils", () => {
6767
expect(response).toBe(`Error approving tokens: ${error}`);
6868
});
6969
});
70+
71+
describe("applyGasMultiplier", () => {
72+
it("should scale gas estimate by multiplier", () => {
73+
const gas = BigInt(21000);
74+
const multiplier = 1.2;
75+
const result = applyGasMultiplier(gas, multiplier);
76+
expect(result).toBe(BigInt(25200));
77+
});
78+
79+
it("should handle multiplier of 1 (no change)", () => {
80+
const gas = BigInt(100000);
81+
const result = applyGasMultiplier(gas, 1);
82+
expect(result).toBe(BigInt(100000));
83+
});
84+
85+
it("should handle multiplier less than 1", () => {
86+
const gas = BigInt(100000);
87+
const result = applyGasMultiplier(gas, 0.5);
88+
expect(result).toBe(BigInt(50000));
89+
});
90+
91+
it("should round to nearest integer", () => {
92+
const gas = BigInt(100);
93+
const multiplier = 1.5;
94+
const result = applyGasMultiplier(gas, multiplier);
95+
expect(result).toBe(BigInt(150));
96+
});
97+
98+
it("should handle very large gas values", () => {
99+
const gas = BigInt("1000000000000");
100+
const multiplier = 1.1;
101+
const result = applyGasMultiplier(gas, multiplier);
102+
expect(result).toBe(BigInt("1100000000000"));
103+
});
104+
105+
it("should handle zero gas", () => {
106+
const gas = BigInt(0);
107+
const result = applyGasMultiplier(gas, 1.5);
108+
expect(result).toBe(BigInt(0));
109+
});
110+
});
111+
112+
describe("retryWithExponentialBackoff", () => {
113+
beforeEach(() => {
114+
jest.useFakeTimers();
115+
});
116+
117+
afterEach(() => {
118+
jest.useRealTimers();
119+
});
120+
121+
it("should return result on first successful attempt", async () => {
122+
const fn = jest.fn().mockResolvedValue("success");
123+
const promise = retryWithExponentialBackoff(fn);
124+
const result = await promise;
125+
expect(result).toBe("success");
126+
expect(fn).toHaveBeenCalledTimes(1);
127+
});
128+
129+
it("should retry on failure and succeed eventually", async () => {
130+
const fn = jest
131+
.fn()
132+
.mockRejectedValueOnce(new Error("fail1"))
133+
.mockRejectedValueOnce(new Error("fail2"))
134+
.mockResolvedValue("success");
135+
136+
const promise = retryWithExponentialBackoff(fn, 3, 100, 0);
137+
138+
// First attempt fails immediately
139+
await jest.advanceTimersByTimeAsync(0);
140+
// Wait for first retry delay (100ms)
141+
await jest.advanceTimersByTimeAsync(100);
142+
// Wait for second retry delay (200ms)
143+
await jest.advanceTimersByTimeAsync(200);
144+
145+
const result = await promise;
146+
expect(result).toBe("success");
147+
expect(fn).toHaveBeenCalledTimes(3);
148+
});
149+
150+
it("should throw after max retries exceeded", async () => {
151+
const error = new Error("persistent failure");
152+
const fn = jest.fn().mockRejectedValue(error);
153+
154+
const promise = retryWithExponentialBackoff(fn, 2, 100, 0);
155+
156+
// Advance through all retry delays
157+
await jest.advanceTimersByTimeAsync(100);
158+
await jest.advanceTimersByTimeAsync(200);
159+
160+
await expect(promise).rejects.toThrow("persistent failure");
161+
expect(fn).toHaveBeenCalledTimes(3); // initial + 2 retries
162+
});
163+
164+
it("should respect initial delay", async () => {
165+
const fn = jest.fn().mockResolvedValue("success");
166+
const promise = retryWithExponentialBackoff(fn, 3, 1000, 500);
167+
168+
// Function should not be called before initial delay
169+
expect(fn).not.toHaveBeenCalled();
170+
171+
// Advance past initial delay
172+
await jest.advanceTimersByTimeAsync(500);
173+
174+
const result = await promise;
175+
expect(result).toBe("success");
176+
expect(fn).toHaveBeenCalledTimes(1);
177+
});
178+
179+
it("should use exponential backoff delays", async () => {
180+
const fn = jest
181+
.fn()
182+
.mockRejectedValueOnce(new Error("fail1"))
183+
.mockRejectedValueOnce(new Error("fail2"))
184+
.mockRejectedValueOnce(new Error("fail3"))
185+
.mockResolvedValue("success");
186+
187+
const baseDelay = 100;
188+
const promise = retryWithExponentialBackoff(fn, 3, baseDelay, 0);
189+
190+
// First attempt
191+
await jest.advanceTimersByTimeAsync(0);
192+
expect(fn).toHaveBeenCalledTimes(1);
193+
194+
// First retry after 100ms (baseDelay * 2^0)
195+
await jest.advanceTimersByTimeAsync(100);
196+
expect(fn).toHaveBeenCalledTimes(2);
197+
198+
// Second retry after 200ms (baseDelay * 2^1)
199+
await jest.advanceTimersByTimeAsync(200);
200+
expect(fn).toHaveBeenCalledTimes(3);
201+
202+
// Third retry after 400ms (baseDelay * 2^2)
203+
await jest.advanceTimersByTimeAsync(400);
204+
expect(fn).toHaveBeenCalledTimes(4);
205+
206+
const result = await promise;
207+
expect(result).toBe("success");
208+
});
209+
});
70210
});
211+

0 commit comments

Comments
 (0)