test(handler): add tests for StreamHandler, ConsoleHandler, StderrHandler

Cover StreamHandler emit to custom Writable, default stream fallback,
and error handling on write failure. Cover ConsoleHandler level-to-
method mapping (error/warn/log). Cover StderrHandler console.error
output. Cover FileHandler browser rejection. Cover Handler base class
handleError not throwing.
This commit is contained in:
Tiara Rodney 2026-03-14 00:00:34 +01:00
parent 3a6332a855
commit 216d2b5892
No known key found for this signature in database
GPG key ID: 5CD8EC1D46106723

View file

@ -1,8 +1,16 @@
import {expect, jest, test} from '@jest/globals'; import {expect, jest, test} from '@jest/globals';
import { Handler, StreamHandler, StderrHandler } from '../../src/handler'; import {
Handler,
StreamHandler,
ConsoleHandler,
StderrHandler,
FileHandler,
} from '../../src/handler';
import { Formatter, DEFAULT_FORMATTER } from '../../src/formatter'; import { Formatter, DEFAULT_FORMATTER } from '../../src/formatter';
import { LogRecord } from '../../src/log-record'; import { LogRecord } from '../../src/log-record';
import { DEBUG, WARNING, ERROR, NOTSET } from '../../src/log-level'; import { Writable } from '../../src/helper/stream';
import { DEBUG, INFO, WARNING, ERROR, CRITICAL, NOTSET } from '../../src/log-level';
function makeRecord(level: number, msg: string): LogRecord { function makeRecord(level: number, msg: string): LogRecord {
return new LogRecord('test', { level, msg }); return new LogRecord('test', { level, msg });
} }
@ -50,15 +58,6 @@ describe('Handler', () => {
expect(typeof result).toBe('string'); expect(typeof result).toBe('string');
}); });
test('uses assigned formatter', () => {
const h = new Handler();
const fmt = new Formatter({ fmt: '%(message)s' });
h.formatter = fmt;
const record = makeRecord(DEBUG, 'hello');
const result = h.format(record);
expect(typeof result).toBe('string');
});
test('returns a string', () => { test('returns a string', () => {
const h = new Handler(); const h = new Handler();
const record = makeRecord(WARNING, 'test message'); const record = makeRecord(WARNING, 'test message');
@ -82,6 +81,103 @@ describe('Handler', () => {
expect(h.closed).toBe(true); expect(h.closed).toBe(true);
}); });
}); });
describe('handleError', () => {
test('does not throw', () => {
const h = new Handler();
const record = makeRecord(DEBUG, 'test');
expect(() => h.handleError(record)).not.toThrow();
});
});
});
describe('StreamHandler', () => {
test('writes formatted output to the stream', () => {
const written: string[] = [];
const stream: Writable = { write: (data: string) => { written.push(data) } };
const h = new StreamHandler(stream);
const record = makeRecord(WARNING, 'stream test');
h.emit(record);
expect(written.length).toBe(1);
expect(written[0]).toContain('stream test');
});
test('uses default stream when none provided', () => {
const h = new StreamHandler();
const record = makeRecord(DEBUG, 'default stream');
expect(() => h.emit(record)).not.toThrow();
});
test('calls handleError on emit failure', () => {
const stream: Writable = {
write: () => { throw new Error('write failed') }
};
const h = new StreamHandler(stream);
const record = makeRecord(DEBUG, 'fail');
expect(() => h.emit(record)).not.toThrow();
});
});
describe('ConsoleHandler', () => {
let origLog: typeof console.log;
let origWarn: typeof console.warn;
let origError: typeof console.error;
let logged: string[];
let warned: string[];
let errored: string[];
beforeEach(() => {
logged = [];
warned = [];
errored = [];
origLog = console.log;
origWarn = console.warn;
origError = console.error;
console.log = (...args: any[]) => { logged.push(args.join(' ')) };
console.warn = (...args: any[]) => { warned.push(args.join(' ')) };
console.error = (...args: any[]) => { errored.push(args.join(' ')) };
});
afterEach(() => {
console.log = origLog;
console.warn = origWarn;
console.error = origError;
});
test('uses console.error for ERROR level', () => {
const h = new ConsoleHandler();
h.emit(makeRecord(ERROR, 'error msg'));
expect(errored.length).toBe(1);
expect(errored[0]).toContain('error msg');
});
test('uses console.error for CRITICAL level', () => {
const h = new ConsoleHandler();
h.emit(makeRecord(CRITICAL, 'critical msg'));
expect(errored.length).toBe(1);
expect(errored[0]).toContain('critical msg');
});
test('uses console.warn for WARNING level', () => {
const h = new ConsoleHandler();
h.emit(makeRecord(WARNING, 'warn msg'));
expect(warned.length).toBe(1);
expect(warned[0]).toContain('warn msg');
});
test('uses console.log for INFO level', () => {
const h = new ConsoleHandler();
h.emit(makeRecord(INFO, 'info msg'));
expect(logged.length).toBe(1);
expect(logged[0]).toContain('info msg');
});
test('uses console.log for DEBUG level', () => {
const h = new ConsoleHandler();
h.emit(makeRecord(DEBUG, 'debug msg'));
expect(logged.length).toBe(1);
expect(logged[0]).toContain('debug msg');
});
}); });
describe('StderrHandler', () => { describe('StderrHandler', () => {
@ -89,4 +185,27 @@ describe('StderrHandler', () => {
const h = new StderrHandler(ERROR); const h = new StderrHandler(ERROR);
expect(h.level).toBe(ERROR); expect(h.level).toBe(ERROR);
}); });
test('emits via console.error', () => {
const errored: string[] = [];
const origError = console.error;
console.error = (...args: any[]) => { errored.push(args.join(' ')) };
try {
const h = new StderrHandler(DEBUG);
h.emit(makeRecord(WARNING, 'stderr test'));
expect(errored.length).toBe(1);
expect(errored[0]).toContain('stderr test');
}
finally {
console.error = origError;
}
});
});
describe('FileHandler', () => {
test('throws NotImplementedError on construction', () => {
expect(() => new FileHandler({
filename: 'test.log'
})).toThrow('not available in browser');
});
}); });