UNPKG

snaplogx

Version:

A high-performance logging library with advanced filtering algorithms

281 lines (258 loc) 9.95 kB
import { createLogger } from '../src/snaplog.js'; import { createFileTransport } from '../src/transports/fileTransport.js'; // Mock fs to avoid real file operations jest.mock('fs', () => ({ existsSync: jest.fn(() => false), mkdirSync: jest.fn(), createWriteStream: jest.fn(() => ({ write: jest.fn(), end: jest.fn(), })), })); // Mock fileTransport.js const mockTransport = { log: jest.fn(), setFile: jest.fn(), }; jest.mock('../src/transports/fileTransport.js', () => ({ createFileTransport: jest.fn(() => mockTransport), })); describe('SnapLog Unit Tests', () => { let logger; beforeEach(() => { jest.clearAllMocks(); logger = createLogger({ fileOptions: { filename: 'test.log', format: 'json', logDir: './test/logs', }, }); }); // 1. Logger Creation test('createLogger initializes with default configuration', () => { expect(logger).toBeDefined(); expect(logger.log).toBeInstanceOf(Function); expect(logger.info).toBeInstanceOf(Function); // Level-specific method expect(logger.addPatternFilter).toBeInstanceOf(Function); expect(logger.setFile).toBeInstanceOf(Function); expect(createFileTransport).toHaveBeenCalledWith({ filename: 'test.log', format: 'json', logDir: './test/logs', }); }); // 2. Basic Logging test('log writes a simple message to transport', () => { logger.log('info', 'Simple message'); expect(mockTransport.log).toHaveBeenCalledWith( expect.objectContaining({ level: 'info', message: 'Simple message', }) ); }); // 3. Logging with Metadata test('log includes metadata in output', () => { logger.log('error', 'Error occurred', { traceId: 'abc123' }); expect(mockTransport.log).toHaveBeenCalledWith( expect.objectContaining({ level: 'error', message: 'Error occurred', traceId: 'abc123', }) ); }); // 4. Logging with Object as Message test('log handles object as message', () => { logger.log('info', { message: 'Object message', custom: 'data' }); expect(mockTransport.log).toHaveBeenCalledWith( expect.objectContaining({ level: 'info', message: { custom: 'data', message: 'Object message' }, }) ); }); // 5. Level-Specific Method test('info method logs correctly', () => { logger.info('Info log'); expect(mockTransport.log).toHaveBeenCalledWith( expect.objectContaining({ level: 'info', message: 'Info log', }) ); }); // 6. KMP Pattern Matching test('findPattern matches patterns using KMP', () => { expect(logger.findPattern('test', 'This is a test string')).toBe(true); expect(logger.findPattern('xyz', 'This is a test string')).toBe(false); expect(logger.findPattern('test', 'TEST uppercase')).toBe(false); // Case-sensitive expect(logger.findPattern('', 'Non-empty')).toBe(false); expect(logger.findPattern('test', '')).toBe(false); }); // 7. KMP Filter (Allow Pattern) test('addPatternFilter allows logs with pattern when allow=true', () => { logger.addPatternFilter('only-errors', 'Error', true); logger.log('info', 'Success message'); // Filtered out logger.log('error', 'Error message'); // Allowed expect(mockTransport.log).not.toHaveBeenCalledWith( expect.objectContaining({ message: 'Success message' }) ); expect(mockTransport.log).toHaveBeenCalledWith( expect.objectContaining({ message: 'Error message' }) ); }); // 8. KMP Filter (Exclude Pattern) test('addPatternFilter excludes logs with pattern when allow=false', () => { logger.addPatternFilter('no-errors', 'Error', false); logger.log('info', 'Success message'); // Allowed logger.log('error', 'Error message'); // Filtered out expect(mockTransport.log).toHaveBeenCalledWith( expect.objectContaining({ message: 'Success message' }) ); expect(mockTransport.log).not.toHaveBeenCalledWith( expect.objectContaining({ message: 'Error message' }) ); }); // 9. Multiple Filters test('multiple filters apply correctly', () => { logger.addPatternFilter('no-errors', 'Error', false); logger.addPatternFilter('only-success', 'Success', true); logger.log('info', 'Success message'); // Allowed (has success, no error) logger.log('error', 'Error message'); // Filtered out (has error) logger.log('info', 'Neutral message'); // Filtered out (no success) expect(mockTransport.log).toHaveBeenCalledTimes(1); expect(mockTransport.log).toHaveBeenCalledWith( expect.objectContaining({ message: 'Success message' }) ); }); // 10. Remove Filter test('removeFilter disables filtering', () => { logger.addPatternFilter('no-errors', 'Error', false); logger.log('error', 'Error message'); // Filtered out logger.removeFilter('no-errors'); logger.log('error', 'Error message'); // Now allowed expect(mockTransport.log).toHaveBeenCalledTimes(1); expect(mockTransport.log).toHaveBeenCalledWith( expect.objectContaining({ message: 'Error message' }) ); }); // 11. Processor Addition test('addProcessor modifies log info', () => { logger.addProcessor('uppercase', (info) => { info.message = info.message.toUpperCase(); }); logger.log('info', 'Process this'); expect(mockTransport.log).toHaveBeenCalledWith( expect.objectContaining({ level: 'info', message: 'PROCESS THIS', }) ); }); // 12. Remove Processor test('removeProcessor stops modification', () => { logger.addProcessor('uppercase', (info) => { info.message = info.message.toUpperCase(); }); logger.removeProcessor('uppercase'); logger.log('info', 'No change'); expect(mockTransport.log).toHaveBeenCalledWith( expect.objectContaining({ level: 'info', message: 'No change', }) ); }); // 13. Set File test('setFile updates transport options', () => { logger.setFile({ filename: 'new.log', logDir: './new/logs' }); expect(mockTransport.setFile).toHaveBeenCalledWith({ filename: 'new.log', format: 'json', logDir: './new/logs', }); }); // 14. Edge Case: Null Message test('log handles null message', () => { logger.log('info', null); expect(mockTransport.log).toHaveBeenCalledWith( expect.objectContaining({ level: 'info', message: null, }) ); }); // 15. Edge Case: Empty Options test('createLogger works with empty options', () => { const emptyLogger = createLogger(); expect(emptyLogger).toBeDefined(); expect(createFileTransport).toHaveBeenCalledWith({}); emptyLogger.log('info', 'Default log'); expect(mockTransport.log).toHaveBeenCalled(); }); // 16. Aho-Corasick Filter (Allow Patterns) test('addMultiPatternFilter allows logs with any pattern when allow=true', () => { logger.addMultiPatternFilter('multi-errors', ['Error', 'Timeout', 'failed'], true); logger.log('info', 'Success message'); // Filtered out logger.log('error', 'Error occurred'); // Allowed logger.log('warn', 'Timeout happened'); // Allowed logger.log('info', 'Task failed'); // Allowed expect(mockTransport.log).toHaveBeenCalledTimes(3); expect(mockTransport.log).toHaveBeenCalledWith( expect.objectContaining({ message: 'Error occurred' }) ); expect(mockTransport.log).toHaveBeenCalledWith( expect.objectContaining({ message: 'Timeout happened' }) ); expect(mockTransport.log).toHaveBeenCalledWith( expect.objectContaining({ message: 'Task failed' }) ); expect(mockTransport.log).not.toHaveBeenCalledWith( expect.objectContaining({ message: 'Success message' }) ); }); // 17. Aho-Corasick Filter (Exclude Patterns) test('addMultiPatternFilter excludes logs with any pattern when allow=false', () => { logger.addMultiPatternFilter('no-errors', ['Error', 'Timeout', 'failed'], false); logger.log('info', 'Success message'); // Allowed logger.log('error', 'Error occurred'); // Filtered out logger.log('warn', 'Timeout happened'); // Filtered out logger.log('info', 'Task failed'); // Filtered out expect(mockTransport.log).toHaveBeenCalledTimes(1); expect(mockTransport.log).toHaveBeenCalledWith( expect.objectContaining({ message: 'Success message' }) ); expect(mockTransport.log).not.toHaveBeenCalledWith( expect.objectContaining({ message: 'Error occurred' }) ); }); // 18. Aho-Corasick with Multiple Filters test('multiple Aho-Corasick filters apply correctly', () => { logger.addMultiPatternFilter('only-errors', ['Error', 'failed'], true); logger.addMultiPatternFilter('no-timeouts', ['Timeout'], false); logger.log('info', 'Success message'); // Filtered out (no error/failed) logger.log('error', 'Error occurred'); // Allowed (has error, no timeout) logger.log('warn', 'Timeout happened'); // Filtered out (has timeout) logger.log('info', 'Task failed'); // Allowed (has failed, no timeout) expect(mockTransport.log).toHaveBeenCalledTimes(2); expect(mockTransport.log).toHaveBeenCalledWith( expect.objectContaining({ message: 'Error occurred' }) ); expect(mockTransport.log).toHaveBeenCalledWith( expect.objectContaining({ message: 'Task failed' }) ); }); // 19. Aho-Corasick Filter Removal test('removeFilter disables Aho-Corasick filtering', () => { logger.addMultiPatternFilter('no-errors', ['Error', 'Timeout'], false); logger.log('error', 'Error message'); // Filtered out logger.removeFilter('no-errors'); logger.log('error', 'Error message'); // Now allowed expect(mockTransport.log).toHaveBeenCalledTimes(1); expect(mockTransport.log).toHaveBeenCalledWith( expect.objectContaining({ message: 'Error message' }) ); }); });