Files
gridpilot.gg/core/shared/domain/Logger.test.ts
Marc Mintel 093eece3d7
Some checks failed
Contract Testing / contract-tests (pull_request) Failing after 5m47s
Contract Testing / contract-snapshot (pull_request) Has been skipped
core tests
2026-01-22 18:20:33 +01:00

373 lines
13 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { Logger } from './Logger';
describe('Logger', () => {
describe('Logger interface', () => {
it('should have debug method', () => {
const logs: Array<{ message: string; context?: unknown }> = [];
const logger: Logger = {
debug: (message: string, context?: unknown) => {
logs.push({ message, context });
},
info: () => {},
warn: () => {},
error: () => {}
};
logger.debug('Debug message', { userId: 123 });
expect(logs).toHaveLength(1);
expect(logs[0].message).toBe('Debug message');
expect(logs[0].context).toEqual({ userId: 123 });
});
it('should have info method', () => {
const logs: Array<{ message: string; context?: unknown }> = [];
const logger: Logger = {
debug: () => {},
info: (message: string, context?: unknown) => {
logs.push({ message, context });
},
warn: () => {},
error: () => {}
};
logger.info('Info message', { action: 'login' });
expect(logs).toHaveLength(1);
expect(logs[0].message).toBe('Info message');
expect(logs[0].context).toEqual({ action: 'login' });
});
it('should have warn method', () => {
const logs: Array<{ message: string; context?: unknown }> = [];
const logger: Logger = {
debug: () => {},
info: () => {},
warn: (message: string, context?: unknown) => {
logs.push({ message, context });
},
error: () => {}
};
logger.warn('Warning message', { threshold: 0.8 });
expect(logs).toHaveLength(1);
expect(logs[0].message).toBe('Warning message');
expect(logs[0].context).toEqual({ threshold: 0.8 });
});
it('should have error method', () => {
const logs: Array<{ message: string; error?: Error; context?: unknown }> = [];
const logger: Logger = {
debug: () => {},
info: () => {},
warn: () => {},
error: (message: string, error?: Error, context?: unknown) => {
logs.push({ message, error, context });
}
};
const testError = new Error('Test error');
logger.error('Error occurred', testError, { userId: 456 });
expect(logs).toHaveLength(1);
expect(logs[0].message).toBe('Error occurred');
expect(logs[0].error).toBe(testError);
expect(logs[0].context).toEqual({ userId: 456 });
});
it('should support logging without context', () => {
const logs: string[] = [];
const logger: Logger = {
debug: (message) => logs.push(`DEBUG: ${message}`),
info: (message) => logs.push(`INFO: ${message}`),
warn: (message) => logs.push(`WARN: ${message}`),
error: (message) => logs.push(`ERROR: ${message}`)
};
logger.debug('Debug without context');
logger.info('Info without context');
logger.warn('Warn without context');
logger.error('Error without context');
expect(logs).toHaveLength(4);
expect(logs[0]).toBe('DEBUG: Debug without context');
expect(logs[1]).toBe('INFO: Info without context');
expect(logs[2]).toBe('WARN: Warn without context');
expect(logs[3]).toBe('ERROR: Error without context');
});
});
describe('Logger behavior', () => {
it('should support structured logging', () => {
const logs: Array<{ level: string; message: string; timestamp: string; data: unknown }> = [];
const logger: Logger = {
debug: (message, context) => {
logs.push({ level: 'debug', message, timestamp: new Date().toISOString(), data: context });
},
info: (message, context) => {
logs.push({ level: 'info', message, timestamp: new Date().toISOString(), data: context });
},
warn: (message, context) => {
logs.push({ level: 'warn', message, timestamp: new Date().toISOString(), data: context });
},
error: (message, error, context) => {
const data: Record<string, unknown> = { error };
if (context) {
Object.assign(data, context);
}
logs.push({ level: 'error', message, timestamp: new Date().toISOString(), data });
}
};
logger.info('User logged in', { userId: 'user-123', ip: '192.168.1.1' });
expect(logs).toHaveLength(1);
expect(logs[0].level).toBe('info');
expect(logs[0].message).toBe('User logged in');
expect(logs[0].data).toEqual({ userId: 'user-123', ip: '192.168.1.1' });
});
it('should support log level filtering', () => {
const logs: string[] = [];
const logger: Logger = {
debug: (message) => logs.push(`[DEBUG] ${message}`),
info: (message) => logs.push(`[INFO] ${message}`),
warn: (message) => logs.push(`[WARN] ${message}`),
error: (message) => logs.push(`[ERROR] ${message}`)
};
// Simulate different log levels
logger.debug('This is a debug message');
logger.info('This is an info message');
logger.warn('This is a warning message');
logger.error('This is an error message');
expect(logs).toHaveLength(4);
expect(logs[0]).toContain('[DEBUG]');
expect(logs[1]).toContain('[INFO]');
expect(logs[2]).toContain('[WARN]');
expect(logs[3]).toContain('[ERROR]');
});
it('should support error logging with stack trace', () => {
const logs: Array<{ message: string; error: Error; context?: unknown }> = [];
const logger: Logger = {
debug: () => {},
info: () => {},
warn: () => {},
error: (message: string, error?: Error, context?: unknown) => {
if (error) {
logs.push({ message, error, context });
}
}
};
const error = new Error('Database connection failed');
logger.error('Failed to connect to database', error, { retryCount: 3 });
expect(logs).toHaveLength(1);
expect(logs[0].message).toBe('Failed to connect to database');
expect(logs[0].error.message).toBe('Database connection failed');
expect(logs[0].error.stack).toBeDefined();
expect(logs[0].context).toEqual({ retryCount: 3 });
});
it('should support logging complex objects', () => {
const logs: Array<{ message: string; context: unknown }> = [];
const logger: Logger = {
debug: () => {},
info: (message, context) => logs.push({ message, context }),
warn: () => {},
error: () => {}
};
const complexObject = {
user: {
id: 'user-123',
profile: {
name: 'John Doe',
email: 'john@example.com',
preferences: {
theme: 'dark',
notifications: true
}
}
},
session: {
id: 'session-456',
expiresAt: new Date('2024-12-31T23:59:59Z')
}
};
logger.info('User session data', complexObject);
expect(logs).toHaveLength(1);
expect(logs[0].message).toBe('User session data');
expect(logs[0].context).toEqual(complexObject);
});
it('should support logging arrays', () => {
const logs: Array<{ message: string; context: unknown }> = [];
const logger: Logger = {
debug: () => {},
info: (message, context) => logs.push({ message, context }),
warn: () => {},
error: () => {}
};
const items = [
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' },
{ id: 3, name: 'Item 3' }
];
logger.info('Processing items', { items, count: items.length });
expect(logs).toHaveLength(1);
expect(logs[0].message).toBe('Processing items');
expect(logs[0].context).toEqual({ items, count: 3 });
});
it('should support logging with null and undefined values', () => {
const logs: Array<{ message: string; context?: unknown }> = [];
const logger: Logger = {
debug: () => {},
info: (message, context) => logs.push({ message, context }),
warn: () => {},
error: () => {}
};
logger.info('Null value', null);
logger.info('Undefined value', undefined);
logger.info('Mixed values', { a: null, b: undefined, c: 'value' });
expect(logs).toHaveLength(3);
expect(logs[0].context).toBe(null);
expect(logs[1].context).toBe(undefined);
expect(logs[2].context).toEqual({ a: null, b: undefined, c: 'value' });
});
});
describe('Logger implementation patterns', () => {
it('should support console logger implementation', () => {
const consoleLogs: string[] = [];
const originalConsoleDebug = console.debug;
const originalConsoleInfo = console.info;
const originalConsoleWarn = console.warn;
const originalConsoleError = console.error;
// Mock console methods
console.debug = (...args: unknown[]) => consoleLogs.push(`DEBUG: ${args.join(' ')}`);
console.info = (...args: unknown[]) => consoleLogs.push(`INFO: ${args.join(' ')}`);
console.warn = (...args: unknown[]) => consoleLogs.push(`WARN: ${args.join(' ')}`);
console.error = (...args: unknown[]) => consoleLogs.push(`ERROR: ${args.join(' ')}`);
const consoleLogger: Logger = {
debug: (message, context) => console.debug(message, context),
info: (message, context) => console.info(message, context),
warn: (message, context) => console.warn(message, context),
error: (message, error, context) => console.error(message, error, context)
};
consoleLogger.debug('Debug message', { data: 'test' });
consoleLogger.info('Info message', { data: 'test' });
consoleLogger.warn('Warn message', { data: 'test' });
consoleLogger.error('Error message', new Error('Test'), { data: 'test' });
// Restore console methods
console.debug = originalConsoleDebug;
console.info = originalConsoleInfo;
console.warn = originalConsoleWarn;
console.error = originalConsoleError;
expect(consoleLogs).toHaveLength(4);
expect(consoleLogs[0]).toContain('DEBUG:');
expect(consoleLogs[1]).toContain('INFO:');
expect(consoleLogs[2]).toContain('WARN:');
expect(consoleLogs[3]).toContain('ERROR:');
});
it('should support file logger implementation', () => {
const fileLogs: Array<{ timestamp: string; level: string; message: string; data?: unknown }> = [];
const fileLogger: Logger = {
debug: (message, context) => {
fileLogs.push({ timestamp: new Date().toISOString(), level: 'DEBUG', message, data: context });
},
info: (message, context) => {
fileLogs.push({ timestamp: new Date().toISOString(), level: 'INFO', message, data: context });
},
warn: (message, context) => {
fileLogs.push({ timestamp: new Date().toISOString(), level: 'WARN', message, data: context });
},
error: (message, error, context) => {
const data: Record<string, unknown> = { error };
if (context) {
Object.assign(data, context);
}
fileLogs.push({ timestamp: new Date().toISOString(), level: 'ERROR', message, data });
}
};
fileLogger.info('Application started', { version: '1.0.0' });
fileLogger.warn('High memory usage', { usage: '85%' });
fileLogger.error('Database error', new Error('Connection timeout'), { retry: 3 });
expect(fileLogs).toHaveLength(3);
expect(fileLogs[0].level).toBe('INFO');
expect(fileLogs[1].level).toBe('WARN');
expect(fileLogs[2].level).toBe('ERROR');
expect(fileLogs[0].data).toEqual({ version: '1.0.0' });
});
it('should support remote logger implementation', async () => {
const remoteLogs: Array<{ level: string; message: string; context?: unknown }> = [];
const remoteLogger: Logger = {
debug: async (message, context) => {
remoteLogs.push({ level: 'debug', message, context });
await new Promise(resolve => setTimeout(resolve, 1));
},
info: async (message, context) => {
remoteLogs.push({ level: 'info', message, context });
await new Promise(resolve => setTimeout(resolve, 1));
},
warn: async (message, context) => {
remoteLogs.push({ level: 'warn', message, context });
await new Promise(resolve => setTimeout(resolve, 1));
},
error: async (message, error, context) => {
const errorContext: Record<string, unknown> = { error };
if (context) {
Object.assign(errorContext, context);
}
remoteLogs.push({ level: 'error', message, context: errorContext });
await new Promise(resolve => setTimeout(resolve, 1));
}
};
await remoteLogger.info('User action', { action: 'click', element: 'button' });
await remoteLogger.warn('Performance warning', { duration: '2000ms' });
await remoteLogger.error('API failure', new Error('404 Not Found'), { endpoint: '/api/users' });
expect(remoteLogs).toHaveLength(3);
expect(remoteLogs[0].level).toBe('info');
expect(remoteLogs[1].level).toBe('warn');
expect(remoteLogs[2].level).toBe('error');
});
});
});