mirror of
https://github.com/orangecoding/fredy.git
synced 2026-06-16 12:31:07 +00:00
90 lines
2.9 KiB
JavaScript
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();
|
|
});
|
|
});
|