diff --git a/lib/colors.test.js b/lib/colors.test.js index 89de15be..8bad5a1a 100644 --- a/lib/colors.test.js +++ b/lib/colors.test.js @@ -1,8 +1,7 @@ 'use strict' const { test } = require('tap') -const getColorizerPrivate = require('./colors') -const { colorizerFactory: getColorizerPublic } = require('../index') +const getColorizer = require('./colors') const testDefaultColorizer = getColorizer => async t => { const colorizer = getColorizer() @@ -122,9 +121,12 @@ const testCustomColoringColorizer = getColorizer => async t => { t.equal(colorized, '\u001B[37mUSERLVL\u001B[39m') } -test('returns default colorizer - private export', testDefaultColorizer(getColorizerPrivate)) -test('returns default colorizer - public export', testDefaultColorizer(getColorizerPublic)) -test('returns colorizing colorizer - private export', testColoringColorizer(getColorizerPrivate)) -test('returns colorizing colorizer - public export', testColoringColorizer(getColorizerPublic)) -test('returns custom colorizing colorizer - private export', testCustomColoringColorizer(getColorizerPrivate)) -test('returns custom colorizing colorizer - public export', testCustomColoringColorizer(getColorizerPublic)) +test('returns default colorizer - private export', testDefaultColorizer(getColorizer)) +test('returns colorizing colorizer - private export', testColoringColorizer(getColorizer)) +test('returns custom colorizing colorizer - private export', testCustomColoringColorizer(getColorizer)) + +test('custom props defaults to standard levels', async t => { + const colorizer = getColorizer(true, [], true) + const colorized = colorizer('info') + t.equal(colorized, '\u001B[37mINFO\u001B[39m') +}) diff --git a/lib/utils/build-safe-sonic-boom.test.js b/lib/utils/build-safe-sonic-boom.test.js index 6510bb8b..235393af 100644 --- a/lib/utils/build-safe-sonic-boom.test.js +++ b/lib/utils/build-safe-sonic-boom.test.js @@ -45,3 +45,42 @@ tap.test('should stream.write works when error code is not "EPIPE"', async t => const dataFile = fs.readFileSync(dest) t.equal(dataFile.toString(), 'will work') }) + +tap.test('cover setupOnExit', async t => { + t.plan(3) + const { fd, dest } = file() + const stream = buildSafeSonicBoom({ sync: false, fd, mkdir: true }) + + t.teardown(() => rimraf(dest, noop)) + + stream.on('error', () => t.pass('error emitted')) + stream.emit('error', 'fake error description') + + t.ok(stream.write('will work')) + + await watchFileCreated(dest) + + const dataFile = fs.readFileSync(dest) + t.equal(dataFile.toString(), 'will work') +}) + +function watchFileCreated (filename) { + return new Promise((resolve, reject) => { + const TIMEOUT = 2000 + const INTERVAL = 100 + const threshold = TIMEOUT / INTERVAL + let counter = 0 + const interval = setInterval(() => { + // On some CI runs file is created but not filled + if (fs.existsSync(filename) && fs.statSync(filename).size !== 0) { + clearInterval(interval) + resolve() + } else if (counter <= threshold) { + counter++ + } else { + clearInterval(interval) + reject(new Error(`${filename} was not created.`)) + } + }, INTERVAL) + }) +} diff --git a/lib/utils/delete-log-property.js b/lib/utils/delete-log-property.js index 7cc68350..502fcee4 100644 --- a/lib/utils/delete-log-property.js +++ b/lib/utils/delete-log-property.js @@ -21,6 +21,7 @@ function deleteLogProperty (log, property) { log = getPropertyValue(log, props) + /* istanbul ignore else */ if (log !== null && typeof log === 'object' && Object.prototype.hasOwnProperty.call(log, propToDelete)) { delete log[propToDelete] } diff --git a/lib/utils/format-time.test.js b/lib/utils/format-time.test.js index e0e70122..be7399a9 100644 --- a/lib/utils/format-time.test.js +++ b/lib/utils/format-time.test.js @@ -14,6 +14,12 @@ tap.test('passes through epoch if `translateTime` is `false`', async t => { t.equal(formattedTime, epochMS) }) +tap.test('passes through epoch if date is invalid', async t => { + const input = 'this is not a date' + const formattedTime = formatTime(input, true) + t.equal(formattedTime, input) +}) + tap.test('translates epoch milliseconds if `translateTime` is `true`', async t => { const formattedTime = formatTime(epochMS, true) t.equal(formattedTime, '17:30:00.000') diff --git a/lib/utils/handle-custom-levels-names-opts.js b/lib/utils/handle-custom-levels-names-opts.js index 2bbf724c..0c0257e2 100644 --- a/lib/utils/handle-custom-levels-names-opts.js +++ b/lib/utils/handle-custom-levels-names-opts.js @@ -21,14 +21,14 @@ function handleCustomLevelsNamesOpts (cLevels) { return cLevels .split(',') .reduce((agg, value, idx) => { - const [levelName, levelIdx = idx] = value.split(':') - agg[levelName.toLowerCase()] = levelIdx + const [levelName, levelNum = idx] = value.split(':') + agg[levelName.toLowerCase()] = levelNum return agg }, {}) } else if (Object.prototype.toString.call(cLevels) === '[object Object]') { return Object .keys(cLevels) - .reduce((agg, levelName, idx) => { + .reduce((agg, levelName) => { agg[levelName.toLowerCase()] = cLevels[levelName] return agg }, {}) diff --git a/lib/utils/handle-custom-levels-names-opts.test.js b/lib/utils/handle-custom-levels-names-opts.test.js index 489e3cf1..cdc89103 100644 --- a/lib/utils/handle-custom-levels-names-opts.test.js +++ b/lib/utils/handle-custom-levels-names-opts.test.js @@ -3,6 +3,11 @@ const tap = require('tap') const handleCustomLevelsNamesOpts = require('./handle-custom-levels-names-opts') +tap.test('returns a empty object `{}` for undefined parameter', async t => { + const handledCustomLevelNames = handleCustomLevelsNamesOpts() + t.same(handledCustomLevelNames, {}) +}) + tap.test('returns a empty object `{}` for unknown parameter', async t => { const handledCustomLevelNames = handleCustomLevelsNamesOpts(123) t.same(handledCustomLevelNames, {}) @@ -29,3 +34,11 @@ tap.test('returns a filled object for object parameter', async t => { error: 35 }) }) + +tap.test('defaults missing level num to first index', async t => { + const result = handleCustomLevelsNamesOpts('ok:10,info') + t.same(result, { + ok: 10, + info: 1 + }) +}) diff --git a/lib/utils/handle-custom-levels-opts.js b/lib/utils/handle-custom-levels-opts.js index 099acad9..33931da1 100644 --- a/lib/utils/handle-custom-levels-opts.js +++ b/lib/utils/handle-custom-levels-opts.js @@ -21,15 +21,15 @@ function handleCustomLevelsOpts (cLevels) { return cLevels .split(',') .reduce((agg, value, idx) => { - const [levelName, levelIdx = idx] = value.split(':') - agg[levelIdx] = levelName.toUpperCase() + const [levelName, levelNum = idx] = value.split(':') + agg[levelNum] = levelName.toUpperCase() return agg }, { default: 'USERLVL' }) } else if (Object.prototype.toString.call(cLevels) === '[object Object]') { return Object .keys(cLevels) - .reduce((agg, levelName, idx) => { + .reduce((agg, levelName) => { agg[cLevels[levelName]] = levelName.toUpperCase() return agg }, { default: 'USERLVL' }) diff --git a/lib/utils/handle-custom-levels-opts.test.js b/lib/utils/handle-custom-levels-opts.test.js index fae50a53..3f4f8f19 100644 --- a/lib/utils/handle-custom-levels-opts.test.js +++ b/lib/utils/handle-custom-levels-opts.test.js @@ -3,6 +3,11 @@ const tap = require('tap') const handleCustomLevelsOpts = require('./handle-custom-levels-opts') +tap.test('returns a empty object `{}` for undefined parameter', async t => { + const handledCustomLevel = handleCustomLevelsOpts() + t.same(handledCustomLevel, {}) +}) + tap.test('returns a empty object `{}` for unknown parameter', async t => { const handledCustomLevel = handleCustomLevelsOpts(123) t.same(handledCustomLevel, {}) @@ -31,3 +36,12 @@ tap.test('returns a filled object for object parameter', async t => { default: 'USERLVL' }) }) + +tap.test('defaults missing level num to first index', async t => { + const result = handleCustomLevelsOpts('ok:10,info') + t.same(result, { + 10: 'OK', + 1: 'INFO', + default: 'USERLVL' + }) +}) diff --git a/lib/utils/prettify-error-log.js b/lib/utils/prettify-error-log.js index 24b34933..1e636b66 100644 --- a/lib/utils/prettify-error-log.js +++ b/lib/utils/prettify-error-log.js @@ -28,9 +28,9 @@ const prettifyObject = require('./prettify-object') * @param {string[]} [input.errorProperties] A set of specific error object * properties, that are not the value of `messageKey`, `type`, or `stack`, to * include in the prettified result. The first entry in the list may be `'*'` - * to indicate that all sibiling properties should be prettified. Default: `[]`. + * to indicate that all sibling properties should be prettified. Default: `[]`. * - * @returns {string} A sring that represents the prettified error log. + * @returns {string} A string that represents the prettified error log. */ function prettifyErrorLog ({ log, diff --git a/lib/utils/prettify-error-log.test.js b/lib/utils/prettify-error-log.test.js index d2363f6f..5d0f7db1 100644 --- a/lib/utils/prettify-error-log.test.js +++ b/lib/utils/prettify-error-log.test.js @@ -20,3 +20,42 @@ tap.test('returns string with custom eol', async t => { const str = prettifyErrorLog({ log: err, eol: '\r\n' }) t.ok(str.startsWith(' Error: Something went wrong\r\n')) }) + +tap.test('errorProperties', t => { + t.test('excludes all for wildcard', async t => { + const err = Error('boom') + err.foo = 'foo' + const str = prettifyErrorLog({ log: err, errorProperties: ['*'] }) + t.ok(str.startsWith(' Error: boom')) + t.equal(str.includes('foo: "foo"'), false) + }) + + t.test('excludes only selected properties', async t => { + const err = Error('boom') + err.foo = 'foo' + const str = prettifyErrorLog({ log: err, errorProperties: ['foo'] }) + t.ok(str.startsWith(' Error: boom')) + t.equal(str.includes('foo: foo'), true) + }) + + t.test('ignores specified properties if not present', async t => { + const err = Error('boom') + err.foo = 'foo' + const str = prettifyErrorLog({ log: err, errorProperties: ['foo', 'bar'] }) + t.ok(str.startsWith(' Error: boom')) + t.equal(str.includes('foo: foo'), true) + t.equal(str.includes('bar'), false) + }) + + t.test('processes nested objects', async t => { + const err = Error('boom') + err.foo = { bar: 'bar', message: 'included' } + const str = prettifyErrorLog({ log: err, errorProperties: ['foo'] }) + t.ok(str.startsWith(' Error: boom')) + t.equal(str.includes('foo: {'), true) + t.equal(str.includes('bar: "bar"'), true) + t.equal(str.includes('message: "included"'), true) + }) + + t.end() +}) diff --git a/lib/utils/prettify-level.test.js b/lib/utils/prettify-level.test.js index 438cbe8a..e127da10 100644 --- a/lib/utils/prettify-level.test.js +++ b/lib/utils/prettify-level.test.js @@ -25,3 +25,15 @@ tap.test('returns colorized value for color colorizer', async t => { const colorized = prettifyLevel({ log, colorizer }) t.equal(colorized, '\u001B[32mINFO\u001B[39m') }) + +tap.test('passes output through provided prettifier', async t => { + const log = { + level: 30 + } + const colorized = prettifyLevel({ log, prettifier }) + t.equal(colorized, 'modified') + + function prettifier () { + return 'modified' + } +}) diff --git a/lib/utils/prettify-metadata.test.js b/lib/utils/prettify-metadata.test.js index a1342fb7..e665db0e 100644 --- a/lib/utils/prettify-metadata.test.js +++ b/lib/utils/prettify-metadata.test.js @@ -82,3 +82,25 @@ tap.test('works with all four present', async t => { const str = prettifyMetadata({ log: { name: 'foo', pid: '1234', hostname: 'bar', caller: 'baz' } }) t.equal(str, '(foo/1234 on bar) ') }) + +tap.test('uses prettifiers from passed prettifiers object', async t => { + const prettifiers = { + name (input) { + return input.toUpperCase() + }, + pid (input) { + return input + '__' + }, + hostname (input) { + return input.toUpperCase() + }, + caller (input) { + return input.toUpperCase() + } + } + const str = prettifyMetadata({ + log: { pid: '1234', hostname: 'bar', caller: 'baz', name: 'joe' }, + prettifiers + }) + t.equal(str, '(JOE/1234__ on BAR) ') +}) diff --git a/lib/utils/prettify-object.js b/lib/utils/prettify-object.js index 0b36949d..acee463f 100644 --- a/lib/utils/prettify-object.js +++ b/lib/utils/prettify-object.js @@ -49,6 +49,7 @@ function prettifyObject ({ }) { const keysToIgnore = [].concat(skipKeys) + /* istanbul ignore else */ if (excludeLoggerKeys === true) Array.prototype.push.apply(keysToIgnore, LOGGER_KEYS) let result = '' @@ -71,6 +72,7 @@ function prettifyObject ({ if (singleLine) { // Stringify the entire object as a single JSON line + /* istanbul ignore else */ if (Object.keys(plain).length > 0) { result += colorizer.greyMessage(stringifySafe(plain)) } diff --git a/lib/utils/prettify-object.test.js b/lib/utils/prettify-object.test.js index 7d67d912..a3492403 100644 --- a/lib/utils/prettify-object.test.js +++ b/lib/utils/prettify-object.test.js @@ -49,3 +49,54 @@ tap.test('works with error props', async t => { t.ok(str.includes(' "message": "Something went wrong",')) t.ok(str.includes(' Error: Something went wrong')) }) + +tap.test('customPrettifiers gets applied', async t => { + const customPrettifiers = { + foo: v => v.toUpperCase() + } + const str = prettifyObject({ input: { foo: 'foo' }, customPrettifiers }) + t.equal(str.startsWith(' foo: FOO'), true) +}) + +tap.test('skips lines omitted by customPrettifiers', async t => { + const customPrettifiers = { + foo: () => { return undefined } + } + const str = prettifyObject({ input: { foo: 'foo', bar: 'bar' }, customPrettifiers }) + t.equal(str.includes('bar: "bar"'), true) + t.equal(str.includes('foo: "foo"'), false) +}) + +tap.test('joined lines omits starting eol', async t => { + const str = prettifyObject({ + input: { msg: 'doing work', calls: ['step 1', 'step 2', 'step 3'], level: 30 }, + ident: '', + customPrettifiers: { + calls: val => '\n' + val.map(it => ' ' + it).join('\n') + } + }) + t.equal(str, [ + 'msg: "doing work"', + 'calls:', + ' step 1', + ' step 2', + ' step 3', + '' + ].join('\n')) +}) + +tap.test('errors skips prettifiers', async t => { + const customPrettifiers = { + err: () => { return 'is_err' } + } + const str = prettifyObject({ input: { err: Error('boom') }, customPrettifiers }) + t.equal(str.includes('err: is_err'), true) +}) + +tap.test('errors skips prettifying if no lines are present', async t => { + const customPrettifiers = { + err: () => { return undefined } + } + const str = prettifyObject({ input: { err: Error('boom') }, customPrettifiers }) + t.equal(str, '') +}) diff --git a/lib/utils/prettify-time.test.js b/lib/utils/prettify-time.test.js index 5a3cc504..1b3e55e5 100644 --- a/lib/utils/prettify-time.test.js +++ b/lib/utils/prettify-time.test.js @@ -100,3 +100,13 @@ tap.test('works with epoch as a number or string', (t) => { t.same(asNumber, '[17:35:28.992]') t.same(invalid, '[2 days ago]') }) + +tap.test('uses custom prettifier', async t => { + const str = prettifyTime({ + log: { time: 0 }, + prettifier () { + return 'done' + } + }) + t.equal(str, 'done') +}) diff --git a/package.json b/package.json index ab9e9190..941148c0 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "lint": "standard | snazzy", "test": "tap", "test-types": "tsc && tsd", - "test:watch": "tap --no-coverage-report -w" + "test:watch": "tap --no-coverage-report -w", + "test:report": "tap --coverage-report=html" }, "repository": { "type": "git",