214 lines
6.9 KiB
JavaScript
Executable File
214 lines
6.9 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
||
|
||
/**
|
||
* Test script to verify signer reconnection behavior
|
||
*
|
||
* Usage:
|
||
* node test-signer-reconnection.js
|
||
*
|
||
* This script will:
|
||
* 1. Connect to your API
|
||
* 2. Monitor signer health
|
||
* 3. Simulate operations during connection issues
|
||
* 4. Verify reconnection behavior
|
||
*/
|
||
|
||
const fetch = require('node-fetch');
|
||
|
||
const API_BASE = process.env.API_BASE || 'http://localhost:8080';
|
||
const TEST_DURATION = 60000; // 1 minute test
|
||
const CHECK_INTERVAL = 2000; // Check every 2 seconds
|
||
|
||
async function checkSignerHealth() {
|
||
try {
|
||
const response = await fetch(`${API_BASE}/api/v1/process/health/signer`, {
|
||
timeout: 5000
|
||
});
|
||
|
||
if (!response.ok) {
|
||
return { error: `HTTP ${response.status}` };
|
||
}
|
||
|
||
const data = await response.json();
|
||
return data.data?.signer || { error: 'Invalid response format' };
|
||
} catch (error) {
|
||
return { error: error.message };
|
||
}
|
||
}
|
||
|
||
async function forceReconnect() {
|
||
try {
|
||
const response = await fetch(`${API_BASE}/api/v1/process/admin/signer/reconnect`, {
|
||
method: 'POST',
|
||
timeout: 5000
|
||
});
|
||
|
||
const data = await response.json();
|
||
return data;
|
||
} catch (error) {
|
||
return { error: error.message };
|
||
}
|
||
}
|
||
|
||
function formatTimestamp() {
|
||
return new Date().toISOString().substr(11, 8); // HH:MM:SS
|
||
}
|
||
|
||
function formatDuration(ms) {
|
||
if (ms < 1000) return `${ms}ms`;
|
||
if (ms < 60000) return `${(ms/1000).toFixed(1)}s`;
|
||
return `${(ms/60000).toFixed(1)}m`;
|
||
}
|
||
|
||
async function runTest() {
|
||
console.log(`🧪 Starting Signer Reconnection Test`);
|
||
console.log(`📍 API Base: ${API_BASE}`);
|
||
console.log(`⏱️ Test Duration: ${formatDuration(TEST_DURATION)}`);
|
||
console.log(`🔄 Check Interval: ${formatDuration(CHECK_INTERVAL)}`);
|
||
console.log(`\n${'='.repeat(80)}\n`);
|
||
|
||
const startTime = Date.now();
|
||
let lastState = null;
|
||
let stateChanges = [];
|
||
let totalChecks = 0;
|
||
let healthyChecks = 0;
|
||
let errors = [];
|
||
|
||
console.log(`Time | State | Attempts | Last Connected | Details`);
|
||
console.log(`${'-'.repeat(80)}`);
|
||
|
||
const testInterval = setInterval(async () => {
|
||
const checkTime = Date.now();
|
||
const health = await checkSignerHealth();
|
||
totalChecks++;
|
||
|
||
const timestamp = formatTimestamp();
|
||
|
||
if (health.error) {
|
||
errors.push({ time: checkTime, error: health.error });
|
||
console.log(`${timestamp} | ❌ ERROR | - | - | ${health.error}`);
|
||
} else {
|
||
healthyChecks++;
|
||
|
||
const state = health.state || 'unknown';
|
||
const attempts = health.reconnectAttempts || 0;
|
||
const lastConnected = health.lastConnected ?
|
||
formatDuration(checkTime - health.lastConnected) + ' ago' :
|
||
'never';
|
||
const lastError = health.lastError ? ` (${health.lastError})` : '';
|
||
|
||
// Track state changes
|
||
if (lastState !== state) {
|
||
stateChanges.push({
|
||
time: checkTime,
|
||
from: lastState,
|
||
to: state,
|
||
duration: lastState ? checkTime - stateChanges[stateChanges.length - 1]?.time : 0
|
||
});
|
||
lastState = state;
|
||
}
|
||
|
||
const stateIcon = {
|
||
'connected': '✅',
|
||
'connecting': '🔄',
|
||
'reconnecting': '🔄',
|
||
'disconnected': '🔌',
|
||
'failed': '❌'
|
||
}[state] || '❓';
|
||
|
||
console.log(`${timestamp} | ${stateIcon} ${state.padEnd(10)} | ${attempts.toString().padStart(8)} | ${lastConnected.padEnd(14)} | ${lastError}`);
|
||
}
|
||
|
||
// Check if test duration is over
|
||
if (Date.now() - startTime >= TEST_DURATION) {
|
||
clearInterval(testInterval);
|
||
await printTestSummary(startTime, totalChecks, healthyChecks, errors, stateChanges);
|
||
}
|
||
}, CHECK_INTERVAL);
|
||
|
||
// Handle Ctrl+C gracefully
|
||
process.on('SIGINT', async () => {
|
||
console.log('\n\n⏹️ Test interrupted by user');
|
||
clearInterval(testInterval);
|
||
await printTestSummary(startTime, totalChecks, healthyChecks, errors, stateChanges);
|
||
process.exit(0);
|
||
});
|
||
|
||
// Optional: Test force reconnection after 30 seconds
|
||
setTimeout(async () => {
|
||
console.log(`\n${formatTimestamp()} | 🔧 TESTING | - | - | Forcing reconnection...`);
|
||
const result = await forceReconnect();
|
||
if (result.error) {
|
||
console.log(`${formatTimestamp()} | ❌ ERROR | - | - | Force reconnect failed: ${result.error}`);
|
||
} else {
|
||
console.log(`${formatTimestamp()} | 🔧 TESTING | - | - | Force reconnection initiated`);
|
||
}
|
||
}, 30000);
|
||
}
|
||
|
||
async function printTestSummary(startTime, totalChecks, healthyChecks, errors, stateChanges) {
|
||
const duration = Date.now() - startTime;
|
||
const successRate = totalChecks > 0 ? (healthyChecks / totalChecks * 100).toFixed(1) : 0;
|
||
|
||
console.log(`\n${'='.repeat(80)}`);
|
||
console.log(`📊 Test Summary`);
|
||
console.log(`${'='.repeat(80)}`);
|
||
console.log(`⏱️ Total Duration: ${formatDuration(duration)}`);
|
||
console.log(`📈 Health Checks: ${healthyChecks}/${totalChecks} (${successRate}% success)`);
|
||
console.log(`🔄 State Changes: ${stateChanges.length}`);
|
||
console.log(`❌ Errors: ${errors.length}`);
|
||
|
||
if (stateChanges.length > 0) {
|
||
console.log(`\n📋 State Change Timeline:`);
|
||
stateChanges.forEach((change, i) => {
|
||
const time = formatTimestamp(new Date(change.time));
|
||
const duration = change.duration ? ` (${formatDuration(change.duration)})` : '';
|
||
console.log(` ${time} | ${change.from || 'initial'} → ${change.to}${duration}`);
|
||
});
|
||
}
|
||
|
||
if (errors.length > 0) {
|
||
console.log(`\n❌ Error Summary:`);
|
||
const errorCounts = {};
|
||
errors.forEach(error => {
|
||
errorCounts[error.error] = (errorCounts[error.error] || 0) + 1;
|
||
});
|
||
Object.entries(errorCounts).forEach(([error, count]) => {
|
||
console.log(` ${count}x: ${error}`);
|
||
});
|
||
}
|
||
|
||
// Recommendations
|
||
console.log(`\n💡 Recommendations:`);
|
||
if (successRate < 95) {
|
||
console.log(` ⚠️ Low success rate (${successRate}%) - check signer service health`);
|
||
}
|
||
if (stateChanges.filter(c => c.to === 'connected').length === 0) {
|
||
console.log(` ⚠️ Never achieved connected state - check configuration`);
|
||
}
|
||
if (errors.length > totalChecks * 0.1) {
|
||
console.log(` ⚠️ High error rate - check network connectivity`);
|
||
}
|
||
if (stateChanges.length > 10) {
|
||
console.log(` ⚠️ Frequent state changes - possible connection instability`);
|
||
}
|
||
|
||
if (successRate >= 95 && stateChanges.some(c => c.to === 'connected')) {
|
||
console.log(` ✅ Connection resilience looks good!`);
|
||
}
|
||
|
||
console.log(`\n🔗 Manual Commands:`);
|
||
console.log(` Health Check: curl ${API_BASE}/api/v1/process/health/signer`);
|
||
console.log(` Force Reconnect: curl -X POST ${API_BASE}/api/v1/process/admin/signer/reconnect`);
|
||
}
|
||
|
||
function formatTimestamp(date = new Date()) {
|
||
return date.toISOString().substr(11, 8);
|
||
}
|
||
|
||
// Run the test
|
||
runTest().catch(error => {
|
||
console.error('❌ Test failed:', error.message);
|
||
process.exit(1);
|
||
});
|