-
Notifications
You must be signed in to change notification settings - Fork 360
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
test: Coverage more @hyperlane-xyz/utils test (#4758)
### Description Add more coverage `utils` package test <!-- What's included in this PR? --> ### Drive-by changes <!-- Are there any minor or drive-by changes also included? --> ### Related issues <!-- - Fixes #[issue number here] --> ### Backward compatibility <!-- Are these changes backward compatible? Are there any infrastructure implications, e.g. changes that would prohibit deploying older commits using this infra tooling? Yes/No --> ### Testing <!-- What kind of testing have these changes undergone? None/Manual/Unit Tests --> More unittests function
- Loading branch information
Showing
17 changed files
with
736 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import { expect } from 'chai'; | ||
|
||
import { chunk, exclude, randomElement } from './arrays.js'; | ||
|
||
describe('Arrays utilities', () => { | ||
describe('chunk', () => { | ||
it('should split an array into chunks of the specified size', () => { | ||
const result = chunk([1, 2, 3, 4, 5], 2); | ||
expect(result).to.deep.equal([[1, 2], [3, 4], [5]]); | ||
}); | ||
|
||
it('should return an empty array when input is empty', () => { | ||
const result = chunk([], 2); | ||
expect(result).to.deep.equal([]); | ||
}); | ||
|
||
it('should handle chunk size larger than array length', () => { | ||
const result = chunk([1, 2], 5); | ||
expect(result).to.deep.equal([[1, 2]]); | ||
}); | ||
}); | ||
|
||
describe('exclude', () => { | ||
it('should exclude the specified item from the list', () => { | ||
const result = exclude(2, [1, 2, 3, 2]); | ||
expect(result).to.deep.equal([1, 3]); | ||
}); | ||
|
||
it('should return the same list if item is not found', () => { | ||
const result = exclude(4, [1, 2, 3]); | ||
expect(result).to.deep.equal([1, 2, 3]); | ||
}); | ||
|
||
it('should return an empty list if all items are excluded', () => { | ||
const result = exclude(1, [1, 1, 1]); | ||
expect(result).to.deep.equal([]); | ||
}); | ||
}); | ||
|
||
describe('randomElement', () => { | ||
beforeEach(() => {}); | ||
|
||
it('should return a random element from the list', () => { | ||
const list = [10, 20, 30]; | ||
const result = randomElement(list); | ||
expect(result).to.be.oneOf(list); | ||
}); | ||
|
||
it('should handle an empty list gracefully', () => { | ||
const result = randomElement([]); | ||
expect(result).to.be.undefined; | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
import { expect } from 'chai'; | ||
|
||
import { | ||
concurrentMap, | ||
fetchWithTimeout, | ||
pollAsync, | ||
raceWithContext, | ||
retryAsync, | ||
runWithTimeout, | ||
sleep, | ||
timeout, | ||
} from './async.js'; | ||
|
||
describe('Async Utilities', () => { | ||
describe('sleep', () => { | ||
it('should resolve after sleep duration', async () => { | ||
const start = Date.now(); | ||
await sleep(100); | ||
const duration = Date.now() - start; | ||
expect(duration).to.be.at.least(100); | ||
expect(duration).to.be.lessThan(200); | ||
}); | ||
}); | ||
|
||
describe('timeout', () => { | ||
it('should timeout a promise', async () => { | ||
const promise = new Promise((resolve) => setTimeout(resolve, 200)); | ||
try { | ||
await timeout(promise, 100); | ||
throw new Error('Expected timeout error'); | ||
} catch (error: any) { | ||
expect(error.message).to.equal('Timeout reached'); | ||
} | ||
}); | ||
}); | ||
|
||
describe('runWithTimeout', () => { | ||
it('should run a callback with a timeout', async () => { | ||
const result = await runWithTimeout(100, async () => { | ||
await sleep(50); | ||
return 'success'; | ||
}); | ||
expect(result).to.equal('success'); | ||
}); | ||
}); | ||
|
||
describe('fetchWithTimeout', () => { | ||
it('should fetch with timeout', async () => { | ||
// Mock fetch for testing | ||
global.fetch = async () => { | ||
await sleep(50); | ||
return new Response('ok'); | ||
}; | ||
|
||
const response = await fetchWithTimeout('https://example.com', {}, 100); | ||
expect(await response.text()).to.equal('ok'); | ||
}); | ||
}); | ||
|
||
describe('retryAsync', () => { | ||
it('should retry async function with exponential backoff', async () => { | ||
let attempt = 0; | ||
const runner = async () => { | ||
attempt++; | ||
if (attempt < 3) throw new Error('fail'); | ||
return 'success'; | ||
}; | ||
|
||
const result = await retryAsync(runner, 5, 10); | ||
expect(result).to.equal('success'); | ||
}); | ||
}); | ||
|
||
describe('pollAsync', () => { | ||
it('should poll async function until success', async () => { | ||
let attempt = 0; | ||
const runner = async () => { | ||
attempt++; | ||
if (attempt < 3) throw new Error('fail'); | ||
return 'success'; | ||
}; | ||
|
||
const result = await pollAsync(runner, 10, 5); | ||
expect(result).to.equal('success'); | ||
}); | ||
|
||
it('should fail after reaching max retries', async () => { | ||
let attempt = 0; | ||
const runner = async () => { | ||
attempt++; | ||
throw new Error('fail'); | ||
}; | ||
|
||
try { | ||
await pollAsync(runner, 10, 3); // Set maxAttempts to 3 | ||
throw new Error('Expected pollAsync to throw an error'); | ||
} catch (error: any) { | ||
expect(attempt).to.equal(3); // Ensure it attempted 3 times | ||
expect(error.message).to.equal('fail'); | ||
} | ||
}); | ||
}); | ||
|
||
describe('raceWithContext', () => { | ||
it('should race with context', async () => { | ||
const promises = [ | ||
sleep(50).then(() => 'first'), | ||
sleep(100).then(() => 'second'), | ||
]; | ||
|
||
const result = await raceWithContext(promises); | ||
expect(result.resolved).to.equal('first'); | ||
expect(result.index).to.equal(0); | ||
}); | ||
}); | ||
|
||
describe('concurrentMap', () => { | ||
it('should map concurrently with correct results', async () => { | ||
const xs = [1, 2, 3, 4, 5, 6]; | ||
const mapFn = async (val: number) => { | ||
await new Promise((resolve) => setTimeout(resolve, 50)); // Simulate async work | ||
return val * 2; | ||
}; | ||
const result = await concurrentMap(2, xs, mapFn); | ||
expect(result).to.deep.equal([2, 4, 6, 8, 10, 12]); | ||
}); | ||
|
||
it('should respect concurrency limit', async () => { | ||
const xs = [1, 2, 3, 4, 5, 6]; | ||
const concurrency = 2; | ||
let activeTasks = 0; | ||
let maxActiveTasks = 0; | ||
|
||
const mapFn = async (val: number) => { | ||
activeTasks++; | ||
maxActiveTasks = Math.max(maxActiveTasks, activeTasks); | ||
await new Promise((resolve) => setTimeout(resolve, 50)); // Simulate async work | ||
activeTasks--; | ||
return val * 2; | ||
}; | ||
|
||
await concurrentMap(concurrency, xs, mapFn); | ||
expect(maxActiveTasks).to.equal(concurrency); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { expect } from 'chai'; | ||
import { utils } from 'ethers'; | ||
|
||
import { base58ToBuffer, bufferToBase58, hexOrBase58ToHex } from './base58.js'; | ||
|
||
describe('Base58 Utilities', () => { | ||
describe('base58ToBuffer', () => { | ||
it('should convert a base58 string to a buffer', () => { | ||
const base58String = '3mJr7AoUXx2Wqd'; | ||
const expectedBuffer = Buffer.from(utils.base58.decode(base58String)); | ||
expect(base58ToBuffer(base58String)).to.deep.equal(expectedBuffer); | ||
}); | ||
}); | ||
|
||
describe('bufferToBase58', () => { | ||
it('should convert a buffer to a base58 string', () => { | ||
const buffer = Buffer.from([1, 2, 3, 4]); | ||
const expectedBase58String = utils.base58.encode(buffer); | ||
expect(bufferToBase58(buffer)).to.equal(expectedBase58String); | ||
}); | ||
}); | ||
|
||
describe('hexOrBase58ToHex', () => { | ||
it('should return the hex string as is if it starts with 0x', () => { | ||
const hexString = '0x1234abcd'; | ||
expect(hexOrBase58ToHex(hexString)).to.equal(hexString); | ||
}); | ||
|
||
it('should convert a base58 string to a hex string', () => { | ||
const base58String = '3mJr7AoUXx2Wqd'; | ||
const expectedHexString = utils.hexlify( | ||
Buffer.from(utils.base58.decode(base58String)), | ||
); | ||
expect(hexOrBase58ToHex(base58String)).to.equal(expectedHexString); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import { expect } from 'chai'; | ||
import Sinon from 'sinon'; | ||
|
||
import { fromBase64, toBase64 } from './base64.js'; | ||
import { rootLogger } from './logging.js'; | ||
|
||
describe('Base64 Utility Functions', () => { | ||
let loggerStub: sinon.SinonStub; | ||
|
||
beforeEach(() => { | ||
loggerStub = Sinon.stub(rootLogger, 'error'); | ||
}); | ||
|
||
afterEach(() => { | ||
loggerStub.restore(); | ||
}); | ||
|
||
describe('toBase64', () => { | ||
it('should encode a valid object to a base64 string', () => { | ||
const data = { key: 'value' }; | ||
const result = toBase64(data); | ||
expect(result).to.be.a('string'); | ||
expect(result).to.equal(btoa(JSON.stringify(data))); | ||
}); | ||
|
||
it('should return undefined for null or undefined input', () => { | ||
expect(toBase64(null)).to.be.undefined; | ||
expect(toBase64(undefined)).to.be.undefined; | ||
}); | ||
|
||
it('should log an error for invalid input', () => { | ||
toBase64(null); | ||
expect(loggerStub.calledOnce).to.be.true; | ||
expect( | ||
loggerStub.calledWith( | ||
'Unable to serialize + encode data to base64', | ||
null, | ||
), | ||
).to.be.true; | ||
}); | ||
}); | ||
|
||
describe('fromBase64', () => { | ||
it('should decode a valid base64 string to an object', () => { | ||
const data = { key: 'value' }; | ||
const base64String = btoa(JSON.stringify(data)); | ||
const result = fromBase64(base64String); | ||
expect(result).to.deep.equal(data); | ||
}); | ||
|
||
it('should return undefined for null or undefined input', () => { | ||
expect(fromBase64(null as any)).to.be.undefined; | ||
expect(fromBase64(undefined as any)).to.be.undefined; | ||
}); | ||
|
||
it('should handle array input and decode the first element', () => { | ||
const data = { key: 'value' }; | ||
const base64String = btoa(JSON.stringify(data)); | ||
const result = fromBase64([base64String, 'anotherString']); | ||
expect(result).to.deep.equal(data); | ||
}); | ||
|
||
it('should log an error for invalid base64 input', () => { | ||
fromBase64('invalidBase64'); | ||
expect(loggerStub.calledOnce).to.be.true; | ||
expect( | ||
loggerStub.calledWith( | ||
'Unable to decode + deserialize data from base64', | ||
'invalidBase64', | ||
), | ||
).to.be.true; | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.