/** * Performance Test for Hot-Reload Configuration * * Validates that configuration hot-reload operations meet the <100ms performance requirement * and that service remains available during configuration updates. */ import { CredentialManager } from '@/core/credential-manager/CredentialManager' import { ConfigLoader } from '@/core/credential-manager/ConfigLoader' import { Platform } from '@/types/credential' import fs from 'fs/promises' import path from 'path' import { tmpdir } from 'os' describe('Hot-Reload Performance Test', () => { let credentialManager: CredentialManager let configLoader: ConfigLoader let tempConfigPath: string let tempDir: string beforeEach(async () => { // Create temporary directory for test configs tempDir = await fs.mkdtemp(path.join(tmpdir(), 'hot-reload-perf-test-')) tempConfigPath = path.join(tempDir, 'performance-config.json') // Initial small configuration const initialConfig = { accounts: [ { id: 'initial-perf-account', name: 'Initial Performance Account', platform: Platform.PACIFICA, enabled: true, credentials: { type: 'ed25519' as const, privateKey: 'f26670e2ca334117f8859f9f32e50251641953a30b54f6ffcf82db836cfdfea5' } } ], hedging: { platforms: { [Platform.PACIFICA]: { enabled: true, primaryAccounts: ['initial-perf-account'], backupAccounts: [], loadBalanceStrategy: 'round-robin', healthCheckInterval: 30000, failoverThreshold: 3 } }, hedging: { enableCrossplatformBalancing: false, maxAccountsPerPlatform: 5, reservationTimeoutMs: 60000 } } } await fs.writeFile(tempConfigPath, JSON.stringify(initialConfig, null, 2)) credentialManager = new CredentialManager() configLoader = new ConfigLoader() await credentialManager.loadConfigurationFromFile(tempConfigPath, { enableHotReload: true, watchDebounceMs: 50 // Faster for testing }) }) afterEach(async () => { await credentialManager.shutdown() // Clean up temp files try { await fs.rm(tempDir, { recursive: true }) } catch (error) { // Ignore cleanup errors } }) describe('Single Configuration Reload Performance', () => { test('should reload small configuration within 100ms', async () => { const updatedConfig = { accounts: [ { id: 'updated-perf-account', name: 'Updated Performance Account', platform: Platform.PACIFICA, enabled: true, credentials: { type: 'ed25519' as const, privateKey: 'f26670e2ca334117f8859f9f32e50251641953a30b54f6ffcf82db836cfdfea5' } } ], hedging: { platforms: { [Platform.PACIFICA]: { enabled: true, primaryAccounts: ['updated-perf-account'], backupAccounts: [], loadBalanceStrategy: 'round-robin', healthCheckInterval: 30000, failoverThreshold: 3 } }, hedging: { enableCrossplatformBalancing: false, maxAccountsPerPlatform: 5, reservationTimeoutMs: 60000 } } } const startTime = performance.now() await fs.writeFile(tempConfigPath, JSON.stringify(updatedConfig, null, 2)) // Wait for reload to complete let reloaded = false const maxWaitTime = 100 const checkInterval = 5 for (let elapsed = 0; elapsed < maxWaitTime; elapsed += checkInterval) { await new Promise(resolve => setTimeout(resolve, checkInterval)) const account = credentialManager.getAccount('updated-perf-account') if (account) { reloaded = true break } } const reloadTime = performance.now() - startTime expect(reloaded).toBe(true) expect(reloadTime).toBeLessThan(100) // Performance requirement }) test('should reload medium-sized configuration within 100ms', async () => { // Configuration with 10 accounts across 3 platforms const mediumConfig = { accounts: Array.from({ length: 10 }, (_, i) => ({ id: `perf-account-${i}`, name: `Performance Account ${i}`, platform: [Platform.PACIFICA, Platform.ASTER, Platform.BINANCE][i % 3], enabled: true, credentials: i % 3 === 0 ? { type: 'ed25519' as const, privateKey: 'f26670e2ca334117f8859f9f32e50251641953a30b54f6ffcf82db836cfdfea5' } : i % 3 === 1 ? { type: 'secp256k1' as const, privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' } : { type: 'hmac' as const, apiKey: `api-key-${i}`, secretKey: `secret-key-${i}` } })), hedging: { platforms: { [Platform.PACIFICA]: { enabled: true, primaryAccounts: ['perf-account-0', 'perf-account-3', 'perf-account-6'], backupAccounts: ['perf-account-9'], loadBalanceStrategy: 'round-robin', healthCheckInterval: 30000, failoverThreshold: 3 }, [Platform.ASTER]: { enabled: true, primaryAccounts: ['perf-account-1', 'perf-account-4', 'perf-account-7'], backupAccounts: [], loadBalanceStrategy: 'weighted', healthCheckInterval: 30000, failoverThreshold: 3 }, [Platform.BINANCE]: { enabled: true, primaryAccounts: ['perf-account-2', 'perf-account-5', 'perf-account-8'], backupAccounts: [], loadBalanceStrategy: 'least-used', healthCheckInterval: 30000, failoverThreshold: 3 } }, hedging: { enableCrossplatformBalancing: true, maxAccountsPerPlatform: 10, reservationTimeoutMs: 60000 } } } const startTime = performance.now() await fs.writeFile(tempConfigPath, JSON.stringify(mediumConfig, null, 2)) // Wait for reload to complete let reloaded = false const maxWaitTime = 100 for (let elapsed = 0; elapsed < maxWaitTime; elapsed += 5) { await new Promise(resolve => setTimeout(resolve, 5)) const accounts = credentialManager.getAllAccounts() if (accounts.length === 10) { reloaded = true break } } const reloadTime = performance.now() - startTime expect(reloaded).toBe(true) expect(reloadTime).toBeLessThan(100) // Performance requirement // Verify all accounts were loaded const accounts = credentialManager.getAllAccounts() expect(accounts).toHaveLength(10) }) test('should handle large configuration efficiently', async () => { // Configuration with 50 accounts (stress test) const largeConfig = { accounts: Array.from({ length: 50 }, (_, i) => ({ id: `large-perf-account-${i}`, name: `Large Performance Account ${i}`, platform: [Platform.PACIFICA, Platform.ASTER, Platform.BINANCE][i % 3], enabled: true, credentials: i % 3 === 0 ? { type: 'ed25519' as const, privateKey: 'f26670e2ca334117f8859f9f32e50251641953a30b54f6ffcf82db836cfdfea5' } : i % 3 === 1 ? { type: 'secp256k1' as const, privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' } : { type: 'hmac' as const, apiKey: `large-api-key-${i}`, secretKey: `large-secret-key-${i}` } })), hedging: { platforms: { [Platform.PACIFICA]: { enabled: true, primaryAccounts: Array.from({ length: 8 }, (_, i) => `large-perf-account-${i * 3}`), backupAccounts: Array.from({ length: 4 }, (_, i) => `large-perf-account-${30 + i * 3}`), loadBalanceStrategy: 'round-robin', healthCheckInterval: 30000, failoverThreshold: 3 }, [Platform.ASTER]: { enabled: true, primaryAccounts: Array.from({ length: 8 }, (_, i) => `large-perf-account-${1 + i * 3}`), backupAccounts: Array.from({ length: 4 }, (_, i) => `large-perf-account-${31 + i * 3}`), loadBalanceStrategy: 'weighted', healthCheckInterval: 30000, failoverThreshold: 3 }, [Platform.BINANCE]: { enabled: true, primaryAccounts: Array.from({ length: 8 }, (_, i) => `large-perf-account-${2 + i * 3}`), backupAccounts: Array.from({ length: 4 }, (_, i) => `large-perf-account-${32 + i * 3}`), loadBalanceStrategy: 'least-used', healthCheckInterval: 30000, failoverThreshold: 3 } }, hedging: { enableCrossplatformBalancing: true, maxAccountsPerPlatform: 20, reservationTimeoutMs: 60000 } } } const startTime = performance.now() await fs.writeFile(tempConfigPath, JSON.stringify(largeConfig, null, 2)) // Wait for reload to complete (allow more time for large config) let reloaded = false const maxWaitTime = 200 // Allow 200ms for large config for (let elapsed = 0; elapsed < maxWaitTime; elapsed += 10) { await new Promise(resolve => setTimeout(resolve, 10)) const accounts = credentialManager.getAllAccounts() if (accounts.length === 50) { reloaded = true break } } const reloadTime = performance.now() - startTime expect(reloaded).toBe(true) expect(reloadTime).toBeLessThan(200) // Allow more time for large config // Verify all accounts were loaded const accounts = credentialManager.getAllAccounts() expect(accounts).toHaveLength(50) }) }) describe('Rapid Configuration Changes Performance', () => { test('should handle rapid successive configuration changes', async () => { const configs = Array.from({ length: 5 }, (_, i) => ({ accounts: [ { id: `rapid-account-${i}`, name: `Rapid Account ${i}`, platform: Platform.PACIFICA, enabled: true, credentials: { type: 'ed25519' as const, privateKey: 'f26670e2ca334117f8859f9f32e50251641953a30b54f6ffcf82db836cfdfea5' } } ], hedging: { platforms: { [Platform.PACIFICA]: { enabled: true, primaryAccounts: [`rapid-account-${i}`], backupAccounts: [], loadBalanceStrategy: 'round-robin', healthCheckInterval: 30000, failoverThreshold: 3 } }, hedging: { enableCrossplatformBalancing: false, maxAccountsPerPlatform: 5, reservationTimeoutMs: 60000 } } })) const startTime = performance.now() // Rapid succession of changes for (const config of configs) { await fs.writeFile(tempConfigPath, JSON.stringify(config, null, 2)) await new Promise(resolve => setTimeout(resolve, 20)) // Small delay } // Wait for final state let finalStateReached = false const maxWaitTime = 200 for (let elapsed = 0; elapsed < maxWaitTime; elapsed += 10) { await new Promise(resolve => setTimeout(resolve, 10)) const account = credentialManager.getAccount('rapid-account-4') if (account) { finalStateReached = true break } } const totalTime = performance.now() - startTime expect(finalStateReached).toBe(true) expect(totalTime).toBeLessThan(300) // All changes should complete within 300ms // Verify final state const accounts = credentialManager.getAllAccounts() expect(accounts).toHaveLength(1) expect(accounts[0].id).toBe('rapid-account-4') }) test('should maintain service availability during rapid changes', async () => { const message = new TextEncoder().encode('availability test during changes') let serviceAvailability = 0 let totalChecks = 0 // Start making configuration changes in background const configChangePromise = (async () => { for (let i = 0; i < 10; i++) { const config = { accounts: [ { id: 'service-test-account', name: `Service Test Account ${i}`, platform: Platform.PACIFICA, enabled: true, credentials: { type: 'ed25519' as const, privateKey: 'f26670e2ca334117f8859f9f32e50251641953a30b54f6ffcf82db836cfdfea5' } } ], hedging: { platforms: { [Platform.PACIFICA]: { enabled: true, primaryAccounts: ['service-test-account'], backupAccounts: [], loadBalanceStrategy: 'round-robin', healthCheckInterval: 30000, failoverThreshold: 3 } }, hedging: { enableCrossplatformBalancing: false, maxAccountsPerPlatform: 5, reservationTimeoutMs: 60000 } } } await fs.writeFile(tempConfigPath, JSON.stringify(config, null, 2)) await new Promise(resolve => setTimeout(resolve, 30)) } })() // Continuously check service availability const availabilityCheckPromise = (async () => { const startTime = Date.now() while (Date.now() - startTime < 500) { // Check for 500ms try { const result = await credentialManager.sign('service-test-account', message) if (result.success) { serviceAvailability++ } } catch (error) { // Service unavailable during this check } totalChecks++ await new Promise(resolve => setTimeout(resolve, 25)) } })() await Promise.all([configChangePromise, availabilityCheckPromise]) // Service should be available most of the time (>80%) const availabilityRate = serviceAvailability / totalChecks expect(availabilityRate).toBeGreaterThan(0.8) expect(totalChecks).toBeGreaterThan(10) // Should have made multiple checks }) }) describe('File System Performance', () => { test('should efficiently detect file changes', async () => { const changeCount = 10 const detectedChanges: number[] = [] // Monitor how quickly changes are detected for (let i = 0; i < changeCount; i++) { const config = { accounts: [ { id: `detection-account-${i}`, name: `Detection Account ${i}`, platform: Platform.PACIFICA, enabled: true, credentials: { type: 'ed25519' as const, privateKey: 'f26670e2ca334117f8859f9f32e50251641953a30b54f6ffcf82db836cfdfea5' } } ], hedging: { platforms: { [Platform.PACIFICA]: { enabled: true, primaryAccounts: [`detection-account-${i}`], backupAccounts: [], loadBalanceStrategy: 'round-robin', healthCheckInterval: 30000, failoverThreshold: 3 } }, hedging: { enableCrossplatformBalancing: false, maxAccountsPerPlatform: 5, reservationTimeoutMs: 60000 } } } const writeStartTime = performance.now() await fs.writeFile(tempConfigPath, JSON.stringify(config, null, 2)) // Wait for detection let detected = false const maxDetectionTime = 100 for (let elapsed = 0; elapsed < maxDetectionTime; elapsed += 5) { await new Promise(resolve => setTimeout(resolve, 5)) const account = credentialManager.getAccount(`detection-account-${i}`) if (account) { detected = true detectedChanges.push(performance.now() - writeStartTime) break } } expect(detected).toBe(true) } // All changes should be detected quickly expect(detectedChanges).toHaveLength(changeCount) const averageDetectionTime = detectedChanges.reduce((a, b) => a + b, 0) / changeCount const maxDetectionTime = Math.max(...detectedChanges) expect(averageDetectionTime).toBeLessThan(75) // Average < 75ms expect(maxDetectionTime).toBeLessThan(100) // Max < 100ms }) test('should handle file system errors gracefully', async () => { // Create invalid JSON to test error handling performance const invalidConfigs = [ '{ invalid json', '{ "accounts": [{ "invalid": }', '{ "accounts": [], "hedging": { missing bracket', '', // Empty file 'not json at all' ] for (const invalidConfig of invalidConfigs) { const startTime = performance.now() await fs.writeFile(tempConfigPath, invalidConfig) // Wait a bit to see if system handles error await new Promise(resolve => setTimeout(resolve, 100)) const errorHandlingTime = performance.now() - startTime // Should handle errors quickly without crashing expect(errorHandlingTime).toBeLessThan(150) // System should still be responsive const accounts = credentialManager.getAllAccounts() expect(Array.isArray(accounts)).toBe(true) // Should not crash } }) }) describe('Memory Performance During Reloads', () => { test('should not leak memory during repeated reloads', async () => { const initialMemory = process.memoryUsage() // Perform many configuration reloads for (let i = 0; i < 20; i++) { const config = { accounts: [ { id: `memory-test-${i}`, name: `Memory Test ${i}`, platform: Platform.PACIFICA, enabled: true, credentials: { type: 'ed25519' as const, privateKey: 'f26670e2ca334117f8859f9f32e50251641953a30b54f6ffcf82db836cfdfea5' } } ], hedging: { platforms: { [Platform.PACIFICA]: { enabled: true, primaryAccounts: [`memory-test-${i}`], backupAccounts: [], loadBalanceStrategy: 'round-robin', healthCheckInterval: 30000, failoverThreshold: 3 } }, hedging: { enableCrossplatformBalancing: false, maxAccountsPerPlatform: 5, reservationTimeoutMs: 60000 } } } await fs.writeFile(tempConfigPath, JSON.stringify(config, null, 2)) await new Promise(resolve => setTimeout(resolve, 60)) // Wait for reload } // Force garbage collection if available if (global.gc) { global.gc() await new Promise(resolve => setTimeout(resolve, 100)) } const finalMemory = process.memoryUsage() // Memory increase should be reasonable (less than 5MB) const heapIncrease = finalMemory.heapUsed - initialMemory.heapUsed expect(heapIncrease).toBeLessThan(5 * 1024 * 1024) // 5MB }) test('should maintain performance under memory pressure', async () => { // Create some memory pressure const memoryPressure: string[] = [] for (let i = 0; i < 1000; i++) { memoryPressure.push('x'.repeat(1000)) // 1MB total } const config = { accounts: [ { id: 'pressure-test-account', name: 'Pressure Test Account', platform: Platform.PACIFICA, enabled: true, credentials: { type: 'ed25519' as const, privateKey: 'f26670e2ca334117f8859f9f32e50251641953a30b54f6ffcf82db836cfdfea5' } } ], hedging: { platforms: { [Platform.PACIFICA]: { enabled: true, primaryAccounts: ['pressure-test-account'], backupAccounts: [], loadBalanceStrategy: 'round-robin', healthCheckInterval: 30000, failoverThreshold: 3 } }, hedging: { enableCrossplatformBalancing: false, maxAccountsPerPlatform: 5, reservationTimeoutMs: 60000 } } } const startTime = performance.now() await fs.writeFile(tempConfigPath, JSON.stringify(config, null, 2)) // Wait for reload let reloaded = false const maxWaitTime = 150 // Allow extra time under pressure for (let elapsed = 0; elapsed < maxWaitTime; elapsed += 10) { await new Promise(resolve => setTimeout(resolve, 10)) const account = credentialManager.getAccount('pressure-test-account') if (account) { reloaded = true break } } const reloadTime = performance.now() - startTime expect(reloaded).toBe(true) expect(reloadTime).toBeLessThan(150) // Should still meet requirements under pressure // Clean up memory pressure memoryPressure.length = 0 }) }) })