-
Notifications
You must be signed in to change notification settings - Fork 29.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
test: add
common.mustNotMutateObjectDeep()
This function returns a Proxy object that throws on attempt to mutate it Functions and primitives are returned directly PR-URL: #43196 Reviewed-By: Darshan Sen <raisinten@gmail.com> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
- Loading branch information
1 parent
a057510
commit 42f2deb
Showing
4 changed files
with
309 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
225 changes: 225 additions & 0 deletions
225
test/parallel/test-common-must-not-mutate-object-deep.mjs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,225 @@ | ||
import { mustNotMutateObjectDeep } from '../common/index.mjs'; | ||
import assert from 'node:assert'; | ||
import { promisify } from 'node:util'; | ||
|
||
// Test common.mustNotMutateObjectDeep() | ||
|
||
const original = { | ||
foo: { bar: 'baz' }, | ||
qux: null, | ||
quux: [ | ||
'quuz', | ||
{ corge: 'grault' }, | ||
], | ||
}; | ||
|
||
// Make a copy to make sure original doesn't get altered by the function itself. | ||
const backup = structuredClone(original); | ||
|
||
// Wrapper for convenience: | ||
const obj = () => mustNotMutateObjectDeep(original); | ||
|
||
function testOriginal(root) { | ||
assert.deepStrictEqual(root, backup); | ||
return root.foo.bar === 'baz' && root.quux[1].corge.length === 6; | ||
} | ||
|
||
function definePropertyOnRoot(root) { | ||
Object.defineProperty(root, 'xyzzy', {}); | ||
} | ||
|
||
function definePropertyOnFoo(root) { | ||
Object.defineProperty(root.foo, 'xyzzy', {}); | ||
} | ||
|
||
function deletePropertyOnRoot(root) { | ||
delete root.foo; | ||
} | ||
|
||
function deletePropertyOnFoo(root) { | ||
delete root.foo.bar; | ||
} | ||
|
||
function preventExtensionsOnRoot(root) { | ||
Object.preventExtensions(root); | ||
} | ||
|
||
function preventExtensionsOnFoo(root) { | ||
Object.preventExtensions(root.foo); | ||
} | ||
|
||
function preventExtensionsOnRootViaSeal(root) { | ||
Object.seal(root); | ||
} | ||
|
||
function preventExtensionsOnFooViaSeal(root) { | ||
Object.seal(root.foo); | ||
} | ||
|
||
function preventExtensionsOnRootViaFreeze(root) { | ||
Object.freeze(root); | ||
} | ||
|
||
function preventExtensionsOnFooViaFreeze(root) { | ||
Object.freeze(root.foo); | ||
} | ||
|
||
function setOnRoot(root) { | ||
root.xyzzy = 'gwak'; | ||
} | ||
|
||
function setOnFoo(root) { | ||
root.foo.xyzzy = 'gwak'; | ||
} | ||
|
||
function setQux(root) { | ||
root.qux = 'gwak'; | ||
} | ||
|
||
function setQuux(root) { | ||
root.quux.push('gwak'); | ||
} | ||
|
||
function setQuuxItem(root) { | ||
root.quux[0] = 'gwak'; | ||
} | ||
|
||
function setQuuxProperty(root) { | ||
root.quux[1].corge = 'gwak'; | ||
} | ||
|
||
function setPrototypeOfRoot(root) { | ||
Object.setPrototypeOf(root, Array); | ||
} | ||
|
||
function setPrototypeOfFoo(root) { | ||
Object.setPrototypeOf(root.foo, Array); | ||
} | ||
|
||
function setPrototypeOfQuux(root) { | ||
Object.setPrototypeOf(root.quux, Array); | ||
} | ||
|
||
|
||
{ | ||
assert.ok(testOriginal(obj())); | ||
|
||
assert.throws( | ||
() => definePropertyOnRoot(obj()), | ||
{ code: 'ERR_ASSERTION' } | ||
); | ||
assert.throws( | ||
() => definePropertyOnFoo(obj()), | ||
{ code: 'ERR_ASSERTION' } | ||
); | ||
assert.throws( | ||
() => deletePropertyOnRoot(obj()), | ||
{ code: 'ERR_ASSERTION' } | ||
); | ||
assert.throws( | ||
() => deletePropertyOnFoo(obj()), | ||
{ code: 'ERR_ASSERTION' } | ||
); | ||
assert.throws( | ||
() => preventExtensionsOnRoot(obj()), | ||
{ code: 'ERR_ASSERTION' } | ||
); | ||
assert.throws( | ||
() => preventExtensionsOnFoo(obj()), | ||
{ code: 'ERR_ASSERTION' } | ||
); | ||
assert.throws( | ||
() => preventExtensionsOnRootViaSeal(obj()), | ||
{ code: 'ERR_ASSERTION' } | ||
); | ||
assert.throws( | ||
() => preventExtensionsOnFooViaSeal(obj()), | ||
{ code: 'ERR_ASSERTION' } | ||
); | ||
assert.throws( | ||
() => preventExtensionsOnRootViaFreeze(obj()), | ||
{ code: 'ERR_ASSERTION' } | ||
); | ||
assert.throws( | ||
() => preventExtensionsOnFooViaFreeze(obj()), | ||
{ code: 'ERR_ASSERTION' } | ||
); | ||
assert.throws( | ||
() => setOnRoot(obj()), | ||
{ code: 'ERR_ASSERTION' } | ||
); | ||
assert.throws( | ||
() => setOnFoo(obj()), | ||
{ code: 'ERR_ASSERTION' } | ||
); | ||
assert.throws( | ||
() => setQux(obj()), | ||
{ code: 'ERR_ASSERTION' } | ||
); | ||
assert.throws( | ||
() => setQuux(obj()), | ||
{ code: 'ERR_ASSERTION' } | ||
); | ||
assert.throws( | ||
() => setQuux(obj()), | ||
{ code: 'ERR_ASSERTION' } | ||
); | ||
assert.throws( | ||
() => setQuuxItem(obj()), | ||
{ code: 'ERR_ASSERTION' } | ||
); | ||
assert.throws( | ||
() => setQuuxProperty(obj()), | ||
{ code: 'ERR_ASSERTION' } | ||
); | ||
assert.throws( | ||
() => setPrototypeOfRoot(obj()), | ||
{ code: 'ERR_ASSERTION' } | ||
); | ||
assert.throws( | ||
() => setPrototypeOfFoo(obj()), | ||
{ code: 'ERR_ASSERTION' } | ||
); | ||
assert.throws( | ||
() => setPrototypeOfQuux(obj()), | ||
{ code: 'ERR_ASSERTION' } | ||
); | ||
|
||
// Test that no mutation happened: | ||
assert.ok(testOriginal(obj())); | ||
} | ||
|
||
// Test various supported types, directly and nested: | ||
[ | ||
undefined, null, false, true, 42, 42n, Symbol('42'), NaN, Infinity, {}, [], | ||
() => {}, async () => {}, Promise.resolve(), Math, Object.create(null), | ||
].forEach((target) => { | ||
assert.deepStrictEqual(mustNotMutateObjectDeep(target), target); | ||
assert.deepStrictEqual(mustNotMutateObjectDeep({ target }), { target }); | ||
assert.deepStrictEqual(mustNotMutateObjectDeep([ target ]), [ target ]); | ||
}); | ||
|
||
// Test that passed functions keep working correctly: | ||
{ | ||
const fn = () => 'blep'; | ||
fn.foo = {}; | ||
const fnImmutableView = mustNotMutateObjectDeep(fn); | ||
assert.deepStrictEqual(fnImmutableView, fn); | ||
|
||
// Test that the function still works: | ||
assert.strictEqual(fn(), 'blep'); | ||
assert.strictEqual(fnImmutableView(), 'blep'); | ||
|
||
// Test that the original function is not deeply frozen: | ||
fn.foo.bar = 'baz'; | ||
assert.strictEqual(fn.foo.bar, 'baz'); | ||
assert.strictEqual(fnImmutableView.foo.bar, 'baz'); | ||
|
||
// Test the original function is not frozen: | ||
fn.qux = 'quux'; | ||
assert.strictEqual(fn.qux, 'quux'); | ||
assert.strictEqual(fnImmutableView.qux, 'quux'); | ||
|
||
// Redefining util.promisify.custom also works: | ||
promisify(mustNotMutateObjectDeep(promisify(fn))); | ||
} |