Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 153 additions & 0 deletions spec/Utils.spec.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const Utils = require('../lib/Utils');
const { createSanitizedError, createSanitizedHttpError } = require("../lib/Error")
const vm = require('vm');

describe('Utils', () => {
describe('encodeForUrl', () => {
Expand Down Expand Up @@ -287,4 +288,156 @@ describe('Utils', () => {
expect(error.message).toBe('Detailed error message');
});
});

describe('isDate', () => {
it('should return true for a Date', () => {
expect(Utils.isDate(new Date())).toBe(true);
});
it('should return true for a cross-realm Date', () => {
const crossRealmDate = vm.runInNewContext('new Date()');
expect(crossRealmDate instanceof Date).toBe(false);
expect(Utils.isDate(crossRealmDate)).toBe(true);
});
it('should return false for non-Date values', () => {
expect(Utils.isDate(null)).toBe(false);
expect(Utils.isDate(undefined)).toBe(false);
expect(Utils.isDate('2021-01-01')).toBe(false);
expect(Utils.isDate(123)).toBe(false);
expect(Utils.isDate({})).toBe(false);
});
});

describe('isRegExp', () => {
it('should return true for a RegExp', () => {
expect(Utils.isRegExp(/test/)).toBe(true);
expect(Utils.isRegExp(new RegExp('test'))).toBe(true);
});
it('should return true for a cross-realm RegExp', () => {
const crossRealmRegExp = vm.runInNewContext('/test/');
expect(crossRealmRegExp instanceof RegExp).toBe(false);
expect(Utils.isRegExp(crossRealmRegExp)).toBe(true);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
});
it('should return false for non-RegExp values', () => {
expect(Utils.isRegExp(null)).toBe(false);
expect(Utils.isRegExp(undefined)).toBe(false);
expect(Utils.isRegExp('/test/')).toBe(false);
expect(Utils.isRegExp({})).toBe(false);
});
});

describe('isMap', () => {
it('should return true for a Map', () => {
expect(Utils.isMap(new Map())).toBe(true);
});
it('should return true for a cross-realm Map', () => {
const crossRealmMap = vm.runInNewContext('new Map()');
expect(crossRealmMap instanceof Map).toBe(false);
expect(Utils.isMap(crossRealmMap)).toBe(true);
});
it('should return false for non-Map values', () => {
expect(Utils.isMap(null)).toBe(false);
expect(Utils.isMap(undefined)).toBe(false);
expect(Utils.isMap({})).toBe(false);
expect(Utils.isMap(new Set())).toBe(false);
});
});

describe('isSet', () => {
it('should return true for a Set', () => {
expect(Utils.isSet(new Set())).toBe(true);
});
it('should return true for a cross-realm Set', () => {
const crossRealmSet = vm.runInNewContext('new Set()');
expect(crossRealmSet instanceof Set).toBe(false);
expect(Utils.isSet(crossRealmSet)).toBe(true);
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.
it('should return false for non-Set values', () => {
expect(Utils.isSet(null)).toBe(false);
expect(Utils.isSet(undefined)).toBe(false);
expect(Utils.isSet({})).toBe(false);
expect(Utils.isSet(new Map())).toBe(false);
});
});

describe('isNativeError', () => {
it('should return true for an Error', () => {
expect(Utils.isNativeError(new Error('test'))).toBe(true);
});
it('should return true for Error subclasses', () => {
expect(Utils.isNativeError(new TypeError('test'))).toBe(true);
expect(Utils.isNativeError(new RangeError('test'))).toBe(true);
});
it('should return true for a cross-realm Error', () => {
const crossRealmError = vm.runInNewContext('new Error("test")');
expect(crossRealmError instanceof Error).toBe(false);
expect(Utils.isNativeError(crossRealmError)).toBe(true);
});
it('should return false for non-Error values', () => {
expect(Utils.isNativeError(null)).toBe(false);
expect(Utils.isNativeError(undefined)).toBe(false);
expect(Utils.isNativeError({ message: 'fake' })).toBe(false);
expect(Utils.isNativeError('error')).toBe(false);
});
});

describe('isPromise', () => {
it('should return true for a Promise', () => {
expect(Utils.isPromise(Promise.resolve())).toBe(true);
});
it('should return true for a cross-realm Promise', () => {
const crossRealmPromise = vm.runInNewContext('Promise.resolve()');
expect(crossRealmPromise instanceof Promise).toBe(false);
expect(Utils.isPromise(crossRealmPromise)).toBe(true);
});
it('should return true for a thenable', () => {
expect(Utils.isPromise({ then: () => {} })).toBe(true);
});
it('should return false for non-Promise values', () => {
expect(Utils.isPromise(null)).toBe(false);
expect(Utils.isPromise(undefined)).toBe(false);
expect(Utils.isPromise({})).toBe(false);
expect(Utils.isPromise(42)).toBe(false);
});
it('should return false for plain objects when Object.prototype.then is polluted', () => {
Object.prototype.then = () => {};
try {
expect(Utils.isPromise({})).toBe(false);
expect(Utils.isPromise({ a: 1 })).toBe(false);
} finally {
delete Object.prototype.then;
}
});
it('should return true for real thenables even when Object.prototype.then is polluted', () => {
Object.prototype.then = () => {};
try {
expect(Utils.isPromise({ then: () => {} })).toBe(true);
Comment thread
mtrezza marked this conversation as resolved.
expect(Utils.isPromise(Promise.resolve())).toBe(true);
} finally {
delete Object.prototype.then;
}
});
});

describe('isObject', () => {
it('should return true for plain objects', () => {
expect(Utils.isObject({})).toBe(true);
expect(Utils.isObject({ a: 1 })).toBe(true);
});
it('should return true for a cross-realm object', () => {
const crossRealmObj = vm.runInNewContext('({ a: 1 })');
expect(crossRealmObj instanceof Object).toBe(false);
expect(Utils.isObject(crossRealmObj)).toBe(true);
});
it('should return true for arrays and other objects', () => {
expect(Utils.isObject([])).toBe(true);
expect(Utils.isObject(new Date())).toBe(true);
});
it('should return false for non-object values', () => {
expect(Utils.isObject(null)).toBe(false);
expect(Utils.isObject(undefined)).toBe(false);
expect(Utils.isObject(42)).toBe(false);
expect(Utils.isObject('string')).toBe(false);
expect(Utils.isObject(true)).toBe(false);
});
});
});
70 changes: 65 additions & 5 deletions src/Utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

const path = require('path');
const fs = require('fs').promises;
const { types } = require('util');

/**
* The general purpose utilities.
Expand Down Expand Up @@ -120,12 +121,71 @@ class Utils {
}

/**
* Determines whether an object is a Promise.
* @param {any} object The object to validate.
* @returns {Boolean} Returns true if the object is a promise.
* Realm-safe check for Date.
* @param {any} value The value to check.
* @returns {Boolean} Returns true if the value is a Date.
*/
static isPromise(object) {
return object instanceof Promise;
static isDate(value) {
return types.isDate(value);
}

/**
* Realm-safe check for RegExp.
* @param {any} value The value to check.
* @returns {Boolean} Returns true if the value is a RegExp.
*/
static isRegExp(value) {
return types.isRegExp(value);
}

/**
* Realm-safe check for Map.
* @param {any} value The value to check.
* @returns {Boolean} Returns true if the value is a Map.
*/
static isMap(value) {
return types.isMap(value);
}

/**
* Realm-safe check for Set.
* @param {any} value The value to check.
* @returns {Boolean} Returns true if the value is a Set.
*/
static isSet(value) {
return types.isSet(value);
}

/**
* Realm-safe check for native Error.
* @param {any} value The value to check.
* @returns {Boolean} Returns true if the value is a native Error.
*/
static isNativeError(value) {
return types.isNativeError(value);
}

/**
* Realm-safe check for Promise (duck-typed as thenable).
* Guards against Object.prototype pollution by ensuring `then` is not
* inherited solely from Object.prototype.
* @param {any} value The value to check.
* @returns {Boolean} Returns true if the value is a Promise or thenable.
*/
static isPromise(value) {
if (value == null || typeof value.then !== 'function') {
return false;
}
return Object.getPrototypeOf(value) !== Object.prototype || Object.prototype.hasOwnProperty.call(value, 'then');
}

/**
* Realm-safe check for object (non-null, typeof object).
* @param {any} value The value to check.
* @returns {Boolean} Returns true if the value is a non-null object.
*/
static isObject(value) {
return typeof value === 'object' && value !== null;
}

/**
Expand Down
Loading