updating logger tests
This commit is contained in:
parent
b52765ecd8
commit
e76489e8f3
6 changed files with 358 additions and 1089 deletions
100
libs/logger/test/advanced.test.ts
Normal file
100
libs/logger/test/advanced.test.ts
Normal file
|
|
@ -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');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
164
libs/logger/test/basic.test.ts
Normal file
164
libs/logger/test/basic.test.ts
Normal file
|
|
@ -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');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -1,21 +1,14 @@
|
||||||
/**
|
/**
|
||||||
* Logger Integration Tests
|
* Logger Integration Tests
|
||||||
*
|
*
|
||||||
* Tests the complete functionality of the @stock-bot/logger package,
|
* Tests the core functionality of the simplified @stock-bot/logger package.
|
||||||
* verifying that all components work together correctly.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { describe, it, expect, beforeEach, afterEach } from 'bun:test';
|
import { describe, it, expect, beforeEach } from 'bun:test';
|
||||||
import {
|
import {
|
||||||
Logger,
|
Logger,
|
||||||
createLogger,
|
createLogger,
|
||||||
getLogger,
|
getLogger
|
||||||
createTimer,
|
|
||||||
formatError,
|
|
||||||
sanitizeMetadata,
|
|
||||||
generateCorrelationId,
|
|
||||||
calculateLogSize,
|
|
||||||
LogThrottle
|
|
||||||
} from '../src';
|
} from '../src';
|
||||||
import { loggerTestHelpers } from './setup';
|
import { loggerTestHelpers } from './setup';
|
||||||
|
|
||||||
|
|
@ -63,35 +56,24 @@ describe('Logger Integration Tests', () => {
|
||||||
expect(logs[0].userId).toBe('123');
|
expect(logs[0].userId).toBe('123');
|
||||||
expect(logs[0].action).toBe('login');
|
expect(logs[0].action).toBe('login');
|
||||||
expect(logs[0].msg).toBe('User logged in');
|
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');
|
it('should handle error objects in error logs', () => {
|
||||||
(contextLogger as any).context = {
|
const testError = new Error('Test error message');
|
||||||
requestId: 'req-123',
|
|
||||||
userId: 'user-456'
|
|
||||||
};
|
|
||||||
|
|
||||||
// Log with context
|
// Log error with error object
|
||||||
contextLogger.info('Context test');
|
logger.error('Something went wrong', { error: testError });
|
||||||
|
|
||||||
// Get captured logs
|
// Get captured logs
|
||||||
const logs = loggerTestHelpers.getCapturedLogs();
|
const logs = loggerTestHelpers.getCapturedLogs();
|
||||||
|
|
||||||
// Verify context is included (implementation dependent)
|
// Verify error was logged
|
||||||
expect(logs.length).toBe(1);
|
expect(logs.length).toBe(1);
|
||||||
expect(logs[0].msg).toBe('Context test');
|
expect(logs[0].level).toBe('error');
|
||||||
|
expect(logs[0].msg).toBe('Something went wrong');
|
||||||
// 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');
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create child loggers with additional context', () => {
|
it('should create child loggers with additional context', () => {
|
||||||
// Create a child logger with additional context
|
// Create a child logger with additional context
|
||||||
const childLogger = logger.child({
|
const childLogger = logger.child({
|
||||||
transactionId: 'tx-789',
|
transactionId: 'tx-789',
|
||||||
|
|
@ -107,180 +89,98 @@ describe('Logger Integration Tests', () => {
|
||||||
// Verify child logger logged something
|
// Verify child logger logged something
|
||||||
expect(logs.length).toBe(1);
|
expect(logs.length).toBe(1);
|
||||||
expect(logs[0].msg).toBe('Child logger test');
|
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', () => {
|
describe('Factory Functions', () => {
|
||||||
it('should create performance timers', async () => {
|
it('should export factory functions', () => {
|
||||||
// Create and use timer
|
// Verify that the factory functions are exported and callable
|
||||||
const timer = createTimer('test-operation');
|
expect(typeof createLogger).toBe('function');
|
||||||
|
expect(typeof getLogger).toBe('function');
|
||||||
// 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');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should format errors for logging', () => {
|
it('should create different logger instances', () => {
|
||||||
// Create error
|
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');
|
const error = new Error('Test error');
|
||||||
error.name = 'TestError';
|
error.stack = 'Error stack trace';
|
||||||
|
|
||||||
// Format error
|
logger.error('Error occurred', { error });
|
||||||
const formattedError = formatError(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();
|
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.length).toBe(1);
|
||||||
expect(logs[0].level).toBe('error');
|
expect(logs[0].level).toBe('error');
|
||||||
expect(logs[0].error.message).toBe('Test exception');
|
expect(logs[0].msg).toBe('Error occurred');
|
||||||
expect(logs[0].msg).toBe('Caught an exception');
|
});
|
||||||
|
|
||||||
|
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');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -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();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -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();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue