218 lines
7.1 KiB
TypeScript
218 lines
7.1 KiB
TypeScript
import { LoggingService, LogLevel } from '../../../src/Application/Services/LoggingService';
|
|
import { logAuth, logError, logDatabase, logStartup } from '../../../src/Application/Services/Logger';
|
|
import fs from 'fs';
|
|
import path from 'path';
|
|
|
|
describe('LoggingService', () => {
|
|
let loggingService: LoggingService;
|
|
const testLogsDir = path.join(process.cwd(), 'test-logs');
|
|
|
|
beforeEach(() => {
|
|
// Clean up any existing test logs
|
|
if (fs.existsSync(testLogsDir)) {
|
|
fs.rmSync(testLogsDir, { recursive: true, force: true });
|
|
}
|
|
|
|
// Mock environment variables for testing
|
|
process.env.MAX_LOGS_PER_FILE = '10';
|
|
process.env.MINIO_ENDPOINT = '';
|
|
|
|
loggingService = LoggingService.getInstance();
|
|
});
|
|
|
|
afterEach(() => {
|
|
// Clean up test logs
|
|
if (fs.existsSync(testLogsDir)) {
|
|
fs.rmSync(testLogsDir, { recursive: true, force: true });
|
|
}
|
|
|
|
// Clean up environment variables
|
|
delete process.env.MAX_LOGS_PER_FILE;
|
|
delete process.env.MINIO_ENDPOINT;
|
|
});
|
|
|
|
describe('Log Level Functions', () => {
|
|
it('should log authentication events', () => {
|
|
const consoleSpy = jest.spyOn(console, 'info').mockImplementation();
|
|
|
|
logAuth('Test auth message', 'user123', { action: 'login' });
|
|
|
|
expect(consoleSpy).toHaveBeenCalled();
|
|
const logCall = consoleSpy.mock.calls[0][0];
|
|
expect(logCall).toContain('[AUTH]');
|
|
expect(logCall).toContain('Test auth message');
|
|
|
|
consoleSpy.mockRestore();
|
|
});
|
|
|
|
it('should log error events with stack trace', () => {
|
|
const consoleSpy = jest.spyOn(console, 'error').mockImplementation();
|
|
const testError = new Error('Test error message');
|
|
|
|
logError('Test error occurred', testError);
|
|
|
|
expect(consoleSpy).toHaveBeenCalled();
|
|
const logCall = consoleSpy.mock.calls[0][0];
|
|
expect(logCall).toContain('[ERROR]');
|
|
expect(logCall).toContain('Test error occurred');
|
|
|
|
consoleSpy.mockRestore();
|
|
});
|
|
|
|
it('should log database operations with timing', () => {
|
|
const consoleSpy = jest.spyOn(console, 'info').mockImplementation();
|
|
|
|
logDatabase('Query executed', 'SELECT * FROM users', 45);
|
|
|
|
expect(consoleSpy).toHaveBeenCalled();
|
|
const logCall = consoleSpy.mock.calls[0][0];
|
|
expect(logCall).toContain('[DATABASE]');
|
|
expect(logCall).toContain('Query executed');
|
|
|
|
consoleSpy.mockRestore();
|
|
});
|
|
|
|
it('should log startup events', () => {
|
|
const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
|
|
|
|
logStartup('Application started', { version: '1.0.0' });
|
|
|
|
expect(consoleSpy).toHaveBeenCalled();
|
|
const logCall = consoleSpy.mock.calls[0][0];
|
|
expect(logCall).toContain('[STARTUP]');
|
|
expect(logCall).toContain('Application started');
|
|
|
|
consoleSpy.mockRestore();
|
|
});
|
|
});
|
|
|
|
describe('Log Formatting', () => {
|
|
it('should include timestamp in log entries', () => {
|
|
const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
|
|
|
|
logStartup('Test message');
|
|
|
|
expect(consoleSpy).toHaveBeenCalled();
|
|
const logCall = consoleSpy.mock.calls[0][0];
|
|
|
|
// Check if timestamp is in ISO format
|
|
const timestampRegex = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z/;
|
|
expect(logCall).toMatch(timestampRegex);
|
|
|
|
consoleSpy.mockRestore();
|
|
});
|
|
|
|
it('should include metadata in log entries', () => {
|
|
const consoleSpy = jest.spyOn(console, 'info').mockImplementation();
|
|
const metadata = { userId: '123', action: 'test' };
|
|
|
|
logAuth('Test with metadata', 'user123', metadata);
|
|
|
|
expect(consoleSpy).toHaveBeenCalled();
|
|
const logCall = consoleSpy.mock.calls[0][0];
|
|
expect(logCall).toContain('Meta:');
|
|
expect(logCall).toContain('"userId":"123"');
|
|
expect(logCall).toContain('"action":"test"');
|
|
|
|
consoleSpy.mockRestore();
|
|
});
|
|
});
|
|
|
|
describe('Request Logging Middleware', () => {
|
|
it('should create request logging middleware', () => {
|
|
const middleware = loggingService.requestLoggingMiddleware();
|
|
|
|
expect(typeof middleware).toBe('function');
|
|
expect(middleware.length).toBe(3); // req, res, next
|
|
});
|
|
|
|
it('should create error logging middleware', () => {
|
|
const middleware = loggingService.errorLoggingMiddleware();
|
|
|
|
expect(typeof middleware).toBe('function');
|
|
expect(middleware.length).toBe(4); // error, req, res, next
|
|
});
|
|
});
|
|
|
|
describe('Log Levels', () => {
|
|
it('should have all required log levels defined', () => {
|
|
expect(LogLevel.REQUEST).toBe('REQUEST');
|
|
expect(LogLevel.ERROR).toBe('ERROR');
|
|
expect(LogLevel.WARNING).toBe('WARNING');
|
|
expect(LogLevel.AUTH).toBe('AUTH');
|
|
expect(LogLevel.DATABASE).toBe('DATABASE');
|
|
expect(LogLevel.STARTUP).toBe('STARTUP');
|
|
expect(LogLevel.CONNECTION).toBe('CONNECTION');
|
|
expect(LogLevel.OTHER).toBe('OTHER');
|
|
});
|
|
});
|
|
|
|
describe('Singleton Pattern', () => {
|
|
it('should return the same instance', () => {
|
|
const instance1 = LoggingService.getInstance();
|
|
const instance2 = LoggingService.getInstance();
|
|
|
|
expect(instance1).toBe(instance2);
|
|
});
|
|
});
|
|
|
|
describe('File Operations', () => {
|
|
it('should handle missing Minio configuration gracefully', () => {
|
|
// Test that the service starts without Minio config
|
|
expect(() => LoggingService.getInstance()).not.toThrow();
|
|
});
|
|
|
|
it('should generate monthly directory structure', () => {
|
|
const now = new Date();
|
|
const year = now.getFullYear();
|
|
const month = String(now.getMonth() + 1).padStart(2, '0');
|
|
const expectedPath = path.join('logs', `${year}-${month}`);
|
|
|
|
// This tests the internal logic through the public interface
|
|
logStartup('Test for directory creation');
|
|
|
|
// Since we can't directly test the private method, we verify the service doesn't crash
|
|
expect(loggingService).toBeDefined();
|
|
});
|
|
});
|
|
|
|
describe('Error Handling', () => {
|
|
it('should handle logging errors gracefully', () => {
|
|
// Mock fs.appendFileSync to throw an error
|
|
const originalAppendFileSync = fs.appendFileSync;
|
|
const consoleSpy = jest.spyOn(console, 'error').mockImplementation();
|
|
|
|
fs.appendFileSync = jest.fn(() => {
|
|
throw new Error('Disk full');
|
|
});
|
|
|
|
expect(() => {
|
|
logStartup('This should not crash');
|
|
}).not.toThrow();
|
|
|
|
// Restore original function
|
|
fs.appendFileSync = originalAppendFileSync;
|
|
consoleSpy.mockRestore();
|
|
});
|
|
|
|
it('should continue logging to console even if file logging fails', () => {
|
|
const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
|
|
|
|
// Mock file system to fail
|
|
const originalAppendFileSync = fs.appendFileSync;
|
|
fs.appendFileSync = jest.fn(() => {
|
|
throw new Error('File system error');
|
|
});
|
|
|
|
logStartup('Test message');
|
|
|
|
// Should still log to console
|
|
expect(consoleSpy).toHaveBeenCalled();
|
|
|
|
// Restore
|
|
fs.appendFileSync = originalAppendFileSync;
|
|
consoleSpy.mockRestore();
|
|
});
|
|
});
|
|
});
|