/**
* Unit tests for network configuration
* Tests all network configuration functions
*/
import {
getChainId,
getNetworkByChainId,
getNetworkBySlug,
isValidNetwork,
getAllNetworks,
getMainnets,
getTestnets,
NETWORKS,
CHAIN_ID_TO_NETWORK,
V2_API_BASE_URL,
type NetworkConfig
} from '../../config/networks.js';
interface TestResult {
name: string;
passed: boolean;
error?: string;
}
const results: TestResult[] = [];
function test(name: string, fn: () => void): void {
try {
fn();
results.push({ name, passed: true });
console.log(`✓ ${name}`);
} catch (error) {
results.push({
name,
passed: false,
error: error instanceof Error ? error.message : String(error)
});
console.log(`✗ ${name}`);
console.log(` Error: ${error instanceof Error ? error.message : String(error)}`);
}
}
function assertEquals<T>(actual: T, expected: T, message?: string): void {
if (actual !== expected) {
throw new Error(message || `Expected ${expected}, got ${actual}`);
}
}
function assertThrows(fn: () => void, expectedMessage?: string): void {
try {
fn();
throw new Error('Expected function to throw an error');
} catch (error) {
if (expectedMessage && error instanceof Error) {
if (!error.message.includes(expectedMessage)) {
throw new Error(`Expected error message to include "${expectedMessage}", got "${error.message}"`);
}
}
}
}
function assertNotNull<T>(value: T | null | undefined, message?: string): asserts value is T {
if (value === null || value === undefined) {
throw new Error(message || 'Expected value to not be null or undefined');
}
}
function assertGreaterThan(actual: number, expected: number, message?: string): void {
if (actual <= expected) {
throw new Error(message || `Expected ${actual} to be greater than ${expected}`);
}
}
// Test Suite 1: getChainId function
console.log('\n=== getChainId Function Tests ===\n');
test('should return chain ID for valid network slug (ethereum)', () => {
const chainId = getChainId('ethereum');
assertEquals(chainId, 1);
});
test('should return chain ID for valid network slug (mainnet alias)', () => {
const chainId = getChainId('mainnet');
assertEquals(chainId, 1);
});
test('should return chain ID for polygon', () => {
const chainId = getChainId('polygon');
assertEquals(chainId, 137);
});
test('should return chain ID for arbitrum', () => {
const chainId = getChainId('arbitrum');
assertEquals(chainId, 42161);
});
test('should return chain ID for optimism', () => {
const chainId = getChainId('optimism');
assertEquals(chainId, 10);
});
test('should return chain ID for base', () => {
const chainId = getChainId('base');
assertEquals(chainId, 8453);
});
test('should return chain ID when passed a number (mainnet)', () => {
const chainId = getChainId(1);
assertEquals(chainId, 1);
});
test('should return chain ID when passed a number (polygon)', () => {
const chainId = getChainId(137);
assertEquals(chainId, 137);
});
test('should throw for invalid network slug', () => {
assertThrows(() => getChainId('invalid-network'), 'Unknown network');
});
test('should throw for invalid chain ID', () => {
assertThrows(() => getChainId(999999), 'Invalid chain ID');
});
test('should handle mainnet and ethereum as aliases', () => {
const mainnetId = getChainId('mainnet');
const ethereumId = getChainId('ethereum');
assertEquals(mainnetId, ethereumId, 'mainnet and ethereum should resolve to same chain ID');
});
// Test Suite 2: getNetworkByChainId function
console.log('\n=== getNetworkByChainId Function Tests ===\n');
test('should return network config for valid chain ID (mainnet)', () => {
const network = getNetworkByChainId(1);
assertNotNull(network);
assertEquals(network.chainId, 1);
assertEquals(network.name, 'Ethereum Mainnet');
assertEquals(network.isTestnet, false);
});
test('should return network config for polygon', () => {
const network = getNetworkByChainId(137);
assertNotNull(network);
assertEquals(network.chainId, 137);
assertEquals(network.name, 'Polygon Mainnet');
});
test('should return network config for arbitrum', () => {
const network = getNetworkByChainId(42161);
assertNotNull(network);
assertEquals(network.chainId, 42161);
assertEquals(network.name, 'Arbitrum One Mainnet');
assertEquals(network.isL2, true);
});
test('should return undefined for invalid chain ID', () => {
const network = getNetworkByChainId(999999);
assertEquals(network, undefined);
});
test('should return network with correct currency info', () => {
const network = getNetworkByChainId(1);
assertNotNull(network);
assertEquals(network.nativeCurrency.symbol, 'ETH');
assertEquals(network.nativeCurrency.decimals, 18);
});
// Test Suite 3: getNetworkBySlug function
console.log('\n=== getNetworkBySlug Function Tests ===\n');
test('should return network config for valid slug', () => {
const network = getNetworkBySlug('ethereum');
assertNotNull(network);
assertEquals(network.chainId, 1);
});
test('should return network config for polygon slug', () => {
const network = getNetworkBySlug('polygon');
assertNotNull(network);
assertEquals(network.chainId, 137);
});
test('should return undefined for invalid slug', () => {
const network = getNetworkBySlug('invalid');
assertEquals(network, undefined);
});
// Test Suite 4: isValidNetwork function
console.log('\n=== isValidNetwork Function Tests ===\n');
test('should return true for valid network slug (ethereum)', () => {
assertEquals(isValidNetwork('ethereum'), true);
});
test('should return true for valid network slug (mainnet)', () => {
assertEquals(isValidNetwork('mainnet'), true);
});
test('should return true for valid network slug (polygon)', () => {
assertEquals(isValidNetwork('polygon'), true);
});
test('should return true for valid chain ID (1)', () => {
assertEquals(isValidNetwork(1), true);
});
test('should return true for valid chain ID (137)', () => {
assertEquals(isValidNetwork(137), true);
});
test('should return true for valid chain ID (42161)', () => {
assertEquals(isValidNetwork(42161), true);
});
test('should return false for invalid network slug', () => {
assertEquals(isValidNetwork('invalid-network'), false);
});
test('should return false for invalid chain ID', () => {
assertEquals(isValidNetwork(999999), false);
});
test('should return false for zero chain ID', () => {
assertEquals(isValidNetwork(0), false);
});
// Test Suite 5: getAllNetworks function
console.log('\n=== getAllNetworks Function Tests ===\n');
test('should return array of all unique networks', () => {
const networks = getAllNetworks();
assertEquals(Array.isArray(networks), true);
assertGreaterThan(networks.length, 0, 'Should return at least one network');
});
test('should have at least 70 networks', () => {
const networks = getAllNetworks();
assertGreaterThan(networks.length, 69, 'Should have at least 70 networks');
});
test('should return unique networks (no duplicates by chain ID)', () => {
const networks = getAllNetworks();
const chainIds = networks.map(n => n.chainId);
const uniqueChainIds = new Set(chainIds);
assertEquals(chainIds.length, uniqueChainIds.size, 'All chain IDs should be unique');
});
test('should include Ethereum mainnet', () => {
const networks = getAllNetworks();
const ethereum = networks.find(n => n.chainId === 1);
assertNotNull(ethereum, 'Should include Ethereum mainnet');
});
test('should include popular networks', () => {
const networks = getAllNetworks();
const chainIds = networks.map(n => n.chainId);
// Check for popular networks
const popularChains = [1, 137, 42161, 10, 8453]; // ETH, Polygon, Arbitrum, Optimism, Base
popularChains.forEach(chainId => {
assertEquals(chainIds.includes(chainId), true, `Should include chain ${chainId}`);
});
});
// Test Suite 6: getMainnets function
console.log('\n=== getMainnets Function Tests ===\n');
test('should return only mainnet networks', () => {
const mainnets = getMainnets();
assertEquals(Array.isArray(mainnets), true);
// All should be mainnets
mainnets.forEach(network => {
assertEquals(network.isTestnet, false, `${network.name} should not be a testnet`);
});
});
test('should include Ethereum mainnet', () => {
const mainnets = getMainnets();
const ethereum = mainnets.find(n => n.chainId === 1);
assertNotNull(ethereum, 'Should include Ethereum mainnet');
});
test('should not include testnets', () => {
const mainnets = getMainnets();
const testnet = mainnets.find(n => n.isTestnet === true);
assertEquals(testnet, undefined, 'Should not include any testnets');
});
// Test Suite 7: getTestnets function
console.log('\n=== getTestnets Function Tests ===\n');
test('should return only testnet networks', () => {
const testnets = getTestnets();
assertEquals(Array.isArray(testnets), true);
// All should be testnets
testnets.forEach(network => {
assertEquals(network.isTestnet, true, `${network.name} should be a testnet`);
});
});
test('should include Sepolia testnet', () => {
const testnets = getTestnets();
const sepolia = testnets.find(n => n.chainId === 11155111);
assertNotNull(sepolia, 'Should include Sepolia testnet');
});
test('should not include mainnets', () => {
const testnets = getTestnets();
const mainnet = testnets.find(n => n.isTestnet === false);
assertEquals(mainnet, undefined, 'Should not include any mainnets');
});
// Test Suite 8: Constants and structure validation
console.log('\n=== Constants and Structure Validation ===\n');
test('should have correct V2 API base URL', () => {
assertEquals(V2_API_BASE_URL, 'https://api.etherscan.io/v2/api');
});
test('NETWORKS object should not be empty', () => {
const keys = Object.keys(NETWORKS);
assertGreaterThan(keys.length, 0, 'NETWORKS should have at least one entry');
});
test('CHAIN_ID_TO_NETWORK should not be empty', () => {
const keys = Object.keys(CHAIN_ID_TO_NETWORK);
assertGreaterThan(keys.length, 0, 'CHAIN_ID_TO_NETWORK should have at least one entry');
});
test('all networks should have required fields', () => {
const networks = getAllNetworks();
networks.forEach(network => {
assertNotNull(network.chainId, `${network.name} should have chainId`);
assertNotNull(network.name, `Chain ${network.chainId} should have name`);
assertNotNull(network.shortName, `${network.name} should have shortName`);
assertEquals(typeof network.isTestnet, 'boolean', `${network.name} should have boolean isTestnet`);
assertEquals(typeof network.isL2, 'boolean', `${network.name} should have boolean isL2`);
assertNotNull(network.nativeCurrency, `${network.name} should have nativeCurrency`);
});
});
test('all native currencies should have required fields', () => {
const networks = getAllNetworks();
networks.forEach(network => {
assertNotNull(network.nativeCurrency.name, `${network.name} currency should have name`);
assertNotNull(network.nativeCurrency.symbol, `${network.name} currency should have symbol`);
assertEquals(network.nativeCurrency.decimals, 18, `${network.name} currency should have 18 decimals`);
});
});
test('CHAIN_ID_TO_NETWORK should map correctly to NETWORKS', () => {
Object.entries(CHAIN_ID_TO_NETWORK).forEach(([chainIdStr, slug]) => {
const chainId = parseInt(chainIdStr);
const network = NETWORKS[slug];
assertNotNull(network, `Chain ID ${chainId} should map to valid network slug ${slug}`);
assertEquals(network.chainId, chainId, `Network ${slug} should have chain ID ${chainId}`);
});
});
// Summary
console.log('\n=== Test Summary ===\n');
const passed = results.filter(r => r.passed).length;
const failed = results.filter(r => !r.passed).length;
console.log(`Total: ${results.length}`);
console.log(`Passed: ${passed}`);
console.log(`Failed: ${failed}`);
if (failed > 0) {
console.log('\nFailed tests:');
results.filter(r => !r.passed).forEach(r => {
console.log(` - ${r.name}`);
console.log(` ${r.error}`);
});
process.exit(1);
} else {
console.log('\nAll tests passed! ✓');
process.exit(0);
}