'use strict'; const assume = require('assume'); const Writable = require('readable-stream/writable'); const LegacyTransportStream = require('../legacy'); const LegacyTransport = require('./fixtures/legacy-transport'); const { testLevels, testOrder } = require('./fixtures'); const { infosFor, logFor, levelAndMessage } = require('abstract-winston-transport/utils'); const { LEVEL } = require('triple-beam'); // // Silence the deprecation notice for sanity in test output. // TODO: Test coverage for the deprecation notice because why not? // const deprecated = { original: LegacyTransportStream.prototype._deprecated, silence() { LegacyTransportStream.prototype._deprecated = () => {}; }, restore() { LegacyTransportStream.prototype._deprecated = this.original; } }; describe('LegacyTransportStream', () => { let legacy; let transport; before(deprecated.silence); beforeEach(() => { legacy = new LegacyTransport(); transport = new LegacyTransportStream({ transport: legacy }); }); it('should have the appropriate methods defined', () => { assume(transport).instanceof(Writable); assume(transport._write).is.a('function'); // eslint-disable-next-line no-undefined assume(transport.log).equals(undefined); }); it('should error with no transport', () => { assume(() => { transport = new LegacyTransportStream(); assume(transport).instanceof(stream.Writable); }).throws(/Invalid transport, must be an object with a log method./); }); it('should error with invalid transport', () => { assume(() => { transport = new LegacyTransportStream(); assume(transport).instanceof(stream.Writable); }).throws(/Invalid transport, must be an object with a log method./); }); it('should display a deprecation notice', done => { deprecated.restore(); const error = console.error; // eslint-disable-line no-console console.error = msg => { // eslint-disable-line no-console assume(msg).to.include('is a legacy winston transport. Consider upgrading'); setImmediate(done); }; legacy = new LegacyTransport(); transport = new LegacyTransportStream({ transport: legacy }); console.error = error; // eslint-disable-line no-console deprecated.silence(); }); it('sets __winstonError on the LegacyTransport instance', function () { assume(legacy.__winstonError).is.a('function'); assume(legacy.listeners('error')).deep.equals([ legacy.__winstonError ]); }); it('emits an error on LegacyTransport error', done => { const err = new Error('Pass-through from stream'); transport.on('error', actual => { assume(err).equals(actual); done(); }); legacy.emit('error', err); }); describe('_write(info, enc, callback)', () => { it('should log to any level when { level: undefined }', done => { const expected = testOrder.map(levelAndMessage); legacy.on('logged', logFor(testOrder.length, (err, infos) => { if (err) { return done(err); } assume(infos.length).equals(expected.length); assume(infos).deep.equals(expected); done(); })); expected.forEach(transport.write.bind(transport)); }); it('should only log messages BELOW the level priority', done => { const expected = testOrder.map(levelAndMessage); transport = new LegacyTransportStream({ level: 'info', transport: legacy }); legacy.on('logged', logFor(5, (err, infos) => { if (err) { return done(err); } assume(infos.length).equals(5); assume(infos).deep.equals(expected.slice(0, 5)); done(); })); transport.levels = testLevels; expected.forEach(transport.write.bind(transport)); }); it('{ level } should be ignored when { handleExceptions: true }', () => { const expected = testOrder.map(levelAndMessage).map(info => { info.exception = true; return info; }); transport = new LegacyTransportStream({ transport: legacy, level: 'info' }); legacy.on('logged', logFor(testOrder.length, (err, infos) => { // eslint-disable-next-line no-undefined assume(err).equals(undefined); assume(infos.length).equals(expected.length); assume(infos).deep.equals(expected); })); transport.levels = testLevels; expected.forEach(transport.write.bind(transport)); }); describe('when { exception: true } in info', () => { it('should not invoke log when { handleExceptions: false }', done => { const expected = [{ exception: true, [LEVEL]: 'error', level: 'error', message: 'Test exception handling' }, { [LEVEL]: 'test', level: 'test', message: 'Testing ... 1 2 3.' }]; legacy.on('logged', info => { // eslint-disable-next-line no-undefined assume(info.exception).equals(undefined); done(); }); expected.forEach(transport.write.bind(transport)); }); it('should invoke log when { handleExceptions: true }', done => { const actual = []; const expected = [{ exception: true, [LEVEL]: 'error', level: 'error', message: 'Test exception handling' }, { [LEVEL]: 'test', level: 'test', message: 'Testing ... 1 2 3.' }]; transport = new LegacyTransportStream({ handleExceptions: true, transport: legacy }); legacy.on('logged', info => { actual.push(info); if (actual.length === expected.length) { assume(actual).deep.equals(expected); return done(); } }); expected.forEach(transport.write.bind(transport)); }); }); }); describe('_writev(chunks, callback)', () => { it('should be called when necessary in streams plumbing', done => { const expected = infosFor({ count: 50, levels: testOrder }); legacy.on('logged', logFor(50 * testOrder.length, (err, infos) => { if (err) { return done(err); } assume(infos.length).equals(expected.length); assume(infos).deep.equals(expected); done(); })); // // Make the standard _write throw to ensure that _writev is called. // transport._write = () => { throw new Error('This should never be called'); }; transport.cork(); expected.forEach(transport.write.bind(transport)); transport.uncork(); }); }); describe('close()', () => { it('removes __winstonError from the transport', () => { assume(legacy.__winstonError).is.a('function'); assume(legacy.listenerCount('error')).equal(1); transport.close(); assume(legacy.__winstonError).falsy(); assume(legacy.listenerCount('error')).equal(0); }); it('invokes .close() on the transport', done => { legacy.on('closed:custom', done); transport.close(); }); }); describe('{ silent }', () => { it('{ silent: true } ._write() never calls `.log`', done => { const expected = { [LEVEL]: 'info', level: 'info', message: 'there will be json' }; legacy.once('logged', () => ( assume(true).false('"logged" event emitted unexpectedly') )); transport.silent = true; transport.write(expected); setImmediate(() => done()); }); it('{ silent: true } ._writev() never calls `.log`', done => { const expected = { [LEVEL]: 'info', level: 'info', message: 'there will be json' }; legacy.once('logged', () => ( assume(true).false('"logged" event emitted unexpectedly') )); transport.cork(); for (let i = 0; i < 15; i++) { transport.write(expected); } transport.silent = true; transport.uncork(); setImmediate(() => done()); }); it('{ silent: true } ensures ._accept(write) always returns false', () => { transport.silent = true; const accepted = transport._accept({ chunk: {} }); assume(accepted).false(); }); }); // // Restore the deprecation notice after tests complete. // after(() => deprecated.restore()); });