Files
fredy/test/services/logger.test.js
2026-06-09 15:42:25 +02:00

90 lines
2.9 KiB
JavaScript

/*
* Copyright (c) 2026 by Christian Kellner.
* Licensed under Apache-2.0 with Commons Clause and Attribution/Naming Clause
*/
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import path from 'node:path';
describe('services/logger.js - debug log sink', () => {
let logger;
let setDebugLogSink;
let consoleSpies;
beforeEach(async () => {
vi.resetModules();
const mod = await import(path.resolve('lib/services/logger.js'));
logger = mod.default;
setDebugLogSink = mod.setDebugLogSink;
// Silence console output so test runner stdout stays readable while still
// letting us inspect what the logger emitted if a test wants to.
consoleSpies = {
debug: vi.spyOn(console, 'debug').mockImplementation(() => {}),
info: vi.spyOn(console, 'info').mockImplementation(() => {}),
warn: vi.spyOn(console, 'warn').mockImplementation(() => {}),
error: vi.spyOn(console, 'error').mockImplementation(() => {}),
};
});
afterEach(() => {
setDebugLogSink(null);
for (const spy of Object.values(consoleSpies)) spy.mockRestore();
});
it('is a no-op for the sink when none is registered', () => {
// Just make sure nothing throws.
expect(() => logger.info('hello')).not.toThrow();
expect(() => logger.error(new Error('boom'))).not.toThrow();
});
it('forwards every log level (including debug) to the registered sink', () => {
const captured = [];
setDebugLogSink((entry) => captured.push(entry));
logger.debug('debug-line');
logger.info('info-line');
logger.warn('warn-line');
logger.error('error-line');
expect(captured).toHaveLength(4);
expect(captured.map((c) => c.level)).toEqual(['debug', 'info', 'warn', 'error']);
expect(captured[0].message).toContain('debug-line');
expect(captured[1].message).toContain('info-line');
expect(captured[2].message).toContain('warn-line');
expect(captured[3].message).toContain('error-line');
for (const c of captured) {
expect(typeof c.ts).toBe('number');
}
});
it('serializes Error stacks for the sink instead of "[object Object]"', () => {
const captured = [];
setDebugLogSink((entry) => captured.push(entry));
logger.error(new Error('boom'));
expect(captured).toHaveLength(1);
expect(captured[0].message).toContain('Error: boom');
});
it('stops forwarding once the sink is unregistered', () => {
const captured = [];
setDebugLogSink((entry) => captured.push(entry));
logger.info('one');
setDebugLogSink(null);
logger.info('two');
expect(captured).toHaveLength(1);
expect(captured[0].message).toContain('one');
});
it('does not break the caller when the sink throws', () => {
setDebugLogSink(() => {
throw new Error('sink exploded');
});
expect(() => logger.info('still works')).not.toThrow();
expect(consoleSpies.info).toHaveBeenCalled();
});
});