UNPKG

@ufdevsllc/authme2.0

Version:

SDK for license management and remote monitoring with automatic system tracking, license validation, and remote control capabilities

341 lines (265 loc) 12.4 kB
/** * Tests for Error Handler */ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import ErrorHandler from '../error-handler.js'; describe('ErrorHandler', () => { let errorHandler; beforeEach(() => { errorHandler = new ErrorHandler(); // Mock console methods to avoid noise in tests vi.spyOn(console, 'error').mockImplementation(() => { }); vi.spyOn(console, 'warn').mockImplementation(() => { }); vi.spyOn(console, 'log').mockImplementation(() => { }); }); afterEach(async () => { await errorHandler.cleanup(); vi.restoreAllMocks(); }); describe('Error Handling', () => { it('should handle basic errors', async () => { const error = new Error('Test error'); const context = { component: 'test', operation: 'test-op' }; const result = await errorHandler.handleError(error, context); expect(result).toMatchObject({ handled: true, severity: 'error' }); expect(result.errorId).toBeDefined(); expect(result.timestamp).toBeInstanceOf(Date); }); it('should handle errors with different severity levels', async () => { const error = new Error('Warning error'); const context = { component: 'test', operation: 'test-op' }; const result = await errorHandler.handleError(error, context, errorHandler.logLevels.WARN); expect(result.severity).toBe('warn'); }); it('should sanitize sensitive context data', async () => { const error = new Error('Test error'); const context = { component: 'test', operation: 'test-op', password: 'secret123', apiKey: 'key123', normalData: 'visible' }; const result = await errorHandler.handleError(error, context); expect(result.handled).toBe(true); // Sensitive data should be redacted in logs }); }); describe('License Error Handling', () => { it('should handle license validation errors', async () => { const error = new Error('License not found'); const licenseKey = 'test-license-key'; const result = await errorHandler.handleLicenseError(error, licenseKey); expect(result).toMatchObject({ errorType: 'LICENSE_NOT_FOUND', canRetry: false }); expect(result.userMessage).toContain('license key was not found'); expect(result.technicalMessage).toBe('License not found'); }); it('should handle expired license errors', async () => { const error = new Error('License expired on 2023-01-01'); const licenseKey = 'test-license-key'; const result = await errorHandler.handleLicenseError(error, licenseKey); expect(result.errorType).toBe('LICENSE_EXPIRED'); expect(result.userMessage).toContain('license has expired'); }); it('should handle distribution limit errors', async () => { const error = new Error('Distribution limit exceeded'); const licenseKey = 'test-license-key'; const result = await errorHandler.handleLicenseError(error, licenseKey); expect(result.errorType).toBe('DISTRIBUTION_LIMIT_EXCEEDED'); expect(result.userMessage).toContain('distribution limit has been reached'); }); it('should handle connection errors as retryable', async () => { const error = new Error('Connection failed'); const licenseKey = 'test-license-key'; const result = await errorHandler.handleLicenseError(error, licenseKey); expect(result.errorType).toBe('CONNECTION_ERROR'); expect(result.canRetry).toBe(true); }); }); describe('Database Error Handling', () => { it('should handle retryable database errors', async () => { const error = new Error('Connection timeout'); error.name = 'MongoTimeoutError'; const operation = { type: 'find', collection: 'test' }; const result = await errorHandler.handleDatabaseError(error, operation, 1); expect(result.isRetryable).toBe(true); expect(result.shouldRetry).toBe(true); expect(result.retryDelay).toBeGreaterThan(0); }); it('should handle non-retryable database errors', async () => { const error = new Error('Validation failed'); error.name = 'ValidationError'; const operation = { type: 'insert', collection: 'test' }; const result = await errorHandler.handleDatabaseError(error, operation, 1); expect(result.isRetryable).toBe(false); expect(result.shouldRetry).toBe(false); }); it('should stop retrying after max attempts', async () => { const error = new Error('Connection timeout'); error.name = 'MongoTimeoutError'; const operation = { type: 'find', collection: 'test' }; const result = await errorHandler.handleDatabaseError(error, operation, 3); expect(result.shouldRetry).toBe(false); expect(result.attemptNumber).toBe(3); }); }); describe('Retry Logic', () => { it('should execute operation successfully on first try', async () => { const operation = vi.fn().mockResolvedValue('success'); const context = { component: 'test', operation: 'test-op' }; const result = await errorHandler.executeWithRetry(operation, context); expect(result).toBe('success'); expect(operation).toHaveBeenCalledTimes(1); }); it('should retry failed operations', async () => { const operation = vi.fn() .mockRejectedValueOnce(new Error('ECONNRESET')) .mockResolvedValue('success'); const context = { component: 'test', operation: 'test-op' }; const result = await errorHandler.executeWithRetry(operation, context, { maxRetries: 2 }); expect(result).toBe('success'); expect(operation).toHaveBeenCalledTimes(2); }); it('should fail after max retries', async () => { const operation = vi.fn().mockRejectedValue(new Error('ECONNRESET')); const context = { component: 'test', operation: 'test-op' }; await expect( errorHandler.executeWithRetry(operation, context, { maxRetries: 2 }) ).rejects.toThrow('ECONNRESET'); expect(operation).toHaveBeenCalledTimes(3); // Initial + 2 retries }); it('should not retry non-retryable errors', async () => { const operation = vi.fn().mockRejectedValue(new Error('Validation failed')); const context = { component: 'test', operation: 'test-op' }; await expect( errorHandler.executeWithRetry(operation, context, { maxRetries: 2 }) ).rejects.toThrow('Validation failed'); expect(operation).toHaveBeenCalledTimes(1); }); }); describe('Input Validation', () => { it('should validate string inputs', () => { const result = errorHandler.validateInput('test', { type: 'string', required: true, minLength: 2, maxLength: 10 }); expect(result.isValid).toBe(true); expect(result.errors).toHaveLength(0); }); it('should fail validation for invalid string length', () => { const result = errorHandler.validateInput('a', { type: 'string', required: true, minLength: 2 }); expect(result.isValid).toBe(false); expect(result.errors).toContain('Minimum length is 2, got 1'); }); it('should validate required fields', () => { const result = errorHandler.validateInput(null, { type: 'string', required: true }); expect(result.isValid).toBe(false); expect(result.errors).toContain('Field is required'); }); it('should validate number ranges', () => { const result = errorHandler.validateInput(5, { type: 'number', min: 1, max: 10 }); expect(result.isValid).toBe(true); expect(result.errors).toHaveLength(0); }); it('should fail validation for numbers out of range', () => { const result = errorHandler.validateInput(15, { type: 'number', min: 1, max: 10 }); expect(result.isValid).toBe(false); expect(result.errors).toContain('Maximum value is 10, got 15'); }); it('should detect potentially unsafe content', () => { const result = errorHandler.validateInput('<script>alert("xss")</script>', { type: 'string' }); expect(result.warnings).toContain('Potentially unsafe content detected'); }); it('should sanitize data', () => { const result = errorHandler.validateInput('<script>test</script>', { type: 'string' }); expect(result.sanitizedData).not.toContain('<script>'); }); }); describe('Circuit Breaker', () => { it('should track error frequency', async () => { const error = new Error('Test error'); const context = { component: 'test-component', operation: 'test-op' }; // Generate multiple errors to trigger circuit breaker for (let i = 0; i < 12; i++) { await errorHandler.handleError(error, context); } const stats = errorHandler.getErrorStatistics(); expect(stats.circuitBreakers['test-component']).toBeDefined(); expect(stats.circuitBreakers['test-component'].isOpen).toBe(true); }); }); describe('Logging', () => { it('should log info messages', async () => { await errorHandler.logInfo('Test info message', { component: 'test', operation: 'test-op' }); // Should not throw and should call console.log expect(console.log).toHaveBeenCalled(); }); it('should log warning messages', async () => { await errorHandler.logWarn('Test warning message', { component: 'test', operation: 'test-op' }); expect(console.warn).toHaveBeenCalled(); }); it('should log debug messages', async () => { await errorHandler.logDebug('Test debug message', { component: 'test', operation: 'test-op' }); expect(console.log).toHaveBeenCalled(); }); }); describe('Error Statistics', () => { it('should provide error statistics', async () => { const error1 = new Error('Error 1'); const error2 = new Error('Error 2'); await errorHandler.handleError(error1, { component: 'comp1', operation: 'op1' }); await errorHandler.handleError(error2, { component: 'comp1', operation: 'op2' }); await errorHandler.handleError(error1, { component: 'comp2', operation: 'op1' }); const stats = errorHandler.getErrorStatistics(); expect(stats.totalErrors).toBe(3); expect(stats.errorsByComponent.comp1).toBe(2); expect(stats.errorsByComponent.comp2).toBe(1); expect(stats.timestamp).toBeInstanceOf(Date); }); }); describe('Cleanup', () => { it('should cleanup resources', async () => { // Add some errors to track await errorHandler.handleError(new Error('Test'), { component: 'test' }); await errorHandler.cleanup(); const stats = errorHandler.getErrorStatistics(); expect(stats.totalErrors).toBe(0); }); }); });