From 3491a3b156906053ba5c6d372ea28901e86e3b98 Mon Sep 17 00:00:00 2001 From: Alec Gibson <12036746+alecgibson@users.noreply.github.com> Date: Tue, 14 May 2024 16:25:28 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=85=20Allow=20deleting=20ops=20in=20`Memo?= =?UTF-8?q?ryDb`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It can be useful to delete ops from `MemoryDb` to test how a system will act with TTLed ops. This change: - adds a `MemoryDb.deleteOps()` helper method - adapts `MemoryDb.getOps()` to filter out deleted ops, and check that the correct number of ops are being returned, and error if not (similar to [`sharedb-mongo`][1]) [1]: https://github.com/share/sharedb-mongo/blob/0013d15df717ac8af32aa7847ed64bca96be7f66/index.js#L550 --- lib/db/memory.js | 20 +++++- test/client/snapshot-version-request.js | 2 +- test/db-memory.js | 81 +++++++++++++++++++++---- 3 files changed, 87 insertions(+), 16 deletions(-) diff --git a/lib/db/memory.js b/lib/db/memory.js index 568c52aa8..2db274d6e 100644 --- a/lib/db/memory.js +++ b/lib/db/memory.js @@ -84,10 +84,12 @@ MemoryDB.prototype.getOps = function(collection, id, from, to, options, callback if (typeof callback !== 'function') throw new Error('Callback required'); util.nextTick(function() { var opLog = db._getOpLogSync(collection, id); - if (to == null) { - to = opLog.length; + if (!from) from = 0; + if (to == null) to = opLog.length; + var ops = clone(opLog.slice(from, to).filter(Boolean)); + if (ops.length < to - from) { + return callback(new Error('Missing ops')); } - var ops = clone(opLog.slice(from, to)); if (!includeMetadata) { for (var i = 0; i < ops.length; i++) { delete ops[i].m; @@ -97,6 +99,18 @@ MemoryDB.prototype.getOps = function(collection, id, from, to, options, callback }); }; +MemoryDB.prototype.deleteOps = function(collection, id, from, to, options, callback) { + if (typeof callback !== 'function') throw new Error('Callback required'); + var db = this; + util.nextTick(function() { + var opLog = db._getOpLogSync(collection, id); + if (!from) from = 0; + if (to == null) to = opLog.length; + for (var i = from; i < to; i++) opLog[i] = null; + callback(null); + }); +}; + // The memory database query function returns all documents in a collection // regardless of query by default MemoryDB.prototype.query = function(collection, query, fields, options, callback) { diff --git a/test/client/snapshot-version-request.js b/test/client/snapshot-version-request.js index ae3f2ce72..9f60bf910 100644 --- a/test/client/snapshot-version-request.js +++ b/test/client/snapshot-version-request.js @@ -144,7 +144,7 @@ describe('SnapshotVersionRequest', function() { it('errors if asking for a version that does not exist', function(done) { backend.connect().fetchSnapshot('books', 'don-quixote', 4, function(error, snapshot) { - expect(error.code).to.equal('ERR_OP_VERSION_NEWER_THAN_CURRENT_SNAPSHOT'); + expect(error).to.be.ok; expect(snapshot).to.equal(undefined); done(); }); diff --git a/test/db-memory.js b/test/db-memory.js index e27ce0fea..acd3ad1d2 100644 --- a/test/db-memory.js +++ b/test/db-memory.js @@ -1,6 +1,8 @@ var expect = require('chai').expect; +var Backend = require('../lib/backend'); var DB = require('../lib/db'); var BasicQueryableMemoryDB = require('./BasicQueryableMemoryDB'); +var async = require('async'); describe('DB base class', function() { it('can call db.close() without callback', function() { @@ -54,17 +56,72 @@ describe('DB base class', function() { }); }); -// Run all the DB-based tests against the BasicQueryableMemoryDB. -require('./db')({ - create: function(options, callback) { - if (typeof options === 'function') { - callback = options; - options = null; +describe('MemoryDB', function() { + // Run all the DB-based tests against the BasicQueryableMemoryDB. + require('./db')({ + create: function(options, callback) { + if (typeof options === 'function') { + callback = options; + options = null; + } + var db = new BasicQueryableMemoryDB(options); + callback(null, db); + }, + getQuery: function(options) { + return {filter: options.query, sort: options.sort}; } - var db = new BasicQueryableMemoryDB(options); - callback(null, db); - }, - getQuery: function(options) { - return {filter: options.query, sort: options.sort}; - } + }); + + describe('deleteOps', function() { + describe('with some ops', function() { + var backend; + var db; + var connection; + var doc; + + beforeEach(function(done) { + backend = new Backend(); + db = backend.db; + connection = backend.connect(); + doc = connection.get('dogs', 'fido'); + + async.waterfall([ + doc.create.bind(doc, {name: 'Fido'}), + doc.submitOp.bind(doc, [{p: ['tricks'], oi: ['fetch']}]), + db.getOps.bind(db, 'dogs', 'fido', null, null, null), + function(ops, next) { + expect(ops).to.have.length(2); + next(); + } + ], done); + }); + + it('deletes all ops', function(done) { + async.waterfall([ + db.deleteOps.bind(db, 'dogs', 'fido', null, null, null), + function(next) { + db.getOps('dogs', 'fido', null, null, null, function(error) { + expect(error.message).to.equal('Missing ops'); + next(); + }); + } + ], done); + }); + + it('deletes some ops', function(done) { + async.waterfall([ + db.deleteOps.bind(db, 'dogs', 'fido', 0, 1, null), + db.getOps.bind(db, 'dogs', 'fido', 1, 2, null), + function(ops, next) { + expect(ops).to.have.length(1); + expect(ops[0].op).to.eql([{p: ['tricks'], oi: ['fetch']}]); + db.getOps('dogs', 'fido', 0, 1, null, function(error) { + expect(error.message).to.equal('Missing ops'); + next(); + }); + } + ], done); + }); + }); + }); });