diff --git a/lib/collection.js b/lib/collection.js index e5d457c..09e6384 100644 --- a/lib/collection.js +++ b/lib/collection.js @@ -290,7 +290,76 @@ module.exports = function Collection(db, state) { } }, rename: NotImplemented, - replaceOne: NotImplemented, + replaceOne: function (selector, data, options, callback) { + callback = arguments[arguments.length - 1]; + if (typeof options !== 'object') options = {}; + + var opResult = { + result: { + ok: 1, + nModified: 0, + n: 0 + }, + connection: db, + matchedCount: 0, + modifiedCount: 0, + upsertedCount: 0, + upsertedId: null + }; + var action = (options.upsert ? "upsert: " : "replace: "); + debug('%s.%s %j', name, action, selector); + + asyncish(function () { + var docs = first(selector, state.documents || []) || []; + if (!Array.isArray(docs)) docs = [docs]; + debug('%s.%s %j', name, action, docs); + + + if(!docs.length && options.upsert) { + var cloneData = upsertClone(selector, data); + var cloned = _.cloneDeepWith(cloneData, cloneObjectIDs); + cloned._id = cloned._id || pk(); + + debug('%s.%s checking for index conflict', name, action); + var conflict = state.findConflict(cloned); + if (conflict) { + debug('conflict found %j', conflict); + return callback(conflict); + } + + if (!state.documents) state.documents = [cloned]; + else state.documents.push(cloned); + + opResult.matchedCount = opResult.result.n = 1; + opResult.upsertedCount = opResult.result.nModified = 1; + opResult.upsertedId = { _id: cloned._id }; + } + else if (docs.length > 0) { + debug('%s.%s modifying doc', name, action); + debug('%s.%s %j', name, action, docs); + Object.keys(docs[0]).forEach(function (k) { + if (k === "_id") { + return; + } + _.unset(docs[0], k); + }); + modify(docs[0], data, state); + debug('%s.%s %j', name, action, docs); + opResult.matchedCount = opResult.result.n = docs.length; + opResult.modifiedCount = opResult.result.nModified = docs.length; + opResult.upsertedId = docs[0]._id; + } + + state.persist(); + callback(null, opResult); + }); + + if (typeof callback !== 'function') { + return new Promise(function (resolve, reject) { + callback = function (e, r) { e ? reject(e) : resolve(r) }; + }) + } + }, save: function (doc, options, callback) { callback = arguments[arguments.length-1]; if(typeof options!=='object') options = {}; diff --git a/test/mock.test.js b/test/mock.test.js index e55cdde..6ff1c27 100644 --- a/test/mock.test.js +++ b/test/mock.test.js @@ -342,6 +342,99 @@ describe('mock tests', function () { }); }) + it('should NOT replace one (replaceOne) with no document found', function (done) { + //query, data, options, callback + collection.replaceOne({ test: 1456 }, { test: 1456, b: false, alsoDeep: { deepC: 'C' } }, function (err, opResult) { + if(err) return done(err); + opResult.result.n.should.equal(0); + + collection.findOne({ test:1456 }, function (err, doc) { + if(err) return done(err); + (!!doc).should.be.false; + done(); + }); + }); + }); + + it('should replace one (replaceOne) with matching document found', function (done) { + //query, data, options, callback + collection.insertOne({ test: 1457, a: true, deep: { bar: "buzz", fang: "dang" } }, function (insertErr, insertResult) { + if(insertErr) return done(insertErr); + insertResult.result.n.should.equal(1); + + collection.replaceOne({ test: 1457 }, { test: 1457, b: false, alsoDeep: { deepC: 'C' } }, function (err, opResult) { + if(err) return done(err); + opResult.result.n.should.equal(1); + + collection.findOne({ test: 1457 }, function (err, doc) { + if(err) return done(err); + (!!doc).should.be.true; + doc.should.not.have.property('a'); + doc.should.not.have.property('deep'); + doc.should.have.property('b', false); + doc.should.have.property('alsoDeep'); + doc.alsoDeep.should.have.property('deepC', 'C'); + + collection.remove({ test: 1457 }, function (errRemove) { + if (errRemove) return done(errRemove); + done(); + }); + }); + }); + }); + }); + + it('should replace one (replaceOne) with upsert and matching document found', function (done) { + //query, data, options, callback + collection.insertOne({ test:1458, a: true, deep: { bar: "buzz", fang: "dang" } }, function (insertErr, insertResult) { + if(insertErr) return done(insertErr); + insertResult.result.n.should.equal(1); + + collection.replaceOne({ test: 1458 }, { test: 1458, b: false, alsoDeep: { deepC: 'C' } }, { upsert: true }, function (err, opResult) { + if(err) return done(err); + opResult.result.n.should.equal(1); + + collection.findOne({ test: 1458 }, function (err, doc) { + if(err) return done(err); + (!!doc).should.be.true; + doc.should.not.have.property('a'); + doc.should.not.have.property('deep'); + doc.should.have.property('b', false); + doc.should.have.property('alsoDeep'); + doc.alsoDeep.should.have.property('deepC', 'C'); + + collection.remove({ test: 1458 }, function (errRemove) { + if (errRemove) return done(errRemove); + done(); + }); + }); + }); + }); + }); + + it('should replace one (replaceOne) with upsert and no document found', function (done) { + //query, data, options, callback + collection.replaceOne({ test: 1459 }, { test: 1459, b: false, alsoDeep: { deepC: 'C' } }, { upsert: true }, function (err, opResult) { + if(err) return done(err); + opResult.result.n.should.equal(1); + + collection.findOne({ test:1459 }, function (err, doc) { + if(err) return done(err); + (!!doc).should.be.true; + doc.should.not.have.property('a'); + doc.should.not.have.property('deep'); + doc.should.have.property('b', false); + doc.should.have.property('alsoDeep'); + doc.alsoDeep.should.have.property('deepC', 'C'); + + collection.remove({ test: 1459 }, function (errRemove) { + if (errRemove) return done(errRemove); + done(); + }); + }); + }); + }); + it('should update one (updateOne)', function (done) { //query, data, options, callback collection.updateOne({test:123}, { $set: { foo: { bar: "buzz", fang: "dang" } } }, function (err, opResult) {