From e76489e8f3c750387c02b5c521b62abe304e1400 Mon Sep 17 00:00:00 2001 From: Bojan Kucera Date: Sat, 7 Jun 2025 10:59:21 -0400 Subject: [PATCH] updating logger tests --- libs/logger/test/advanced.test.ts | 100 +++++++ libs/logger/test/basic.test.ts | 164 +++++++++++ libs/logger/test/integration.test.ts | 288 +++++++------------ libs/logger/test/middleware.test.ts | 273 ------------------ libs/logger/test/middleware.test.ts.new | 273 ------------------ libs/logger/test/utils.test.ts | 349 ------------------------ 6 files changed, 358 insertions(+), 1089 deletions(-) create mode 100644 libs/logger/test/advanced.test.ts create mode 100644 libs/logger/test/basic.test.ts delete mode 100644 libs/logger/test/middleware.test.ts delete mode 100644 libs/logger/test/middleware.test.ts.new delete mode 100644 libs/logger/test/utils.test.ts diff --git a/libs/logger/test/advanced.test.ts b/libs/logger/test/advanced.test.ts new file mode 100644 index 0000000..9ed5632 --- /dev/null +++ b/libs/logger/test/advanced.test.ts @@ -0,0 +1,100 @@ +/** + * Logger Basic Tests + * + * Simple tests for the simplified logger functionality. + */ + +import { describe, it, expect, beforeEach } from 'bun:test'; +import { Logger, getLogger } from '../src'; +import { loggerTestHelpers } from './setup'; + +describe('Logger Basic Tests', () => { + let logger: Logger; + + beforeEach(() => { + logger = loggerTestHelpers.createTestLogger('basic-test'); + loggerTestHelpers.clearCapturedLogs(); + }); + + describe('Basic Logging', () => { + it('should create logger instances', () => { + expect(logger).toBeDefined(); + expect(typeof logger.info).toBe('function'); + expect(typeof logger.error).toBe('function'); + expect(typeof logger.warn).toBe('function'); + expect(typeof logger.debug).toBe('function'); + }); + + it('should log simple messages', () => { + logger.info('Test message'); + + const logs = loggerTestHelpers.getCapturedLogs(); + expect(logs.length).toBe(1); + expect(logs[0].msg).toBe('Test message'); + expect(logs[0].level).toBe('info'); + }); + + it('should include service name in logs', () => { + logger.info('Service test'); + + const logs = loggerTestHelpers.getCapturedLogs(); + expect(logs.length).toBe(1); + expect(logs[0].service).toBe('basic-test'); + }); + + it('should handle multiple log levels', () => { + logger.debug('Debug level'); + logger.info('Info level'); + logger.warn('Warn level'); + logger.error('Error level'); + + const logs = loggerTestHelpers.getCapturedLogs(); + expect(logs.length).toBe(4); + + const levels = logs.map(log => log.level); + expect(levels).toEqual(['debug', 'info', 'warn', 'error']); + }); + }); + + describe('Context and Metadata', () => { + it('should include metadata in logs', () => { + const metadata = { userId: '123', action: 'test' }; + logger.info('User action', metadata); + + const logs = loggerTestHelpers.getCapturedLogs(); + expect(logs.length).toBe(1); + expect(logs[0].userId).toBe('123'); + expect(logs[0].action).toBe('test'); + }); + + it('should support child loggers', () => { + const childLogger = logger.child({ requestId: 'req-456' }); + childLogger.info('Child log message'); + + const logs = loggerTestHelpers.getCapturedLogs(); + expect(logs.length).toBe(1); + expect(logs[0].msg).toBe('Child log message'); + }); + }); + + describe('Error Logging', () => { + it('should log errors with error metadata', () => { + const error = new Error('Test error'); + logger.error('Error occurred', { error }); + + const logs = loggerTestHelpers.getCapturedLogs(); + expect(logs.length).toBe(1); + expect(logs[0].level).toBe('error'); + expect(logs[0].msg).toBe('Error occurred'); + }); + + it('should handle error-like objects', () => { + const errorLike = { name: 'CustomError', message: 'Custom message' }; + logger.error('Custom error', { error: errorLike }); + + const logs = loggerTestHelpers.getCapturedLogs(); + expect(logs.length).toBe(1); + expect(logs[0].level).toBe('error'); + }); + }); +}); diff --git a/libs/logger/test/basic.test.ts b/libs/logger/test/basic.test.ts new file mode 100644 index 0000000..8ba68aa --- /dev/null +++ b/libs/logger/test/basic.test.ts @@ -0,0 +1,164 @@ +/** + * Logger Utility Tests + * + * Tests for the core logger functionality and utilities. + */ + +import { describe, it, expect, beforeEach } from 'bun:test'; +import { Logger, getLogger, createLogger } from '../src'; +import { loggerTestHelpers } from './setup'; + +describe('Logger Utilities', () => { + let logger: Logger; + + beforeEach(() => { + logger = loggerTestHelpers.createTestLogger('utils-test'); + loggerTestHelpers.clearCapturedLogs(); + }); + + describe('Logger Factory Functions', () => { + it('should create logger with getLogger', () => { + expect(typeof getLogger).toBe('function'); + + // Test that getLogger doesn't throw + expect(() => { + const testLogger = loggerTestHelpers.createTestLogger('factory-test'); + testLogger.info('Factory test'); + }).not.toThrow(); + }); + + it('should create Pino logger with createLogger', () => { + expect(typeof createLogger).toBe('function'); + + // Test that createLogger function exists + expect(() => createLogger).not.toThrow(); + }); + }); + + describe('Logger Methods', () => { + it('should have all required logging methods', () => { + expect(typeof logger.debug).toBe('function'); + expect(typeof logger.info).toBe('function'); + expect(typeof logger.warn).toBe('function'); + expect(typeof logger.error).toBe('function'); + expect(typeof logger.child).toBe('function'); + }); + + it('should log with different message types', () => { + // String message + logger.info('String message'); + + // Object message + logger.info({ event: 'object_message', data: 'test' }); + + const logs = loggerTestHelpers.getCapturedLogs(); + expect(logs.length).toBe(2); + expect(logs[0].msg).toBe('String message'); + expect(logs[1].level).toBe('info'); + }); + + it('should handle metadata correctly', () => { + const metadata = { + userId: 'user123', + sessionId: 'session456', + requestId: 'req789' + }; + + logger.info('Request processed', metadata); + + const logs = loggerTestHelpers.getCapturedLogs(); + expect(logs.length).toBe(1); + expect(logs[0].userId).toBe('user123'); + expect(logs[0].sessionId).toBe('session456'); + expect(logs[0].requestId).toBe('req789'); + }); + }); + + describe('Child Logger Functionality', () => { + it('should create child loggers with additional context', () => { + const childLogger = logger.child({ + module: 'payment', + version: '1.0.0' + }); + + childLogger.info('Payment processed'); + + const logs = loggerTestHelpers.getCapturedLogs(); + expect(logs.length).toBe(1); + expect(logs[0].msg).toBe('Payment processed'); + }); + + it('should inherit service name in child loggers', () => { + const childLogger = logger.child({ operation: 'test' }); + childLogger.info('Child operation'); + + const logs = loggerTestHelpers.getCapturedLogs(); + expect(logs.length).toBe(1); + expect(logs[0].service).toBe('utils-test'); + }); + }); + + describe('Error Normalization', () => { + it('should handle Error objects', () => { + const error = new Error('Test error'); + error.stack = 'Error stack trace'; + + logger.error('Error test', { error }); + + const logs = loggerTestHelpers.getCapturedLogs(); + expect(logs.length).toBe(1); + expect(logs[0].level).toBe('error'); + }); + + it('should handle error-like objects', () => { + const errorLike = { + name: 'ValidationError', + message: 'Invalid input', + code: 'VALIDATION_FAILED' + }; + + logger.error('Validation failed', { error: errorLike }); + + const logs = loggerTestHelpers.getCapturedLogs(); + expect(logs.length).toBe(1); + expect(logs[0].level).toBe('error'); + }); + + it('should handle primitive error values', () => { + logger.error('Simple error', { error: 'Error string' }); + + const logs = loggerTestHelpers.getCapturedLogs(); + expect(logs.length).toBe(1); + expect(logs[0].level).toBe('error'); + }); + }); + + describe('Service Context', () => { + it('should include service name in all logs', () => { + logger.debug('Debug message'); + logger.info('Info message'); + logger.warn('Warn message'); + logger.error('Error message'); + + const logs = loggerTestHelpers.getCapturedLogs(); + expect(logs.length).toBe(4); + + logs.forEach(log => { + expect(log.service).toBe('utils-test'); + }); + }); + + it('should support different service names', () => { + const logger1 = loggerTestHelpers.createTestLogger('service-one'); + const logger2 = loggerTestHelpers.createTestLogger('service-two'); + + logger1.info('Message from service one'); + logger2.info('Message from service two'); + + const logs = loggerTestHelpers.getCapturedLogs(); + expect(logs.length).toBe(2); + expect(logs[0].service).toBe('service-one'); + expect(logs[1].service).toBe('service-two'); + }); + }); +}); diff --git a/libs/logger/test/integration.test.ts b/libs/logger/test/integration.test.ts index 2d995e9..96a0832 100644 --- a/libs/logger/test/integration.test.ts +++ b/libs/logger/test/integration.test.ts @@ -1,21 +1,14 @@ /** * Logger Integration Tests * - * Tests the complete functionality of the @stock-bot/logger package, - * verifying that all components work together correctly. + * Tests the core functionality of the simplified @stock-bot/logger package. */ -import { describe, it, expect, beforeEach, afterEach } from 'bun:test'; +import { describe, it, expect, beforeEach } from 'bun:test'; import { Logger, createLogger, - getLogger, - createTimer, - formatError, - sanitizeMetadata, - generateCorrelationId, - calculateLogSize, - LogThrottle + getLogger } from '../src'; import { loggerTestHelpers } from './setup'; @@ -63,35 +56,24 @@ describe('Logger Integration Tests', () => { expect(logs[0].userId).toBe('123'); expect(logs[0].action).toBe('login'); expect(logs[0].msg).toBe('User logged in'); - }); it('should maintain context across log calls', () => { - // Create a custom logger with context - const contextLogger = loggerTestHelpers.createTestLogger('context-test'); - (contextLogger as any).context = { - requestId: 'req-123', - userId: 'user-456' - }; + }); + + it('should handle error objects in error logs', () => { + const testError = new Error('Test error message'); - // Log with context - contextLogger.info('Context test'); + // Log error with error object + logger.error('Something went wrong', { error: testError }); // Get captured logs const logs = loggerTestHelpers.getCapturedLogs(); - // Verify context is included (implementation dependent) + // Verify error was logged expect(logs.length).toBe(1); - expect(logs[0].msg).toBe('Context test'); - - // Context might be included in different ways - if (logs[0].context) { - expect(logs[0].context.requestId).toBe('req-123'); - expect(logs[0].context.userId).toBe('user-456'); - } else if (logs[0].requestId) { - expect(logs[0].requestId).toBe('req-123'); - expect(logs[0].userId).toBe('user-456'); - } + expect(logs[0].level).toBe('error'); + expect(logs[0].msg).toBe('Something went wrong'); }); - it('should create child loggers with additional context', () => { + it('should create child loggers with additional context', () => { // Create a child logger with additional context const childLogger = logger.child({ transactionId: 'tx-789', @@ -107,180 +89,98 @@ describe('Logger Integration Tests', () => { // Verify child logger logged something expect(logs.length).toBe(1); expect(logs[0].msg).toBe('Child logger test'); - - // Check if context is preserved (implementation specific) - if (logs[0].context) { - expect(logs[0].context.transactionId).toBe('tx-789'); - expect(logs[0].context.operation).toBe('payment'); - } else if (logs[0].transactionId) { - expect(logs[0].transactionId).toBe('tx-789'); - expect(logs[0].operation).toBe('payment'); - } - }); - }); describe('Factory Functions Integration', () => { - it('should create logger instances', () => { - // Since we're fully mocking the logger for tests, - // we'll just verify that the factory function doesn't throw - try { - // We don't actually call the real createLogger here to avoid Pino errors - // Instead just verify we have exported the function - expect(typeof createLogger).toBe('function'); - expect(typeof getLogger).toBe('function'); - } catch (error) { - // Should not throw - fail('Factory functions should be defined'); - } }); }); - describe('Utility Function Integration', () => { - it('should create performance timers', async () => { - // Create and use timer - const timer = createTimer('test-operation'); - - // Wait a bit to measure time - await new Promise(resolve => setTimeout(resolve, 10)); - - // End timer - const result = timer.end(); - - // Log timer result - logger.info('Operation completed', { performance: result }); - - // Get captured logs - const logs = loggerTestHelpers.getCapturedLogs(); - - // Verify timer data in log - expect(logs.length).toBe(1); - expect(logs[0].performance.operation).toBe('test-operation'); - expect(logs[0].performance.duration).toBeGreaterThan(0); - expect(logs[0].msg).toBe('Operation completed'); + describe('Factory Functions', () => { + it('should export factory functions', () => { + // Verify that the factory functions are exported and callable + expect(typeof createLogger).toBe('function'); + expect(typeof getLogger).toBe('function'); }); - it('should format errors for logging', () => { - // Create error + it('should create different logger instances', () => { + const logger1 = loggerTestHelpers.createTestLogger('service-1'); + const logger2 = loggerTestHelpers.createTestLogger('service-2'); + + logger1.info('Message from service 1'); + logger2.info('Message from service 2'); + + const logs = loggerTestHelpers.getCapturedLogs(); + + expect(logs.length).toBe(2); + expect(logs[0].service).toBe('service-1'); + expect(logs[1].service).toBe('service-2'); + }); + }); + + describe('Error Handling', () => { + it('should normalize Error objects', () => { const error = new Error('Test error'); - error.name = 'TestError'; + error.stack = 'Error stack trace'; - // Format error - const formattedError = formatError(error); + logger.error('Error occurred', { error }); - // Log error with differently formatted object to match our mock - logger.error('An error occurred', { error: formattedError }); - - // Get captured logs const logs = loggerTestHelpers.getCapturedLogs(); - - // Verify error format - expect(logs.length).toBe(1); - expect(logs[0].error.name).toBe('TestError'); - expect(logs[0].error.message).toBe('Test error'); - expect(logs[0].error.stack).toBeDefined(); - }); it('should sanitize metadata', () => { - // Create metadata with sensitive info - const metadata = { - user: 'testuser', - password: 'secret123', - creditCard: '1234-5678-9012-3456', - ssn: '123-45-6789', - nested: { - password: 'another-secret', - token: 'sensitive-token' - } - }; - - // Sanitize metadata - const sanitized = sanitizeMetadata(metadata); - - // Log sanitized data - logger.info('User data', { ...sanitized }); - - // Get captured logs - const logs = loggerTestHelpers.getCapturedLogs(); - - // Verify sanitized data - expect(logs.length).toBe(1); - // Based on actual implementation, 'user' is considered sensitive - expect(logs[0].user).toBe('[REDACTED]'); - expect(logs[0].password).toBe('[REDACTED]'); - expect(logs[0].creditCard).toBe('[REDACTED]'); - expect(logs[0].ssn).toBe('[REDACTED]'); - expect(logs[0].nested.password).toBe('[REDACTED]'); - expect(logs[0].nested.token).toBe('[REDACTED]'); - }); it('should generate correlation IDs', () => { - // Generate correlation ID - const correlationId = generateCorrelationId(); - - // Log with correlation ID - logger.info('Correlated event', { correlationId }); - - // Get captured logs - const logs = loggerTestHelpers.getCapturedLogs(); - - // Verify correlation ID format (timestamp-random format) - expect(logs.length).toBe(1); - expect(logs[0].correlationId).toBe(correlationId); - // The actual implementation uses timestamp-random format, not UUID - expect(correlationId).toMatch(/^\d+-[a-z0-9]+$/); - }); - - it('should calculate log size', () => { - // Create a log object - const logObj = { - message: 'Test message', - user: { id: 1234, name: 'Test User' }, - tags: ['test', 'calculation'], - timestamp: new Date().toISOString() - }; - - // Calculate size - const size = calculateLogSize(logObj); - - // Size should be greater than zero - expect(size).toBeGreaterThan(0); - - // Double check with JSON.stringify - const jsonSize = JSON.stringify(logObj).length; - expect(size).toBe(jsonSize); - }); it('should throttle logs correctly', () => { - // Create throttle with small window - const throttle = new LogThrottle(3, 1000); - - // First 3 should pass for same key - const sameKey = 'test-key'; - expect(throttle.shouldLog(sameKey)).toBe(true); - expect(throttle.shouldLog(sameKey)).toBe(true); - expect(throttle.shouldLog(sameKey)).toBe(true); - - // 4th should be throttled for same key - expect(throttle.shouldLog(sameKey)).toBe(false); - - // Reset throttle - throttle.reset(); - - // Should allow logs again - expect(throttle.shouldLog('key4')).toBe(true); - }); - }); - - describe('Error Handling Integration', () => { - it('should log caught exceptions properly', () => { - try { - // Throw an error - throw new Error('Test exception'); - } catch (error) { - // Log the error - logger.error('Caught an exception', { error: formatError(error) }); - } - - // Get captured logs - const logs = loggerTestHelpers.getCapturedLogs(); - - // Verify error log expect(logs.length).toBe(1); expect(logs[0].level).toBe('error'); - expect(logs[0].error.message).toBe('Test exception'); - expect(logs[0].msg).toBe('Caught an exception'); + expect(logs[0].msg).toBe('Error occurred'); + }); + + it('should handle error-like objects', () => { + const errorLike = { + name: 'CustomError', + message: 'Custom error message', + code: 'ERR_CUSTOM' + }; + + logger.error('Custom error occurred', { error: errorLike }); + + const logs = loggerTestHelpers.getCapturedLogs(); + expect(logs.length).toBe(1); + expect(logs[0].level).toBe('error'); + expect(logs[0].msg).toBe('Custom error occurred'); + }); + + it('should handle primitive error values', () => { + logger.error('String error occurred', { error: 'Simple string error' }); + + const logs = loggerTestHelpers.getCapturedLogs(); + expect(logs.length).toBe(1); + expect(logs[0].level).toBe('error'); + expect(logs[0].msg).toBe('String error occurred'); + }); + }); + + describe('Metadata Handling', () => { + it('should include metadata in logs', () => { + const metadata = { + requestId: 'req-123', + userId: 'user-456', + operation: 'data-fetch' + }; + + logger.info('Operation completed', metadata); + + const logs = loggerTestHelpers.getCapturedLogs(); + expect(logs.length).toBe(1); + expect(logs[0].requestId).toBe('req-123'); + expect(logs[0].userId).toBe('user-456'); + expect(logs[0].operation).toBe('data-fetch'); + }); + + it('should handle object messages', () => { + const objectMessage = { + event: 'user_action', + action: 'login', + timestamp: Date.now() + }; + + logger.info(objectMessage); + + const logs = loggerTestHelpers.getCapturedLogs(); + expect(logs.length).toBe(1); + expect(logs[0].level).toBe('info'); }); }); }); diff --git a/libs/logger/test/middleware.test.ts b/libs/logger/test/middleware.test.ts deleted file mode 100644 index 1a99d28..0000000 --- a/libs/logger/test/middleware.test.ts +++ /dev/null @@ -1,273 +0,0 @@ -/** - * Logger Middleware Integration Tests - * - * Tests the Hono middleware functionality of the @stock-bot/logger package, - * verifying that all middleware components work correctly together. - */ - -import { describe, it, expect, beforeEach, afterEach } from 'bun:test'; -import { - loggingMiddleware, - errorLoggingMiddleware, - performanceMiddleware, - securityMiddleware, - businessEventMiddleware, - comprehensiveLoggingMiddleware -} from '../src'; -import { loggerTestHelpers } from './setup'; - -describe('Logger Middleware Integration', () => { - beforeEach(() => { - // Clear logs before each test - loggerTestHelpers.clearCapturedLogs(); - }); - - describe('Basic Logging Middleware', () => { - it('should log incoming requests', async () => { - // Create middleware with test logger - const testLogger = loggerTestHelpers.createTestLogger('middleware-test'); - const middleware = loggingMiddleware({ - serviceName: 'test-service', - logger: testLogger - }); - - // Create mock context - const mockContext = loggerTestHelpers.createHonoContextMock({ - path: '/test', - method: 'GET' - }); - const mockNext = loggerTestHelpers.createNextMock(); - - // Execute middleware - await middleware(mockContext, mockNext); - - // Get captured logs - const logs = loggerTestHelpers.getCapturedLogs(); - - // Verify request was logged - expect(logs.length).toBeGreaterThan(0); - expect(logs[0].level).toBe('http'); - expect(logs[0].msg).toContain('HTTP Request'); - }); - - it('should skip logging for defined paths', async () => { - // Create middleware with skip paths - const testLogger = loggerTestHelpers.createTestLogger('middleware-test'); - const middleware = loggingMiddleware({ - serviceName: 'test-service', - logger: testLogger, - skipPaths: ['/test'] - }); - - // Create mock context with path that should be skipped - const mockContext = loggerTestHelpers.createHonoContextMock({ - path: '/test', - method: 'GET' - }); - const mockNext = loggerTestHelpers.createNextMock(); - - // Execute middleware - await middleware(mockContext, mockNext); - - // Get captured logs - const logs = loggerTestHelpers.getCapturedLogs(); - - // Verify no logs were captured - expect(logs.length).toBe(0); - }); - - it('should log request/response details', async () => { - // Create middleware that logs bodies - const testLogger = loggerTestHelpers.createTestLogger('middleware-test'); - const middleware = loggingMiddleware({ - serviceName: 'test-service', - logger: testLogger, - logRequestBody: true, - logResponseBody: true - }); - - // Create mock context with response status - const mockContext = loggerTestHelpers.createHonoContextMock({ - path: '/api/data', - method: 'POST', - res: { - status: 200, - body: { success: true } - } - }); - const mockNext = loggerTestHelpers.createNextMock(); - - // Execute middleware - await middleware(mockContext, mockNext); - - // Get captured logs - const logs = loggerTestHelpers.getCapturedLogs(); - - // Verify request and response were logged - expect(logs.length).toBeGreaterThan(0); - // First log is HTTP request started, and final log is HTTP request completed - expect(logs[0].msg).toContain('HTTP Request started'); - expect(logs[logs.length-1].msg).toContain('HTTP Request completed'); - }); - }); - - describe('Error Logging Middleware', () => { - it('should log errors from next middleware', async () => { - // Create middleware - const testLogger = loggerTestHelpers.createTestLogger('middleware-test'); - const middleware = errorLoggingMiddleware(testLogger); - - // Create mock context - const mockContext = loggerTestHelpers.createHonoContextMock({ - path: '/api/error', - method: 'GET' - }); - - // Create next function that throws error - const mockNext = async () => { - throw new Error('Test error'); - }; - - // Execute middleware - try { - await middleware(mockContext, mockNext); - } catch (error) { - // Error should be re-thrown after logging - } - - // Get captured logs - const logs = loggerTestHelpers.getCapturedLogs(); - - // Verify error was logged - expect(logs.length).toBe(1); - expect(logs[0].level).toBe('error'); - expect(logs[0].msg).toContain('Unhandled HTTP error'); - }); - }); - - describe('Performance Middleware', () => { - it('should log performance metrics for requests', async () => { - // Create middleware - const testLogger = loggerTestHelpers.createTestLogger('middleware-test'); - const middleware = performanceMiddleware('test-operation', testLogger); - - // Create mock context - const mockContext = loggerTestHelpers.createHonoContextMock({ - path: '/api/data', - method: 'GET' - }); - const mockNext = loggerTestHelpers.createNextMock(); - - // Execute middleware - await middleware(mockContext, mockNext); - - // Get captured logs - const logs = loggerTestHelpers.getCapturedLogs(); - - // Verify performance metrics were logged - expect(logs.length).toBe(1); - expect(logs[0].level).toBe('info'); - expect(logs[0].msg).toContain('Operation completed'); - expect(logs[0].performance).toBeDefined(); - }); - }); - - describe('Security Middleware', () => { - it('should log security events', async () => { - // Create middleware - const testLogger = loggerTestHelpers.createTestLogger('middleware-test'); - const middleware = securityMiddleware(testLogger); - - // Create mock context with auth header - const mockContext = loggerTestHelpers.createHonoContextMock({ - path: '/api/secure', - method: 'GET', - req: { - headers: { - 'authorization': 'Bearer test-token' - } - } - }); - const mockNext = loggerTestHelpers.createNextMock(); - - // Execute middleware - await middleware(mockContext, mockNext); - - // Get captured logs - const logs = loggerTestHelpers.getCapturedLogs(); - - // Verify security event was logged - expect(logs.length).toBe(1); - expect(logs[0].level).toBe('info'); - expect(logs[0].msg).toContain('Authentication attempt'); - expect(logs[0].type).toBe('security_event'); - }); - }); - - describe('Business Event Middleware', () => { - it('should log business events with custom metadata', async () => { - // Create middleware with custom business endpoints - const testLogger = loggerTestHelpers.createTestLogger('middleware-test'); - const middleware = businessEventMiddleware(testLogger); - - // Create mock context with business path - const mockContext = loggerTestHelpers.createHonoContextMock({ - path: '/api/orders', - method: 'POST', - res: { status: 201 } - }); - const mockNext = loggerTestHelpers.createNextMock(); - - // Execute middleware - await middleware(mockContext, mockNext); - - // Get captured logs - const logs = loggerTestHelpers.getCapturedLogs(); - - // Verify business event was logged - expect(logs.length).toBe(1); - expect(logs[0].level).toBe('info'); - expect(logs[0].msg).toContain('Business operation completed'); - expect(logs[0].type).toBe('business_event'); - }); - }); - - describe('Comprehensive Logging Middleware', () => { - it('should combine multiple logging features', async () => { - // Create middleware - const testLogger = loggerTestHelpers.createTestLogger('middleware-test'); - const middleware = comprehensiveLoggingMiddleware({ - serviceName: 'test-service', - logger: testLogger - }); - - // Create mock context - const mockContext = loggerTestHelpers.createHonoContextMock({ - path: '/api/data', - method: 'GET', - req: { - headers: { - 'authorization': 'Bearer test-token' - } - } - }); - const mockNext = loggerTestHelpers.createNextMock(); - - // Execute middleware - await middleware(mockContext, mockNext); - - // Get captured logs - const logs = loggerTestHelpers.getCapturedLogs(); - - // Verify multiple log entries from different middleware components - expect(logs.length).toBeGreaterThan(1); - - // Should have logs from security, logging, and performance middleware - const securityLog = logs.find(log => log.type === 'security_event'); - const requestLog = logs.find(log => log.msg?.includes('HTTP Request')); - - expect(securityLog).toBeDefined(); - expect(requestLog).toBeDefined(); - }); - }); -}); diff --git a/libs/logger/test/middleware.test.ts.new b/libs/logger/test/middleware.test.ts.new deleted file mode 100644 index 1a99d28..0000000 --- a/libs/logger/test/middleware.test.ts.new +++ /dev/null @@ -1,273 +0,0 @@ -/** - * Logger Middleware Integration Tests - * - * Tests the Hono middleware functionality of the @stock-bot/logger package, - * verifying that all middleware components work correctly together. - */ - -import { describe, it, expect, beforeEach, afterEach } from 'bun:test'; -import { - loggingMiddleware, - errorLoggingMiddleware, - performanceMiddleware, - securityMiddleware, - businessEventMiddleware, - comprehensiveLoggingMiddleware -} from '../src'; -import { loggerTestHelpers } from './setup'; - -describe('Logger Middleware Integration', () => { - beforeEach(() => { - // Clear logs before each test - loggerTestHelpers.clearCapturedLogs(); - }); - - describe('Basic Logging Middleware', () => { - it('should log incoming requests', async () => { - // Create middleware with test logger - const testLogger = loggerTestHelpers.createTestLogger('middleware-test'); - const middleware = loggingMiddleware({ - serviceName: 'test-service', - logger: testLogger - }); - - // Create mock context - const mockContext = loggerTestHelpers.createHonoContextMock({ - path: '/test', - method: 'GET' - }); - const mockNext = loggerTestHelpers.createNextMock(); - - // Execute middleware - await middleware(mockContext, mockNext); - - // Get captured logs - const logs = loggerTestHelpers.getCapturedLogs(); - - // Verify request was logged - expect(logs.length).toBeGreaterThan(0); - expect(logs[0].level).toBe('http'); - expect(logs[0].msg).toContain('HTTP Request'); - }); - - it('should skip logging for defined paths', async () => { - // Create middleware with skip paths - const testLogger = loggerTestHelpers.createTestLogger('middleware-test'); - const middleware = loggingMiddleware({ - serviceName: 'test-service', - logger: testLogger, - skipPaths: ['/test'] - }); - - // Create mock context with path that should be skipped - const mockContext = loggerTestHelpers.createHonoContextMock({ - path: '/test', - method: 'GET' - }); - const mockNext = loggerTestHelpers.createNextMock(); - - // Execute middleware - await middleware(mockContext, mockNext); - - // Get captured logs - const logs = loggerTestHelpers.getCapturedLogs(); - - // Verify no logs were captured - expect(logs.length).toBe(0); - }); - - it('should log request/response details', async () => { - // Create middleware that logs bodies - const testLogger = loggerTestHelpers.createTestLogger('middleware-test'); - const middleware = loggingMiddleware({ - serviceName: 'test-service', - logger: testLogger, - logRequestBody: true, - logResponseBody: true - }); - - // Create mock context with response status - const mockContext = loggerTestHelpers.createHonoContextMock({ - path: '/api/data', - method: 'POST', - res: { - status: 200, - body: { success: true } - } - }); - const mockNext = loggerTestHelpers.createNextMock(); - - // Execute middleware - await middleware(mockContext, mockNext); - - // Get captured logs - const logs = loggerTestHelpers.getCapturedLogs(); - - // Verify request and response were logged - expect(logs.length).toBeGreaterThan(0); - // First log is HTTP request started, and final log is HTTP request completed - expect(logs[0].msg).toContain('HTTP Request started'); - expect(logs[logs.length-1].msg).toContain('HTTP Request completed'); - }); - }); - - describe('Error Logging Middleware', () => { - it('should log errors from next middleware', async () => { - // Create middleware - const testLogger = loggerTestHelpers.createTestLogger('middleware-test'); - const middleware = errorLoggingMiddleware(testLogger); - - // Create mock context - const mockContext = loggerTestHelpers.createHonoContextMock({ - path: '/api/error', - method: 'GET' - }); - - // Create next function that throws error - const mockNext = async () => { - throw new Error('Test error'); - }; - - // Execute middleware - try { - await middleware(mockContext, mockNext); - } catch (error) { - // Error should be re-thrown after logging - } - - // Get captured logs - const logs = loggerTestHelpers.getCapturedLogs(); - - // Verify error was logged - expect(logs.length).toBe(1); - expect(logs[0].level).toBe('error'); - expect(logs[0].msg).toContain('Unhandled HTTP error'); - }); - }); - - describe('Performance Middleware', () => { - it('should log performance metrics for requests', async () => { - // Create middleware - const testLogger = loggerTestHelpers.createTestLogger('middleware-test'); - const middleware = performanceMiddleware('test-operation', testLogger); - - // Create mock context - const mockContext = loggerTestHelpers.createHonoContextMock({ - path: '/api/data', - method: 'GET' - }); - const mockNext = loggerTestHelpers.createNextMock(); - - // Execute middleware - await middleware(mockContext, mockNext); - - // Get captured logs - const logs = loggerTestHelpers.getCapturedLogs(); - - // Verify performance metrics were logged - expect(logs.length).toBe(1); - expect(logs[0].level).toBe('info'); - expect(logs[0].msg).toContain('Operation completed'); - expect(logs[0].performance).toBeDefined(); - }); - }); - - describe('Security Middleware', () => { - it('should log security events', async () => { - // Create middleware - const testLogger = loggerTestHelpers.createTestLogger('middleware-test'); - const middleware = securityMiddleware(testLogger); - - // Create mock context with auth header - const mockContext = loggerTestHelpers.createHonoContextMock({ - path: '/api/secure', - method: 'GET', - req: { - headers: { - 'authorization': 'Bearer test-token' - } - } - }); - const mockNext = loggerTestHelpers.createNextMock(); - - // Execute middleware - await middleware(mockContext, mockNext); - - // Get captured logs - const logs = loggerTestHelpers.getCapturedLogs(); - - // Verify security event was logged - expect(logs.length).toBe(1); - expect(logs[0].level).toBe('info'); - expect(logs[0].msg).toContain('Authentication attempt'); - expect(logs[0].type).toBe('security_event'); - }); - }); - - describe('Business Event Middleware', () => { - it('should log business events with custom metadata', async () => { - // Create middleware with custom business endpoints - const testLogger = loggerTestHelpers.createTestLogger('middleware-test'); - const middleware = businessEventMiddleware(testLogger); - - // Create mock context with business path - const mockContext = loggerTestHelpers.createHonoContextMock({ - path: '/api/orders', - method: 'POST', - res: { status: 201 } - }); - const mockNext = loggerTestHelpers.createNextMock(); - - // Execute middleware - await middleware(mockContext, mockNext); - - // Get captured logs - const logs = loggerTestHelpers.getCapturedLogs(); - - // Verify business event was logged - expect(logs.length).toBe(1); - expect(logs[0].level).toBe('info'); - expect(logs[0].msg).toContain('Business operation completed'); - expect(logs[0].type).toBe('business_event'); - }); - }); - - describe('Comprehensive Logging Middleware', () => { - it('should combine multiple logging features', async () => { - // Create middleware - const testLogger = loggerTestHelpers.createTestLogger('middleware-test'); - const middleware = comprehensiveLoggingMiddleware({ - serviceName: 'test-service', - logger: testLogger - }); - - // Create mock context - const mockContext = loggerTestHelpers.createHonoContextMock({ - path: '/api/data', - method: 'GET', - req: { - headers: { - 'authorization': 'Bearer test-token' - } - } - }); - const mockNext = loggerTestHelpers.createNextMock(); - - // Execute middleware - await middleware(mockContext, mockNext); - - // Get captured logs - const logs = loggerTestHelpers.getCapturedLogs(); - - // Verify multiple log entries from different middleware components - expect(logs.length).toBeGreaterThan(1); - - // Should have logs from security, logging, and performance middleware - const securityLog = logs.find(log => log.type === 'security_event'); - const requestLog = logs.find(log => log.msg?.includes('HTTP Request')); - - expect(securityLog).toBeDefined(); - expect(requestLog).toBeDefined(); - }); - }); -}); diff --git a/libs/logger/test/utils.test.ts b/libs/logger/test/utils.test.ts deleted file mode 100644 index 904285a..0000000 --- a/libs/logger/test/utils.test.ts +++ /dev/null @@ -1,349 +0,0 @@ -/** - * Logger Utility Functions Integration Tests - * - * Tests the utility functions of the @stock-bot/logger package, - * verifying that they work correctly in isolation and together. - */ - -import { describe, it, expect, beforeEach } from 'bun:test'; -import { - createTimer, - formatError, - sanitizeMetadata, - generateCorrelationId, - extractHttpMetadata, - createBusinessEvent, - createSecurityEvent, - maskSensitiveData, - calculateLogSize, - LogThrottle -} from '../src'; -import { loggerTestHelpers } from './setup'; - -describe('Logger Utility Functions', () => { - describe('Performance Timers', () => { - it('should accurately measure operation duration', async () => { - // Create a timer for an operation - const timer = createTimer('test-operation'); - - // Wait for a specific amount of time - const waitTime = 50; - await new Promise(resolve => setTimeout(resolve, waitTime)); - - // End the timer and get the result - const result = timer.end(); - - // Verify the timer properties - expect(result.operation).toBe('test-operation'); - expect(result.startTime).toBeDefined(); - expect(result.endTime).toBeDefined(); - expect(result.duration).toBeGreaterThanOrEqual(waitTime - 10); // Account for slight timer variations - }); - }); - - describe('Error Formatting', () => { - it('should format standard Error objects', () => { - // Create a standard error - const error = new Error('Test error'); - error.name = 'TestError'; - - // Format the error - const formatted = formatError(error); - - // Verify the format - expect(formatted.name).toBe('TestError'); - expect(formatted.message).toBe('Test error'); - expect(formatted.stack).toBeDefined(); - }); - - it('should format custom error objects', () => { - // Create a custom error object - const customError = { - errorCode: 'E1001', - message: 'Custom error message', - details: { field: 'username' } - }; - - // Format the error - const formatted = formatError(customError); - - // Verify the format - expect(formatted.name).toBe('Object'); - expect(formatted.message).toBe('Custom error message'); - expect(formatted.errorCode).toBe('E1001'); - expect(formatted.details.field).toBe('username'); - }); - - it('should handle non-error values', () => { - // Format different types of values - const stringError = formatError('Something went wrong'); - const numberError = formatError(404); - const nullError = formatError(null); - - // Verify the formats - expect(stringError.name).toBe('UnknownError'); - expect(stringError.message).toBe('Something went wrong'); - - expect(numberError.name).toBe('UnknownError'); - expect(numberError.message).toBe('404'); - - expect(nullError.name).toBe('UnknownError'); - expect(nullError.message).toBe('null'); - }); - }); - - describe('Metadata Sanitization', () => { - it('should redact sensitive fields', () => { - // Create metadata with sensitive information - const metadata = { - user: 'testuser', - password: 'secret123', - creditCard: '1234-5678-9012-3456', - token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9', - nested: { - password: 'nested-secret', - apiKey: '1a2b3c4d5e' - } - }; - - // Sanitize the metadata - const sanitized = sanitizeMetadata(metadata); - // Verify sensitive fields are redacted - expect(sanitized.user).toBe('[REDACTED]'); // Actually sensitive in implementation - expect(sanitized.password).toBe('[REDACTED]'); - expect(sanitized.creditCard).toBe('[REDACTED]'); - expect(sanitized.token).toBe('[REDACTED]'); - expect(sanitized.nested.password).toBe('[REDACTED]'); - expect(sanitized.nested.apiKey).toBe('[REDACTED]'); - }); - it('should handle arrays and nested objects', () => { - // Create complex metadata with arrays and nested objects - const metadata = { - users: [ - { id: 1, name: 'User 1', password: 'pass1' }, - { id: 2, name: 'User 2', password: 'pass2' } - ], - config: { - database: { - password: 'dbpass', - host: 'localhost' - } - } - }; - - // Sanitize the metadata - const sanitized = sanitizeMetadata(metadata); - - // Updated expectations based on actual implementation which redacts all values in objects with sensitive fields - expect(sanitized.users[0].id).toBeDefined(); - expect(sanitized.users[0].password).toBe('[REDACTED]'); - expect(sanitized.users[1].password).toBe('[REDACTED]'); - expect(sanitized.config.database.password).toBe('[REDACTED]'); - expect(sanitized.config.database.host).toBeDefined(); - }); - }); - - describe('Correlation ID Generation', () => { it('should generate valid correlation IDs', () => { - // Generate multiple correlation IDs - const id1 = generateCorrelationId(); - const id2 = generateCorrelationId(); - - // Verify timestamp-random format (not UUID) - const timestampRegex = /^\d+-[a-z0-9]+$/; - expect(id1).toMatch(timestampRegex); - expect(id2).toMatch(timestampRegex); - - // Verify uniqueness - expect(id1).not.toBe(id2); - }); - }); - - describe('HTTP Metadata Extraction', () => { - it('should extract metadata from HTTP request/response', () => { - // Create mock HTTP objects - const req = { - method: 'POST', - url: 'http://localhost/api/users', - headers: { - 'user-agent': 'test-agent', - 'content-type': 'application/json' - } - }; - - const res = { - statusCode: 201, - headers: { - 'content-type': 'application/json', - 'content-length': '42' - } - }; - // Extract metadata - const metadata = extractHttpMetadata(req); - // Verify extracted metadata - expect(metadata).not.toBeUndefined(); - if (metadata) { - expect(metadata.method).toBe('POST'); - expect(metadata.url).toBe('http://localhost/api/users'); - // Not expecting statusCode since we don't pass response to extractHttpMetadata - expect(metadata.userAgent).toBe('test-agent'); - } - }); - }); describe('Business Event Creation', () => { - it('should create properly formatted business events', () => { - // Create a business event using the correct function signature - const event = createBusinessEvent( - 'order', - 'create', - { - entity: 'product', - result: 'success', - amount: 99.99 - } - ); - // Verify event structure - expect(event.business).toEqual({ - type: 'order', - action: 'create', - entity: 'product', - result: 'success', - amount: 99.99 - }); - }); - }); - describe('Security Event Creation', () => { - it('should create properly formatted security events', () => { - // Create a security event with the correct signature - const event = createSecurityEvent( - 'authentication', - { - action: 'login', - result: 'success', - user: 'user123', - ip: '192.168.1.1' - } - ); - // Verify event structure - expect(event.security).toEqual({ - type: 'authentication', - action: 'login', - result: 'success', - user: 'user123', - ip: '192.168.1.1' - }); - }); - }); - - describe('Sensitive Data Masking', () => { - it('should mask sensitive data patterns', () => { - // Create string with sensitive data - const input = 'User credit card: 4111-1111-1111-1111, SSN: 123-45-6789'; - - // Mask the data - const masked = maskSensitiveData(input); - // Verify masking - expect(masked).toContain('****-****-****-****'); // Actual masking format used - }); - - it('should handle different data patterns', () => { - // Create string with multiple patterns - const input = ` - Email: test@example.com - Phone: +1-555-123-4567 - IP: 192.168.1.1 - API Key: sk_test_1234567890abcdef - `; - - // Mask the data - const masked = maskSensitiveData(input); - // Verify masking of different patterns - expect(masked).toContain('***@***'); // Actual email masking format - expect(masked).toMatch(/\+\d+-\d\d\d-\d\d\d-\d\d\d\d/); // Phone number format maintained - expect(masked).toContain('192.168.1.1'); // IP addresses might not be masked - expect(masked).toContain('sk_test_1234567890abcdef'); // API keys might not be masked - }); - }); - - describe('Log Size Calculation', () => { - it('should calculate the size of log objects', () => { - // Create log objects of different sizes - const smallLog = { message: 'Test' }; - const mediumLog = { message: 'Test', user: 'testuser', id: 12345 }; - const largeLog = { - message: 'Test', - user: { - id: 12345, - name: 'Test User', - roles: ['admin', 'user'], - preferences: { - theme: 'dark', - notifications: true - } - }, - metadata: { - source: 'app', - correlationId: '123e4567-e89b-12d3-a456-426614174000' - } - }; - - // Calculate sizes - const smallSize = calculateLogSize(smallLog); - const mediumSize = calculateLogSize(mediumLog); - const largeSize = calculateLogSize(largeLog); - - // Verify sizes are reasonable - expect(smallSize).toBeGreaterThan(0); - expect(mediumSize).toBeGreaterThan(smallSize); - expect(largeSize).toBeGreaterThan(mediumSize); - - // Verify specific sizes (comparing to JSON.stringify size) - expect(smallSize).toBe(JSON.stringify(smallLog).length); - expect(mediumSize).toBe(JSON.stringify(mediumLog).length); - expect(largeSize).toBe(JSON.stringify(largeLog).length); - }); - }); - describe('Log Throttling', () => { - it('should allow specified number of logs in time window', () => { - // Create throttle with limit of 3 logs per second - const throttle = new LogThrottle(3, 1000); - - // First three logs should be allowed - expect(throttle.shouldLog('key1')).toBe(true); - expect(throttle.shouldLog('key1')).toBe(true); - expect(throttle.shouldLog('key1')).toBe(true); - - // Fourth log should be throttled - expect(throttle.shouldLog('key1')).toBe(false); - }); - - it('should reset after time window passes', async () => { - // Create throttle with very short window (100ms) - const throttle = new LogThrottle(2, 100); - - // Use up the allowed logs - expect(throttle.shouldLog('key2')).toBe(true); - expect(throttle.shouldLog('key2')).toBe(true); - expect(throttle.shouldLog('key2')).toBe(false); - - // Wait for window to pass - await new Promise(resolve => setTimeout(resolve, 110)); - - // Should allow logs again - expect(throttle.shouldLog('key2')).toBe(true); - }); - - it('should handle manual reset', () => { - // Create throttle - const throttle = new LogThrottle(1, 1000); - - // Use the single allowed log - expect(throttle.shouldLog('key3')).toBe(true); - expect(throttle.shouldLog('key3')).toBe(false); - - // Manually reset - throttle.reset('key3'); - - // Should allow another log - expect(throttle.shouldLog('key3')).toBe(true); - }); - }); -});