/**
* Integration tests for EtherscanService
* Tests the v2 API integration and service methods
*/
import { EtherscanService } from '../../services/etherscanService.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 assertTrue(value: boolean, message?: string): void {
if (!value) {
throw new Error(message || 'Expected value to be true');
}
}
// Test Suite 1: Constructor tests
console.log('\n=== EtherscanService Constructor Tests ===\n');
test('should accept network slug (ethereum)', () => {
const service = new EtherscanService('test-api-key', 'ethereum');
assertEquals(service.getChainId(), 1);
});
test('should accept network slug (mainnet)', () => {
const service = new EtherscanService('test-api-key', 'mainnet');
assertEquals(service.getChainId(), 1);
});
test('should accept network slug (polygon)', () => {
const service = new EtherscanService('test-api-key', 'polygon');
assertEquals(service.getChainId(), 137);
});
test('should accept network slug (arbitrum)', () => {
const service = new EtherscanService('test-api-key', 'arbitrum');
assertEquals(service.getChainId(), 42161);
});
test('should accept network slug (optimism)', () => {
const service = new EtherscanService('test-api-key', 'optimism');
assertEquals(service.getChainId(), 10);
});
test('should accept network slug (base)', () => {
const service = new EtherscanService('test-api-key', 'base');
assertEquals(service.getChainId(), 8453);
});
test('should accept chain ID number (1)', () => {
const service = new EtherscanService('test-api-key', 1);
assertEquals(service.getChainId(), 1);
});
test('should accept chain ID number (137)', () => {
const service = new EtherscanService('test-api-key', 137);
assertEquals(service.getChainId(), 137);
});
test('should accept chain ID number (42161)', () => {
const service = new EtherscanService('test-api-key', 42161);
assertEquals(service.getChainId(), 42161);
});
test('should default to ethereum mainnet when no network provided', () => {
const service = new EtherscanService('test-api-key');
assertEquals(service.getChainId(), 1);
});
test('should throw error for missing API key', () => {
assertThrows(() => new EtherscanService(''), 'API key is required');
});
test('should throw error for invalid network slug', () => {
assertThrows(() => new EtherscanService('test-api-key', 'invalid-network'), 'Invalid network');
});
test('should throw error for invalid chain ID', () => {
assertThrows(() => new EtherscanService('test-api-key', 999999), 'Invalid network or chain ID');
});
// Test Suite 2: V2 API methods availability
console.log('\n=== V2 API Methods Availability Tests ===\n');
test('should have getBeaconWithdrawals method', () => {
const service = new EtherscanService('test-api-key', 'ethereum');
assertEquals(typeof service.getBeaconWithdrawals, 'function');
});
test('should have getTokenInfo method', () => {
const service = new EtherscanService('test-api-key', 'ethereum');
assertEquals(typeof service.getTokenInfo, 'function');
});
test('should have getTokenPortfolio method', () => {
const service = new EtherscanService('test-api-key', 'ethereum');
assertEquals(typeof service.getTokenPortfolio, 'function');
});
test('should have getTokenHolders method', () => {
const service = new EtherscanService('test-api-key', 'ethereum');
assertEquals(typeof service.getTokenHolders, 'function');
});
test('should have getLogs method', () => {
const service = new EtherscanService('test-api-key', 'ethereum');
assertEquals(typeof service.getLogs, 'function');
});
test('should have getNetworkStats method', () => {
const service = new EtherscanService('test-api-key', 'ethereum');
assertEquals(typeof service.getNetworkStats, 'function');
});
test('should have getDailyTxCount method', () => {
const service = new EtherscanService('test-api-key', 'ethereum');
assertEquals(typeof service.getDailyTxCount, 'function');
});
// Test Suite 3: Legacy methods availability
console.log('\n=== Legacy Methods Availability Tests ===\n');
test('should have getAddressBalance method', () => {
const service = new EtherscanService('test-api-key', 'ethereum');
assertEquals(typeof service.getAddressBalance, 'function');
});
test('should have getTransactionHistory method', () => {
const service = new EtherscanService('test-api-key', 'ethereum');
assertEquals(typeof service.getTransactionHistory, 'function');
});
test('should have getTokenTransfers method', () => {
const service = new EtherscanService('test-api-key', 'ethereum');
assertEquals(typeof service.getTokenTransfers, 'function');
});
test('should have getContractABI method', () => {
const service = new EtherscanService('test-api-key', 'ethereum');
assertEquals(typeof service.getContractABI, 'function');
});
test('should have getGasOracle method', () => {
const service = new EtherscanService('test-api-key', 'ethereum');
assertEquals(typeof service.getGasOracle, 'function');
});
test('should have getENSName method', () => {
const service = new EtherscanService('test-api-key', 'ethereum');
assertEquals(typeof service.getENSName, 'function');
});
test('should have getMinedBlocks method', () => {
const service = new EtherscanService('test-api-key', 'ethereum');
assertEquals(typeof service.getMinedBlocks, 'function');
});
test('should have getInternalTransactions method', () => {
const service = new EtherscanService('test-api-key', 'ethereum');
assertEquals(typeof service.getInternalTransactions, 'function');
});
test('should have getBlockReward method', () => {
const service = new EtherscanService('test-api-key', 'ethereum');
assertEquals(typeof service.getBlockReward, 'function');
});
test('should have getBlockDetails method', () => {
const service = new EtherscanService('test-api-key', 'ethereum');
assertEquals(typeof service.getBlockDetails, 'function');
});
test('should have getContractSourceCode method', () => {
const service = new EtherscanService('test-api-key', 'ethereum');
assertEquals(typeof service.getContractSourceCode, 'function');
});
test('should have getContractCreation method', () => {
const service = new EtherscanService('test-api-key', 'ethereum');
assertEquals(typeof service.getContractCreation, 'function');
});
test('should have verifyContract method', () => {
const service = new EtherscanService('test-api-key', 'ethereum');
assertEquals(typeof service.verifyContract, 'function');
});
test('should have checkVerificationStatus method', () => {
const service = new EtherscanService('test-api-key', 'ethereum');
assertEquals(typeof service.checkVerificationStatus, 'function');
});
test('should have verifyProxyContract method', () => {
const service = new EtherscanService('test-api-key', 'ethereum');
assertEquals(typeof service.verifyProxyContract, 'function');
});
test('should have getVerifiedContracts method', () => {
const service = new EtherscanService('test-api-key', 'ethereum');
assertEquals(typeof service.getVerifiedContracts, 'function');
});
// Test Suite 4: Chain ID management
console.log('\n=== Chain ID Management Tests ===\n');
test('should get current chain ID', () => {
const service = new EtherscanService('test-api-key', 'polygon');
assertEquals(service.getChainId(), 137);
});
test('should set new chain ID', () => {
const service = new EtherscanService('test-api-key', 'ethereum');
assertEquals(service.getChainId(), 1);
service.setChainId(137);
assertEquals(service.getChainId(), 137);
});
test('should allow switching between networks', () => {
const service = new EtherscanService('test-api-key', 'ethereum');
assertEquals(service.getChainId(), 1, 'Should start with Ethereum');
service.setChainId(137);
assertEquals(service.getChainId(), 137, 'Should switch to Polygon');
service.setChainId(42161);
assertEquals(service.getChainId(), 42161, 'Should switch to Arbitrum');
service.setChainId(1);
assertEquals(service.getChainId(), 1, 'Should switch back to Ethereum');
});
// Test Suite 5: Multi-network support
console.log('\n=== Multi-Network Support Tests ===\n');
test('should support all major mainnets', () => {
const networks = [
{ name: 'ethereum', chainId: 1 },
{ name: 'polygon', chainId: 137 },
{ name: 'arbitrum', chainId: 42161 },
{ name: 'optimism', chainId: 10 },
{ name: 'base', chainId: 8453 },
{ name: 'avalanche', chainId: 43114 },
{ name: 'bnb', chainId: 56 },
{ name: 'fantom', chainId: 250 }
];
networks.forEach(network => {
const service = new EtherscanService('test-api-key', network.name);
assertEquals(service.getChainId(), network.chainId, `${network.name} should have chain ID ${network.chainId}`);
});
});
test('should support popular testnets', () => {
const testnets = [
{ name: 'sepolia', chainId: 11155111 },
{ name: 'holesky', chainId: 17000 },
{ name: 'arbitrum-sepolia', chainId: 421614 },
{ name: 'optimism-sepolia', chainId: 11155420 },
{ name: 'base-sepolia', chainId: 84532 },
{ name: 'polygon-amoy', chainId: 80002 }
];
testnets.forEach(testnet => {
const service = new EtherscanService('test-api-key', testnet.name);
assertEquals(service.getChainId(), testnet.chainId, `${testnet.name} should have chain ID ${testnet.chainId}`);
});
});
test('should support L2 networks', () => {
const l2Networks = [
{ name: 'arbitrum', chainId: 42161 },
{ name: 'optimism', chainId: 10 },
{ name: 'base', chainId: 8453 },
{ name: 'linea', chainId: 59144 },
{ name: 'scroll', chainId: 534352 },
{ name: 'zksync', chainId: 324 }
];
l2Networks.forEach(network => {
const service = new EtherscanService('test-api-key', network.name);
assertEquals(service.getChainId(), network.chainId, `${network.name} should have chain ID ${network.chainId}`);
});
});
test('should support emerging networks', () => {
const emergingNetworks = [
{ name: 'blast', chainId: 81457 },
{ name: 'mantle', chainId: 5000 },
{ name: 'taiko', chainId: 167000 },
{ name: 'sonic', chainId: 146 }
];
emergingNetworks.forEach(network => {
const service = new EtherscanService('test-api-key', network.name);
assertEquals(service.getChainId(), network.chainId, `${network.name} should have chain ID ${network.chainId}`);
});
});
// Test Suite 6: Service configuration
console.log('\n=== Service Configuration Tests ===\n');
test('should accept and store API key', () => {
const apiKey = 'my-test-api-key-12345';
const service = new EtherscanService(apiKey, 'ethereum');
// We can't directly access the private apiKey, but we can verify the service was created
assertNotNull(service);
});
test('should create service for each network independently', () => {
const ethService = new EtherscanService('key1', 'ethereum');
const polyService = new EtherscanService('key2', 'polygon');
const arbService = new EtherscanService('key3', 'arbitrum');
assertEquals(ethService.getChainId(), 1);
assertEquals(polyService.getChainId(), 137);
assertEquals(arbService.getChainId(), 42161);
});
// Test Suite 7: Backward compatibility
console.log('\n=== Backward Compatibility Tests ===\n');
test('should maintain backward compatibility with legacy network names', () => {
const serviceEth = new EtherscanService('test-key', 'ethereum');
const serviceMainnet = new EtherscanService('test-key', 'mainnet');
assertEquals(serviceEth.getChainId(), serviceMainnet.getChainId(),
'ethereum and mainnet should resolve to same chain');
});
test('should support legacy polygon network name', () => {
const service = new EtherscanService('test-key', 'polygon');
assertEquals(service.getChainId(), 137);
});
test('should support legacy arbitrum network name', () => {
const service = new EtherscanService('test-key', 'arbitrum');
assertEquals(service.getChainId(), 42161);
});
test('should support legacy optimism network name', () => {
const service = new EtherscanService('test-key', 'optimism');
assertEquals(service.getChainId(), 10);
});
// 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);
}