Skip to content

Commit

Permalink
Properly handle objects with null prototype in metadata (#220)
Browse files Browse the repository at this point in the history
* refactor tests for more flexibility

* safeguard existing functionality with tests

* properly handle objects with null prototype
  • Loading branch information
curledUpSheep authored Jul 10, 2023
1 parent 9d81162 commit 31e2c81
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 10 deletions.
6 changes: 3 additions & 3 deletions lib/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ exports.prepareMetaData = meta=>{
* @param {Array=} opt_parents Object's parents
*/
function cloneMeta(node, opt_parents) {
if (!(node instanceof Object) || (node instanceof ObjectID)
|| (node instanceof Buffer)) {
if (!((node !== null && typeof node === 'object') || typeof node === 'function')
|| (node instanceof ObjectID) || (node instanceof Buffer)) {
return node;
}
let copy = Array.isArray(node) ? [] : {};
Expand All @@ -48,7 +48,7 @@ function cloneMeta(node, opt_parents) {
if (newKey.includes('.') || newKey.includes('$')) {
newKey = newKey.replace(/\./g, '[dot]').replace(/\$/g, '[$]');
}
if (value instanceof Object) {
if ((value !== null && typeof value === 'object') || typeof value === 'function') {
if (opt_parents.indexOf(value) === -1) {
copy[newKey] = cloneMeta(value, opt_parents);
} else {
Expand Down
113 changes: 106 additions & 7 deletions test/helpers-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/
'use strict';
const assert = require('assert');
const ObjectID = require('mongodb').ObjectID;
const helpers = require('../lib/helpers');

class CustomError extends Error {
Expand All @@ -15,33 +16,131 @@ class CustomError extends Error {
}
}

const originalData = {
customDate: new Date(),
standardError: new Error('some error'),
customError: new CustomError()
};

describe('winston-mongodb-helpers', function() {
describe('#prepareMetaData()', function() {
let preparedData = helpers.prepareMetaData(originalData);
it('should preserve Date instances', function() {
const originalData = { customDate: new Date() };

const preparedData = helpers.prepareMetaData(originalData);

assert(preparedData.customDate instanceof Date);
assert.strictEqual(+preparedData.customDate, +originalData.customDate);
});
it('should store Error objects', function() {
const originalData = { standardError: new Error('some error') };

const preparedData = helpers.prepareMetaData(originalData);

assert(preparedData.standardError instanceof Object);
assert(!(preparedData.standardError instanceof Error));
assert.strictEqual(preparedData.standardError.message, originalData.standardError.message);
assert.strictEqual(preparedData.standardError.name, originalData.standardError.name);
assert.strictEqual(preparedData.standardError.stack, originalData.standardError.stack);
});
it('should store extra fields for custom Error objects', function() {
const originalData = { customError: new CustomError() };

const preparedData = helpers.prepareMetaData(originalData);

assert(preparedData.customError instanceof Object);
assert(!(preparedData.customError instanceof Error));
assert.strictEqual(preparedData.customError.message, originalData.customError.message);
assert.strictEqual(preparedData.customError.name, originalData.customError.name);
assert.strictEqual(preparedData.customError.stack, originalData.customError.stack);
assert.strictEqual(preparedData.customError.testField, originalData.customError.testField);
});
it('should preserve ObjectIds', function() {
const originalData = { objectId: new ObjectID() };

const preparedData = helpers.prepareMetaData(originalData);

assert.strictEqual(preparedData.objectId, originalData.objectId);
});
it('should preserve Buffers', function() {
const originalData = { buffer: new Buffer.from('test') };

const preparedData = helpers.prepareMetaData(originalData);

assert.strictEqual(preparedData.buffer, originalData.buffer);
});
it('should handle objects containing all kinds of values, including arrays, nested objects and functions', function() {
const originalData = {
undefinedValue: undefined,
nullValue: null,
booleanValue: true,
numberValue: 1,
bigIntValue: BigInt(9007199254740991),
stringValue: 'test',
symbolValue: Symbol(),
arrayValue: ['this', 'is', 'an', 'array'],
nestedObjectValue: { objectKey: true },
functionValue: (a, b) => a + b
};

const preparedData = helpers.prepareMetaData(originalData);

const expected = { ...originalData, functionValue: {} }
assert.deepStrictEqual(preparedData, expected);
});
it('should handle arrays containing all kinds of values, including objects, nested arrays and functions', function() {
const originalData = [
undefined,
null,
true,
1,
BigInt(9007199254740991),
'test',
Symbol(),
{ objectKey: true },
['this', 'is', 'an', 'array'],
(a, b) => a + b
];

const preparedData = helpers.prepareMetaData(originalData);

const expected = [...originalData];
expected[expected.length - 1] = {}; // function gets converted to empty object
assert.deepStrictEqual(preparedData, expected);
});
it('should replace dots and dollar signs in object keys', function() {
const originalData = { 'key.with.dots': true, '$test$': true };

const preparedData = helpers.prepareMetaData(originalData);

const expected = { 'key[dot]with[dot]dots': true, '[$]test[$]': true };
assert.deepStrictEqual(preparedData, expected);
});
it('should break circular dependencies', function() {
const originalData = {};
originalData.nestedObjectValue = { nestedKey: originalData };
originalData.arrayValue = [originalData, 'test', { nestedKey: originalData }];

const preparedData = helpers.prepareMetaData(originalData);

const expected = {
nestedObjectValue: { nestedKey: '[Circular]' },
arrayValue: ['[Circular]', 'test', { nestedKey: '[Circular]' }]
};

assert.deepStrictEqual(preparedData, expected);
});
it('should handle objects with null prototype', function() {
const originalData = Object.create(null);
originalData['key.with.dots'] = true;
originalData['$test$'] = true;
originalData.nestedObjectValue = { nestedKey: originalData };
originalData.arrayValue = [originalData, 'test', { nestedKey: originalData }];

const preparedData = helpers.prepareMetaData(originalData);

const expected = {
'key[dot]with[dot]dots': true,
'[$]test[$]': true,
nestedObjectValue: { nestedKey: '[Circular]' },
arrayValue: ['[Circular]', 'test', { nestedKey: '[Circular]' }]
};

assert.deepStrictEqual(preparedData, expected);
});
});
});

0 comments on commit 31e2c81

Please sign in to comment.