Skip to content

Commit c7a9eb6

Browse files
committed
fix(document): avoid unmarking modified on nested path if no initial value stored and already modified
Fix #14022
1 parent be441a0 commit c7a9eb6

File tree

2 files changed

+47
-1
lines changed

2 files changed

+47
-1
lines changed

lib/document.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1204,6 +1204,7 @@ Document.prototype.$set = function $set(path, val, type, options) {
12041204
this.invalidate(path, new MongooseError.CastError('Object', val, path));
12051205
return this;
12061206
}
1207+
const wasModified = this.$isModified(path);
12071208
const hasInitialVal = this.$__.savedState != null && this.$__.savedState.hasOwnProperty(path);
12081209
if (this.$__.savedState != null && !this.$isNew && !this.$__.savedState.hasOwnProperty(path)) {
12091210
const initialVal = this.$__getValue(path);
@@ -1228,7 +1229,9 @@ Document.prototype.$set = function $set(path, val, type, options) {
12281229
for (const key of keys) {
12291230
this.$set(path + '.' + key, val[key], constructing, options);
12301231
}
1231-
if (priorVal != null && utils.deepEqual(hasInitialVal ? this.$__.savedState[path] : priorVal, val)) {
1232+
if (priorVal != null &&
1233+
(!wasModified || hasInitialVal) &&
1234+
utils.deepEqual(hasInitialVal ? this.$__.savedState[path] : priorVal, val)) {
12321235
this.unmarkModified(path);
12331236
} else {
12341237
this.markModified(path);

test/document.test.js

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9239,6 +9239,49 @@ describe('document', function() {
92399239
assert.ok(foo.isModified('subdoc.bar'));
92409240
});
92419241

9242+
it('does not unmark modified if there is no initial value (gh-9396)', async function() {
9243+
const IClientSchema = new Schema({
9244+
jwt: {
9245+
token_crypt: { type: String, template: false, maxSize: 8 * 1024 },
9246+
token_salt: { type: String, template: false }
9247+
}
9248+
});
9249+
9250+
const encrypt = function(doc, path, value) {
9251+
doc.set(path + '_crypt', value + '_crypt');
9252+
doc.set(path + '_salt', value + '_salt');
9253+
};
9254+
9255+
const decrypt = function(doc, path) {
9256+
return doc.get(path + '_crypt').replace('_crypt', '');
9257+
};
9258+
9259+
IClientSchema.virtual('jwt.token')
9260+
.get(function() {
9261+
return decrypt(this, 'jwt.token');
9262+
})
9263+
.set(function(value) {
9264+
encrypt(this, 'jwt.token', value);
9265+
});
9266+
9267+
9268+
const iclient = db.model('Test', IClientSchema);
9269+
const test = new iclient({
9270+
jwt: {
9271+
token: 'firstToken'
9272+
}
9273+
});
9274+
9275+
await test.save();
9276+
const entry = await iclient.findById(test._id).orFail();
9277+
entry.set('jwt.token', 'secondToken');
9278+
entry.set(entry.toJSON());
9279+
await entry.save();
9280+
9281+
const { jwt } = await iclient.findById(test._id).orFail();
9282+
assert.strictEqual(jwt.token, 'secondToken');
9283+
});
9284+
92429285
it('correctly tracks saved state for deeply nested objects (gh-10773) (gh-9396)', async function() {
92439286
const PaymentSchema = Schema({ status: String }, { _id: false });
92449287
const OrderSchema = new Schema({

0 commit comments

Comments
 (0)