diff --git a/index.d.ts b/index.d.ts index 54991a9..374efee 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,54 +1,54 @@ declare module PersonalDataFilter { - interface IPersonalDataFilter { - filter(data: any): any; - } - - - interface IPersonalDataFilterConfig { - /** - * Regular expressions which will be added to the default ones. - */ - additionalRegularExpressions?: string[]; - - /** - * Regular expression which will be used instead of the default one. - */ - regularExpression?: RegExp; - - /** - * String which will be used to replace the personal data. - */ - personalDataMask?: string; - - /** - * Personal data properties which will be added to the default ones. - */ - additionalPersonalDataProperties?: string[]; - - /** - * Personal data properties which will be used instead of the default ones. - */ - personalDataProperties?: string[]; - - /** - * Defines if the filter should use the default match replacer to replace each regular - * expression match. - */ - useDefaultMatchReplacer?: boolean; - - /** - * Sets the match replacer which will be used to replace each regular - * expression match. - */ - matchReplacer: MatchReplacer; - } - - /** - * Function which will be used to replace each regular expression match. - */ - type MatchReplacer = (match: string) => string + interface IPersonalDataFilter { + filter(data: any): any; + } + + + interface IPersonalDataFilterConfig { + /** + * Regular expressions which will be added to the default ones. + */ + additionalRegularExpressions?: string[]; + + /** + * Regular expression which will be used instead of the default one. + */ + regularExpression?: RegExp; + + /** + * String which will be used to replace the personal data. + */ + personalDataMask?: string; + + /** + * Personal data properties which will be added to the default ones. + */ + additionalPersonalDataProperties?: string[]; + + /** + * Personal data properties which will be used instead of the default ones. + */ + personalDataProperties?: string[]; + + /** + * Defines if the filter should use the default match replacer to replace each regular + * expression match. + */ + useDefaultMatchReplacer?: boolean; + + /** + * Sets the match replacer which will be used to replace each regular + * expression match. + */ + matchReplacer: MatchReplacer; + } + + /** + * Function which will be used to replace each regular expression match. + */ + type MatchReplacer = (match: string) => string } declare module "node-personal-data-filter" { - export const newFilter: (cfg?: PersonalDataFilter.IPersonalDataFilterConfig) => PersonalDataFilter.IPersonalDataFilter; + export const newFilter: (cfg?: PersonalDataFilter.IPersonalDataFilterConfig) => PersonalDataFilter.IPersonalDataFilter; } diff --git a/index.js b/index.js index 887f851..bc22b20 100644 --- a/index.js +++ b/index.js @@ -3,7 +3,21 @@ const _ = require("lodash"); const crypto = require("crypto"); -const personalDataProperties = ["email", "useremail", "user", "username", "userid", "accountid", "account", "password", "pass", "pwd", "ip", "ipaddress"]; +const personalDataProperties = [ + "account", + "accountid", + "authorization", + "email", + "ip", + "ipaddress", + "pass", + "password", + "pwd", + "user", + "useremail", + "userid", + "username", +]; // Source: https://stackoverflow.com/a/46181/4922411 const emailRegExpTemplate = `(([^<>()\\[\\]\\\\.,;:\\s@"]+(\\.[^<>()\\[\\]\\\\.,;:\\s@"]+)*)|(".+"))@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\])|(([a-zA-Z\\-0-9]+\\.)+[a-zA-Z]{2,}))`; @@ -17,131 +31,131 @@ const ipV6RegExpTemplate = "((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9 const defaultRegularExpressions = [emailRegExpTemplate, guidRegExpTemplate, ipV4RegExpTemplate, ipV6RegExpTemplate]; class PersonalDataFilter { - constructor(config) { - config = config || {}; - this._setRegularExpressions(config); - this._setMask(config); - this._setPersonalDataProperties(config); - this._setMatchReplacer(config); - } - - filter(data) { - const result = this._filterRecursively(data, []); - - return result; - } - - _filterRecursively(data, referencesCache) { - if (_.isString(data)) { - return this._filterString(data); - } else if (_.isArray(data)) { - return _.map(data, v => this._filterRecursively(v, referencesCache)); - } else if (_.isObject(data)) { // isObject check should always be after the isArray check because isObject returns true for []. - // if the current reference has already been traversed this means we've reaced a circular reference - // simply mask it in order to avoid maximum callstack due to endless traversal - if (_.find(referencesCache, (obj) => obj === data)) { - return this._mask; - } else { - referencesCache.push(data) - return this._maskPersonalDataProperties(data, referencesCache); - } - } - - return data; - } - - _maskPersonalDataProperties(data, referencesCache) { - // It's important to use getOwnPropertyNames here - // because if someone were to pass something with non-enumerable properties (e.g. an instance of the Error class) - // then a simple _.reduce would simply skip over the non-enumerable properties ultimately removing them from the result - const propertyNames = Object.getOwnPropertyNames(data); - return _.reduce(propertyNames, (result, key) => { - let value; - - try { - value = data[key]; - } catch (err) { - // We can't get the value and we should not include it in the result because we can't filter it. - return result; - } - - if (this._personalDataProperties.indexOf(key.toString().toLowerCase()) >= 0) { - result[key] = this._mask; - } else { - result[key] = this._filterRecursively(value, referencesCache); - } - - return result; - }, {}); - } - - _filterString(input) { - if (this._matchReplacer) { - return input.replace(this._filterRegExp, this._matchReplacer); - } - - return input.replace(this._filterRegExp, this._mask) - } - - _setRegularExpressions(config) { - const additionalRegularExpressionsSize = _.size(config.additionalRegularExpressions); - if (additionalRegularExpressionsSize > 0 && config.regularExpression) { - throw new Error("You can't use additionalRegularExpressions and regularExpression at the same time."); - } - - if (config.regularExpression) { - this._filterRegExp = config.regularExpression; - return; - } - - let regExps = _.concat([], defaultRegularExpressions); - if (additionalRegularExpressionsSize > 0) { - regExps = _.concat(regExps, config.additionalRegularExpressions); - } - - const regExpString = regExps.map(r => `(${r})`).join("|"); - this._filterRegExp = new RegExp(regExpString, "gi"); - } - - _setMask(config) { - this._mask = config.personalDataMask || ""; - } - - _setPersonalDataProperties(config) { - const additionalPropertiesSize = _.size(config.additionalPersonalDataProperties); - const propertiesSize = _.size(config.personalDataProperties); - if (additionalPropertiesSize > 0 && propertiesSize > 0) { - throw new Error("You can't use additionalPersonalDataProperties and personalDataProperties at the same time."); - } - - if (propertiesSize > 0) { - this._personalDataProperties = config.personalDataProperties; - return; - } - - let props = _.concat([], personalDataProperties); - if (additionalPropertiesSize > 0) { - props = _.concat(props, config.additionalPersonalDataProperties); - } - - this._personalDataProperties = props; - } - - _setMatchReplacer(config) { - if (config.useDefaultMatchReplacer) { - if (config.matchReplacer) { - throw new Error("You can't use the default match replacer and a cutom one."); - } - - this._matchReplacer = (match) => { - return crypto.createHash("sha256").update(match).digest("hex"); - }; - } - - if (config.matchReplacer) { - this._matchReplacer = config.matchReplacer; - } - } + constructor(config) { + config = config || {}; + this._setRegularExpressions(config); + this._setMask(config); + this._setPersonalDataProperties(config); + this._setMatchReplacer(config); + } + + filter(data) { + const result = this._filterRecursively(data, []); + + return result; + } + + _filterRecursively(data, referencesCache) { + if (_.isString(data)) { + return this._filterString(data); + } else if (_.isArray(data)) { + return _.map(data, v => this._filterRecursively(v, referencesCache)); + } else if (_.isObject(data)) { // isObject check should always be after the isArray check because isObject returns true for []. + // if the current reference has already been traversed this means we've reaced a circular reference + // simply mask it in order to avoid maximum callstack due to endless traversal + if (_.find(referencesCache, (obj) => obj === data)) { + return this._mask; + } else { + referencesCache.push(data) + return this._maskPersonalDataProperties(data, referencesCache); + } + } + + return data; + } + + _maskPersonalDataProperties(data, referencesCache) { + // It's important to use getOwnPropertyNames here + // because if someone were to pass something with non-enumerable properties (e.g. an instance of the Error class) + // then a simple _.reduce would simply skip over the non-enumerable properties ultimately removing them from the result + const propertyNames = Object.getOwnPropertyNames(data); + return _.reduce(propertyNames, (result, key) => { + let value; + + try { + value = data[key]; + } catch (err) { + // We can't get the value and we should not include it in the result because we can't filter it. + return result; + } + + if (this._personalDataProperties.indexOf(key.toString().toLowerCase()) >= 0) { + result[key] = this._mask; + } else { + result[key] = this._filterRecursively(value, referencesCache); + } + + return result; + }, {}); + } + + _filterString(input) { + if (this._matchReplacer) { + return input.replace(this._filterRegExp, this._matchReplacer); + } + + return input.replace(this._filterRegExp, this._mask) + } + + _setRegularExpressions(config) { + const additionalRegularExpressionsSize = _.size(config.additionalRegularExpressions); + if (additionalRegularExpressionsSize > 0 && config.regularExpression) { + throw new Error("You can't use additionalRegularExpressions and regularExpression at the same time."); + } + + if (config.regularExpression) { + this._filterRegExp = config.regularExpression; + return; + } + + let regExps = _.concat([], defaultRegularExpressions); + if (additionalRegularExpressionsSize > 0) { + regExps = _.concat(regExps, config.additionalRegularExpressions); + } + + const regExpString = regExps.map(r => `(${r})`).join("|"); + this._filterRegExp = new RegExp(regExpString, "gi"); + } + + _setMask(config) { + this._mask = config.personalDataMask || ""; + } + + _setPersonalDataProperties(config) { + const additionalPropertiesSize = _.size(config.additionalPersonalDataProperties); + const propertiesSize = _.size(config.personalDataProperties); + if (additionalPropertiesSize > 0 && propertiesSize > 0) { + throw new Error("You can't use additionalPersonalDataProperties and personalDataProperties at the same time."); + } + + if (propertiesSize > 0) { + this._personalDataProperties = config.personalDataProperties; + return; + } + + let props = _.concat([], personalDataProperties); + if (additionalPropertiesSize > 0) { + props = _.concat(props, config.additionalPersonalDataProperties); + } + + this._personalDataProperties = props; + } + + _setMatchReplacer(config) { + if (config.useDefaultMatchReplacer) { + if (config.matchReplacer) { + throw new Error("You can't use the default match replacer and a cutom one."); + } + + this._matchReplacer = (match) => { + return crypto.createHash("sha256").update(match).digest("hex"); + }; + } + + if (config.matchReplacer) { + this._matchReplacer = config.matchReplacer; + } + } } module.exports.newFilter = (cfg) => new PersonalDataFilter(cfg); diff --git a/package-lock.json b/package-lock.json index 621ac3f..e62f1da 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "node-personal-data-filter", - "version": "0.2.3", + "version": "0.2.4", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -155,9 +155,9 @@ "dev": true }, "lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" }, "minimatch": { "version": "3.0.4", diff --git a/package.json b/package.json index 2073af9..afd7afe 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-personal-data-filter", - "version": "0.2.3", + "version": "0.2.4", "description": "node-personal-data-filter", "main": "index.js", "scripts": { @@ -19,7 +19,7 @@ }, "homepage": "https://github.com/telerik/node-personal-data-filter#readme", "dependencies": { - "lodash": "4.17.10" + "lodash": "4.17.20" }, "devDependencies": { "chai": "4.1.2", diff --git a/test/personal-data-filter.js b/test/personal-data-filter.js index 75e6e24..b7cdb0f 100644 --- a/test/personal-data-filter.js +++ b/test/personal-data-filter.js @@ -8,182 +8,182 @@ const expectedMaskedOutput = "*****"; const personalDataFilter = pdf.newFilter({ personalDataMask: expectedMaskedOutput }); describe("PersonalDataFilter", () => { - describe("filter", () => { - const email = "qweasd12_=+3!#$%^&@ice.cold"; - const guid = "1fec999a-7e81-4bce-8b32-1b6ddd144f1b"; - const ipV4 = "169.254.139.119"; - const ipV6 = "fe80::f991:38d8:27e6:8b77"; - const notPersonalData = "not-personal-data"; - const secondPropertyName = "second"; - const emailMixedCasePropertyName = "eMaIl"; - - describe("properties", () => { - const personalDataProperties = ["email", "useremail", "user", "username", "userid", "accountid", "account", "password", "pass", "pwd", "ip", "ipaddress"]; - - personalDataProperties.forEach(p => { - it(`should filter ${p} on first level`, () => { - const data = {}; - data[p] = "personal-data"; - - const result = personalDataFilter.filter(data); - - const expected = {}; - expected[p] = expectedMaskedOutput; - assert.deepEqual(result, expected); - }); - - it(`should filter ${p} on second level`, () => { - const data = {}; - data[secondPropertyName] = {}; - data[secondPropertyName][p] = "personal-data"; - - const result = personalDataFilter.filter(data); - - const expected = {}; - expected[secondPropertyName] = {}; - expected[secondPropertyName][p] = expectedMaskedOutput; - assert.deepEqual(result, expected); - }); - }); - - it("should search for properties case insensitive", () => { - const data = {}; - data[emailMixedCasePropertyName] = "personal-data"; - - const result = personalDataFilter.filter(data); - - const expected = {}; - expected[emailMixedCasePropertyName] = expectedMaskedOutput; - - assert.deepEqual(result, expected); - }); - - it("should check the values of all properties, not only the ones in the list.", () => { - const data = { - filterMe: email, - dontFilterMe: notPersonalData, - nextLevel: { - filterMe: guid, - dontFilterMe: notPersonalData, - email - }, - email - }; - - const result = personalDataFilter.filter(data); - - const expected = { - filterMe: expectedMaskedOutput, - dontFilterMe: notPersonalData, - nextLevel: { - filterMe: expectedMaskedOutput, - dontFilterMe: notPersonalData, - email: expectedMaskedOutput - }, - email: expectedMaskedOutput - }; - - assert.deepEqual(result, expected); - }); - }); - - describe("strings", () => { - const hashMatchReplacer = (match) => crypto.createHash("sha256").update(match).digest("hex"); - const tests = [{ - title: "filter with default match replacer", - expectedTextTransformer: hashMatchReplacer, - filter: pdf.newFilter({ useDefaultMatchReplacer: true }) - }, { - title: "filter with mask", - expectedTextTransformer: () => expectedMaskedOutput, - filter: personalDataFilter - }]; - - tests.forEach(test => { - describe(test.title, () => { - const testCaces = [ - { msg: email, expected: test.expectedTextTransformer(email) }, - { msg: `text ${email} text`, expected: `text ${test.expectedTextTransformer(email)} text` }, - { msg: `text ${email} ${email} text`, expected: `text ${test.expectedTextTransformer(email)} ${test.expectedTextTransformer(email)} text` }, - { msg: guid, expected: test.expectedTextTransformer(guid) }, - { msg: "487818704899480c907e2c0549664116", expected: "487818704899480c907e2c0549664116" }, - { msg: `text ${guid} text`, expected: `text ${test.expectedTextTransformer(guid)} text` }, - { msg: `${guid}${guid}`, expected: `${test.expectedTextTransformer(guid)}${test.expectedTextTransformer(guid)}` }, - { msg: `text${guid}text${guid}text`, expected: `text${test.expectedTextTransformer(guid)}text${test.expectedTextTransformer(guid)}text` }, - { msg: `text${guid}${guid}text`, expected: `text${test.expectedTextTransformer(guid)}${test.expectedTextTransformer(guid)}text` }, - { msg: `${email}${guid}`, expected: `${test.expectedTextTransformer(email)}${test.expectedTextTransformer(guid)}` }, - { msg: `${email} ${guid} ${email}`, expected: `${test.expectedTextTransformer(email)} ${test.expectedTextTransformer(guid)} ${test.expectedTextTransformer(email)}` }, - { msg: ipV4, expected: test.expectedTextTransformer(ipV4) }, - { msg: `text${ipV4}text`, expected: `text${test.expectedTextTransformer(ipV4)}text` }, - { msg: ipV6, expected: test.expectedTextTransformer(ipV6) }, - { msg: `text${ipV6}text`, expected: `text${test.expectedTextTransformer(ipV6)}text` } - ]; - - testCaces.forEach(t => { - it(`should transform "${t.msg}" to "${t.expected}"`, () => { - const result = test.filter.filter(t.msg); - assert.deepEqual(result, t.expected); - }); - }); - }); - }); - }); - - describe("arrays", () => { - it("should filter all array objects.", () => { - const data = [email, guid, notPersonalData, { userEmail: email, filterMe: email, dontFilterMe: notPersonalData, filterMeToo: guid }]; - - const result = personalDataFilter.filter(data); - - const expected = [expectedMaskedOutput, expectedMaskedOutput, notPersonalData, { - userEmail: expectedMaskedOutput, - filterMe: expectedMaskedOutput, - dontFilterMe: notPersonalData, - filterMeToo: expectedMaskedOutput - }]; - - assert.deepEqual(result, expected); - }); - }); - - describe("errors", () => { - it("should handle errors.", () => { - const test = new Error('some message'); - - const result = personalDataFilter.filter(test); - - assert.deepEqual(result.message, test.message); - assert.deepEqual(result.stack, test.stack); - }); - - it("should filter errors.", () => { - const test = new Error(`some message with personal data ${email}`); - - const result = personalDataFilter.filter(test); - - assert.deepEqual(result.message, result.message.replace(email, expectedMaskedOutput)); - assert.deepEqual(result.stack, result.stack.replace(email, expectedMaskedOutput)); - }); - }); - - describe("objects", () => { - it("should handle circular references.", () => { - const nestedObject = { userEmail: email, filterMe: email, dontFilterMe: notPersonalData, filterMeToo: guid }; - nestedObject.circular = nestedObject; - const data = [email, nestedObject]; - - const result = personalDataFilter.filter(data); - - const expected = [expectedMaskedOutput, { - userEmail: expectedMaskedOutput, - filterMe: expectedMaskedOutput, - dontFilterMe: notPersonalData, - filterMeToo: expectedMaskedOutput, - circular: expectedMaskedOutput, - }]; - - assert.deepEqual(result, expected); - }); - }); - }); + describe("filter", () => { + const email = "qweasd12_=+3!#$%^&@ice.cold"; + const guid = "1fec999a-7e81-4bce-8b32-1b6ddd144f1b"; + const ipV4 = "169.254.139.119"; + const ipV6 = "fe80::f991:38d8:27e6:8b77"; + const notPersonalData = "not-personal-data"; + const secondPropertyName = "second"; + const emailMixedCasePropertyName = "eMaIl"; + + describe("properties", () => { + const personalDataProperties = ["Authorization", "email", "useremail", "user", "username", "userid", "accountid", "account", "password", "pass", "pwd", "ip", "ipaddress"]; + + personalDataProperties.forEach(p => { + it(`should filter ${p} on first level`, () => { + const data = {}; + data[p] = "personal-data"; + + const result = personalDataFilter.filter(data); + + const expected = {}; + expected[p] = expectedMaskedOutput; + assert.deepEqual(result, expected); + }); + + it(`should filter ${p} on second level`, () => { + const data = {}; + data[secondPropertyName] = {}; + data[secondPropertyName][p] = "personal-data"; + + const result = personalDataFilter.filter(data); + + const expected = {}; + expected[secondPropertyName] = {}; + expected[secondPropertyName][p] = expectedMaskedOutput; + assert.deepEqual(result, expected); + }); + }); + + it("should search for properties case insensitive", () => { + const data = {}; + data[emailMixedCasePropertyName] = "personal-data"; + + const result = personalDataFilter.filter(data); + + const expected = {}; + expected[emailMixedCasePropertyName] = expectedMaskedOutput; + + assert.deepEqual(result, expected); + }); + + it("should check the values of all properties, not only the ones in the list.", () => { + const data = { + filterMe: email, + dontFilterMe: notPersonalData, + nextLevel: { + filterMe: guid, + dontFilterMe: notPersonalData, + email + }, + email + }; + + const result = personalDataFilter.filter(data); + + const expected = { + filterMe: expectedMaskedOutput, + dontFilterMe: notPersonalData, + nextLevel: { + filterMe: expectedMaskedOutput, + dontFilterMe: notPersonalData, + email: expectedMaskedOutput + }, + email: expectedMaskedOutput + }; + + assert.deepEqual(result, expected); + }); + }); + + describe("strings", () => { + const hashMatchReplacer = (match) => crypto.createHash("sha256").update(match).digest("hex"); + const tests = [{ + title: "filter with default match replacer", + expectedTextTransformer: hashMatchReplacer, + filter: pdf.newFilter({ useDefaultMatchReplacer: true }) + }, { + title: "filter with mask", + expectedTextTransformer: () => expectedMaskedOutput, + filter: personalDataFilter + }]; + + tests.forEach(test => { + describe(test.title, () => { + const testCaces = [ + { msg: email, expected: test.expectedTextTransformer(email) }, + { msg: `text ${email} text`, expected: `text ${test.expectedTextTransformer(email)} text` }, + { msg: `text ${email} ${email} text`, expected: `text ${test.expectedTextTransformer(email)} ${test.expectedTextTransformer(email)} text` }, + { msg: guid, expected: test.expectedTextTransformer(guid) }, + { msg: "487818704899480c907e2c0549664116", expected: "487818704899480c907e2c0549664116" }, + { msg: `text ${guid} text`, expected: `text ${test.expectedTextTransformer(guid)} text` }, + { msg: `${guid}${guid}`, expected: `${test.expectedTextTransformer(guid)}${test.expectedTextTransformer(guid)}` }, + { msg: `text${guid}text${guid}text`, expected: `text${test.expectedTextTransformer(guid)}text${test.expectedTextTransformer(guid)}text` }, + { msg: `text${guid}${guid}text`, expected: `text${test.expectedTextTransformer(guid)}${test.expectedTextTransformer(guid)}text` }, + { msg: `${email}${guid}`, expected: `${test.expectedTextTransformer(email)}${test.expectedTextTransformer(guid)}` }, + { msg: `${email} ${guid} ${email}`, expected: `${test.expectedTextTransformer(email)} ${test.expectedTextTransformer(guid)} ${test.expectedTextTransformer(email)}` }, + { msg: ipV4, expected: test.expectedTextTransformer(ipV4) }, + { msg: `text${ipV4}text`, expected: `text${test.expectedTextTransformer(ipV4)}text` }, + { msg: ipV6, expected: test.expectedTextTransformer(ipV6) }, + { msg: `text${ipV6}text`, expected: `text${test.expectedTextTransformer(ipV6)}text` } + ]; + + testCaces.forEach(t => { + it(`should transform "${t.msg}" to "${t.expected}"`, () => { + const result = test.filter.filter(t.msg); + assert.deepEqual(result, t.expected); + }); + }); + }); + }); + }); + + describe("arrays", () => { + it("should filter all array objects.", () => { + const data = [email, guid, notPersonalData, { userEmail: email, filterMe: email, dontFilterMe: notPersonalData, filterMeToo: guid }]; + + const result = personalDataFilter.filter(data); + + const expected = [expectedMaskedOutput, expectedMaskedOutput, notPersonalData, { + userEmail: expectedMaskedOutput, + filterMe: expectedMaskedOutput, + dontFilterMe: notPersonalData, + filterMeToo: expectedMaskedOutput + }]; + + assert.deepEqual(result, expected); + }); + }); + + describe("errors", () => { + it("should handle errors.", () => { + const test = new Error('some message'); + + const result = personalDataFilter.filter(test); + + assert.deepEqual(result.message, test.message); + assert.deepEqual(result.stack, test.stack); + }); + + it("should filter errors.", () => { + const test = new Error(`some message with personal data ${email}`); + + const result = personalDataFilter.filter(test); + + assert.deepEqual(result.message, result.message.replace(email, expectedMaskedOutput)); + assert.deepEqual(result.stack, result.stack.replace(email, expectedMaskedOutput)); + }); + }); + + describe("objects", () => { + it("should handle circular references.", () => { + const nestedObject = { userEmail: email, filterMe: email, dontFilterMe: notPersonalData, filterMeToo: guid }; + nestedObject.circular = nestedObject; + const data = [email, nestedObject]; + + const result = personalDataFilter.filter(data); + + const expected = [expectedMaskedOutput, { + userEmail: expectedMaskedOutput, + filterMe: expectedMaskedOutput, + dontFilterMe: notPersonalData, + filterMeToo: expectedMaskedOutput, + circular: expectedMaskedOutput, + }]; + + assert.deepEqual(result, expected); + }); + }); + }); });