You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

965 lines
33 KiB

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

'use strict'
const { test } = require('tap')
const fastRedact = require('..')
const censor = '[REDACTED]'
const censorFct = value => !value ? value : 'xxx' + value.substr(-2)
test('returns no-op when passed no paths [serialize: false]', ({ end, doesNotThrow }) => {
const redact = fastRedact({ paths: [], serialize: false })
doesNotThrow(() => redact({}))
doesNotThrow(() => {
const o = redact({})
redact.restore(o)
})
end()
})
test('returns serializer when passed no paths [serialize: default]', ({ end, is }) => {
is(fastRedact({ paths: [] }), JSON.stringify)
is(fastRedact(), JSON.stringify)
end()
})
test('throws when passed non-object using defaults', ({ end, throws }) => {
const redact = fastRedact({ paths: ['a.b.c'] })
throws(() => redact(1))
end()
})
test('throws when passed non-object number using [strict: true]', ({ end, throws }) => {
const redact = fastRedact({ paths: ['a.b.c'], strict: true })
throws(() => redact(1))
end()
})
test('returns JSON.stringified value when passed non-object using [strict: false] and no serialize option', ({ end, is, doesNotThrow }) => {
const redactDefaultSerialize = fastRedact({ paths: ['a.b.c'], strict: false })
// expectedOutputs holds `JSON.stringify`ied versions of each primitive.
// We write them out explicitly though to make the test a bit clearer.
const primitives = [null, undefined, 'A', 1, false]
const expectedOutputs = ['null', undefined, '"A"', '1', 'false']
primitives.forEach((it, i) => {
doesNotThrow(() => redactDefaultSerialize(it))
const res = redactDefaultSerialize(it)
is(res, expectedOutputs[i])
})
end()
})
test('returns custom serialized value when passed non-object using [strict: false, serialize: fn]', ({ end, is, doesNotThrow }) => {
const customSerialize = (v) => `Hello ${v}!`
const redactCustomSerialize = fastRedact({
paths: ['a.b.c'],
strict: false,
serialize: customSerialize
})
const primitives = [null, undefined, 'A', 1, false]
primitives.forEach((it) => {
doesNotThrow(() => redactCustomSerialize(it))
const res = redactCustomSerialize(it)
is(res, customSerialize(it))
})
end()
})
test('returns original value when passed non-object using [strict: false, serialize: false]', ({ end, is, doesNotThrow }) => {
const redactSerializeFalse = fastRedact({
paths: ['a.b.c'],
strict: false,
serialize: false
})
const primitives = [null, undefined, 'A', 1, false]
primitives.forEach((it) => {
doesNotThrow(() => redactSerializeFalse(it))
const res = redactSerializeFalse(it)
is(res, it)
})
end()
})
test('throws if a path is not a string', ({ end, is, throws }) => {
throws((e) => {
fastRedact({ paths: [1] })
}, Error('fast-redact - Paths must be strings'))
throws((e) => {
fastRedact({ paths: [null] })
}, Error('fast-redact - Paths must be strings'))
throws((e) => {
fastRedact({ paths: [undefined] })
}, Error('fast-redact - Paths must be strings'))
throws((e) => {
fastRedact({ paths: [{}] })
}, Error('fast-redact - Paths must be strings'))
end()
})
test('throws when passed illegal paths', ({ end, is, throws }) => {
const err = (s) => Error(`fast-redact Invalid path (${s})`)
throws((e) => {
fastRedact({ paths: ['@'] })
}, err('@'))
throws((e) => {
fastRedact({ paths: ['0'] })
}, err('0'))
throws((e) => {
fastRedact({ paths: [''] })
}, err(''))
throws((e) => {
fastRedact({ paths: ['a.1.c'] })
}, err('a.1.c'))
throws((e) => {
fastRedact({ paths: ['a..c'] })
}, err('a..c'))
throws((e) => {
fastRedact({ paths: ['1..c'] })
}, err('1..c'))
throws((e) => {
fastRedact({ paths: ['a = b'] })
}, err('a = b'))
throws((e) => {
fastRedact({ paths: ['a(b)'] })
}, err('a(b)'))
throws((e) => {
fastRedact({ paths: ['//a.b.c'] })
}, err('//a.b.c'))
throws((e) => {
fastRedact({ paths: ['\\a.b.c'] })
}, err('\\a.b.c'))
throws((e) => {
fastRedact({ paths: ['a.#.c'] })
}, err('a.#.c'))
throws((e) => {
fastRedact({ paths: ['~~a.b.c'] })
}, err('~~a.b.c'))
throws((e) => {
fastRedact({ paths: ['^a.b.c'] })
}, err('^a.b.c'))
throws((e) => {
fastRedact({ paths: ['a + b'] })
}, err('a + b'))
throws((e) => {
fastRedact({ paths: ['return a + b'] })
}, err('return a + b'))
throws((e) => {
fastRedact({ paths: ['a / b'] })
}, err('a / b'))
throws((e) => {
fastRedact({ paths: ['a * b'] })
}, err('a * b'))
throws((e) => {
fastRedact({ paths: ['a - b'] })
}, err('a - b'))
throws((e) => {
fastRedact({ paths: ['a ** b'] })
}, err('a ** b'))
throws((e) => {
fastRedact({ paths: ['a % b'] })
}, err('a % b'))
throws((e) => {
fastRedact({ paths: ['a.b*.c'] })
}, err('a.b*.c'))
throws((e) => {
fastRedact({ paths: ['a;global.foo = "bar"'] })
}, err('a;global.foo = "bar"'))
throws((e) => {
fastRedact({ paths: ['a;while(1){}'] })
}, err('a;while(1){}'))
throws((e) => {
fastRedact({ paths: ['a//'] })
}, err('a//'))
throws((e) => {
fastRedact({ paths: ['a/*foo*/'] })
}, err('a/*foo*/'))
throws((e) => {
fastRedact({ paths: ['a,o.b'] })
}, err('a,o.b'))
throws((e) => {
fastRedact({ paths: ['a = o.b'] })
}, err('a = o.b'))
throws((e) => {
fastRedact({ paths: ['a\n'] })
}, err('a\n'))
throws((e) => {
fastRedact({ paths: ['a\r'] })
}, err('a\r'))
end()
})
test('throws if more than one wildcard in a path', ({ end, throws }) => {
throws(() => {
fastRedact({ paths: ['a.*.x.*'], serialize: false })
}, Error('fast-redact Only one wildcard per path is supported'))
end()
})
test('throws if a custom serializer is used and remove is true', ({ end, throws }) => {
throws(() => {
fastRedact({ paths: ['a'], serialize: (o) => o, remove: true })
}, Error('fast-redact remove option may only be set when serializer is JSON.stringify'))
end()
})
test('throws if serialize is false and remove is true', ({ end, throws }) => {
throws(() => {
fastRedact({ paths: ['a'], serialize: false, remove: true })
}, Error('fast-redact remove option may only be set when serializer is JSON.stringify'))
end()
})
test('masks according to supplied censor', ({ end, is }) => {
const censor = 'test'
const redact = fastRedact({ paths: ['a'], censor, serialize: false })
is(redact({ a: 'a' }).a, censor)
end()
})
test('redact.restore function is available when serialize is false', ({ end, is }) => {
const censor = 'test'
const redact = fastRedact({ paths: ['a'], censor, serialize: false })
is(typeof redact.restore, 'function')
end()
})
test('redact.restore function places original values back in place', ({ end, is }) => {
const censor = 'test'
const redact = fastRedact({ paths: ['a'], censor, serialize: false })
const o = { a: 'a' }
redact(o)
is(o.a, censor)
redact.restore(o)
is(o.a, 'a')
end()
})
test('masks according to supplied censor function', ({ end, is }) => {
const redact = fastRedact({ paths: ['a'], censor: censorFct, serialize: false })
is(redact({ a: '0123456' }).a, 'xxx56')
end()
})
test('masks according to supplied censor function with wildcards', ({ end, is }) => {
const redact = fastRedact({ paths: '*', censor: censorFct, serialize: false })
is(redact({ a: '0123456' }).a, 'xxx56')
end()
})
test('masks according to supplied censor function with nested wildcards', ({ end, is }) => {
const redact = fastRedact({ paths: ['*.b'], censor: censorFct, serialize: false })
is(redact({ a: { b: '0123456' } }).a.b, 'xxx56')
is(redact({ c: { b: '0123456', d: 'pristine' } }).c.b, 'xxx56')
is(redact({ c: { b: '0123456', d: 'pristine' } }).c.d, 'pristine')
end()
})
test('redact.restore function places original values back in place with censor function', ({ end, is }) => {
const redact = fastRedact({ paths: ['a'], censor: censorFct, serialize: false })
const o = { a: 'qwerty' }
redact(o)
is(o.a, 'xxxty')
redact.restore(o)
is(o.a, 'qwerty')
end()
})
test('serializes with JSON.stringify by default', ({ end, is }) => {
const redact = fastRedact({ paths: ['a'] })
const o = { a: 'a' }
is(redact(o), `{"a":"${censor}"}`)
is(o.a, 'a')
end()
})
test('removes during serialization instead of redacting when remove option is true', ({ end, is }) => {
const redact = fastRedact({ paths: ['a'], remove: true })
const o = { a: 'a', b: 'b' }
is(redact(o), `{"b":"b"}`)
is(o.a, 'a')
end()
})
test('serializes with JSON.stringify if serialize is true', ({ end, is }) => {
const redact = fastRedact({ paths: ['a'], serialize: true })
const o = { a: 'a' }
is(redact(o), `{"a":"${censor}"}`)
is(o.a, 'a')
end()
})
test('serializes with JSON.stringify if serialize is not a function', ({ end, is }) => {
const redact = fastRedact({ paths: ['a'], serialize: {} })
const o = { a: 'a' }
is(redact(o), `{"a":"${censor}"}`)
is(o.a, 'a')
end()
})
test('serializes with custom serializer if supplied', ({ end, is }) => {
const redact = fastRedact({ paths: ['a'], serialize: (o) => JSON.stringify(o, 0, 2) })
const o = { a: 'a' }
is(redact(o), `{\n "a": "${censor}"\n}`)
is(o.a, 'a')
end()
})
test('redacts parent keys', ({ end, is }) => {
const redact = fastRedact({ paths: ['a.b.c'], serialize: false })
const result = redact({ a: { b: { c: 's' }, d: { a: 's', b: 's', c: 's' } } })
is(result.a.b.c, censor)
end()
})
test('supports paths with array indexes', ({ end, same }) => {
const redact = fastRedact({ paths: ['insideArray.like[3].this'], serialize: false })
same(redact({ insideArray: { like: ['a', 'b', 'c', { this: { foo: 'meow' } }] } }), { insideArray: { like: ['a', 'b', 'c', { this: censor }] } })
end()
})
test('censor may be any type, including function', ({ end, same }) => {
const redactToString = fastRedact({ paths: ['a.b.c', 'a.b.d.*'], censor: 'censor', serialize: false })
const redactToUndefined = fastRedact({ paths: ['a.b.c', 'a.b.d.*'], censor: undefined, serialize: false })
const sym = Symbol('sym')
const redactToSymbol = fastRedact({ paths: ['a.b.c', 'a.b.d.*'], censor: sym, serialize: false })
const redactToNumber = fastRedact({ paths: ['a.b.c', 'a.b.d.*'], censor: 0, serialize: false })
const redactToBoolean = fastRedact({ paths: ['a.b.c', 'a.b.d.*'], censor: false, serialize: false })
const redactToNull = fastRedact({ paths: ['a.b.c', 'a.b.d.*'], censor: null, serialize: false })
const redactToObject = fastRedact({ paths: ['a.b.c', 'a.b.d.*'], censor: { redacted: true }, serialize: false })
const redactToArray = fastRedact({ paths: ['a.b.c', 'a.b.d.*'], censor: ['redacted'], serialize: false })
const redactToBuffer = fastRedact({ paths: ['a.b.c', 'a.b.d.*'], censor: Buffer.from('redacted'), serialize: false })
const redactToError = fastRedact({ paths: ['a.b.c', 'a.b.d.*'], censor: Error('redacted'), serialize: false })
const redactToFunction = fastRedact({ paths: ['a.b.c', 'a.b.d.*'], censor: () => 'redacted', serialize: false })
same(redactToString({ a: { b: { c: 's', d: { x: 's', y: 's' } } } }), { a: { b: { c: 'censor', d: { x: 'censor', y: 'censor' } } } })
same(redactToUndefined({ a: { b: { c: 's', d: { x: 's', y: 's' } } } }), { a: { b: { c: undefined, d: { x: undefined, y: undefined } } } })
same(redactToSymbol({ a: { b: { c: 's', d: { x: 's', y: 's' } } } }), { a: { b: { c: sym, d: { x: sym, y: sym } } } })
same(redactToNumber({ a: { b: { c: 's', d: { x: 's', y: 's' } } } }), { a: { b: { c: 0, d: { x: 0, y: 0 } } } })
same(redactToBoolean({ a: { b: { c: 's', d: { x: 's', y: 's' } } } }), { a: { b: { c: false, d: { x: false, y: false } } } })
same(redactToNull({ a: { b: { c: 's', d: { x: 's', y: 's' } } } }), { a: { b: { c: null, d: { x: null, y: null } } } })
same(redactToObject({ a: { b: { c: 's', d: { x: 's', y: 's' } } } }), { a: { b: { c: { redacted: true }, d: { x: { redacted: true }, y: { redacted: true } } } } })
same(redactToArray({ a: { b: { c: 's', d: { x: 's', y: 's' } } } }), { a: { b: { c: ['redacted'], d: { x: ['redacted'], y: ['redacted'] } } } })
same(redactToBuffer({ a: { b: { c: 's', d: { x: 's', y: 's' } } } }), { a: { b: { c: Buffer.from('redacted'), d: { x: Buffer.from('redacted'), y: Buffer.from('redacted') } } } })
same(redactToError({ a: { b: { c: 's', d: { x: 's', y: 's' } } } }), { a: { b: { c: Error('redacted'), d: { x: Error('redacted'), y: Error('redacted') } } } })
same(redactToFunction({ a: { b: { c: 's', d: { x: 's', y: 's' } } } }), { a: { b: { c: 'redacted', d: { x: 'redacted', y: 'redacted' } } } })
end()
})
test('supports multiple paths from the same root', ({ end, same }) => {
const redact = fastRedact({ paths: ['deep.bar.shoe', 'deep.baz.shoe', 'deep.foo', 'deep.not.there.sooo', 'deep.fum.shoe'], serialize: false })
same(redact({ deep: { bar: 'hmm', baz: { shoe: { k: 1 } }, foo: {}, fum: { shoe: 'moo' } } }), { deep: { bar: 'hmm', baz: { shoe: censor }, foo: censor, fum: { shoe: censor } } })
end()
})
test('supports strings in bracket notation paths (single quote)', ({ end, is }) => {
const redact = fastRedact({ paths: [`a['@#!='].c`], serialize: false })
const result = redact({ a: { '@#!=': { c: 's' }, d: { a: 's', b: 's', c: 's' } } })
is(result.a['@#!='].c, censor)
end()
})
test('supports strings in bracket notation paths (double quote)', ({ end, is }) => {
const redact = fastRedact({ paths: [`a["@#!="].c`], serialize: false })
const result = redact({ a: { '@#!=': { c: 's' }, d: { a: 's', b: 's', c: 's' } } })
is(result.a['@#!='].c, censor)
end()
})
test('supports strings in bracket notation paths (backtick quote)', ({ end, is }) => {
const redact = fastRedact({ paths: ['a[`@#!=`].c'], serialize: false })
const result = redact({ a: { '@#!=': { c: 's' }, d: { a: 's', b: 's', c: 's' } } })
is(result.a['@#!='].c, censor)
end()
})
test('allows * within a bracket notation string', ({ end, is }) => {
const redact = fastRedact({ paths: ['a["*"].c'], serialize: false })
const result = redact({ a: { '*': { c: 's', x: 1 } } })
is(result.a['*'].c, censor)
is(result.a['*'].x, 1)
end()
})
test('redacts parent keys restore', ({ end, is }) => {
const redact = fastRedact({ paths: ['a.b.c'], serialize: false })
const result = redact({ a: { b: { c: 's' }, d: { a: 's', b: 's', c: 's' } } })
is(result.a.b.c, censor)
redact.restore(result)
is(result.a.b.c, 's')
end()
})
test('handles null proto objects', ({ end, is }) => {
const redact = fastRedact({ paths: ['a.b.c'], serialize: false })
const result = redact({ __proto__: null, a: { b: { c: 's' }, d: { a: 's', b: 's', c: 's' } } })
is(result.a.b.c, censor)
end()
})
test('handles null proto objects  restore', ({ end, is }) => {
const redact = fastRedact({ paths: ['a.b.c'], serialize: false })
const result = redact({ __proto__: null, a: { b: { c: 's' }, d: { a: 's', b: 's', c: 's' } } })
is(result.a.b.c, censor)
redact.restore(result, 's')
is(result.a.b.c, 's')
end()
})
test('handles paths that do not match object structure', ({ end, same }) => {
const redact = fastRedact({ paths: ['x.y.z'], serialize: false })
same(redact({ a: { b: { c: 's' } } }), { a: { b: { c: 's' } } })
end()
})
test('ignores missing paths in object', ({ end, same }) => {
const redact = fastRedact({ paths: ['a.b.c', 'a.z.d', 'a.b.z'], serialize: false })
same(redact({ a: { b: { c: 's' } } }), { a: { b: { c: censor } } })
end()
})
test('ignores missing paths in object restore', ({ end, doesNotThrow }) => {
const redact = fastRedact({ paths: ['a.b.c', 'a.z.d', 'a.b.z'], serialize: false })
const o = { a: { b: { c: 's' } } }
redact(o)
doesNotThrow(() => {
redact.restore(o)
})
end()
})
test('gracefully handles primitives that match intermediate keys in paths', ({ end, same }) => {
const redact = fastRedact({ paths: ['a.b.c', 'a.b.c.d'], serialize: false })
same(redact({ a: { b: null } }), { a: { b: null } })
same(redact({ a: { b: 's' } }), { a: { b: 's' } })
same(redact({ a: { b: 1 } }), { a: { b: 1 } })
same(redact({ a: { b: undefined } }), { a: { b: undefined } })
same(redact({ a: { b: true } }), { a: { b: true } })
const sym = Symbol('sym')
same(redact({ a: { b: sym } }), { a: { b: sym } })
end()
})
test('handles circulars', ({ end, is, same }) => {
const redact = fastRedact({ paths: ['bar.baz.baz'], serialize: false })
const bar = { b: 2 }
const o = { a: 1, bar }
bar.baz = bar
o.bar.baz = o.bar
same(redact(o), { a: 1, bar: { b: 2, baz: censor } })
end()
})
test('handles circulars  restore', ({ end, is, same }) => {
const redact = fastRedact({ paths: ['bar.baz.baz'], serialize: false })
const bar = { b: 2 }
const o = { a: 1, bar }
bar.baz = bar
o.bar.baz = o.bar
is(o.bar.baz, bar)
redact(o)
is(o.bar.baz, censor)
redact.restore(o)
is(o.bar.baz, bar)
end()
})
test('handles circulars and cross references  restore', ({ end, is, same }) => {
const redact = fastRedact({ paths: ['bar.baz.baz', 'cf.bar'], serialize: false })
const bar = { b: 2 }
const o = { a: 1, bar, cf: { bar } }
bar.baz = bar
o.bar.baz = o.bar
is(o.bar.baz, bar)
is(o.cf.bar, bar)
redact(o)
is(o.bar.baz, censor)
is(o.cf.bar, censor)
redact.restore(o)
is(o.bar.baz, bar)
is(o.cf.bar, bar)
end()
})
test('ultimate wildcards shallow', ({ end, same }) => {
const redact = fastRedact({ paths: ['test.*'], serialize: false })
same(redact({ test: { baz: 1, bar: 'private' } }), { test: { baz: censor, bar: censor } })
end()
})
test('ultimate wildcards  deep', ({ end, same }) => {
const redact = fastRedact({ paths: ['deep.bar.baz.ding.*'], serialize: false })
same(redact({ deep: { a: 1, bar: { b: 2, baz: { c: 3, ding: { d: 4, e: 5, f: 'six' } } } } }), { deep: { a: 1, bar: { b: 2, baz: { c: 3, ding: { d: censor, e: censor, f: censor } } } } })
end()
})
test('ultimate wildcards - array  shallow', ({ end, same }) => {
const redact = fastRedact({ paths: ['array[*]'], serialize: false })
same(redact({ array: ['a', 'b', 'c', 'd'] }), { array: [censor, censor, censor, censor] })
end()
})
test('ultimate wildcards array deep', ({ end, same }) => {
const redact = fastRedact({ paths: ['deepArray.down.here[*]'], serialize: false })
same(redact({ deepArray: { down: { here: ['a', 'b', 'c'] } } }), { deepArray: { down: { here: [censor, censor, censor] } } })
end()
})
test('ultimate wildcards array single index', ({ end, same }) => {
const redact = fastRedact({ paths: ['insideArray.like[3].this.*'], serialize: false })
same(redact({ insideArray: { like: ['a', 'b', 'c', { this: { foo: 'meow' } }] } }), { insideArray: { like: ['a', 'b', 'c', { this: { foo: censor } }] } })
end()
})
test('ultimate wildcards - handles null proto objects', ({ end, is }) => {
const redact = fastRedact({ paths: ['a.b.c'], serialize: false })
const result = redact({ __proto__: null, a: { b: { c: 's' }, d: { a: 's', b: 's', c: 's' } } })
is(result.a.b.c, censor)
end()
})
test('ultimate wildcards - handles paths that do not match object structure', ({ end, same }) => {
const redact = fastRedact({ paths: ['x.y.z'], serialize: false })
same(redact({ a: { b: { c: 's' } } }), { a: { b: { c: 's' } } })
end()
})
test('ultimate wildcards - gracefully handles primitives that match intermediate keys in paths', ({ end, same }) => {
const redact = fastRedact({ paths: ['a.b.c', 'a.b.c.d'], serialize: false })
same(redact({ a: { b: null } }), { a: { b: null } })
same(redact({ a: { b: 's' } }), { a: { b: 's' } })
same(redact({ a: { b: 1 } }), { a: { b: 1 } })
same(redact({ a: { b: undefined } }), { a: { b: undefined } })
same(redact({ a: { b: true } }), { a: { b: true } })
const sym = Symbol('sym')
same(redact({ a: { b: sym } }), { a: { b: sym } })
end()
})
test('ultimate wildcards handles circulars', ({ end, is, same }) => {
const redact = fastRedact({ paths: ['bar.baz.*'], serialize: false })
const bar = { b: 2 }
const o = { a: 1, bar }
bar.baz = bar
o.bar.baz = o.bar
same(redact(o), { a: 1, bar: { b: censor, baz: censor } })
end()
})
test('ultimate wildcards handles circulars  restore', ({ end, is, same }) => {
const redact = fastRedact({ paths: ['bar.baz.*'], serialize: false })
const bar = { b: 2 }
const o = { a: 1, bar }
bar.baz = bar
o.bar.baz = o.bar
is(o.bar.baz, bar)
redact(o)
is(o.bar.baz, censor)
redact.restore(o)
is(o.bar.baz, bar)
end()
})
test('ultimate wildcards handles circulars and cross references  restore', ({ end, is, same }) => {
const redact = fastRedact({ paths: ['bar.baz.*', 'cf.*'], serialize: false })
const bar = { b: 2 }
const o = { a: 1, bar, cf: { bar } }
bar.baz = bar
o.bar.baz = o.bar
is(o.bar.baz, bar)
is(o.cf.bar, bar)
redact(o)
is(o.bar.baz, censor)
is(o.cf.bar, censor)
redact.restore(o)
is(o.bar.baz, bar)
is(o.cf.bar, bar)
end()
})
test('static + wildcards', ({ end, is }) => {
const redact = fastRedact({ paths: ['a.b.c', 'a.d.*', 'a.b.z.*'], serialize: false })
const result = redact({ a: { b: { c: 's', z: { x: 's', y: 's' } }, d: { a: 's', b: 's', c: 's' } } })
is(result.a.b.c, censor)
is(result.a.d.a, censor)
is(result.a.d.b, censor)
is(result.a.d.c, censor)
is(result.a.b.z.x, censor)
is(result.a.b.z.y, censor)
end()
})
test('static + wildcards reuse', ({ end, is }) => {
const redact = fastRedact({ paths: ['a.b.c', 'a.d.*'], serialize: false })
const result = redact({ a: { b: { c: 's' }, d: { a: 's', b: 's', c: 's' } } })
is(result.a.b.c, censor)
is(result.a.d.a, censor)
is(result.a.d.b, censor)
is(result.a.d.c, censor)
redact.restore(result)
const result2 = redact({ a: { b: { c: 's' }, d: { a: 's', b: 's', c: 's' } } })
is(result2.a.b.c, censor)
is(result2.a.d.a, censor)
is(result2.a.d.b, censor)
is(result2.a.d.c, censor)
redact.restore(result2)
end()
})
test('parent wildcard first position', ({ end, is }) => {
const redact = fastRedact({ paths: ['*.c'], serialize: false })
const result = redact({ b: { c: 's' }, d: { a: 's', b: 's', c: 's' } })
is(result.b.c, censor)
is(result.d.a, 's')
is(result.d.b, 's')
is(result.d.c, censor)
end()
})
test('parent wildcard one following key', ({ end, is }) => {
const redact = fastRedact({ paths: ['a.*.c'], serialize: false })
const result = redact({ a: { b: { c: 's' }, d: { a: 's', b: 's', c: 's' } } })
is(result.a.b.c, censor)
is(result.a.d.a, 's')
is(result.a.d.b, 's')
is(result.a.d.c, censor)
end()
})
test('restore parent wildcard one following key', ({ end, is }) => {
const redact = fastRedact({ paths: ['a.*.c'], serialize: false })
const result = redact({ a: { b: { c: 's' }, d: { a: 's', b: 's', c: 's' } } })
redact.restore(result)
is(result.a.b.c, 's')
is(result.a.d.a, 's')
is(result.a.d.b, 's')
is(result.a.d.c, 's')
end()
})
test('parent wildcard one following key reuse', ({ end, is }) => {
const redact = fastRedact({ paths: ['a.*.c'], serialize: false })
const result = redact({ a: { b: { c: 's' }, d: { a: 's', b: 's', c: 's' } } })
is(result.a.b.c, censor)
is(result.a.d.a, 's')
is(result.a.d.b, 's')
is(result.a.d.c, censor)
const result2 = redact({ a: { b: { c: 's' }, d: { a: 's', b: 's', c: 's' } } })
is(result2.a.b.c, censor)
is(result2.a.d.a, 's')
is(result2.a.d.b, 's')
is(result2.a.d.c, censor)
redact.restore(result2)
end()
})
test('parent wildcard two following keys', ({ end, is }) => {
const redact = fastRedact({ paths: ['a.*.x.c'], serialize: false })
const result = redact({ a: { b: { x: { c: 's' } }, d: { x: { a: 's', b: 's', c: 's' } } } })
is(result.a.b.x.c, censor)
is(result.a.d.x.a, 's')
is(result.a.d.x.b, 's')
is(result.a.d.x.c, censor)
end()
})
test('parent wildcard two following keys  reuse', ({ end, is }) => {
const redact = fastRedact({ paths: ['a.*.x.c'], serialize: false })
const result = redact({ a: { b: { x: { c: 's' } }, d: { x: { a: 's', b: 's', c: 's' } } } })
is(result.a.b.x.c, censor)
is(result.a.d.x.a, 's')
is(result.a.d.x.b, 's')
is(result.a.d.x.c, censor)
redact.restore(result)
const result2 = redact({ a: { b: { x: { c: 's' } }, d: { x: { a: 's', b: 's', c: 's' } } } })
is(result2.a.b.x.c, censor)
is(result2.a.d.x.a, 's')
is(result2.a.d.x.b, 's')
is(result2.a.d.x.c, censor)
end()
})
test('restore parent wildcard two following keys', ({ end, is }) => {
const redact = fastRedact({ paths: ['a.*.x.c'], serialize: false })
const result = redact({ a: { b: { x: { c: 's' } }, d: { x: { a: 's', b: 's', c: 's' } } } })
redact.restore(result)
is(result.a.b.x.c, 's')
is(result.a.d.x.a, 's')
is(result.a.d.x.b, 's')
is(result.a.d.x.c, 's')
end()
})
test('parent wildcard - array', ({ end, is }) => {
const redact = fastRedact({ paths: ['a.b[*].x'], serialize: false })
const result = redact({ a: { b: [{ x: 1 }, { a: 2 }], d: { a: 's', b: 's', c: 's' } } })
is(result.a.b[0].x, censor)
is(result.a.b[1].a, 2)
is(result.a.d.a, 's')
is(result.a.d.b, 's')
end()
})
test('parent wildcards array single index', ({ end, same }) => {
const redact = fastRedact({ paths: ['insideArray.like[3].*.foo'], serialize: false })
same(redact({ insideArray: { like: ['a', 'b', 'c', { this: { foo: 'meow' } }] } }), { insideArray: { like: ['a', 'b', 'c', { this: { foo: censor } }] } })
end()
})
test('parent wildcards - handles null proto objects', ({ end, is }) => {
const redact = fastRedact({ paths: ['a.*.c'], serialize: false })
const result = redact({ __proto__: null, a: { b: { c: 's' }, d: { a: 's', b: 's', c: 's' } } })
is(result.a.b.c, censor)
end()
})
test('parent wildcards - handles paths that do not match object structure', ({ end, same }) => {
const redact = fastRedact({ paths: ['a.*.y.z'], serialize: false })
same(redact({ a: { b: { c: 's' } } }), { a: { b: { c: 's' } } })
end()
})
test('parent wildcards - gracefully handles primitives that match intermediate keys in paths', ({ end, same }) => {
const redact = fastRedact({ paths: ['a.*.c'], serialize: false })
same(redact({ a: { b: null } }), { a: { b: null } })
same(redact({ a: { b: 's' } }), { a: { b: 's' } })
same(redact({ a: { b: 1 } }), { a: { b: 1 } })
same(redact({ a: { b: undefined } }), { a: { b: undefined } })
same(redact({ a: { b: true } }), { a: { b: true } })
const sym = Symbol('sym')
same(redact({ a: { b: sym } }), { a: { b: sym } })
end()
})
test('parent wildcards handles circulars', ({ end, is, same }) => {
const redact = fastRedact({ paths: ['x.*.baz'], serialize: false })
const bar = { b: 2 }
const o = { x: { a: 1, bar } }
bar.baz = bar
o.x.bar.baz = o.x.bar
same(redact(o), { x: { a: 1, bar: { b: 2, baz: censor } } })
end()
})
test('parent wildcards handles circulars  restore', ({ end, is, same }) => {
const redact = fastRedact({ paths: ['x.*.baz'], serialize: false })
const bar = { b: 2 }
const o = { x: { a: 1, bar } }
bar.baz = bar
o.x.bar.baz = o.x.bar
is(o.x.bar.baz, bar)
redact(o)
is(o.x.a, 1)
is(o.x.bar.baz, censor)
redact.restore(o)
is(o.x.bar.baz, bar)
end()
})
test('parent wildcards handles circulars and cross references  restore', ({ end, is, same }) => {
const redact = fastRedact({ paths: ['x.*.baz', 'x.*.cf.bar'], serialize: false })
const bar = { b: 2 }
const o = { x: { a: 1, bar, y: { cf: { bar } } } }
bar.baz = bar
o.x.bar.baz = o.x.bar
is(o.x.bar.baz, bar)
is(o.x.y.cf.bar, bar)
redact(o)
is(o.x.bar.baz, censor)
is(o.x.y.cf.bar, censor)
redact.restore(o)
is(o.x.bar.baz, bar)
is(o.x.y.cf.bar, bar)
end()
})
test('parent wildcards handles missing paths', ({ end, is, same }) => {
const redact = fastRedact({ paths: ['z.*.baz'] })
const o = { a: { b: { c: 's' }, d: { a: 's', b: 's', c: 's' } } }
is(redact(o), JSON.stringify(o))
end()
})
test('ultimate wildcards handles missing paths', ({ end, is, same }) => {
const redact = fastRedact({ paths: ['z.*'] })
const o = { a: { b: { c: 's' }, d: { a: 's', b: 's', c: 's' } } }
is(redact(o), JSON.stringify(o))
end()
})
test('parent wildcards  removes during serialization instead of redacting when remove option is true', ({ end, is }) => {
const redact = fastRedact({ paths: ['a.*.c'], remove: true })
const o = { a: { b: { c: 'c' }, x: { c: 1 } } }
is(redact(o), `{"a":{"b":{},"x":{}}}`)
end()
})
test('ultimate wildcards  removes during serialization instead of redacting when remove option is true', ({ end, is }) => {
const redact = fastRedact({ paths: ['a.b.*'], remove: true })
const o = { a: { b: { c: 'c' }, x: { c: 1 } } }
is(redact(o), `{"a":{"b":{},"x":{"c":1}}}`)
end()
})
test('supports leading bracket notation', ({ end, is }) => {
const redact = fastRedact({ paths: ['["a"].b.c'] })
const o = { a: { b: { c: 'd' } } }
is(redact(o), `{"a":{"b":{"c":"${censor}"}}}`)
end()
})
test('supports leading bracket notation containing non-legal keyword characters', ({ end, is }) => {
const redact = fastRedact({ paths: ['["a-x"].b.c'] })
const o = { 'a-x': { b: { c: 'd' } } }
is(redact(o), `{"a-x":{"b":{"c":"${censor}"}}}`)
end()
})
test('supports single leading bracket', ({ end, is }) => {
const censor = 'test'
const redact = fastRedact({ paths: ['["a"]'], censor, serialize: false })
is(redact({ a: 'a' }).a, censor)
end()
})
test('supports single leading bracket containing non-legal keyword characters', ({ end, is }) => {
const censor = 'test'
const redact = fastRedact({ paths: ['["a-x"]'], censor, serialize: false })
is(redact({ 'a-x': 'a' })['a-x'], censor)
end()
})
test('(leading brackets) ultimate wildcards handles circulars and cross references  restore', ({ end, is, same }) => {
const redact = fastRedact({ paths: ['bar.baz.*', 'cf.*'], serialize: false })
const bar = { b: 2 }
const o = { a: 1, bar, cf: { bar } }
bar.baz = bar
o.bar.baz = o.bar
is(o.bar.baz, bar)
is(o.cf.bar, bar)
redact(o)
is(o.bar.baz, censor)
is(o.cf.bar, censor)
redact.restore(o)
is(o.bar.baz, bar)
is(o.cf.bar, bar)
end()
})
test('(leading brackets) parent wildcards handles circulars and cross references  restore', ({ end, is, same }) => {
const redact = fastRedact({ paths: ['["x"].*.baz', '["x"].*.cf.bar'], serialize: false })
const bar = { b: 2 }
const o = { x: { a: 1, bar, y: { cf: { bar } } } }
bar.baz = bar
o.x.bar.baz = o.x.bar
is(o.x.bar.baz, bar)
is(o.x.y.cf.bar, bar)
redact(o)
is(o.x.bar.baz, censor)
is(o.x.y.cf.bar, censor)
redact.restore(o)
is(o.x.bar.baz, bar)
is(o.x.y.cf.bar, bar)
end()
})
test('(leading brackets) ultimate wildcards handles missing paths', ({ end, is, same }) => {
const redact = fastRedact({ paths: ['["z"].*'] })
const o = { a: { b: { c: 's' }, d: { a: 's', b: 's', c: 's' } } }
is(redact(o), JSON.stringify(o))
end()
})
test('(leading brackets) static + wildcards reuse', ({ end, is }) => {
const redact = fastRedact({ paths: ['["a"].b.c', '["a"].d.*'], serialize: false })
const result = redact({ a: { b: { c: 's' }, d: { a: 's', b: 's', c: 's' } } })
is(result.a.b.c, censor)
is(result.a.d.a, censor)
is(result.a.d.b, censor)
is(result.a.d.c, censor)
redact.restore(result)
const result2 = redact({ a: { b: { c: 's' }, d: { a: 's', b: 's', c: 's' } } })
is(result2.a.b.c, censor)
is(result2.a.d.a, censor)
is(result2.a.d.b, censor)
is(result2.a.d.c, censor)
redact.restore(result2)
end()
})
test('correctly restores original object when a path does not match object', ({ end, is }) => {
const redact = fastRedact({ paths: ['foo.bar'], strict: false })
const o = {}
is(redact({ foo: o }), '{"foo":{}}')
is(o.hasOwnProperty('bar'), false)
end()
})
test('correctly restores original object when a matching path has value of `undefined`', ({ end, is }) => {
const redact = fastRedact({ paths: ['foo.bar'], strict: false })
const o = { bar: undefined }
is(redact({ foo: o }), '{"foo":{}}')
is(o.hasOwnProperty('bar'), true)
is(o.bar, undefined)
end()
})
test('handles multiple paths with leading brackets', ({ end, is }) => {
const redact = fastRedact({ paths: ['["x-y"]', '["y-x"]'] })
const o = { 'x-y': 'test', 'y-x': 'test2' }
is(redact(o), '{"x-y":"[REDACTED]","y-x":"[REDACTED]"}')
end()
})
test('handles objects with and then without target paths', ({ end, is }) => {
const redact = fastRedact({ paths: ['test'] })
const o1 = { test: 'check' }
const o2 = {}
is(redact(o1), '{"test":"[REDACTED]"}')
is(redact(o2), '{}')
// run each check twice to ensure no mutations
is(redact(o1), '{"test":"[REDACTED]"}')
is(redact(o2), '{}')
is('test' in o1, true)
is('test' in o2, false)
end()
})
test('handles leading wildcards and null values', ({ end, is }) => {
const redact = fastRedact({ paths: ['*.test'] })
const o = { prop: null }
is(redact(o), '{"prop":null}')
is(o.prop, null)
end()
})
test('handles keys with dots', ({ end, is }) => {
const redactSingleQ = fastRedact({ paths: [`a['b.c']`], serialize: false })
const redactDoubleQ = fastRedact({ paths: [`a["b.c"]`], serialize: false })
const redactBacktickQ = fastRedact({ paths: ['a[`b.c`]'], serialize: false })
const redactNum = fastRedact({ paths: [`a[-1.2]`], serialize: false })
const redactLeading = fastRedact({ paths: [`["b.c"]`], serialize: false })
is(redactSingleQ({ a: { 'b.c': 'x', '-1.2': 'x' } }).a['b.c'], censor)
is(redactDoubleQ({ a: { 'b.c': 'x', '-1.2': 'x' } }).a['b.c'], censor)
is(redactBacktickQ({ a: { 'b.c': 'x', '-1.2': 'x' } }).a['b.c'], censor)
is(redactNum({ a: { 'b.c': 'x', '-1.2': 'x' } }).a['-1.2'], censor)
is(redactLeading({ 'b.c': 'x', '-1.2': 'x' })['b.c'], censor)
end()
})