228 lines
6.0 KiB
TypeScript
228 lines
6.0 KiB
TypeScript
import { describe, it, expect, beforeEach, vi, type Mock } from 'bun:test';
|
|
import Logger from '../src/Logger';
|
|
import { LoggingLevel } from '../src/level/LoggingLevel';
|
|
import Threshold from '../src/level/Threshold';
|
|
import type IAppender from '../src/appenders/IAppender';
|
|
|
|
describe('Logger', () => {
|
|
let mockAppender: IAppender;
|
|
let threshold: Threshold;
|
|
let logger: Logger;
|
|
|
|
beforeEach(() => {
|
|
mockAppender = {
|
|
log: vi.fn(),
|
|
};
|
|
|
|
threshold = new Threshold(LoggingLevel.Info);
|
|
logger = new Logger('test-category', threshold, new Set([mockAppender]));
|
|
});
|
|
|
|
describe('threshold filtering', () => {
|
|
it('should log info messages when threshold is Info', () => {
|
|
logger.info('test message');
|
|
|
|
expect(mockAppender.log).toHaveBeenCalledWith(
|
|
expect.any(String), // timestamp
|
|
'Info',
|
|
'test-category',
|
|
'test message'
|
|
);
|
|
});
|
|
|
|
it('should log warn messages when threshold is Info', () => {
|
|
logger.warn('test message');
|
|
|
|
expect(mockAppender.log).toHaveBeenCalledWith(
|
|
expect.any(String),
|
|
'Warn',
|
|
'test-category',
|
|
'test message'
|
|
);
|
|
});
|
|
|
|
it('should log error messages when threshold is Info', () => {
|
|
logger.error('test message');
|
|
|
|
expect(mockAppender.log).toHaveBeenCalledWith(
|
|
expect.any(String),
|
|
'Error',
|
|
'test-category',
|
|
'test message'
|
|
);
|
|
});
|
|
|
|
it('should not log debug messages when threshold is Info', () => {
|
|
logger.debug('test message');
|
|
|
|
expect(mockAppender.log).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should not log trace messages when threshold is Info', () => {
|
|
logger.trace('test message');
|
|
|
|
expect(mockAppender.log).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should not log silly messages when threshold is Info', () => {
|
|
logger.silly('test message');
|
|
|
|
expect(mockAppender.log).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should log all messages when threshold is Debug', () => {
|
|
threshold.value = LoggingLevel.Debug;
|
|
|
|
logger.debug('debug message');
|
|
logger.info('info message');
|
|
logger.warn('warn message');
|
|
logger.error('error message');
|
|
|
|
expect(mockAppender.log).toHaveBeenCalledTimes(4);
|
|
});
|
|
|
|
it('should log only error messages when threshold is Error', () => {
|
|
threshold.value = LoggingLevel.Error;
|
|
|
|
logger.error('error message');
|
|
logger.warn('warn message');
|
|
logger.info('info message');
|
|
|
|
expect(mockAppender.log).toHaveBeenCalledTimes(1);
|
|
expect(mockAppender.log).toHaveBeenCalledWith(
|
|
expect.any(String),
|
|
'Error',
|
|
'test-category',
|
|
'error message'
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('message formatting', () => {
|
|
beforeEach(() => {
|
|
threshold.value = LoggingLevel.Debug; // Allow all messages for formatting tests
|
|
});
|
|
|
|
it('should format messages with %s placeholder', () => {
|
|
logger.info('Hello %s', 'world');
|
|
|
|
expect(mockAppender.log).toHaveBeenCalledWith(
|
|
expect.any(String),
|
|
'Info',
|
|
'test-category',
|
|
'Hello world'
|
|
);
|
|
});
|
|
|
|
it('should format messages with %d placeholder', () => {
|
|
logger.info('Count: %d', 42);
|
|
|
|
expect(mockAppender.log).toHaveBeenCalledWith(
|
|
expect.any(String),
|
|
'Info',
|
|
'test-category',
|
|
'Count: 42'
|
|
);
|
|
});
|
|
|
|
it('should format messages with %j placeholder', () => {
|
|
logger.info('Object: %j', { key: 'value' });
|
|
|
|
expect(mockAppender.log).toHaveBeenCalledWith(
|
|
expect.any(String),
|
|
'Info',
|
|
'test-category',
|
|
'Object: {"key":"value"}'
|
|
);
|
|
});
|
|
|
|
it('should handle circular references in %j placeholder', () => {
|
|
const obj: any = { key: 'value' };
|
|
obj.self = obj;
|
|
|
|
logger.info('Circular: %j', obj);
|
|
|
|
expect(mockAppender.log).toHaveBeenCalledWith(
|
|
expect.any(String),
|
|
'Info',
|
|
'test-category',
|
|
'Circular: [Circular]'
|
|
);
|
|
});
|
|
|
|
it('should handle multiple placeholders', () => {
|
|
logger.info('User %s has %d items: %j', 'Alice', 5, ['a', 'b']);
|
|
|
|
expect(mockAppender.log).toHaveBeenCalledWith(
|
|
expect.any(String),
|
|
'Info',
|
|
'test-category',
|
|
'User Alice has 5 items: ["a","b"]'
|
|
);
|
|
});
|
|
|
|
it('should handle more placeholders than arguments', () => {
|
|
logger.info('Hello %s %s', 'world');
|
|
|
|
expect(mockAppender.log).toHaveBeenCalledWith(
|
|
expect.any(String),
|
|
'Info',
|
|
'test-category',
|
|
'Hello world %s'
|
|
);
|
|
});
|
|
|
|
it('should handle fewer placeholders than arguments', () => {
|
|
logger.info('Hello %s', 'world', 'extra');
|
|
|
|
expect(mockAppender.log).toHaveBeenCalledWith(
|
|
expect.any(String),
|
|
'Info',
|
|
'test-category',
|
|
'Hello world'
|
|
);
|
|
});
|
|
|
|
it('should handle non-number values for %d', () => {
|
|
logger.info('Value: %d', 'not-a-number');
|
|
|
|
expect(mockAppender.log).toHaveBeenCalledWith(
|
|
expect.any(String),
|
|
'Info',
|
|
'test-category',
|
|
'Value: NaN'
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('timestamp formatting', () => {
|
|
it('should include ISO timestamp in log messages', () => {
|
|
const before = new Date().toISOString();
|
|
logger.info('test');
|
|
const after = new Date().toISOString();
|
|
|
|
const call = (mockAppender.log as Mock).mock.calls[0];
|
|
const timestamp = call[0];
|
|
|
|
expect(timestamp).toBeDefined();
|
|
expect(() => new Date(timestamp)).not.toThrow();
|
|
expect(timestamp >= before || timestamp <= after).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('multiple appenders', () => {
|
|
it('should call all appenders', () => {
|
|
const mockAppender2: IAppender = {
|
|
log: vi.fn(),
|
|
};
|
|
|
|
const loggerWithMultiple = new Logger('test', threshold, new Set([mockAppender, mockAppender2]));
|
|
|
|
loggerWithMultiple.info('test message');
|
|
|
|
expect(mockAppender.log).toHaveBeenCalledTimes(1);
|
|
expect(mockAppender2.log).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|
|
});
|