From 216d2b5892739f7f8678b38476e22659f44853ac Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 14 Mar 2026 00:00:34 +0100 Subject: [PATCH] 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. --- tests/unit/handler.test.ts | 141 ++++++++++++++++++++++++++++++++++--- 1 file changed, 130 insertions(+), 11 deletions(-) diff --git a/tests/unit/handler.test.ts b/tests/unit/handler.test.ts index 99a897a..cc22958 100644 --- a/tests/unit/handler.test.ts +++ b/tests/unit/handler.test.ts @@ -1,8 +1,16 @@ 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 { 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 { return new LogRecord('test', { level, msg }); } @@ -50,15 +58,6 @@ describe('Handler', () => { 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', () => { const h = new Handler(); const record = makeRecord(WARNING, 'test message'); @@ -82,6 +81,103 @@ describe('Handler', () => { 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', () => { @@ -89,4 +185,27 @@ describe('StderrHandler', () => { const h = new StderrHandler(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'); + }); });