diff --git a/lib/collection.js b/lib/collection.js index be4eca1e38..ff5eca7fb6 100644 --- a/lib/collection.js +++ b/lib/collection.js @@ -1515,6 +1515,13 @@ Collection.prototype.findOneAndReplace = function(filter, replacement, options, if (replacement == null || typeof replacement !== 'object') throw toError('replacement parameter must be an object'); + // Check that there are no atomic operators + const keys = Object.keys(replacement); + + if (keys[0] && keys[0][0] === '$') { + throw toError('The replacement document must not contain atomic operators.'); + } + return executeOperation(this.s.topology, findOneAndReplace, [ this, filter, diff --git a/test/functional/collection_tests.js b/test/functional/collection_tests.js index dd06ac44e6..58c3bbf6dc 100644 --- a/test/functional/collection_tests.js +++ b/test/functional/collection_tests.js @@ -1898,4 +1898,24 @@ describe('Collection', function() { } }); }); + + it('should allow an empty replacement document for findOneAndReplace', function() { + const configuration = this.configuration; + const client = configuration.newClient({}, { w: 1 }); + + client.connect((err, client) => { + expect(err).to.be.null; + + const db = client.db(configuration.db); + const collection = db.collection('find_one_and_replace'); + + collection.insertOne({ a: 1 }, err => { + expect(err).to.be.null; + + expect(collection.findOneAndReplace.bind(collection, { a: 1 }, {})).to.not.throw(); + }); + + client.close(); + }); + }); }); diff --git a/test/unit/collection_tests.js b/test/unit/collection_tests.js new file mode 100644 index 0000000000..7e7d609f08 --- /dev/null +++ b/test/unit/collection_tests.js @@ -0,0 +1,32 @@ +'use strict'; + +const EventEmitter = require('events'); +const chai = require('chai'); +const expect = chai.expect; +const Db = require('../../lib/db'); + +class MockTopology extends EventEmitter { + constructor() { + super(); + } + + capabilities() { + return {}; + } +} + +describe('Collection', function() { + /** + * @ignore + */ + it('should not allow atomic operators for findOneAndReplace', { + metadata: { requires: { topology: 'single' } }, + test: function() { + const db = new Db('fakeDb', new MockTopology()); + const collection = db.collection('test'); + expect(() => { + collection.findOneAndReplace({ a: 1 }, { $set: { a: 14 } }); + }).to.throw('The replacement document must not contain atomic operators.'); + } + }); +});