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 functionpull/4772/head
parent
5dabdf3887
commit
c622bfbcf5
@ -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; |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
@ -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); |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
@ -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); |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
@ -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; |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
@ -0,0 +1,120 @@ |
|||||||
|
import { expect } from 'chai'; |
||||||
|
|
||||||
|
import { |
||||||
|
isCheckpoint, |
||||||
|
isS3Checkpoint, |
||||||
|
isS3CheckpointWithId, |
||||||
|
isValidSignature, |
||||||
|
} from './checkpoints.js'; |
||||||
|
import { Checkpoint, S3Checkpoint, S3CheckpointWithId } from './types.js'; |
||||||
|
|
||||||
|
describe('Checkpoints', () => { |
||||||
|
describe('isValidSignature', () => { |
||||||
|
it('should return true for valid string signature', () => { |
||||||
|
const signature = '0x' + 'a'.repeat(130); // Example of a valid hex string
|
||||||
|
expect(isValidSignature(signature)).to.be.true; |
||||||
|
}); |
||||||
|
|
||||||
|
it('should return true for valid object signature', () => { |
||||||
|
const signature = { |
||||||
|
r: '0x' + 'a'.repeat(64), |
||||||
|
s: '0x' + 'b'.repeat(64), |
||||||
|
v: 27, |
||||||
|
}; |
||||||
|
expect(isValidSignature(signature)).to.be.true; |
||||||
|
}); |
||||||
|
|
||||||
|
it('should return false for invalid signature', () => { |
||||||
|
const signature = { |
||||||
|
r: '0x' + 'a'.repeat(64), |
||||||
|
s: '0x' + 'b'.repeat(64), |
||||||
|
v: 'invalid', |
||||||
|
}; |
||||||
|
expect(isValidSignature(signature)).to.be.false; |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('isCheckpoint', () => { |
||||||
|
it('should return true for valid checkpoint', () => { |
||||||
|
const checkpoint: Checkpoint = { |
||||||
|
root: '0x' + 'a'.repeat(64), |
||||||
|
index: 1, |
||||||
|
merkle_tree_hook_address: '0x' + 'b'.repeat(40), |
||||||
|
mailbox_domain: 123, |
||||||
|
}; |
||||||
|
expect(isCheckpoint(checkpoint)).to.be.true; |
||||||
|
}); |
||||||
|
|
||||||
|
it('should return false for invalid checkpoint', () => { |
||||||
|
const checkpoint = { |
||||||
|
root: 'invalid', |
||||||
|
index: 'invalid', |
||||||
|
merkle_tree_hook_address: 'invalid', |
||||||
|
mailbox_domain: 'invalid', |
||||||
|
}; |
||||||
|
expect(isCheckpoint(checkpoint)).to.be.false; |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('isS3Checkpoint', () => { |
||||||
|
it('should return true for valid S3Checkpoint', () => { |
||||||
|
const s3Checkpoint: S3Checkpoint = { |
||||||
|
signature: '0x' + 'a'.repeat(130), |
||||||
|
value: { |
||||||
|
root: '0x' + 'a'.repeat(64), |
||||||
|
index: 1, |
||||||
|
merkle_tree_hook_address: '0x' + 'b'.repeat(40), |
||||||
|
mailbox_domain: 123, |
||||||
|
}, |
||||||
|
}; |
||||||
|
expect(isS3Checkpoint(s3Checkpoint)).to.be.true; |
||||||
|
}); |
||||||
|
|
||||||
|
it('should return false for invalid S3Checkpoint', () => { |
||||||
|
const s3Checkpoint = { |
||||||
|
signature: 'invalid', |
||||||
|
value: { |
||||||
|
root: 'invalid', |
||||||
|
index: 'invalid', |
||||||
|
merkle_tree_hook_address: 'invalid', |
||||||
|
mailbox_domain: 'invalid', |
||||||
|
}, |
||||||
|
}; |
||||||
|
expect(isS3Checkpoint(s3Checkpoint)).to.be.false; |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('isS3CheckpointWithId', () => { |
||||||
|
it('should return true for valid S3CheckpointWithId', () => { |
||||||
|
const s3CheckpointWithId: S3CheckpointWithId = { |
||||||
|
signature: '0x' + 'a'.repeat(130), |
||||||
|
value: { |
||||||
|
checkpoint: { |
||||||
|
root: '0x' + 'a'.repeat(64), |
||||||
|
index: 1, |
||||||
|
merkle_tree_hook_address: '0x' + 'b'.repeat(40), |
||||||
|
mailbox_domain: 123, |
||||||
|
}, |
||||||
|
message_id: '0x' + 'c'.repeat(64), |
||||||
|
}, |
||||||
|
}; |
||||||
|
expect(isS3CheckpointWithId(s3CheckpointWithId)).to.be.true; |
||||||
|
}); |
||||||
|
|
||||||
|
it('should return false for invalid S3CheckpointWithId', () => { |
||||||
|
const s3CheckpointWithId = { |
||||||
|
signature: 'invalid', |
||||||
|
value: { |
||||||
|
checkpoint: { |
||||||
|
root: 'invalid', |
||||||
|
index: 'invalid', |
||||||
|
merkle_tree_hook_address: 'invalid', |
||||||
|
mailbox_domain: 'invalid', |
||||||
|
}, |
||||||
|
message_id: 'invalid', |
||||||
|
}, |
||||||
|
}; |
||||||
|
expect(isS3CheckpointWithId(s3CheckpointWithId)).to.be.false; |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
@ -0,0 +1,18 @@ |
|||||||
|
import { expect } from 'chai'; |
||||||
|
|
||||||
|
import { safelyAccessEnvVar } from './env.js'; |
||||||
|
|
||||||
|
describe('Env Utilities', () => { |
||||||
|
describe('safelyAccessEnvVar', () => { |
||||||
|
it('should return the environment variable', () => { |
||||||
|
process.env.TEST_VAR = '0xTEST_VAR'; |
||||||
|
expect(safelyAccessEnvVar('TEST_VAR')).to.equal('0xTEST_VAR'); |
||||||
|
expect(safelyAccessEnvVar('TEST_VAR', true)).to.equal('0xtest_var'); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should return undefined if the environment variable is not set', () => { |
||||||
|
expect(safelyAccessEnvVar('NON_EXISTENT_VAR')).to.be.undefined; |
||||||
|
expect(safelyAccessEnvVar('NON_EXISTENT_VAR', true)).to.be.undefined; |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
@ -0,0 +1,52 @@ |
|||||||
|
import { expect } from 'chai'; |
||||||
|
import { utils } from 'ethers'; |
||||||
|
|
||||||
|
import { canonizeId, evmId } from './ids.js'; |
||||||
|
|
||||||
|
describe('ID Utilities', () => { |
||||||
|
describe('canonizeId', () => { |
||||||
|
it('should convert a 20-byte ID to a 32-byte ID', () => { |
||||||
|
const id = '0x1234567890123456789012345678901234567890'; |
||||||
|
const result = canonizeId(id); |
||||||
|
expect(result).to.be.instanceOf(Uint8Array); |
||||||
|
expect(result.length).to.equal(32); |
||||||
|
expect(utils.hexlify(result)).to.equal( |
||||||
|
'0x0000000000000000000000001234567890123456789012345678901234567890', |
||||||
|
); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should throw an error for IDs longer than 32 bytes', () => { |
||||||
|
const id = '0x' + '12'.repeat(33); |
||||||
|
expect(() => canonizeId(id)).to.throw('Too long'); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should throw an error for IDs not 20 or 32 bytes', () => { |
||||||
|
const id = '0x1234567890'; |
||||||
|
expect(() => canonizeId(id)).to.throw( |
||||||
|
'bad input, expect address or bytes32', |
||||||
|
); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('evmId', () => { |
||||||
|
it('should convert a 32-byte ID to a 20-byte EVM address', () => { |
||||||
|
const id = |
||||||
|
'0x' + '00'.repeat(12) + '1234567890123456789012345678901234567890'; |
||||||
|
const result = evmId(id); |
||||||
|
expect(result).to.equal('0x1234567890123456789012345678901234567890'); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should return the same 20-byte ID as a 20-byte EVM address', () => { |
||||||
|
const id = '0x1234567890123456789012345678901234567890'; |
||||||
|
const result = evmId(id); |
||||||
|
expect(result).to.equal(id); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should throw an error for IDs not 20 or 32 bytes', () => { |
||||||
|
const id = '0x1234567890'; |
||||||
|
expect(() => evmId(id)).to.throw( |
||||||
|
'Invalid id length. expected 20 or 32. Got 5', |
||||||
|
); |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
@ -0,0 +1,39 @@ |
|||||||
|
import { expect } from 'chai'; |
||||||
|
import { BigNumber } from 'ethers'; |
||||||
|
|
||||||
|
import { ethersBigNumberSerializer } from './logging.js'; |
||||||
|
|
||||||
|
describe('Logging Utilities', () => { |
||||||
|
describe('ethersBigNumberSerializer', () => { |
||||||
|
it('should serialize a BigNumber object correctly', () => { |
||||||
|
const key = 'testKey'; |
||||||
|
const value = { |
||||||
|
type: 'BigNumber', |
||||||
|
hex: '0x1a', |
||||||
|
}; |
||||||
|
const result = ethersBigNumberSerializer(key, value); |
||||||
|
expect(result).to.equal(BigNumber.from(value.hex).toString()); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should return the value unchanged if it is not a BigNumber', () => { |
||||||
|
const key = 'testKey'; |
||||||
|
const value = { some: 'object' }; |
||||||
|
const result = ethersBigNumberSerializer(key, value); |
||||||
|
expect(result).to.equal(value); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should return the value unchanged if it is null', () => { |
||||||
|
const key = 'testKey'; |
||||||
|
const value = null; |
||||||
|
const result = ethersBigNumberSerializer(key, value); |
||||||
|
expect(result).to.equal(value); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should return the value unchanged if it is not an object', () => { |
||||||
|
const key = 'testKey'; |
||||||
|
const value = 'string'; |
||||||
|
const result = ethersBigNumberSerializer(key, value); |
||||||
|
expect(result).to.equal(value); |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
@ -0,0 +1,39 @@ |
|||||||
|
import { expect } from 'chai'; |
||||||
|
|
||||||
|
import { difference, setEquality, symmetricDifference } from './sets.js'; |
||||||
|
|
||||||
|
describe('Set Operations', () => { |
||||||
|
describe('difference', () => { |
||||||
|
it('should return the difference of two sets', () => { |
||||||
|
const setA = new Set([1, 2, 3, undefined]); |
||||||
|
const setB = new Set([2, 3, 4]); |
||||||
|
const result = difference(setA, setB); |
||||||
|
expect(result).to.deep.equal(new Set([1, undefined])); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('symmetricDifference', () => { |
||||||
|
it('should return the symmetric difference of two sets', () => { |
||||||
|
const setA = new Set([1, 2, 3]); |
||||||
|
const setB = new Set([2, 3, 4]); |
||||||
|
const result = symmetricDifference(setA, setB); |
||||||
|
expect(result).to.deep.equal(new Set([1, 4])); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('setEquality', () => { |
||||||
|
it('should return true for equal sets', () => { |
||||||
|
const setA = new Set([1, 2, 3]); |
||||||
|
const setB = new Set([1, 2, 3]); |
||||||
|
const result = setEquality(setA, setB); |
||||||
|
expect(result).to.be.true; |
||||||
|
}); |
||||||
|
|
||||||
|
it('should return false for non-equal sets', () => { |
||||||
|
const setA = new Set([1, 2, 3]); |
||||||
|
const setB = new Set([1, 2, 4]); |
||||||
|
const result = setEquality(setA, setB); |
||||||
|
expect(result).to.be.false; |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
@ -0,0 +1,57 @@ |
|||||||
|
import { expect } from 'chai'; |
||||||
|
import { Readable } from 'stream'; |
||||||
|
|
||||||
|
import { |
||||||
|
errorToString, |
||||||
|
fromHexString, |
||||||
|
sanitizeString, |
||||||
|
streamToString, |
||||||
|
toHexString, |
||||||
|
toTitleCase, |
||||||
|
trimToLength, |
||||||
|
} from './strings.js'; |
||||||
|
|
||||||
|
describe('String Utilities', () => { |
||||||
|
it('should convert string to title case', () => { |
||||||
|
expect(toTitleCase('hello world')).to.equal('Hello World'); |
||||||
|
expect(toTitleCase('HELLO WORLD')).to.equal('Hello World'); |
||||||
|
expect(toTitleCase('4ELLO WORLD')).to.equal('4ello World'); |
||||||
|
expect(toTitleCase('')).to.equal(''); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should sanitize string by removing non-alphanumeric characters', () => { |
||||||
|
expect(sanitizeString('Hello, World!')).to.equal('helloworld'); |
||||||
|
expect(sanitizeString('123-456')).to.equal('123456'); |
||||||
|
expect(sanitizeString('')).to.equal(''); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should trim string to specified length', () => { |
||||||
|
expect(trimToLength('Hello, World!', 5)).to.equal('Hello...'); |
||||||
|
expect(trimToLength('Short', 10)).to.equal('Short'); |
||||||
|
expect(trimToLength('', 10)).to.equal(''); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should convert stream to string', async () => { |
||||||
|
const stream = new Readable(); |
||||||
|
stream.push('Hello, '); |
||||||
|
stream.push('World!'); |
||||||
|
stream.push(null); |
||||||
|
|
||||||
|
const result = await streamToString(stream); |
||||||
|
expect(result).to.equal('Hello, World!'); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should convert error to string', () => { |
||||||
|
expect(errorToString('Error message')).to.equal('Error message'); |
||||||
|
expect(errorToString({ message: 'Error object' })).to.equal('Error object'); |
||||||
|
expect(errorToString(404)).to.equal('Error code: 404'); |
||||||
|
expect(errorToString(null)).to.equal('Unknown Error'); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should convert hex string to buffer and back', () => { |
||||||
|
const hexString = '0x48656c6c6f'; |
||||||
|
const buffer = fromHexString(hexString); |
||||||
|
expect(buffer.toString('utf8')).to.equal('Hello'); |
||||||
|
expect(toHexString(buffer)).to.equal(hexString); |
||||||
|
}); |
||||||
|
}); |
@ -0,0 +1,42 @@ |
|||||||
|
import { expect } from 'chai'; |
||||||
|
|
||||||
|
import { isNullish, isNumeric } from './typeof.js'; |
||||||
|
|
||||||
|
describe('isNullish', () => { |
||||||
|
it('should return true for null', () => { |
||||||
|
expect(isNullish(null)).to.be.true; |
||||||
|
}); |
||||||
|
|
||||||
|
it('should return true for undefined', () => { |
||||||
|
expect(isNullish(undefined)).to.be.true; |
||||||
|
}); |
||||||
|
|
||||||
|
it('should return false for non-nullish values', () => { |
||||||
|
expect(isNullish('')).to.be.false; |
||||||
|
expect(isNullish(0)).to.be.false; |
||||||
|
expect(isNullish(false)).to.be.false; |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('isNumeric', () => { |
||||||
|
it('should return true for numeric strings', () => { |
||||||
|
expect(isNumeric('123')).to.be.true; |
||||||
|
}); |
||||||
|
|
||||||
|
it('should return true for numbers', () => { |
||||||
|
expect(isNumeric(123)).to.be.true; |
||||||
|
}); |
||||||
|
|
||||||
|
it('should return true for negative numbers', () => { |
||||||
|
expect(isNumeric(-123)).to.be.true; |
||||||
|
}); |
||||||
|
|
||||||
|
it('should return true for floating point numbers', () => { |
||||||
|
expect(isNumeric(123.45)).to.be.true; |
||||||
|
}); |
||||||
|
|
||||||
|
it('should return false for non-numeric strings', () => { |
||||||
|
expect(isNumeric('abc')).to.be.false; |
||||||
|
expect(isNumeric('123abc')).to.be.false; |
||||||
|
}); |
||||||
|
}); |
@ -0,0 +1,13 @@ |
|||||||
|
import { expect } from 'chai'; |
||||||
|
|
||||||
|
import { assert } from './validation.js'; |
||||||
|
|
||||||
|
describe('assert', () => { |
||||||
|
it('should not throw an error when the predicate is true', () => { |
||||||
|
expect(() => assert(true, 'Error message')).to.not.throw(); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should throw an error when the predicate is false', () => { |
||||||
|
expect(() => assert(false, 'Error message')).to.throw('Error message'); |
||||||
|
}); |
||||||
|
}); |
@ -0,0 +1,33 @@ |
|||||||
|
import { expect } from 'chai'; |
||||||
|
|
||||||
|
import { tryParseJsonOrYaml } from './yaml.js'; |
||||||
|
|
||||||
|
describe('tryParseJsonOrYaml', () => { |
||||||
|
it('should parse valid JSON string', () => { |
||||||
|
const jsonString = '{"key": "value"}'; |
||||||
|
const result: any = tryParseJsonOrYaml(jsonString); |
||||||
|
expect(result.success).to.be.true; |
||||||
|
expect(result.data).to.deep.equal({ key: 'value' }); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should parse valid YAML string', () => { |
||||||
|
const yamlString = 'key: value'; |
||||||
|
const result: any = tryParseJsonOrYaml(yamlString); |
||||||
|
expect(result.success).to.be.true; |
||||||
|
expect(result.data).to.deep.equal({ key: 'value' }); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should fail for invalid JSON string', () => { |
||||||
|
const invalidJsonString = '{"key": "value"'; |
||||||
|
const result: any = tryParseJsonOrYaml(invalidJsonString); |
||||||
|
expect(result.success).to.be.false; |
||||||
|
expect(result.error).to.equal('Input is not valid JSON or YAML'); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should fail for invalid YAML string', () => { |
||||||
|
const invalidYamlString = 'key: value:'; |
||||||
|
const result: any = tryParseJsonOrYaml(invalidYamlString); |
||||||
|
expect(result.success).to.be.false; |
||||||
|
expect(result.error).to.equal('Input is not valid JSON or YAML'); |
||||||
|
}); |
||||||
|
}); |
Loading…
Reference in new issue