-
-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Description
Discussed in #11221
Originally posted by Maximusya January 13, 2022
Right now I am migrating from Mongoose 5.x to 6.x and got about half of integration tests fail just because of undocumented changes in ObjectId validation.
These two mongoose validation methods differ in behavior both between the two in one version of mongoose and between mongoose versions (+dependencies):
mongoose.Types.ObjectId.isValid
mongoose.isValidObjectId
I get the expectation that once a variable has passed any of these validations, it can be safely specified both as new mongoose.Type.ObjectId(...)
ctor arg, and any of MyModel.findById(..)
et al.
Another kind of expectation (but probably far fetched): if a variable has passed any of these validations IT IS an id, but not a full document loaded from DB. Imagine a helper method that loads missing data if the argument passed in has properties that are not yet populated/loaded.
Example:
const mongoose = require('mongoose');
const testSubjects = {
'null': null,
'undefined': undefined,
'new ObjectId()': new mongoose.Types.ObjectId(),
'ObjectId().valueOf()': (new mongoose.Types.ObjectId()).valueOf(),
'ObjectId().toString()': (new mongoose.Types.ObjectId()).toString(),
'ObjectId().toHexString()': (new mongoose.Types.ObjectId()).toHexString(),
'\'Z\'.repeat(12)': 'Z'.repeat(12),
'\'F\'.repeat(24)': 'F'.repeat(24),
'\'580e0797bb495f0a200e91ad\'': '580e0797bb495f0a200e91ad',
'{ _id: new ObjectId() }': { _id: new mongoose.Types.ObjectId() },
'{ _id: \'Z\'.repeat(12) }': { _id: 'Z'.repeat(12) },
'{ _id: \'F\'.repeat(24) }': { _id: 'F'.repeat(24) },
'{ _id: \'580e0797bb495f0a200e91ad\' }': { _id: '580e0797bb495f0a200e91ad' },
'{ id: new ObjectId() }': { id: new mongoose.Types.ObjectId() },
'{ id: \'Z\'.repeat(12) }': { id: 'Z'.repeat(12) },
'{ id: \'F\'.repeat(24) }': { id: 'F'.repeat(24) },
'{ id: \'580e0797bb495f0a200e91ad\' }': { id: '580e0797bb495f0a200e91ad' },
'{ id: null }': { id: null },
'{ id: undefined }': { id: undefined },
'{ _id: null }': { _id: null },
'{ _id: undefined }': { _id: undefined },
};
console.table(Object.entries(testSubjects).map(([key, value]) => ({
test: key,
'isValidObjectId': mongoose.isValidObjectId(value),
'ObjectId.isValid': mongoose.Types.ObjectId.isValid(value),
'new ObjectId': newObjectId(value),
})));
function newObjectId(value) {
try {
return new mongoose.Types.ObjectId(value);
} catch (err) {
return 'ERROR';
}
}
[email protected] (same for @6.1.5) + [email protected]:
┌─────────┬───────────────────────────────────────┬─────────────────┬──────────────────┬──────────────────────────────────────────┐
│ (index) │ test │ isValidObjectId │ ObjectId.isValid │ new ObjectId │
├─────────┼───────────────────────────────────────┼─────────────────┼──────────────────┼──────────────────────────────────────────┤
│ 0 │ 'null' │ true │ false │ new ObjectId("61e117de7d6a06233f0cace6") │
│ 1 │ 'undefined' │ true │ false │ new ObjectId("61e117de7d6a06233f0cace7") │
│ 2 │ 'new ObjectId()' │ true │ true │ new ObjectId("61e117de7d6a06233f0cace0") │
│ 3 │ 'ObjectId().valueOf()' │ true │ true │ new ObjectId("61e117de7d6a06233f0cace1") │
│ 4 │ 'ObjectId().toString()' │ true │ true │ new ObjectId("61e117de7d6a06233f0cace2") │
│ 5 │ 'ObjectId().toHexString()' │ true │ true │ new ObjectId("61e117de7d6a06233f0cace3") │
│ 6 │ "'Z'.repeat(12)" │ true │ true │ new ObjectId("5a5a5a5a5a5a5a5a5a5a5a5a") │
│ 7 │ "'F'.repeat(24)" │ true │ true │ new ObjectId("ffffffffffffffffffffffff") │
│ 8 │ "'580e0797bb495f0a200e91ad'" │ true │ true │ new ObjectId("580e0797bb495f0a200e91ad") │
│ 9 │ '{ _id: new ObjectId() }' │ true │ false │ 'ERROR' │
│ 10 │ "{ _id: 'Z'.repeat(12) }" │ true │ false │ 'ERROR' │
│ 11 │ "{ _id: 'F'.repeat(24) }" │ true │ false │ 'ERROR' │
│ 12 │ "{ _id: '580e0797bb495f0a200e91ad' }" │ true │ false │ 'ERROR' │
│ 13 │ '{ id: new ObjectId() }' │ false │ false │ 'ERROR' │
│ 14 │ "{ id: 'Z'.repeat(12) }" │ false │ true │ new ObjectId("5a5a5a5a5a5a5a5a5a5a5a5a") │
│ 15 │ "{ id: 'F'.repeat(24) }" │ false │ true │ new ObjectId("ffffffffffffffffffffffff") │
│ 16 │ "{ id: '580e0797bb495f0a200e91ad' }" │ false │ true │ new ObjectId("580e0797bb495f0a200e91ad") │
│ 17 │ '{ id: null }' │ false │ false │ 'ERROR' │
│ 18 │ '{ id: undefined }' │ false │ false │ 'ERROR' │
│ 19 │ '{ _id: null }' │ false │ false │ 'ERROR' │
│ 20 │ '{ _id: undefined }' │ false │ false │ 'ERROR' │
└─────────┴───────────────────────────────────────┴─────────────────┴──────────────────┴──────────────────────────────────────────┘
$ npm ls bson
[email protected] /home/max/dev/mongoose616
└─┬ [email protected]
├── [email protected]
└─┬ [email protected]
└── [email protected] deduped
[email protected] + [email protected]
Note: [email protected] is demonstrated because since [email protected] { id: '580e0797bb495f0a200e91ad' }
is considered a valid ObjectId. See #11209.
You can install this version today using version override in package.json:
"overrides": {
"bson": "4.6.0"
}
┌─────────┬───────────────────────────────────────┬─────────────────┬──────────────────┬──────────────────────────────────────────┐
│ (index) │ test │ isValidObjectId │ ObjectId.isValid │ new ObjectId │
├─────────┼───────────────────────────────────────┼─────────────────┼──────────────────┼──────────────────────────────────────────┤
│ 0 │ 'null' │ true │ false │ new ObjectId("61e131d6bee71a108f689f2f") │
│ 1 │ 'undefined' │ true │ false │ new ObjectId("61e131d6bee71a108f689f30") │
│ 2 │ 'new ObjectId()' │ true │ true │ new ObjectId("61e131d6bee71a108f689f29") │
│ 3 │ 'ObjectId().valueOf()' │ true │ true │ new ObjectId("61e131d6bee71a108f689f2a") │
│ 4 │ 'ObjectId().toString()' │ true │ true │ new ObjectId("61e131d6bee71a108f689f2b") │
│ 5 │ 'ObjectId().toHexString()' │ true │ true │ new ObjectId("61e131d6bee71a108f689f2c") │
│ 6 │ "'Z'.repeat(12)" │ true │ true │ new ObjectId("5a5a5a5a5a5a5a5a5a5a5a5a") │
│ 7 │ "'F'.repeat(24)" │ true │ true │ new ObjectId("ffffffffffffffffffffffff") │
│ 8 │ "'580e0797bb495f0a200e91ad'" │ true │ true │ new ObjectId("580e0797bb495f0a200e91ad") │
│ 9 │ '{ _id: new ObjectId() }' │ true │ false │ 'ERROR' │
│ 10 │ "{ _id: 'Z'.repeat(12) }" │ true │ false │ 'ERROR' │
│ 11 │ "{ _id: 'F'.repeat(24) }" │ true │ false │ 'ERROR' │
│ 12 │ "{ _id: '580e0797bb495f0a200e91ad' }" │ true │ false │ 'ERROR' │
│ 13 │ '{ id: new ObjectId() }' │ false │ false │ 'ERROR' │
│ 14 │ "{ id: 'Z'.repeat(12) }" │ false │ false │ new ObjectId("5a5a5a5a5a5a5a5a5a5a5a5a") │
│ 15 │ "{ id: 'F'.repeat(24) }" │ false │ false │ new ObjectId("ffffffffffffffffffffffff") │
│ 16 │ "{ id: '580e0797bb495f0a200e91ad' }" │ false │ false │ new ObjectId("580e0797bb495f0a200e91ad") │
│ 17 │ '{ id: null }' │ false │ false │ 'ERROR' │
│ 18 │ '{ id: undefined }' │ false │ false │ 'ERROR' │
│ 19 │ '{ _id: null }' │ false │ false │ 'ERROR' │
│ 20 │ '{ _id: undefined }' │ false │ false │ 'ERROR' │
└─────────┴───────────────────────────────────────┴─────────────────┴──────────────────┴──────────────────────────────────────────┘
$ npm ls bson
[email protected] /home/max/dev/mongoose615bson460
└─┬ [email protected]
├── [email protected]
└─┬ [email protected]
└── [email protected] deduped
[email protected] + [email protected]
And some old versions (precisely from the codebase I am migrating now)
┌─────────┬───────────────────────────────────────┬─────────────────┬──────────────────┬──────────────────────────┐
│ (index) │ test │ isValidObjectId │ ObjectId.isValid │ new ObjectId │
├─────────┼───────────────────────────────────────┼─────────────────┼──────────────────┼──────────────────────────┤
│ 0 │ 'null' │ true │ false │ 61e13246a66c1202500f2c67 │
│ 1 │ 'undefined' │ true │ false │ 61e13246a66c1202500f2c68 │
│ 2 │ 'new ObjectId()' │ true │ true │ 61e13246a66c1202500f2c61 │
│ 3 │ 'ObjectId().valueOf()' │ true │ true │ 61e13246a66c1202500f2c62 │
│ 4 │ 'ObjectId().toString()' │ true │ true │ 61e13246a66c1202500f2c63 │
│ 5 │ 'ObjectId().toHexString()' │ true │ true │ 61e13246a66c1202500f2c64 │
│ 6 │ "'Z'.repeat(12)" │ true │ true │ 5a5a5a5a5a5a5a5a5a5a5a5a │
│ 7 │ "'F'.repeat(24)" │ false │ true │ ffffffffffffffffffffffff │
│ 8 │ "'580e0797bb495f0a200e91ad'" │ true │ true │ 580e0797bb495f0a200e91ad │
│ 9 │ '{ _id: new ObjectId() }' │ true │ false │ 'ERROR' │
│ 10 │ "{ _id: 'Z'.repeat(12) }" │ true │ false │ 'ERROR' │
│ 11 │ "{ _id: 'F'.repeat(24) }" │ false │ false │ 'ERROR' │
│ 12 │ "{ _id: '580e0797bb495f0a200e91ad' }" │ true │ false │ 'ERROR' │
│ 13 │ '{ id: new ObjectId() }' │ false │ false │ 'ERROR' │
│ 14 │ "{ id: 'Z'.repeat(12) }" │ false │ false │ 'ERROR' │
│ 15 │ "{ id: 'F'.repeat(24) }" │ false │ false │ 'ERROR' │
│ 16 │ "{ id: '580e0797bb495f0a200e91ad' }" │ false │ false │ 'ERROR' │
│ 17 │ '{ id: null }' │ false │ false │ 'ERROR' │
│ 18 │ '{ id: undefined }' │ false │ false │ 'ERROR' │
│ 19 │ '{ _id: null }' │ false │ false │ 'ERROR' │
│ 20 │ '{ _id: undefined }' │ false │ false │ 'ERROR' │
└─────────┴───────────────────────────────────────┴─────────────────┴──────────────────┴──────────────────────────┘
$ npm ls bson
[email protected] /home/max/dev/mongoose5123bson114
└─┬ [email protected]
├── [email protected] deduped
└─┬ [email protected]
└── [email protected] deduped
You can see how much has changed from those "old times".
Notable changes between versions
┌─────────┬───────────────────────────────────────┬─────────────────┬──────────────────┬──────────────────────────────────────────┐
│ (index) │ test │ isValidObjectId │ ObjectId.isValid │ new ObjectId │
├─────────┼───────────────────────────────────────┼─────────────────┼──────────────────┼──────────────────────────────────────────┤
[email protected] [email protected]:
│ 16 │ "{ id: '580e0797bb495f0a200e91ad' }" │ false │ true │ new ObjectId("580e0797bb495f0a200e91ad") │
[email protected] [email protected]:
│ 16 │ "{ id: '580e0797bb495f0a200e91ad' }" │ false │ false │ new ObjectId("580e0797bb495f0a200e91ad") │
[email protected] [email protected]:
│ 16 │ "{ id: '580e0797bb495f0a200e91ad' }" │ false │ false │ 'ERROR' │
┌─────────┬───────────────────────────────────────┬─────────────────┬──────────────────┬──────────────────────────────────────────┐
│ (index) │ test │ isValidObjectId │ ObjectId.isValid │ new ObjectId │
├─────────┼───────────────────────────────────────┼─────────────────┼──────────────────┼──────────────────────────────────────────┤
[email protected] [email protected], 4.6.1:
│ 7 │ "'F'.repeat(24)" │ true │ true │ new ObjectId("ffffffffffffffffffffffff") │
│ 11 │ "{ _id: 'F'.repeat(24) }" │ true │ false │ 'ERROR' │
[email protected] [email protected]:
│ 7 │ "'F'.repeat(24)" │ false │ true │ ffffffffffffffffffffffff │
│ 11 │ "{ _id: 'F'.repeat(24) }" │ false │ false │ 'ERROR' │
I can't help quoting a famous anecdote (translated):
A letter to the Balabanov Match Factory:
"I've been counting matches in your boxes for 11 years - there are 59, then 60, and sometimes 58. Are you all crazy there???"
MongoDB and mongoose seem to be mature products. And having a long history of changes/bugs with such base entity as ObjectId (regarding what is a valid ObjectId) - is appalling to say the least.