import { logger, LogLevel } from '../src/utils/logger.js';
// Mock console methods to test logging
const originalConsole = {
debug: console.debug,
info: console.info,
warn: console.warn,
error: console.error,
};
describe('Utils', () => {
beforeEach(() => {
// Mock console methods
console.debug = jest.fn();
console.info = jest.fn();
console.warn = jest.fn();
console.error = jest.fn();
});
afterEach(() => {
// Restore console methods
console.debug = originalConsole.debug;
console.info = originalConsole.info;
console.warn = originalConsole.warn;
console.error = originalConsole.error;
});
describe('Logger', () => {
describe('log levels', () => {
it('should log debug messages when level is DEBUG', () => {
logger.setLevel(LogLevel.DEBUG);
logger.debug('Debug message');
expect(console.debug).toHaveBeenCalledWith(
expect.stringContaining('DEBUG: Debug message'),
);
});
it('should log info messages when level is INFO', () => {
logger.setLevel(LogLevel.INFO);
logger.info('Info message');
expect(console.info).toHaveBeenCalledWith(
expect.stringContaining('INFO: Info message'),
);
});
it('should log warn messages when level is WARN', () => {
logger.setLevel(LogLevel.WARN);
logger.warn('Warning message');
expect(console.warn).toHaveBeenCalledWith(
expect.stringContaining('WARN: Warning message'),
);
});
it('should log error messages when level is ERROR', () => {
logger.setLevel(LogLevel.ERROR);
logger.error('Error message');
expect(console.error).toHaveBeenCalledWith(
expect.stringContaining('ERROR: Error message'),
);
});
});
describe('log level filtering', () => {
it('should not log debug when level is INFO', () => {
logger.setLevel(LogLevel.INFO);
logger.debug('Debug message');
expect(console.debug).not.toHaveBeenCalled();
});
it('should not log info when level is WARN', () => {
logger.setLevel(LogLevel.WARN);
logger.info('Info message');
expect(console.info).not.toHaveBeenCalled();
});
it('should not log warn when level is ERROR', () => {
logger.setLevel(LogLevel.ERROR);
logger.warn('Warning message');
expect(console.warn).not.toHaveBeenCalled();
});
it('should log higher level messages', () => {
logger.setLevel(LogLevel.WARN);
logger.warn('Warning message');
logger.error('Error message');
expect(console.warn).toHaveBeenCalled();
expect(console.error).toHaveBeenCalled();
});
});
describe('message formatting', () => {
beforeEach(() => {
logger.setLevel(LogLevel.DEBUG);
});
it('should include timestamp in log messages', () => {
logger.info('Test message');
expect(console.info).toHaveBeenCalledWith(
expect.stringMatching(/\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z\]/),
);
});
it('should include log level in message', () => {
logger.info('Test message');
expect(console.info).toHaveBeenCalledWith(
expect.stringContaining('INFO: Test message'),
);
});
it('should format metadata as JSON', () => {
const metadata = { userId: '123', action: 'login' };
logger.info('User action', metadata);
expect(console.info).toHaveBeenCalledWith(
expect.stringContaining(JSON.stringify(metadata, null, 2)),
);
});
it('should handle string metadata', () => {
logger.info('Test message', 'string metadata');
expect(console.info).toHaveBeenCalledWith(
expect.stringContaining('string metadata'),
);
});
it('should handle number metadata', () => {
logger.info('Test message', 42);
expect(console.info).toHaveBeenCalledWith(
expect.stringContaining('42'),
);
});
it('should handle undefined metadata', () => {
logger.info('Test message', undefined);
expect(console.info).toHaveBeenCalledWith(
expect.not.stringContaining('undefined'),
);
});
});
describe('error handling', () => {
beforeEach(() => {
logger.setLevel(LogLevel.DEBUG);
});
it('should handle circular references in metadata', () => {
const circular: any = { prop: 'value' };
circular.circular = circular;
// Should not throw
expect(() => {
logger.info('Test message', circular);
}).not.toThrow();
expect(console.info).toHaveBeenCalled();
});
it('should handle null metadata', () => {
logger.info('Test message', null);
expect(console.info).toHaveBeenCalledWith(
expect.stringContaining('null'),
);
});
it('should handle Error objects as metadata', () => {
const error = new Error('Test error');
logger.error('An error occurred', error);
expect(console.error).toHaveBeenCalledWith(
expect.stringContaining('Test error'),
);
});
});
describe('level changing', () => {
it('should update log level dynamically', () => {
logger.setLevel(LogLevel.ERROR);
logger.info('Should not appear');
expect(console.info).not.toHaveBeenCalled();
logger.setLevel(LogLevel.INFO);
logger.info('Should appear');
expect(console.info).toHaveBeenCalled();
});
it('should preserve new level across multiple calls', () => {
logger.setLevel(LogLevel.WARN);
logger.debug('Debug 1');
logger.info('Info 1');
logger.warn('Warn 1');
expect(console.debug).not.toHaveBeenCalled();
expect(console.info).not.toHaveBeenCalled();
expect(console.warn).toHaveBeenCalled();
});
});
});
describe('Config', () => {
// Note: Config tests would require mocking environment variables
// For the sake of this example, we'll test that the config module loads
it('should load config from environment', async () => {
// This test verifies that the config module can be imported
// In a real scenario, you'd want to test with different env vars
const { appConfig } = await import('../src/utils/config.js');
expect(appConfig).toBeDefined();
expect(appConfig.pocketbase).toBeDefined();
expect(appConfig.server).toBeDefined();
});
it('should have correct config structure', async () => {
const { appConfig } = await import('../src/utils/config.js');
expect(appConfig.pocketbase.url).toBeDefined();
expect(appConfig.pocketbase.superuser.email).toBeDefined();
expect(appConfig.pocketbase.superuser.password).toBeDefined();
expect(appConfig.server.environment).toBeDefined();
expect(appConfig.server.logLevel).toBeDefined();
});
it('should have boolean flags for environment detection', async () => {
const { appConfig } = await import('../src/utils/config.js');
expect(typeof appConfig.server.isDevelopment).toBe('boolean');
expect(typeof appConfig.server.isProduction).toBe('boolean');
expect(typeof appConfig.server.isTest).toBe('boolean');
});
});
});