/** * Unit Tests for AccountStatus * * Tests the AccountStatus monitoring implementation in isolation. */ import { AccountStatus, AccountState, AccountHealthMetrics, AccountBalanceInfo } from '@/core/credential-manager/AccountStatus' import { Platform } from '@/types/credential' describe('AccountStatus Unit Tests', () => { let accountStatus: AccountStatus beforeEach(() => { accountStatus = new AccountStatus('test-account', Platform.PACIFICA, { alias: 'Test Account', description: 'Account for unit testing' }) }) describe('Initialization', () => { test('should initialize with correct properties', () => { expect(accountStatus.accountId).toBe('test-account') expect(accountStatus.platform).toBe(Platform.PACIFICA) expect(accountStatus.createdAt).toBeInstanceOf(Date) }) test('should start in LOADING state', () => { expect(accountStatus.getState()).toBe(AccountState.LOADING) }) test('should initialize with healthy metrics', () => { const metrics = accountStatus.getHealthMetrics() expect(metrics.successCount).toBe(0) expect(metrics.failureCount).toBe(0) expect(metrics.totalOperations).toBe(0) expect(metrics.healthScore).toBe(1.0) expect(metrics.consecutiveFailures).toBe(0) }) test('should store metadata correctly', () => { const metadata = accountStatus.getMetadata() expect(metadata.alias).toBe('Test Account') expect(metadata.description).toBe('Account for unit testing') }) test('should record initial state change', () => { const stateHistory = accountStatus.getStateHistory() expect(stateHistory).toHaveLength(1) expect(stateHistory[0].state).toBe(AccountState.LOADING) expect(stateHistory[0].reason).toBe('Account status initialized') }) }) describe('State Management', () => { test('should change state correctly', () => { accountStatus.setState(AccountState.ACTIVE, 'Account is ready') expect(accountStatus.getState()).toBe(AccountState.ACTIVE) const stateHistory = accountStatus.getStateHistory() expect(stateHistory).toHaveLength(2) expect(stateHistory[1].state).toBe(AccountState.ACTIVE) expect(stateHistory[1].reason).toBe('Account is ready') }) test('should not duplicate state changes', () => { accountStatus.setState(AccountState.ACTIVE) accountStatus.setState(AccountState.ACTIVE) // Same state again const stateHistory = accountStatus.getStateHistory() expect(stateHistory).toHaveLength(2) // Should not add duplicate }) test('should check if account is active', () => { expect(accountStatus.isActive()).toBe(false) // LOADING state accountStatus.setState(AccountState.ACTIVE) expect(accountStatus.isActive()).toBe(true) accountStatus.setState(AccountState.ERROR) expect(accountStatus.isActive()).toBe(false) }) test('should check if account is available', () => { expect(accountStatus.isAvailable()).toBe(false) // LOADING state accountStatus.setState(AccountState.ACTIVE) expect(accountStatus.isAvailable()).toBe(true) accountStatus.setState(AccountState.WARNING) expect(accountStatus.isAvailable()).toBe(true) accountStatus.setState(AccountState.ERROR) expect(accountStatus.isAvailable()).toBe(false) }) }) describe('Health Monitoring', () => { test('should record successful operations', () => { accountStatus.recordSuccess(50) // 50ms response time const metrics = accountStatus.getHealthMetrics() expect(metrics.successCount).toBe(1) expect(metrics.totalOperations).toBe(1) expect(metrics.consecutiveFailures).toBe(0) expect(metrics.lastSuccess).toBeInstanceOf(Date) expect(metrics.averageResponseTime).toBe(50) expect(metrics.healthScore).toBe(1.0) }) test('should record failed operations', () => { const error = new Error('Test failure') accountStatus.recordFailure(error, 100) const metrics = accountStatus.getHealthMetrics() expect(metrics.failureCount).toBe(1) expect(metrics.totalOperations).toBe(1) expect(metrics.consecutiveFailures).toBe(1) expect(metrics.lastFailure).toBeInstanceOf(Date) expect(metrics.averageResponseTime).toBe(100) expect(metrics.healthScore).toBeLessThan(1.0) }) test('should calculate success rate correctly', () => { accountStatus.recordSuccess() accountStatus.recordSuccess() accountStatus.recordFailure(new Error('Test')) expect(accountStatus.getSuccessRate()).toBeCloseTo(2/3, 2) expect(accountStatus.getFailureRate()).toBeCloseTo(1/3, 2) }) test('should reset consecutive failures on success', () => { // Record some failures accountStatus.recordFailure(new Error('Failure 1')) accountStatus.recordFailure(new Error('Failure 2')) expect(accountStatus.getHealthMetrics().consecutiveFailures).toBe(2) // Record success - should reset consecutive failures accountStatus.recordSuccess() expect(accountStatus.getHealthMetrics().consecutiveFailures).toBe(0) }) test('should auto-disable after consecutive failures', () => { // Record 5 consecutive failures (threshold) for (let i = 0; i < 5; i++) { accountStatus.recordFailure(new Error(`Failure ${i + 1}`)) } expect(accountStatus.getState()).toBe(AccountState.ERROR) expect(accountStatus.getHealthMetrics().consecutiveFailures).toBe(5) }) test('should update average response time correctly', () => { accountStatus.recordSuccess(100) accountStatus.recordSuccess(200) const metrics = accountStatus.getHealthMetrics() // With weighted average (80% old, 20% new), expected: 100 * 0.8 + 200 * 0.2 = 120 expect(metrics.averageResponseTime).toBeCloseTo(120, 1) }) test('should update health score based on performance', () => { // Record mostly successful operations for (let i = 0; i < 9; i++) { accountStatus.recordSuccess() } accountStatus.recordFailure(new Error('Single failure')) const healthScore = accountStatus.getHealthMetrics().healthScore expect(healthScore).toBeGreaterThan(0.8) // Should be high with 90% success rate }) test('should penalize health score for slow response times', () => { // Record operations with slow response time accountStatus.recordSuccess(2000) // 2 seconds - slow const healthScore = accountStatus.getHealthMetrics().healthScore expect(healthScore).toBeLessThan(1.0) // Should be penalized for slow response }) test('should automatically update state based on health', () => { // Start with active state accountStatus.setState(AccountState.ACTIVE) // Record many failures to degrade health for (let i = 0; i < 10; i++) { accountStatus.recordFailure(new Error(`Failure ${i + 1}`)) } // Should automatically transition to ERROR state expect(accountStatus.getState()).toBe(AccountState.ERROR) }) }) describe('Balance Information', () => { test('should update balance information', () => { const balanceInfo: AccountBalanceInfo = { totalValue: 10000, positions: { BTC: 0.5, ETH: 2.0 }, lastUpdated: new Date(), currency: 'USD' } accountStatus.updateBalanceInfo(balanceInfo) const retrievedBalance = accountStatus.getBalanceInfo() expect(retrievedBalance).toEqual(expect.objectContaining({ totalValue: 10000, positions: { BTC: 0.5, ETH: 2.0 }, currency: 'USD' })) expect(retrievedBalance?.lastUpdated).toBeInstanceOf(Date) }) test('should detect stale balance information', () => { const oldBalance: AccountBalanceInfo = { totalValue: 5000, positions: {}, lastUpdated: new Date(Date.now() - 10 * 60 * 1000), // 10 minutes ago currency: 'USD' } accountStatus.updateBalanceInfo(oldBalance) expect(accountStatus.isBalanceStale(5 * 60 * 1000)).toBe(true) // 5 minute threshold expect(accountStatus.isBalanceStale(15 * 60 * 1000)).toBe(false) // 15 minute threshold }) test('should return undefined for missing balance info', () => { expect(accountStatus.getBalanceInfo()).toBeUndefined() expect(accountStatus.isBalanceStale()).toBe(true) }) }) describe('Activity Tracking', () => { test('should track last activity timestamp', () => { const beforeActivity = new Date() accountStatus.recordActivity() const afterActivity = new Date() const lastActivity = accountStatus.getLastActivity() expect(lastActivity.getTime()).toBeGreaterThanOrEqual(beforeActivity.getTime()) expect(lastActivity.getTime()).toBeLessThanOrEqual(afterActivity.getTime()) }) test('should detect idle accounts', () => { // Account should not be idle initially expect(accountStatus.isIdle(1000)).toBe(false) // Mock an old last activity time const oldTime = new Date(Date.now() - 15 * 60 * 1000) // 15 minutes ago accountStatus['lastActivity'] = oldTime expect(accountStatus.isIdle(10 * 60 * 1000)).toBe(true) // 10 minute threshold expect(accountStatus.isIdle(20 * 60 * 1000)).toBe(false) // 20 minute threshold }) test('should update activity on success/failure records', () => { const beforeTime = Date.now() accountStatus.recordSuccess() const lastActivity = accountStatus.getLastActivity() expect(lastActivity.getTime()).toBeGreaterThan(beforeTime) }) }) describe('State History and Diagnostics', () => { test('should maintain state history with timestamps', () => { accountStatus.setState(AccountState.ACTIVE, 'Initialized') accountStatus.setState(AccountState.WARNING, 'High latency') accountStatus.setState(AccountState.ERROR, 'Connection failed') const history = accountStatus.getStateHistory() expect(history).toHaveLength(4) // Including initial LOADING state expect(history[1].state).toBe(AccountState.ACTIVE) expect(history[2].state).toBe(AccountState.WARNING) expect(history[3].state).toBe(AccountState.ERROR) history.forEach(entry => { expect(entry.timestamp).toBeInstanceOf(Date) }) }) test('should limit state history size', () => { // Add more than 50 state changes for (let i = 0; i < 55; i++) { accountStatus.setState( i % 2 === 0 ? AccountState.ACTIVE : AccountState.WARNING, `Change ${i}` ) } const history = accountStatus.getStateHistory() expect(history.length).toBeLessThanOrEqual(50) }) test('should provide comprehensive diagnostics', () => { accountStatus.setState(AccountState.ACTIVE) accountStatus.recordSuccess(50) accountStatus.recordFailure(new Error('Test failure'), 100) const diagnostics = accountStatus.getDiagnostics() expect(diagnostics.accountId).toBe('test-account') expect(diagnostics.platform).toBe(Platform.PACIFICA) expect(diagnostics.currentState).toBe(AccountState.ACTIVE) expect(diagnostics.totalOperations).toBe(2) expect(diagnostics.successRate).toBe(0.5) expect(diagnostics.failureRate).toBe(0.5) expect(diagnostics.uptime).toBeGreaterThan(0) expect(diagnostics.timeSinceLastActivity).toBeGreaterThanOrEqual(0) expect(diagnostics.isActive).toBe(true) expect(diagnostics.isAvailable).toBe(true) expect(diagnostics.balanceStale).toBe(true) expect(diagnostics.recentStateChanges).toHaveLength(5) // Last 5 changes }) }) describe('Metadata Management', () => { test('should update metadata', () => { accountStatus.updateMetadata({ tags: ['production', 'primary'], priority: 1 }) const metadata = accountStatus.getMetadata() expect(metadata.alias).toBe('Test Account') // Original expect(metadata.description).toBe('Account for unit testing') // Original expect(metadata.tags).toEqual(['production', 'primary']) // New expect(metadata.priority).toBe(1) // New }) test('should handle maintenance window metadata', () => { accountStatus.updateMetadata({ maintenanceWindow: { start: '02:00', end: '04:00', timezone: 'UTC' } }) const metadata = accountStatus.getMetadata() expect(metadata.maintenanceWindow).toEqual({ start: '02:00', end: '04:00', timezone: 'UTC' }) }) }) describe('Health Check Integration', () => { test('should update health from external checks', () => { accountStatus.setState(AccountState.ACTIVE) // External health check fails accountStatus.updateHealth(false, { lastChecked: new Date() }) expect(accountStatus.getState()).toBe(AccountState.WARNING) // External health check passes accountStatus.updateHealth(true, { lastChecked: new Date() }) expect(accountStatus.getState()).toBe(AccountState.ACTIVE) }) test('should include health check details in metadata', () => { const healthDetails = { lastChecked: new Date(), apiResponse: 'OK', latency: 45 } accountStatus.updateHealth(true, healthDetails) const metadata = accountStatus.getMetadata() expect(metadata).toEqual(expect.objectContaining(healthDetails)) }) }) describe('Edge Cases and Error Handling', () => { test('should handle rapid state changes', () => { const states = [ AccountState.ACTIVE, AccountState.WARNING, AccountState.ERROR, AccountState.ACTIVE, AccountState.WARNING ] states.forEach((state, index) => { accountStatus.setState(state, `Rapid change ${index}`) }) expect(accountStatus.getState()).toBe(AccountState.WARNING) expect(accountStatus.getStateHistory().length).toBeGreaterThan(states.length) }) test('should handle very large numbers of operations', () => { // Simulate many operations for (let i = 0; i < 10000; i++) { if (i % 3 === 0) { accountStatus.recordFailure(new Error('Test'), Math.random() * 100) } else { accountStatus.recordSuccess(Math.random() * 50) } } const metrics = accountStatus.getHealthMetrics() expect(metrics.totalOperations).toBe(10000) expect(metrics.successCount).toBeGreaterThan(6000) expect(metrics.failureCount).toBeGreaterThan(3000) expect(metrics.healthScore).toBeGreaterThan(0) expect(metrics.averageResponseTime).toBeGreaterThan(0) }) test('should handle zero operations gracefully', () => { expect(accountStatus.getSuccessRate()).toBe(1) // No operations = 100% success expect(accountStatus.getFailureRate()).toBe(0) const metrics = accountStatus.getHealthMetrics() expect(metrics.healthScore).toBe(1.0) }) test('should handle extreme response times', () => { // Very fast response accountStatus.recordSuccess(0.1) // Very slow response accountStatus.recordSuccess(60000) // 1 minute const metrics = accountStatus.getHealthMetrics() expect(metrics.averageResponseTime).toBeGreaterThan(0) expect(metrics.healthScore).toBeLessThan(1.0) // Should be penalized for slow response }) test('should maintain consistency during concurrent updates', () => { // Simulate concurrent updates const promises = [] for (let i = 0; i < 100; i++) { promises.push(Promise.resolve().then(() => { if (i % 2 === 0) { accountStatus.recordSuccess(Math.random() * 100) } else { accountStatus.recordFailure(new Error(`Concurrent error ${i}`)) } })) } return Promise.all(promises).then(() => { const metrics = accountStatus.getHealthMetrics() expect(metrics.totalOperations).toBe(100) expect(metrics.successCount + metrics.failureCount).toBe(100) }) }) }) })