UNPKG

create-roadkit

Version:

Beautiful Next.js roadmap website generator with full-screen kanban boards, dark/light mode, and static export

371 lines (313 loc) 11.4 kB
/** * Unit Tests for Logger and ErrorRecovery * * Tests the logging framework and error recovery mechanisms */ import { test, expect, describe, beforeEach, afterEach } from 'bun:test'; import { join } from 'path'; import { existsSync } from 'fs'; import { mkdir, rm } from 'fs/promises'; import { Logger, LogLevel, ErrorRecovery, createLogger } from '../utils/logger.js'; const TEST_LOG_DIR = join(__dirname, '../../test-logs'); describe('Logger', () => { let logger: Logger; beforeEach(async () => { if (existsSync(TEST_LOG_DIR)) { await rm(TEST_LOG_DIR, { recursive: true, force: true }); } await mkdir(TEST_LOG_DIR, { recursive: true }); logger = new Logger({ enableConsole: false, // Disable console for testing enableFile: true, logDir: TEST_LOG_DIR, level: LogLevel.DEBUG, verbose: true }); }); afterEach(async () => { await logger.flush(); if (existsSync(TEST_LOG_DIR)) { await rm(TEST_LOG_DIR, { recursive: true, force: true }); } }); test('should create logger with default configuration', () => { const defaultLogger = new Logger(); expect(defaultLogger).toBeDefined(); }); test('should create logger with custom configuration', () => { const customLogger = createLogger({ level: LogLevel.ERROR, enableConsole: false, enableFile: false }); expect(customLogger).toBeDefined(); }); test('should log messages at different levels', () => { expect(() => { logger.debug('Debug message'); logger.info('Info message'); logger.warn('Warning message'); logger.error('Error message'); }).not.toThrow(); }); test('should log security events', () => { expect(() => { logger.security( 'Test security event', 'test_event', 'high', 'user_input', 'sanitized_input', { key: 'value' } ); }).not.toThrow(); }); test('should log operation lifecycle', () => { expect(() => { logger.startOperation('Test Operation'); logger.completeOperation('Test Operation', 1000); logger.failOperation('Failed Operation', new Error('Test error')); }).not.toThrow(); }); test('should log file operations', () => { expect(() => { logger.fileOperation('read', '/test/file.txt', true); logger.fileOperation('write', '/test/file.txt', false, 'Permission denied'); }).not.toThrow(); }); test('should log template operations', () => { expect(() => { logger.templateOperation('validate', '/templates/basic', { files: 10 }); logger.templateOperation('process', '/templates/advanced'); }).not.toThrow(); }); test('should handle progress logging', () => { expect(() => { for (let i = 0; i <= 100; i += 10) { logger.progress('Processing files', i, 100); } }).not.toThrow(); }); test('should log success and failure messages', () => { expect(() => { logger.success('Operation completed successfully'); logger.failure('Operation failed', new Error('Test error')); }).not.toThrow(); }); test('should flush logs without errors', async () => { logger.info('Test message before flush'); logger.security('Security event', 'test', 'medium'); await expect(logger.flush()).resolves.not.toThrow(); }); test('should handle logging configuration changes', () => { expect(() => { logger.configure({ level: LogLevel.ERROR, verbose: false }); logger.debug('This should not be logged'); logger.error('This should be logged'); }).not.toThrow(); }); test('should handle errors in error logging gracefully', () => { const circularObject = { name: 'test' } as any; circularObject.self = circularObject; expect(() => { logger.error('Error with circular reference', new Error('Test'), 'Test', circularObject); }).not.toThrow(); }); }); describe('ErrorRecovery', () => { let logger: Logger; let errorRecovery: ErrorRecovery; beforeEach(() => { logger = new Logger({ enableConsole: false }); errorRecovery = new ErrorRecovery(logger); }); test('should create ErrorRecovery instance', () => { expect(errorRecovery).toBeDefined(); }); test('should recover from file operation errors', async () => { // Test ENOENT (file not found) const enoentError = new Error('ENOENT: no such file or directory'); const canRecoverEnoent = await errorRecovery.recoverFromFileError( enoentError, 'read', '/nonexistent/file.txt' ); expect(canRecoverEnoent).toBe(true); // Test EACCES (permission denied) const eaccesError = new Error('EACCES: permission denied'); const canRecoverEacces = await errorRecovery.recoverFromFileError( eaccesError, 'write', '/restricted/file.txt' ); expect(canRecoverEacces).toBe(true); // Test EEXIST (file exists) const eexistError = new Error('EEXIST: file already exists'); const canRecoverEexist = await errorRecovery.recoverFromFileError( eexistError, 'create', '/path/to/existing/file.txt' ); expect(canRecoverEexist).toBe(true); // Test unknown error const unknownError = new Error('Unknown file system error'); const canRecoverUnknown = await errorRecovery.recoverFromFileError( unknownError, 'unknown', '/path/to/file.txt' ); expect(canRecoverUnknown).toBe(false); }); test('should recover from template processing errors', async () => { // Test validation error const validationError = new Error('Template validation failed'); const canRecoverValidation = await errorRecovery.recoverFromTemplateError( validationError, '/templates/invalid' ); expect(canRecoverValidation).toBe(true); // Test invalid template error const invalidError = new Error('Invalid template structure'); const canRecoverInvalid = await errorRecovery.recoverFromTemplateError( invalidError, '/templates/corrupt' ); expect(canRecoverInvalid).toBe(true); // Test unknown template error const unknownError = new Error('Unknown template error'); const canRecoverUnknown = await errorRecovery.recoverFromTemplateError( unknownError, '/templates/unknown' ); expect(canRecoverUnknown).toBe(false); }); test('should provide appropriate error recovery suggestions', () => { // Permission error const permissionError = new Error('EACCES: permission denied'); const permissionSuggestion = errorRecovery.getErrorRecoverySuggestion(permissionError); expect(permissionSuggestion).toMatch(/permission/i); expect(permissionSuggestion).toMatch(/elevated/i); // Disk space error const spaceError = new Error('ENOSPC: no space left on device'); const spaceSuggestion = errorRecovery.getErrorRecoverySuggestion(spaceError); expect(spaceSuggestion).toMatch(/disk space/i); expect(spaceSuggestion).toMatch(/free up/i); // File limit error const fileLimitError = new Error('EMFILE: too many open files'); const fileLimitSuggestion = errorRecovery.getErrorRecoverySuggestion(fileLimitError); expect(fileLimitSuggestion).toMatch(/open files/i); expect(fileLimitSuggestion).toMatch(/close/i); // Network error const networkError = new Error('Network fetch failed'); const networkSuggestion = errorRecovery.getErrorRecoverySuggestion(networkError); expect(networkSuggestion).toMatch(/network|connection/i); // Generic error const genericError = new Error('Something went wrong'); const genericSuggestion = errorRecovery.getErrorRecoverySuggestion(genericError); expect(genericSuggestion).toMatch(/unexpected error/i); expect(genericSuggestion).toMatch(/logs/i); }); test('should handle multiple concurrent recovery attempts', async () => { const errors = [ new Error('ENOENT: file not found'), new Error('EACCES: permission denied'), new Error('EEXIST: file exists'), new Error('Unknown error'), ]; const recoveryPromises = errors.map((error, index) => errorRecovery.recoverFromFileError(error, 'test', `/test/file${index}.txt`) ); const results = await Promise.all(recoveryPromises); // Should handle concurrent recovery attempts expect(results.length).toBe(4); expect(results[0]).toBe(true); // ENOENT expect(results[1]).toBe(true); // EACCES expect(results[2]).toBe(true); // EEXIST expect(results[3]).toBe(false); // Unknown }); test('should handle error recovery with null or undefined errors', async () => { // Should not crash with null/undefined errors expect(async () => { await errorRecovery.recoverFromFileError( null as any, 'test', '/test/file.txt' ); }).not.toThrow(); expect(() => { errorRecovery.getErrorRecoverySuggestion(null as any); }).not.toThrow(); }); }); describe('Logger Integration', () => { test('should work together with ErrorRecovery', async () => { const logger = new Logger({ enableConsole: false }); const errorRecovery = new ErrorRecovery(logger); // Simulate error recovery scenario const error = new Error('EACCES: permission denied, open /restricted/file'); // Log the error attempt logger.error('File operation failed', error, 'FileSystem'); // Attempt recovery const canRecover = await errorRecovery.recoverFromFileError( error, 'write', '/restricted/file' ); if (canRecover) { logger.info('Error recovery successful', 'ErrorRecovery'); } else { logger.error('Error recovery failed', error, 'ErrorRecovery'); } // Should not throw during this workflow expect(canRecover).toBe(true); await logger.flush(); }); test('should handle high-frequency logging without memory leaks', () => { const logger = new Logger({ enableConsole: false, enableFile: false // Disable file logging for performance }); // Log many messages rapidly for (let i = 0; i < 1000; i++) { logger.debug(`Debug message ${i}`); logger.info(`Info message ${i}`); if (i % 100 === 0) { logger.security( `Security event ${i}`, 'test_event', 'low', `input_${i}`, `sanitized_${i}` ); } } // Should not crash or consume excessive memory expect(logger).toBeDefined(); }); test('should maintain log entry ordering under concurrent access', async () => { const logger = new Logger({ enableConsole: false }); // Create concurrent logging operations const logOperations = Array.from({ length: 50 }, (_, i) => Promise.resolve().then(() => { logger.info(`Concurrent message ${i}`, 'ConcurrencyTest'); logger.debug(`Debug message ${i}`, 'ConcurrencyTest'); logger.security( `Security event ${i}`, 'concurrency_test', 'low', undefined, undefined, { index: i } ); }) ); // Wait for all operations to complete await Promise.all(logOperations); await logger.flush(); // Should complete without errors expect(logOperations.length).toBe(50); }); });