From 6447b9718b37e52c4df5c4bce8016e836fac0493 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Sun, 1 Oct 2023 17:27:22 -0400 Subject: [PATCH] fix(document): call pre('validate') hooks when modifying a path underneath triply nested subdoc Fix #13876 --- lib/document.js | 11 +++++++++-- test/document.test.js | 21 +++++++++++++++++++++ test/query.middleware.test.js | 2 +- 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/lib/document.js b/lib/document.js index ac08b78adb8..6741955cb97 100644 --- a/lib/document.js +++ b/lib/document.js @@ -1694,6 +1694,13 @@ Document.prototype.$__set = function(pathToMark, path, options, constructing, pa if (last) { if (obj instanceof Map) { obj.set(parts[i], val); + } else if (obj.$isSingleNested) { + if (!(parts[i] in obj)) { + obj[parts[i]] = val; + obj._doc[parts[i]] = val; + } else { + obj._doc[parts[i]] = val; + } } else { obj[parts[i]] = val; } @@ -1705,7 +1712,7 @@ Document.prototype.$__set = function(pathToMark, path, options, constructing, pa } else if (value && value instanceof Embedded) { obj = value; } else if (value && !Array.isArray(value) && value.$isSingleNested) { - obj = value._doc; + obj = value; } else if (value && Array.isArray(value)) { obj = value; } else if (value == null) { @@ -2650,7 +2657,7 @@ function _getPathsToValidate(doc, pathsToValidate, pathsToSkip) { const modifiedPaths = doc.modifiedPaths(); for (const subdoc of subdocs) { if (subdoc.$basePath) { - const fullPathToSubdoc = subdoc.$__fullPathWithIndexes(); + const fullPathToSubdoc = subdoc.$isSingleNested ? subdoc.$__pathRelativeToParent() : subdoc.$__fullPathWithIndexes(); // Remove child paths for now, because we'll be validating the whole // subdoc. diff --git a/test/document.test.js b/test/document.test.js index 1a0ba31721f..2a39069e4b8 100644 --- a/test/document.test.js +++ b/test/document.test.js @@ -12212,6 +12212,8 @@ describe('document', function() { const x = new Test(); x.set('d.x.y', 1); + assert.strictEqual(x.d.x.y, 1); + assert.deepStrictEqual(x.get('d.x'), { y: 1 }); assert.strictEqual(x.get('d.x.y'), 1); await x.save(); @@ -12527,6 +12529,25 @@ describe('document', function() { assert.strictEqual(parent.get('child.concreteProp'), 123); assert.strictEqual(parent.toObject().child.concreteProp, 123); }); + + it('fires pre validate hooks on 4 level single nested subdocs (gh-13876)', async function() { + let attachmentSchemaPreValidateCalls = 0; + const attachmentSchema = new Schema({ name: String }); + attachmentSchema.pre('validate', () => { ++attachmentSchemaPreValidateCalls; }); + + const richImageSchema = new Schema({ attachment: { type: attachmentSchema, required: false } }); + const brandingSchema = new Schema({ logo: richImageSchema }); + const instanceSchema = new Schema({ branding: brandingSchema }); + const TestModel = db.model('Test', instanceSchema); + + const instance = await TestModel.create({ branding: { logo: {} } }); + assert.strictEqual(attachmentSchemaPreValidateCalls, 0); + const doc = await TestModel.findById(instance._id); + + doc.set('branding.logo.attachment', { name: 'coolLogo' }); + await doc.save(); + assert.strictEqual(attachmentSchemaPreValidateCalls, 1); + }); }); describe('Check if instance function that is supplied in schema option is availabe', function() { diff --git a/test/query.middleware.test.js b/test/query.middleware.test.js index 596d4027a99..48c889e98f2 100644 --- a/test/query.middleware.test.js +++ b/test/query.middleware.test.js @@ -359,7 +359,7 @@ describe('query middleware', function() { assert.equal(postCount, 1); }); - it('error handlers XYZ (gh-2284)', async function() { + it('error handlers (gh-2284)', async function() { const testSchema = new Schema({ title: { type: String, unique: true } }); testSchema.post('updateOne', function(error, res, next) {