| 'use strict'; |
| |
| var adapters = [ |
| ['local', 'http'], |
| ['http', 'http'], |
| ['http', 'local'], |
| ['local', 'local'] |
| ]; |
| |
| if ('saucelabs' in testUtils.params()) { |
| adapters = [['local', 'http'], ['http', 'local']]; |
| } |
| |
| var downAdapters = ['local']; |
| |
| adapters.forEach(function (adapters) { |
| describe('suite2 test.replication.js-' + adapters[0] + '-' + adapters[1], function () { |
| |
| var dbs = {}; |
| |
| beforeEach(function () { |
| dbs.name = testUtils.adapterUrl(adapters[0], 'testdb'); |
| dbs.remote = testUtils.adapterUrl(adapters[1], 'test_repl_remote'); |
| }); |
| |
| afterEach(function (done) { |
| testUtils.cleanup([dbs.name, dbs.remote], done); |
| }); |
| |
| var docs = [ |
| {_id: '0', integer: 0, string: '0'}, |
| {_id: '1', integer: 1, string: '1'}, |
| {_id: '2', integer: 2, string: '2'} |
| ]; |
| |
| // simplify for easier deep equality checks |
| function simplifyChanges(res) { |
| var changes = res.results.map(function (change) { |
| return { |
| id: change.id, |
| deleted: change.deleted, |
| changes: change.changes.map(function (x) { |
| return x.rev; |
| }).sort(), |
| doc: change.doc |
| }; |
| }); |
| |
| // in CouchDB 2.0, changes is not guaranteed to be |
| // ordered |
| if (testUtils.isCouchMaster()) { |
| changes.sort(function (a, b) { |
| return a.id > b.id; |
| }); |
| } |
| return changes; |
| } |
| |
| function verifyInfo(info, expected) { |
| if (!testUtils.isCouchMaster()) { |
| info.update_seq.should.equal(expected.update_seq, 'update_seq'); |
| } |
| if (info.doc_count) { |
| info.doc_count.should.equal(expected.doc_count, 'doc_count'); |
| } |
| } |
| |
| it('Test basic pull replication', function (done) { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| remote.bulkDocs({ docs: docs }, {}, function () { |
| db.replicate.from(dbs.remote, function (err, result) { |
| result.ok.should.equal(true); |
| result.docs_written.should.equal(docs.length); |
| db.info(function (err, info) { |
| verifyInfo(info, { |
| update_seq: 3, |
| doc_count: 3 |
| }); |
| done(); |
| }); |
| }); |
| }); |
| }); |
| |
| it('Test basic pull replication plain api', function (done) { |
| var remote = new PouchDB(dbs.remote); |
| remote.bulkDocs({ docs: docs }, {}, function () { |
| PouchDB.replicate(dbs.remote, dbs.name, {}, function (err, result) { |
| result.ok.should.equal(true); |
| result.docs_written.should.equal(docs.length); |
| new PouchDB(dbs.name).info(function (err, info) { |
| verifyInfo(info, { |
| update_seq: 3, |
| doc_count: 3 |
| }); |
| done(); |
| }); |
| }); |
| }); |
| }); |
| |
| it('Test basic pull replication plain api 2', function (done) { |
| var remote = new PouchDB(dbs.remote); |
| remote.bulkDocs({ docs: docs }, {}, function () { |
| PouchDB.replicate( |
| dbs.remote, dbs.name).on('complete', function (result) { |
| result.ok.should.equal(true); |
| result.docs_written.should.equal(docs.length); |
| new PouchDB(dbs.name).info(function (err, info) { |
| verifyInfo(info, { |
| update_seq: 3, |
| doc_count: 3 |
| }); |
| done(); |
| }); |
| }); |
| }); |
| }); |
| |
| it('Test pull replication with many changes', function (done) { |
| var remote = new PouchDB(dbs.remote); |
| |
| var numDocs = 201; |
| var docs = []; |
| for (var i = 0; i < numDocs; i++) { |
| docs.push({_id: i.toString()}); |
| } |
| |
| remote.bulkDocs({ docs: docs }, {}, function (err) { |
| should.not.exist(err); |
| PouchDB.replicate( |
| dbs.remote, dbs.name).on('complete', function (result) { |
| result.ok.should.equal(true); |
| result.docs_written.should.equal(docs.length); |
| new PouchDB(dbs.name).info(function (err, info) { |
| verifyInfo(info, { |
| update_seq: numDocs, |
| doc_count: numDocs |
| }); |
| done(); |
| }); |
| }); |
| }); |
| }); |
| |
| it('Test basic pull replication with funny ids', function (done) { |
| var docs = [ |
| {_id: '4/5', integer: 0, string: '0'}, |
| {_id: '3&2', integer: 1, string: '1'}, |
| {_id: '1>0', integer: 2, string: '2'} |
| ]; |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| remote.bulkDocs({docs: docs}, function () { |
| db.replicate.from(dbs.remote, function (err, result) { |
| result.ok.should.equal(true); |
| result.docs_written.should.equal(docs.length); |
| db.info(function (err, info) { |
| verifyInfo(info, { |
| update_seq: 3, |
| doc_count: 3 |
| }); |
| done(); |
| }); |
| }); |
| }); |
| }); |
| |
| it('pull replication with many changes + a conflict (#2543)', function () { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| // simulate 5000 normal commits with two conflicts at the very end |
| function uuid() { |
| return testUtils.rev(); |
| } |
| |
| var numRevs = 5000; |
| var isSafari = (typeof process === 'undefined' || process.browser) && |
| /Safari/.test(window.navigator.userAgent) && |
| !/Chrome/.test(window.navigator.userAgent); |
| if (isSafari) { |
| numRevs = 10; // fuck safari, we've hit the storage limit again |
| } |
| |
| var uuids = []; |
| for (var i = 0; i < numRevs - 1; i++) { |
| uuids.push(uuid()); |
| } |
| var conflict1 = 'a' + uuid(); |
| var conflict2 = 'b' + uuid(); |
| |
| var doc1 = { |
| _id: 'doc', |
| _rev: numRevs + '-' + conflict1, |
| _revisions: { |
| start: numRevs, |
| ids: [conflict1].concat(uuids) |
| } |
| }; |
| var doc2 = { |
| _id: 'doc', |
| _rev: numRevs + '-' + conflict2, |
| _revisions: { |
| start: numRevs, |
| ids: [conflict2].concat(uuids) |
| } |
| }; |
| return remote.bulkDocs([doc1], {new_edits: false}).then(function () { |
| return remote.replicate.to(db); |
| }).then(function (result) { |
| result.ok.should.equal(true); |
| result.docs_written.should.equal(1, 'correct # docs written (1)'); |
| return db.info(); |
| }).then(function (info) { |
| info.doc_count.should.equal(1, 'doc_count'); |
| return db.get('doc', {open_revs: "all"}); |
| }).then(function (doc) { |
| doc[0].ok._id.should.equal("doc"); |
| doc[0].ok._rev.should.equal(doc1._rev); |
| return remote.bulkDocs([doc2], {new_edits: false}); |
| }).then(function () { |
| return remote.replicate.to(db); |
| }).then(function (result) { |
| result.ok.should.equal(true); |
| result.docs_written.should.equal(1, 'correct # docs written (2)'); |
| return db.info(); |
| }).then(function (info) { |
| info.doc_count.should.equal(1, 'doc_count'); |
| return db.get('doc', {open_revs: "all"}); |
| }).then(function (docs) { |
| // order with open_revs is unspecified |
| docs.sort(function (a, b) { |
| return a.ok._rev < b.ok._rev ? -1 : 1; |
| }); |
| docs[0].ok._id.should.equal("doc"); |
| docs[1].ok._id.should.equal("doc"); |
| docs[0].ok._rev.should.equal(doc1._rev); |
| docs[1].ok._rev.should.equal(doc2._rev); |
| }); |
| }); |
| |
| it('issue 2779, undeletion when replicating', function () { |
| if (testUtils.isCouchMaster()) { |
| return true; |
| } |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| var rev; |
| |
| function checkNumRevisions(num) { |
| return db.get('foo', { |
| open_revs: 'all', |
| revs: true |
| }).then(function (fullDocs) { |
| fullDocs[0].ok._revisions.ids.should.have.length(num, |
| 'local is correct'); |
| }).then(function () { |
| return remote.get('foo', { |
| open_revs: 'all', |
| revs: true |
| }); |
| }).then(function (fullDocs) { |
| fullDocs[0].ok._revisions.ids.should.have.length(num, |
| 'remote is correct'); |
| }); |
| } |
| |
| return db.put({_id: 'foo'}).then(function (resp) { |
| rev = resp.rev; |
| return db.replicate.to(remote); |
| }).then(function () { |
| return checkNumRevisions(1); |
| }).then(function () { |
| return db.remove('foo', rev); |
| }).then(function () { |
| return db.replicate.to(remote); |
| }).then(function () { |
| return checkNumRevisions(2); |
| }).then(function () { |
| return db.allDocs({keys: ['foo']}); |
| }).then(function (res) { |
| rev = res.rows[0].value.rev; |
| return db.put({_id: 'foo', _rev: rev}); |
| }).then(function () { |
| return db.replicate.to(remote); |
| }).then(function () { |
| return checkNumRevisions(3); |
| }); |
| }); |
| |
| it('Test pull replication with many conflicts', function (done) { |
| var remote = new PouchDB(dbs.remote); |
| |
| var numRevs = 200; // repro "url too long" error with open_revs |
| var docs = []; |
| for (var i = 0; i < numRevs; i++) { |
| var rev = '1-' + testUtils.rev(); |
| docs.push({_id: 'doc', _rev: rev}); |
| } |
| |
| remote.bulkDocs({ docs: docs }, {new_edits: false}, function (err) { |
| should.not.exist(err); |
| PouchDB.replicate( |
| dbs.remote, dbs.name).on('complete', function (result) { |
| result.ok.should.equal(true); |
| result.docs_written.should.equal(docs.length); |
| var db = new PouchDB(dbs.name); |
| db.info(function (err, info) { |
| should.not.exist(err); |
| info.doc_count.should.equal(1, 'doc_count'); |
| db.get('doc', {open_revs: "all"}, function (err, docs) { |
| should.not.exist(err); |
| var okDocs = docs.filter(function (doc) { return doc.ok; }); |
| okDocs.should.have.length(numRevs); |
| done(); |
| }); |
| }); |
| }); |
| }); |
| }); |
| |
| it('Test correct # docs replicated with staggered revs', function (done) { |
| // ensure we don't just process all the open_revs with |
| // every replication; we should only process the current subset |
| var remote = new PouchDB(dbs.remote); |
| |
| var docs = [{_id: 'doc', _rev: '1-a'}, {_id: 'doc', _rev: '1-b'}]; |
| remote.bulkDocs({ docs: docs }, {new_edits: false}, function (err) { |
| should.not.exist(err); |
| PouchDB.replicate( |
| dbs.remote, dbs.name).on('complete', function (result) { |
| result.ok.should.equal(true); |
| result.docs_written.should.equal(2); |
| result.docs_read.should.equal(2); |
| var docs = [{_id: 'doc', _rev: '1-c'}, {_id: 'doc', _rev: '1-d'}]; |
| remote.bulkDocs({ docs: docs }, {new_edits: false}, function (err) { |
| should.not.exist(err); |
| PouchDB.replicate( |
| dbs.remote, dbs.name).on('complete', function (result) { |
| result.docs_written.should.equal(2); |
| result.docs_read.should.equal(2); |
| var db = new PouchDB(dbs.name); |
| db.info(function (err, info) { |
| should.not.exist(err); |
| info.doc_count.should.equal(1, 'doc_count'); |
| db.get('doc', {open_revs: "all"}, function (err, docs) { |
| should.not.exist(err); |
| var okDocs = docs.filter(function (doc) { |
| return doc.ok; |
| }); |
| okDocs.should.have.length(4); |
| done(); |
| }); |
| }); |
| }).on('error', done); |
| }); |
| }).on('error', done); |
| }); |
| }); |
| |
| it('Local DB contains documents', function (done) { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| remote.bulkDocs({ docs: docs }, {}, function () { |
| db.bulkDocs({ docs: docs }, {}, function () { |
| db.replicate.from(dbs.remote, function () { |
| db.allDocs(function (err, result) { |
| result.rows.length.should.equal(docs.length); |
| db.info(function (err, info) { |
| if (!testUtils.isCouchMaster()) { |
| info.update_seq.should.be.above(2, 'update_seq local'); |
| } |
| info.doc_count.should.equal(3, 'doc_count local'); |
| remote.info(function (err, info) { |
| if (!testUtils.isCouchMaster()) { |
| info.update_seq.should.be.above(2, 'update_seq remote'); |
| } |
| info.doc_count.should.equal(3, 'doc_count remote'); |
| done(); |
| }); |
| }); |
| }); |
| }); |
| }); |
| }); |
| }); |
| |
| it('Test basic push replication', function (done) { |
| var db = new PouchDB(dbs.name); |
| db.bulkDocs({ docs: docs }, {}, function () { |
| db.replicate.to(dbs.remote, function (err, result) { |
| result.ok.should.equal(true); |
| result.docs_written.should.equal(docs.length); |
| db.info(function (err, info) { |
| verifyInfo(info, { |
| update_seq: 3, |
| doc_count: 3 |
| }); |
| done(); |
| }); |
| }); |
| }); |
| }); |
| |
| it('Test basic push replication take 2', function (done) { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| db.bulkDocs({ docs: docs }, {}, function () { |
| db.replicate.to(dbs.remote, function () { |
| remote.allDocs(function (err, result) { |
| result.rows.length.should.equal(docs.length); |
| db.info(function (err, info) { |
| verifyInfo(info, { |
| update_seq: 3, |
| doc_count: 3 |
| }); |
| done(); |
| }); |
| }); |
| }); |
| }); |
| }); |
| |
| it('Test basic push replication sequence tracking', function (done) { |
| var db = new PouchDB(dbs.name); |
| var doc1 = {_id: 'adoc', foo: 'bar'}; |
| db.put(doc1, function () { |
| db.replicate.to(dbs.remote, function (err, result) { |
| result.docs_read.should.equal(1); |
| db.replicate.to(dbs.remote, function (err, result) { |
| result.docs_read.should.equal(0); |
| db.replicate.to(dbs.remote, function (err, result) { |
| result.docs_read.should.equal(0); |
| db.info(function (err, info) { |
| verifyInfo(info, { |
| update_seq: 1, |
| doc_count: 1 |
| }); |
| done(); |
| }); |
| }); |
| }); |
| }); |
| }); |
| }); |
| |
| it('Test checkpoint', function (done) { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| remote.bulkDocs({ docs: docs }, {}, function () { |
| db.replicate.from(dbs.remote, function (err, result) { |
| result.ok.should.equal(true); |
| result.docs_written.should.equal(docs.length); |
| db.replicate.from(dbs.remote, function (err, result) { |
| result.ok.should.equal(true); |
| result.docs_written.should.equal(0); |
| result.docs_read.should.equal(0); |
| db.info(function (err, info) { |
| verifyInfo(info, { |
| update_seq: 3, |
| doc_count: 3 |
| }); |
| done(); |
| }); |
| }); |
| }); |
| }); |
| }); |
| |
| it('Test live pull checkpoint', function (done) { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| remote.bulkDocs({ docs: docs }).then(function () { |
| var changeCount = docs.length; |
| var changes = db.changes({ |
| live: true |
| }).on('change', function () { |
| if (--changeCount) { |
| return; |
| } |
| replication.cancel(); |
| changes.cancel(); |
| }).on('complete', function () { |
| db.replicate.from(dbs.remote).on('complete', function (details) { |
| details.docs_read.should.equal(0); |
| db.info(function (err, info) { |
| verifyInfo(info, { |
| update_seq: 3, |
| doc_count: 3 |
| }); |
| done(); |
| }); |
| }); |
| }).on('error', done); |
| var replication = db.replicate.from(remote, { live: true }); |
| }); |
| }); |
| |
| it('Test type of progress values', function () { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| return remote.bulkDocs({docs: docs}).then(function () { |
| var sync = db.replicate.from(remote); |
| sync.on('change', function (c) { |
| c.start_time.should.be.a('string'); |
| }); |
| return sync; |
| }).then(function (c) { |
| c.start_time.should.be.a('string'); |
| c.end_time.should.be.a('string'); |
| }); |
| }); |
| |
| it('Test live push checkpoint', function (done) { |
| |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| |
| function complete(details) { |
| details.docs_read.should.equal(0); |
| db.info(function (err, info) { |
| verifyInfo(info, { |
| update_seq: 3, |
| doc_count: 3 |
| }); |
| done(); |
| }); |
| } |
| |
| var finished = 0; |
| function isFinished() { |
| if (++finished !== 2) { |
| return; |
| } |
| db.replicate.to(dbs.remote) |
| .on('error', done) |
| .on('complete', complete); |
| } |
| |
| db.bulkDocs({ docs: docs }).then(function () { |
| |
| var changeCount = docs.length; |
| function onChange() { |
| if (--changeCount) { |
| return; |
| } |
| replication.cancel(); |
| changes.cancel(); |
| } |
| |
| var changes = remote.changes({live: true}) |
| .on('error', done) |
| .on('change', onChange) |
| .on('complete', isFinished); |
| |
| var replication = db.replicate.to(remote, {live: true}) |
| .on('error', done) |
| .on('complete', isFinished); |
| |
| }).catch(done); |
| }); |
| |
| it('Test checkpoint 2', function (done) { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| var doc = {_id: '3', count: 0}; |
| remote.put(doc, {}, function (err, results) { |
| db.replicate.from(dbs.remote, function (err, result) { |
| result.ok.should.equal(true); |
| doc._rev = results.rev; |
| doc.count++; |
| remote.put(doc, {}, function (err, results) { |
| doc._rev = results.rev; |
| doc.count++; |
| remote.put(doc, {}, function () { |
| db.replicate.from(dbs.remote, function (err, result) { |
| result.ok.should.equal(true); |
| result.docs_written.should.equal(1); |
| db.info(function (err, info) { |
| verifyInfo(info, { |
| update_seq: 2, |
| doc_count: 1 |
| }); |
| done(); |
| }); |
| }); |
| }); |
| }); |
| }); |
| }); |
| }); |
| |
| it('Test checkpoint 3 :)', function (done) { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| var doc = {_id: '3', count: 0}; |
| db.put(doc, {}, function (err, results) { |
| PouchDB.replicate(db, remote, {}, function (err, result) { |
| result.ok.should.equal(true); |
| doc._rev = results.rev; |
| doc.count++; |
| db.put(doc, {}, function (err, results) { |
| doc._rev = results.rev; |
| doc.count++; |
| db.put(doc, {}, function () { |
| PouchDB.replicate(db, remote, {}, function (err, result) { |
| result.ok.should.equal(true); |
| result.docs_written.should.equal(1); |
| db.info(function (err, info) { |
| verifyInfo(info, { |
| update_seq: 3, |
| doc_count: 1 |
| }); |
| done(); |
| }); |
| }); |
| }); |
| }); |
| }); |
| }); |
| }); |
| |
| it('Test disable checkpoints on both source and target', function (done) { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| |
| db.bulkDocs({ docs: docs }).then(function () { |
| PouchDB.replicate(db, remote, { checkpoint: false }) |
| .on('error', done) |
| .on('complete', function () { |
| testUtils.generateReplicationId(db, remote, {}).then(function (replicationId) { |
| ensureCheckpointIsMissing(db, replicationId) |
| .then(function () { |
| return ensureCheckpointIsMissing(remote, replicationId); |
| }) |
| .then(done) |
| .catch(done); |
| }).catch(done); |
| }); |
| }).catch(done); |
| |
| function ensureCheckpointIsMissing(db, replicationId) { |
| return db.get(replicationId).then(function () { |
| throw new Error('Found a checkpoint that should not exist for db ' + db.name); |
| }).catch(function (error) { |
| if (error.status === 404) { |
| return; |
| } else { |
| throw error; |
| } |
| }); |
| } |
| }); |
| |
| it('Test write checkpoints on source only', function (done) { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| |
| db.bulkDocs({ docs: docs }).then(function () { |
| PouchDB.replicate(db, remote, { checkpoint: 'source' }) |
| .on('error', done) |
| .on('complete', function () { |
| testUtils.generateReplicationId(db, remote, {}).then(function (replicationId) { |
| db.get(replicationId).then(function () { |
| remote.get(replicationId).then(function () { |
| done(new Error('Found a checkpoint on target that should not exist')); |
| }).catch(function (error) { |
| if (error.status === 404) { |
| done(); |
| } else { |
| done(error); |
| } |
| }); |
| }).catch(done); |
| }).catch(done); |
| }); |
| }).catch(done); |
| }); |
| |
| it('Test write checkpoints on target only', function (done) { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| |
| db.bulkDocs({ docs: docs }).then(function () { |
| PouchDB.replicate(db, remote, { checkpoint: 'target' }) |
| .on('error', done) |
| .on('complete', function () { |
| testUtils.generateReplicationId(db, remote, {}).then(function (replicationId) { |
| remote.get(replicationId).then(function () { |
| db.get(replicationId).then(function () { |
| done(new Error('Found a checkpoint on source that should not exist')); |
| }).catch(function (error) { |
| if (error.status === 404) { |
| done(); |
| } else { |
| done(error); |
| } |
| }); |
| }).catch(done); |
| }).catch(done); |
| }); |
| }).catch(done); |
| }); |
| |
| it('Test replication resumes when checkpointing is enabled', function (done) { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| |
| var expectedSince = false; |
| interceptChanges(db, function (opts) { |
| assertSince(opts, expectedSince); |
| expectedSince = false; |
| }); |
| |
| db.bulkDocs({ docs: docs.slice(0, 1) }) |
| .then(function () { |
| PouchDB.replicate(db, remote) |
| .on('error', done) |
| .on('complete', function (result) { |
| result.docs_read.should.equal(1); |
| result.docs_written.should.equal(1); |
| db.bulkDocs({ docs: docs.slice(1, 2) }) |
| .then(function () { |
| expectedSince = 1; |
| PouchDB.replicate(db, remote) |
| .on('error', done) |
| .on('complete', function (result) { |
| result.docs_read.should.equal(1); |
| result.docs_written.should.equal(1); |
| done(); |
| }); |
| }); |
| }); |
| }) |
| .catch(done); |
| }); |
| |
| it('Test replication resumes when checkpointing is disabled', function (done) { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| |
| var replicateOpts = { checkpoint: false }; |
| |
| var expectedSince = false; |
| interceptChanges(db, function (opts) { |
| assertSince(opts, expectedSince); |
| expectedSince = false; |
| }); |
| |
| db.bulkDocs({ docs: docs.slice(0, 1) }) |
| .then(function () { |
| PouchDB.replicate(db, remote, replicateOpts) |
| .on('error', done) |
| .on('complete', function (result) { |
| result.docs_read.should.equal(1); |
| result.docs_written.should.equal(1); |
| |
| db.bulkDocs({ docs: docs.slice(1, 2) }) |
| .then(function () { |
| expectedSince = 0; |
| PouchDB.replicate(db, remote, replicateOpts) |
| .on('error', done) |
| .on('complete', function (result) { |
| result.docs_read.should.equal(1); |
| result.docs_written.should.equal(1); |
| done(); |
| }); |
| }); |
| }); |
| }) |
| .catch(done); |
| }); |
| |
| it('Test replication resumes when checkpointing on source only', function (done) { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| |
| var replicateOpts = { checkpoint: 'source' }; |
| |
| var expectedSince = false; |
| interceptChanges(db, function (opts) { |
| assertSince(opts, expectedSince); |
| expectedSince = false; |
| }); |
| |
| db.bulkDocs({ docs: docs.slice(0, 1) }) |
| .then(function () { |
| PouchDB.replicate(db, remote, replicateOpts) |
| .on('error', done) |
| .on('complete', function (result) { |
| result.docs_read.should.equal(1); |
| result.docs_written.should.equal(1); |
| |
| db.bulkDocs({ docs: docs.slice(1, 2) }) |
| .then(function () { |
| expectedSince = 1; |
| PouchDB.replicate(db, remote, replicateOpts) |
| .on('error', done) |
| .on('complete', function (result) { |
| result.docs_read.should.equal(1); |
| result.docs_written.should.equal(1); |
| done(); |
| }); |
| }); |
| }); |
| }) |
| .catch(done); |
| }); |
| |
| it('Test replication resumes when checkpointing on target only', function (done) { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| |
| var replicateOpts = { checkpoint: 'target' }; |
| |
| var expectedSince = false; |
| interceptChanges(db, function (opts) { |
| assertSince(opts, expectedSince); |
| expectedSince = false; |
| }); |
| |
| db.bulkDocs({ docs: docs.slice(0, 1) }) |
| .then(function () { |
| PouchDB.replicate(db, remote, replicateOpts) |
| .on('error', done) |
| .on('complete', function (result) { |
| result.docs_read.should.equal(1); |
| result.docs_written.should.equal(1); |
| |
| db.bulkDocs({ docs: docs.slice(1, 2) }) |
| .then(function () { |
| expectedSince = 1; |
| PouchDB.replicate(db, remote, replicateOpts) |
| .on('error', done) |
| .on('complete', function (result) { |
| result.docs_read.should.equal(1); |
| result.docs_written.should.equal(1); |
| done(); |
| }); |
| }); |
| }); |
| }) |
| .catch(done); |
| }); |
| |
| it('#3136 open revs returned correctly 1', function () { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| |
| var doc = {_id: 'foo'}; |
| var chain = testUtils.Promise.resolve().then(function () { |
| return db.put(doc); |
| }); |
| |
| function addConflict(i) { |
| chain = chain.then(function () { |
| return db.bulkDocs({ |
| docs: [{ |
| _id: 'foo', |
| _rev: '2-' + i |
| }], |
| new_edits: false |
| }); |
| }); |
| } |
| |
| for (var i = 0; i < 50; i++) { |
| addConflict(i); |
| } |
| return chain.then(function () { |
| var revs1; |
| var revs2; |
| return db.get('foo', { |
| conflicts: true, |
| revs: true, |
| open_revs: 'all' |
| }).then(function (res) { |
| revs1 = res.map(function (x) { |
| return x.ok._rev; |
| }).sort(); |
| return db.replicate.to(remote); |
| }).then(function () { |
| return remote.get('foo', { |
| conflicts: true, |
| revs: true, |
| open_revs: 'all' |
| }); |
| }).then(function (res) { |
| revs2 = res.map(function (x) { |
| return x.ok._rev; |
| }).sort(); |
| revs1.should.deep.equal(revs2); |
| }); |
| }); |
| }); |
| |
| it('#3136 open revs returned correctly 2', function () { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| |
| var doc = {_id: 'foo'}; |
| var chain = testUtils.Promise.resolve().then(function () { |
| return db.put(doc); |
| }); |
| |
| function addConflict(i) { |
| chain = chain.then(function () { |
| return db.bulkDocs({ |
| docs: [{ |
| _id: 'foo', |
| _rev: '2-' + i, |
| _deleted: (i % 3 === 1) |
| }], |
| new_edits: false |
| }); |
| }); |
| } |
| |
| for (var i = 0; i < 50; i++) { |
| addConflict(i); |
| } |
| return chain.then(function () { |
| var revs1; |
| var revs2; |
| return db.get('foo', { |
| conflicts: true, |
| revs: true, |
| open_revs: 'all' |
| }).then(function (res) { |
| revs1 = res.map(function (x) { |
| return x.ok._rev; |
| }).sort(); |
| return db.replicate.to(remote); |
| }).then(function () { |
| return remote.get('foo', { |
| conflicts: true, |
| revs: true, |
| open_revs: 'all' |
| }); |
| }).then(function (res) { |
| revs2 = res.map(function (x) { |
| return x.ok._rev; |
| }).sort(); |
| revs1.should.deep.equal(revs2); |
| }); |
| }); |
| }); |
| |
| it('#3136 winningRev has a lower seq', function () { |
| var db1 = new PouchDB(dbs.name); |
| var db2 = new PouchDB(dbs.remote); |
| var tree = [ |
| [ |
| {_id: 'foo', _rev: '1-a', |
| _revisions: {start: 1, ids: ['a']}}, |
| {_id: 'foo', _rev: '2-e', _deleted: true, |
| _revisions: { start: 2, ids: ['e', 'a']}}, |
| {_id: 'foo', _rev: '3-g', |
| _revisions: { start: 3, ids: ['g', 'e', 'a']}} |
| ], |
| [ |
| {_id: 'foo', _rev: '1-a', |
| _revisions: {start: 1, ids: ['a']}}, |
| {_id: 'foo', _rev: '2-b', |
| _revisions: {start: 2, ids: ['b', 'a']}}, |
| {_id: 'foo', _rev: '3-c', |
| _revisions: {start: 3, ids: ['c', 'b', 'a']}} |
| ], |
| [ |
| {_id: 'foo', _rev: '1-a', |
| _revisions: {start: 1, ids: ['a']}}, |
| {_id: 'foo', _rev: '2-d', |
| _revisions: {start: 2, ids: ['d', 'a']}}, |
| {_id: 'foo', _rev: '3-h', |
| _revisions: {start: 3, ids: ['h', 'd', 'a']}}, |
| {_id: 'foo', _rev: '4-f', |
| _revisions: {start: 4, ids: ['f', 'h', 'd', 'a']}} |
| ] |
| ]; |
| |
| var chain = testUtils.Promise.resolve(); |
| tree.forEach(function (docs) { |
| chain = chain.then(function () { |
| var revs1; |
| var revs2; |
| |
| return db1.bulkDocs({ |
| docs: docs, |
| new_edits: false |
| }).then(function () { |
| return db1.replicate.to(db2); |
| }).then(function () { |
| return db1.get('foo', { |
| open_revs: 'all', |
| revs: true, |
| conflicts: true |
| }); |
| }).then(function (res1) { |
| revs1 = res1.map(function (x) { |
| return x.ok._rev; |
| }).sort(); |
| |
| return db2.get('foo', { |
| open_revs: 'all', |
| revs: true, |
| conflicts: true |
| }); |
| }).then(function (res2) { |
| revs2 = res2.map(function (x) { |
| return x.ok._rev; |
| }).sort(); |
| revs1.should.deep.equal(revs2, 'same revs'); |
| }); |
| }); |
| }); |
| return chain; |
| }); |
| |
| it('#3136 same changes with style=all_docs', function () { |
| var db1 = new PouchDB(dbs.name); |
| var db2 = new PouchDB(dbs.remote); |
| var tree = [ |
| [ |
| {_id: 'foo', _rev: '1-a', |
| _revisions: {start: 1, ids: ['a']}}, |
| {_id: 'foo', _rev: '2-e', _deleted: true, |
| _revisions: { start: 2, ids: ['e', 'a']}}, |
| {_id: 'foo', _rev: '3-g', |
| _revisions: { start: 3, ids: ['g', 'e', 'a']}} |
| ], |
| [ |
| {_id: 'foo', _rev: '1-a', |
| _revisions: {start: 1, ids: ['a']}}, |
| {_id: 'foo', _rev: '2-b', |
| _revisions: {start: 2, ids: ['b', 'a']}}, |
| {_id: 'foo', _rev: '3-c', |
| _revisions: {start: 3, ids: ['c', 'b', 'a']}} |
| ], |
| [ |
| {_id: 'foo', _rev: '1-a', |
| _revisions: {start: 1, ids: ['a']}}, |
| {_id: 'foo', _rev: '2-d', |
| _revisions: {start: 2, ids: ['d', 'a']}}, |
| {_id: 'foo', _rev: '3-h', |
| _revisions: {start: 3, ids: ['h', 'd', 'a']}}, |
| {_id: 'foo', _rev: '4-f', |
| _revisions: {start: 4, ids: ['f', 'h', 'd', 'a']}} |
| ] |
| ]; |
| |
| var chain = testUtils.Promise.resolve(); |
| tree.forEach(function (docs) { |
| chain = chain.then(function () { |
| var changes1; |
| var changes2; |
| |
| return db1.bulkDocs({ |
| docs: docs, |
| new_edits: false |
| }).then(function () { |
| return db1.replicate.to(db2); |
| }).then(function () { |
| return db1.changes({style: 'all_docs'}); |
| }).then(function (res1) { |
| changes1 = simplifyChanges(res1); |
| return db2.changes({style: 'all_docs'}); |
| }).then(function (res2) { |
| changes2 = simplifyChanges(res2); |
| |
| changes1.should.deep.equal(changes2, 'same changes'); |
| }); |
| }); |
| }); |
| return chain; |
| }); |
| |
| it('#3136 style=all_docs with conflicts', function () { |
| var docs1 = [ |
| {_id: '0', integer: 0}, |
| {_id: '1', integer: 1}, |
| {_id: '2', integer: 2}, |
| {_id: '3', integer: 3} |
| ]; |
| var docs2 = [ |
| {_id: '2', integer: 11}, |
| {_id: '3', integer: 12} |
| ]; |
| var rev2; |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| return db.bulkDocs({ docs: docs1 }).then(function (info) { |
| docs2[0]._rev = info[2].rev; |
| docs2[1]._rev = info[3].rev; |
| return db.put(docs2[0]); |
| }).then(function () { |
| return db.put(docs2[1]); |
| }).then(function (info) { |
| rev2 = info.rev; |
| return PouchDB.replicate(db, remote); |
| }).then(function () { |
| // update remote once, local twice, then replicate from |
| // remote to local so the remote losing conflict is later in |
| // the tree |
| return db.put({ |
| _id: '3', |
| _rev: rev2, |
| integer: 20 |
| }); |
| }).then(function (resp) { |
| var rev3Doc = { |
| _id: '3', |
| _rev: resp.rev, |
| integer: 30 |
| }; |
| return db.put(rev3Doc); |
| }).then(function () { |
| var rev4Doc = { |
| _id: '3', |
| _rev: rev2, |
| integer: 100 |
| }; |
| return remote.put(rev4Doc).then(function () { |
| return PouchDB.replicate(remote, db).then(function () { |
| return PouchDB.replicate(db, remote); |
| }).then(function () { |
| return db.changes({ |
| include_docs: true, |
| style: 'all_docs', |
| conflicts: true |
| }); |
| }).then(function (localChanges) { |
| return remote.changes({ |
| include_docs: true, |
| style: 'all_docs', |
| conflicts: true |
| }).then(function (remoteChanges) { |
| localChanges = simplifyChanges(localChanges); |
| remoteChanges = simplifyChanges(remoteChanges); |
| |
| localChanges.should.deep.equal(remoteChanges, |
| 'same changes'); |
| }); |
| }); |
| }); |
| }); |
| }); |
| |
| it('#3136 style=all_docs with conflicts reversed', function () { |
| var docs1 = [ |
| {_id: '0', integer: 0}, |
| {_id: '1', integer: 1}, |
| {_id: '2', integer: 2}, |
| {_id: '3', integer: 3} |
| ]; |
| var docs2 = [ |
| {_id: '2', integer: 11}, |
| {_id: '3', integer: 12} |
| ]; |
| var rev2; |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| return db.bulkDocs({ docs: docs1 }).then(function (info) { |
| docs2[0]._rev = info[2].rev; |
| docs2[1]._rev = info[3].rev; |
| return db.put(docs2[0]); |
| }).then(function () { |
| return db.put(docs2[1]); |
| }).then(function (info) { |
| rev2 = info.rev; |
| return PouchDB.replicate(db, remote); |
| }).then(function () { |
| // update remote once, local twice, then replicate from |
| // remote to local so the remote losing conflict is later in |
| // the tree |
| return db.put({ |
| _id: '3', |
| _rev: rev2, |
| integer: 20 |
| }); |
| }).then(function (resp) { |
| var rev3Doc = { |
| _id: '3', |
| _rev: resp.rev, |
| integer: 30 |
| }; |
| return db.put(rev3Doc); |
| }).then(function () { |
| var rev4Doc = { |
| _id: '3', |
| _rev: rev2, |
| integer: 100 |
| }; |
| return remote.put(rev4Doc).then(function () { |
| return PouchDB.replicate(remote, db).then(function () { |
| return PouchDB.replicate(db, remote); |
| }).then(function () { |
| return db.changes({ |
| include_docs: true, |
| style: 'all_docs', |
| conflicts: true, |
| descending: true |
| }); |
| }).then(function (localChanges) { |
| return remote.changes({ |
| include_docs: true, |
| style: 'all_docs', |
| conflicts: true, |
| descending: true |
| }).then(function (remoteChanges) { |
| localChanges = simplifyChanges(localChanges); |
| remoteChanges = simplifyChanges(remoteChanges); |
| |
| localChanges.should.deep.equal(remoteChanges, |
| 'same changes'); |
| }); |
| }); |
| }); |
| }); |
| }); |
| |
| it('Test checkpoint read only 3 :)', function (done) { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| var put = function (doc) { |
| return db.bulkDocs({docs: [doc]}).then(function (resp) { |
| return resp[0]; |
| }); |
| }; |
| var err = { |
| "message": "_writer access is required for this request", |
| "name": "unauthorized", |
| "status": 401 |
| }; |
| db.put = function () { |
| if (typeof arguments[arguments.length - 1] === 'function') { |
| arguments[arguments.length - 1](err); |
| } else { |
| return testUtils.Promise.reject(err); |
| } |
| }; |
| var doc = {_id: '3', count: 0}; |
| put(doc).then(function (results) { |
| return PouchDB.replicate(db, remote).then(function (result) { |
| result.ok.should.equal(true); |
| doc._rev = results.rev; |
| doc.count++; |
| return put(doc); |
| }); |
| }).then(function (results) { |
| doc._rev = results.rev; |
| doc.count++; |
| return put(doc); |
| }).then(function () { |
| return PouchDB.replicate(db, remote); |
| }).then(function (result) { |
| result.ok.should.equal(true); |
| result.docs_written.should.equal(1); |
| db.info(function (err, info) { |
| verifyInfo(info, { |
| update_seq: 3, |
| doc_count: 1 |
| }); |
| done(); |
| }); |
| }, function (a) { |
| done(JSON.stringify(a, false, 4)); |
| }); |
| }); |
| |
| it('Testing allDocs with some conflicts (issue #468)', function (done) { |
| var db1 = new PouchDB(dbs.name); |
| var db2 = new PouchDB(dbs.remote); |
| // we indeed needed replication to create failing test here! |
| var doc = {_id: 'foo', _rev: '1-a', value: 'generic'}; |
| db1.put(doc, { new_edits: false }, function () { |
| db2.put(doc, { new_edits: false }, function () { |
| testUtils.putAfter(db2, { |
| _id: 'foo', |
| _rev: '2-b', |
| value: 'db2' |
| }, '1-a', function () { |
| testUtils.putAfter(db1, { |
| _id: 'foo', |
| _rev: '2-c', |
| value: 'whatever' |
| }, '1-a', function () { |
| testUtils.putAfter(db1, { |
| _id: 'foo', |
| _rev: '3-c', |
| value: 'db1' |
| }, '2-c', function () { |
| db1.get('foo', function (err, doc) { |
| doc.value.should.equal('db1'); |
| db2.get('foo', function (err, doc) { |
| doc.value.should.equal('db2'); |
| PouchDB.replicate(db1, db2, function () { |
| PouchDB.replicate(db2, db1, function () { |
| db1.get('foo', function (err, doc) { |
| doc.value.should.equal('db1'); |
| db2.get('foo', function (err, doc) { |
| doc.value.should.equal('db1'); |
| db1.allDocs({ include_docs: true }, |
| function (err, res) { |
| res.rows.should.have.length.above(0, 'first'); |
| // redundant but we want to test it |
| res.rows[0].doc.value.should.equal('db1'); |
| db2.allDocs({ include_docs: true }, |
| function (err, res) { |
| res.rows.should.have.length.above(0, 'second'); |
| res.rows[0].doc.value.should.equal('db1'); |
| db1.info(function (err, info) { |
| // if auto_compaction is enabled, will |
| // be 5 because 2-c goes "missing" and |
| // the other db tries to re-put it |
| if (!testUtils.isCouchMaster()) { |
| info.update_seq.should.be.within(4, 5); |
| } |
| info.doc_count.should.equal(1); |
| db2.info(function (err, info2) { |
| verifyInfo(info2, { |
| update_seq: 3, |
| doc_count: 1 |
| }); |
| done(); |
| }); |
| }); |
| }); |
| }); |
| }); |
| }); |
| }); |
| }); |
| }); |
| }); |
| }); |
| }); |
| }); |
| }); |
| }); |
| }); |
| |
| // CouchDB will not generate a conflict here, it uses a deteministic |
| // method to generate the revision number, however we cannot copy its |
| // method as it depends on erlangs internal data representation |
| it('Test basic conflict', function (done) { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| var doc1 = {_id: 'adoc', foo: 'bar'}; |
| var doc2 = {_id: 'adoc', bar: 'baz'}; |
| db.put(doc1, function () { |
| remote.put(doc2, function () { |
| db.replicate.to(dbs.remote, function () { |
| remote.get('adoc', { conflicts: true }, function (err, result) { |
| result.should.have.property('_conflicts'); |
| db.info(function (err, info) { |
| verifyInfo(info, { |
| update_seq: 1, |
| doc_count: 1 |
| }); |
| done(); |
| }); |
| }); |
| }); |
| }); |
| }); |
| }); |
| |
| it('Test _conflicts key', function (done) { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| |
| // Test invalid if adapter doesnt support mapreduce |
| if (!remote.query) { |
| return done(); |
| } |
| |
| var doc1 = {_id: 'adoc', foo: 'bar'}; |
| var doc2 = {_id: 'adoc', bar: 'baz'}; |
| var ddoc = { |
| "_id": "_design/conflicts", |
| views: { |
| conflicts: { |
| map: function (doc) { |
| if (doc._conflicts) { |
| emit(doc._id, [doc._rev].concat(doc._conflicts)); |
| } |
| }.toString() |
| } |
| } |
| }; |
| remote.put(ddoc, function () { |
| db.put(doc1, function () { |
| remote.put(doc2, function () { |
| db.replicate.to(dbs.remote, function () { |
| remote.query('conflicts/conflicts', { |
| reduce: false, |
| conflicts: true |
| }, function (_, res) { |
| res.rows.length.should.equal(1); |
| db.info(function (err, info) { |
| verifyInfo(info, { |
| update_seq: 1, |
| doc_count: 1 |
| }); |
| done(); |
| }); |
| }); |
| }); |
| }); |
| }); |
| }); |
| }); |
| |
| it('Test basic live pull replication', function (done) { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| var doc1 = {_id: 'adoc', foo: 'bar'}; |
| remote.bulkDocs({ docs: docs }, {}, function () { |
| var count = 0; |
| var finished = 0; |
| var isFinished = function () { |
| if (++finished !== 2) { |
| return; |
| } |
| db.info(function (err, info) { |
| verifyInfo(info, { |
| update_seq: 4, |
| doc_count: 4 |
| }); |
| done(); |
| }); |
| }; |
| var rep = db.replicate.from(dbs.remote, { |
| live: true |
| }).on('complete', isFinished); |
| var changes = db.changes({ |
| live: true |
| }).on('change', function () { |
| ++count; |
| if (count === 3) { |
| return remote.put(doc1); |
| } |
| if (count === 4) { |
| rep.cancel(); |
| changes.cancel(); |
| } |
| }).on('complete', isFinished).on('error', done); |
| }); |
| }); |
| |
| it('Test basic live push replication', function (done) { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| var doc1 = {_id: 'adoc', foo: 'bar'}; |
| db.bulkDocs({ docs: docs }, {}, function () { |
| var count = 0; |
| var finished = 0; |
| var isFinished = function () { |
| if (++finished !== 2) { |
| return; |
| } |
| db.info(function (err, info) { |
| verifyInfo(info, { |
| update_seq: 4, |
| doc_count: 4 |
| }); |
| done(); |
| }); |
| }; |
| var rep = remote.replicate.from(db, { |
| live: true |
| }).on('complete', isFinished); |
| var changes = remote.changes({ |
| live: true |
| }).on('change', function () { |
| ++count; |
| if (count === 3) { |
| return db.put(doc1); |
| } |
| if (count === 4) { |
| rep.cancel(); |
| changes.cancel(); |
| } |
| }).on('complete', isFinished).on('error', done); |
| }); |
| }); |
| |
| it('test-cancel-pull-replication', function (done) { |
| var remote = new PouchDB(dbs.remote); |
| var db = new PouchDB(dbs.name); |
| var docs = [ |
| {_id: '0', integer: 0, string: '0'}, |
| {_id: '1', integer: 1, string: '1'}, |
| {_id: '2', integer: 2, string: '2'} |
| ]; |
| var doc1 = {_id: 'adoc', foo: 'bar' }; |
| var doc2 = {_id: 'anotherdoc', foo: 'baz'}; |
| remote.bulkDocs({ docs: docs }, {}, function () { |
| var count = 0; |
| var replicate = db.replicate.from(remote, { |
| live: true |
| }).on('complete', function () { |
| remote.put(doc2); |
| setTimeout(function () { |
| changes.cancel(); |
| }, 100); |
| }); |
| var changes = db.changes({ |
| live: true |
| }).on('complete', function () { |
| count.should.equal(4); |
| db.info(function (err, info) { |
| verifyInfo(info, { |
| update_seq: 4, |
| doc_count: 4 |
| }); |
| done(); |
| }); |
| }).on('change', function () { |
| ++count; |
| if (count === 3) { |
| remote.put(doc1); |
| } |
| if (count === 4) { |
| replicate.cancel(); |
| } |
| }).on('error', done); |
| }); |
| }); |
| |
| it('Test basic events', function (done) { |
| var db = new PouchDB(dbs.name); |
| db.bulkDocs({ docs: docs }).then(function () { |
| db.replicate.to(dbs.remote) |
| .on('complete', function (res) { |
| should.exist(res); |
| db.replicate.to('http://0.0.0.0:13370') |
| .on('error', function (res) { |
| should.exist(res); |
| done(); |
| }); |
| }); |
| }); |
| }); |
| |
| it('Replication filter', function (done) { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| var docs1 = [ |
| {_id: '0', integer: 0}, |
| {_id: '1', integer: 1}, |
| {_id: '2', integer: 2}, |
| {_id: '3', integer: 3} |
| ]; |
| remote.bulkDocs({ docs: docs1 }, function () { |
| db.replicate.from(remote, { |
| filter: function (doc) { |
| return doc.integer % 2 === 0; |
| } |
| }).on('error', done).on('complete', function () { |
| db.allDocs(function (err, docs) { |
| if (err) { done(err); } |
| docs.rows.length.should.equal(2); |
| db.info(function (err, info) { |
| verifyInfo(info, { |
| update_seq: 2, |
| doc_count: 2 |
| }); |
| done(); |
| }); |
| }); |
| }); |
| }); |
| }); |
| |
| it('Replication with different filters', function (done) { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| var more_docs = [ |
| {_id: '3', integer: 3, string: '3'}, |
| {_id: '4', integer: 4, string: '4'} |
| ]; |
| remote.bulkDocs({ docs: docs }, function () { |
| db.replicate.from(remote, { |
| filter: function (doc) { |
| return doc.integer % 2 === 0; |
| } |
| }, function () { |
| remote.bulkDocs({ docs: more_docs }, function () { |
| db.replicate.from(remote, {}, function (err, response) { |
| response.docs_written.should.equal(3); |
| db.info(function (err, info) { |
| verifyInfo(info, { |
| update_seq: 5, |
| doc_count: 5 |
| }); |
| done(); |
| }); |
| }); |
| }); |
| }); |
| }); |
| }); |
| |
| it('Replication doc ids', function (done) { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| var thedocs = [ |
| {_id: '3', integer: 3, string: '3'}, |
| {_id: '4', integer: 4, string: '4'}, |
| {_id: '5', integer: 5, string: '5'} |
| ]; |
| remote.bulkDocs({ docs: thedocs }, function () { |
| db.replicate.from(remote, { |
| doc_ids: ['3', '4'] |
| }, function (err, response) { |
| response.docs_written.should.equal(2); |
| db.info(function (err, info) { |
| verifyInfo(info, { |
| update_seq: 2, |
| doc_count: 2 |
| }); |
| done(); |
| }); |
| }); |
| }); |
| }); |
| |
| it('2204 Invalid doc_ids', function () { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| var thedocs = [ |
| {_id: '3', integer: 3, string: '3'}, |
| {_id: '4', integer: 4, string: '4'}, |
| {_id: '5', integer: 5, string: '5'} |
| ]; |
| return remote.bulkDocs({docs: thedocs}).then(function () { |
| return db.replicate.from(remote, {doc_ids: 'foo'}); |
| }).catch(function (err) { |
| err.name.should.equal('bad_request'); |
| err.reason.should.equal("`doc_ids` filter parameter is not a list."); |
| }); |
| }); |
| |
| it('Replication since', function (done) { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| var docs1 = [ |
| {_id: '1', integer: 1, string: '1'}, |
| {_id: '2', integer: 2, string: '2'}, |
| {_id: '3', integer: 3, string: '3'} |
| ]; |
| remote.bulkDocs({ docs: docs1 }, function () { |
| remote.info(function (err, info) { |
| var update_seq = info.update_seq; |
| var docs2 = [ |
| {_id: '4', integer: 4, string: '4'}, |
| {_id: '5', integer: 5, string: '5'} |
| ]; |
| remote.bulkDocs({ docs: docs2 }, function () { |
| db.replicate.from(remote, { |
| since: update_seq |
| }).on('complete', function (result) { |
| result.docs_written.should.equal(2); |
| db.replicate.from(remote, { |
| since: 0 |
| }).on('complete', function (result) { |
| result.docs_written.should.equal(3); |
| db.info(function (err, info) { |
| verifyInfo(info, { |
| update_seq: 5, |
| doc_count: 5 |
| }); |
| done(); |
| }); |
| }).on('error', done); |
| }).on('error', done); |
| }); |
| }); |
| }); |
| }); |
| |
| it('Replication with same filters', function (done) { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| var more_docs = [ |
| {_id: '3', integer: 3, string: '3'}, |
| {_id: '4', integer: 4, string: '4'} |
| ]; |
| remote.bulkDocs({ docs: docs }, function () { |
| db.replicate.from(remote, { |
| filter: function (doc) { |
| return doc.integer % 2 === 0; |
| } |
| }, function () { |
| remote.bulkDocs({ docs: more_docs }, function () { |
| db.replicate.from(remote, { |
| filter: function (doc) { |
| return doc.integer % 2 === 0; |
| } |
| }, function (err, response) { |
| response.docs_written.should.equal(1); |
| db.info(function (err, info) { |
| verifyInfo(info, { |
| update_seq: 3, |
| doc_count: 3 |
| }); |
| done(); |
| }); |
| }); |
| }); |
| }); |
| }); |
| }); |
| |
| it('Replication with filter that leads to some empty batches (#2689)', |
| function (done) { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| var docs1 = [ |
| {_id: '0', integer: 0}, |
| {_id: '1', integer: 1}, |
| {_id: '2', integer: 1}, |
| {_id: '3', integer: 1}, |
| {_id: '4', integer: 2}, |
| {_id: '5', integer: 2} |
| ]; |
| remote.bulkDocs({ docs: docs1 }, function () { |
| db.replicate.from(remote, { |
| batch_size: 2, |
| filter: function (doc) { |
| return doc.integer % 2 === 0; |
| } |
| }).on('complete', function () { |
| db.allDocs(function (err, docs) { |
| if (err) { done(err); } |
| docs.rows.length.should.equal(3); |
| db.info(function (err, info) { |
| verifyInfo(info, { |
| update_seq: 3, |
| doc_count: 3 |
| }); |
| done(); |
| }); |
| }); |
| }).on('error', done); |
| }); |
| }); |
| |
| it('Empty replication updates checkpoint (#5145)', function () { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| var changes = remote.changes; |
| remote.changes = function (params) { |
| changesSince.push(params.since); |
| return changes.apply(this, arguments); |
| }; |
| var changesSince = []; |
| var replicationOpts = { |
| filter: function () { |
| return false; |
| } |
| }; |
| return remote.bulkDocs({ docs: docs }).then(function () { |
| return db.replicate.from(remote, replicationOpts); |
| }).then(function (result) { |
| result.ok.should.equal(true); |
| result.docs_written.should.equal(0); |
| result.docs_read.should.equal(0); |
| changesSince.length.should.equal(2); |
| |
| // the 'since' parameter should be different on the |
| // next request |
| changesSince[0].should.not.equal(changesSince[1]); |
| |
| // kick off a second replication |
| return db.replicate.from(remote, replicationOpts); |
| }).then(function (result) { |
| result.ok.should.equal(true); |
| result.docs_written.should.equal(0); |
| result.docs_read.should.equal(0); |
| changesSince.length.should.equal(3); |
| |
| // nothing has changed on the remote so 'since' |
| // should be the same |
| changesSince[1].should.equal(changesSince[2]); |
| }).then(function () { |
| // Restore remote.changes to original |
| remote.changes = changes; |
| }).catch(function (err) { |
| remote.changes = changes; |
| throw err; |
| }); |
| }); |
| |
| it('Does not update checkpoint unncessarily (#5379)', function () { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| var bulkDocs = remote.bulkDocs; |
| var bulkDocsCalled = false; |
| remote.bulkDocs = function () { |
| bulkDocsCalled = true; |
| return bulkDocs.apply(this, arguments); |
| }; |
| return remote.bulkDocs({ docs: docs }).then(function () { |
| return db.replicate.from(remote); |
| }).then(function (result) { |
| result.ok.should.equal(true); |
| bulkDocsCalled = false; |
| |
| // kick off a second replication where there are no changes |
| // checkpoints are written using bulkDocs so |
| // we don't expect any calls |
| return db.replicate.from(remote); |
| }).then(function (result) { |
| result.ok.should.equal(true); |
| bulkDocsCalled.should.equal(false); |
| }).then(function () { |
| // Restore remote.bulkDocs to original |
| remote.bulkDocs = bulkDocs; |
| }).catch(function (err) { |
| remote.bulkDocs = bulkDocs; |
| throw err; |
| }); |
| }); |
| |
| it('Replication with deleted doc', function () { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| var docs1 = [ |
| {_id: '0', integer: 0}, |
| {_id: '1', integer: 1}, |
| {_id: '2', integer: 2}, |
| {_id: '3', integer: 3}, |
| {_id: '4', integer: 4, _deleted: true} |
| ]; |
| return remote.bulkDocs({ docs: docs1 }) |
| .then(function () { |
| return db.replicate.from(remote); |
| }).then(function () { |
| return db.allDocs(); |
| }).then(function (res) { |
| res.total_rows.should.equal(4); |
| return db.info(); |
| }).then(function (info) { |
| verifyInfo(info, { |
| update_seq: 5, |
| doc_count: 4 |
| }); |
| }); |
| }); |
| |
| it('5904 - replication with deleted doc and value', function () { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| var doc = {_id: 'foo', integer: 4, _deleted: true}; |
| var rev; |
| return db.put(doc) |
| .then(function (res) { |
| rev = res.rev; |
| return db.get(doc._id, { rev: rev }); |
| }).then(function (local_doc) { |
| local_doc.integer.should.equal(4); |
| return db.replicate.to(remote); |
| }).then(function () { |
| return remote.get(doc._id, { rev: rev }); |
| }).then(function (remote_doc) { |
| remote_doc.integer.should.equal(4); |
| }); |
| }); |
| |
| it('Replication with doc deleted twice', function (done) { |
| if (testUtils.isCouchMaster()) { |
| return done(); |
| } |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| remote.bulkDocs({ docs: docs }).then(function () { |
| return remote.get('0'); |
| }).then(function (doc) { |
| return remote.remove(doc); |
| }).then(function () { |
| return db.replicate.from(remote); |
| }).then(function () { |
| return db.allDocs(); |
| }).then(function (res) { |
| res.total_rows.should.equal(2); |
| return remote.allDocs({ keys: [ '0' ] }); |
| }).then(function (res) { |
| var row = res.rows[0]; |
| should.not.exist(row.error); |
| // set rev to latest so we go at the end (otherwise new |
| // rev is 1 and the subsequent remove below won't win) |
| var doc = { |
| _id: '0', |
| integer: 10, |
| string: '10', |
| _rev: row.value.rev |
| }; |
| return remote.put(doc); |
| }).then(function () { |
| return remote.get('0'); |
| }).then(function (doc) { |
| return remote.remove(doc); |
| }).then(function () { |
| return db.replicate.from(remote); |
| }).then(function () { |
| return db.allDocs(); |
| }).then(function (res) { |
| res.total_rows.should.equal(2); |
| db.info(function (err, info) { |
| verifyInfo(info, { |
| update_seq: 4, |
| doc_count: 2 |
| }); |
| done(); |
| }); |
| }).catch(function (err) { |
| done(JSON.stringify(err, false, 4)); |
| }); |
| }); |
| |
| it('Replication notifications', function (done) { |
| var changes = 0; |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| var onChange = function (c) { |
| changes += c.docs.length; |
| |
| if (changes === 3) { |
| db.info(function (err, info) { |
| verifyInfo(info, { |
| update_seq: 3, |
| doc_count: 3 |
| }); |
| done(); |
| }); |
| } |
| }; |
| remote.bulkDocs({ docs: docs }, {}, function () { |
| db.replicate.from(dbs.remote).on('change', onChange); |
| }); |
| }); |
| |
| it('Replication with remote conflict', function (done) { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| var doc = {_id: 'test', test: 'Remote 1'}, winningRev; |
| remote.post(doc, function (err, resp) { |
| doc._rev = resp.rev; |
| PouchDB.replicate(remote, db, function () { |
| doc.test = 'Local 1'; |
| db.put(doc, function () { |
| doc.test = 'Remote 2'; |
| remote.put(doc, function (err, resp) { |
| doc._rev = resp.rev; |
| doc.test = 'Remote 3'; |
| remote.put(doc, function (err, resp) { |
| winningRev = resp.rev; |
| PouchDB.replicate(db, remote, function () { |
| PouchDB.replicate(remote, db, function () { |
| remote.get('test', { revs_info: true }, |
| function (err, remotedoc) { |
| db.get('test', { revs_info: true }, |
| function (err, localdoc) { |
| localdoc._rev.should.equal(winningRev); |
| remotedoc._rev.should.equal(winningRev); |
| db.info(function (err, info) { |
| verifyInfo(info, { |
| update_seq: 3, |
| doc_count: 1 |
| }); |
| done(); |
| }); |
| }); |
| }); |
| }); |
| }); |
| }); |
| }); |
| }); |
| }); |
| }); |
| }); |
| |
| it('Replicate and modify three times', function () { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| |
| var doc = { |
| _id: 'foo', |
| generation: 1 |
| }; |
| |
| return db.put(doc).then(function (res) { |
| doc._rev = res.rev; |
| return db.replicate.to(remote); |
| }).then(function () { |
| return remote.get('foo'); |
| }).then(function (doc) { |
| doc.generation.should.equal(1); |
| doc.generation = 2; |
| return remote.put(doc); |
| }).then(function (res) { |
| doc._rev = res.rev; |
| return remote.replicate.to(db); |
| }).then(function () { |
| return db.get('foo'); |
| }).then(function (doc) { |
| doc.generation.should.equal(2); |
| doc.generation = 3; |
| return db.put(doc); |
| }).then(function () { |
| return db.replicate.to(remote); |
| }).then(function () { |
| return db.get('foo', {conflicts: true}); |
| }).then(function (doc) { |
| doc.generation.should.equal(3); |
| should.not.exist(doc._conflicts); |
| }).then(function () { |
| return remote.get('foo', {conflicts: true}); |
| }).then(function (doc) { |
| doc.generation.should.equal(3); |
| should.not.exist(doc._conflicts); |
| }); |
| }); |
| |
| function waitForChange(db, fun) { |
| return new testUtils.Promise(function (resolve) { |
| var remoteChanges = db.changes({live: true, include_docs: true}); |
| remoteChanges.on('change', function (change) { |
| if (fun(change)) { |
| remoteChanges.cancel(); |
| resolve(); |
| } |
| }); |
| }); |
| } |
| |
| it('live replication, starting offline', function () { |
| |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| var Promise = testUtils.Promise; |
| |
| // id() is the first thing called |
| var origId = remote.id; |
| var i = 0; |
| remote.id = function () { |
| // Reject only the first 3 times |
| if (++i <= 3) { |
| return Promise.reject(new Error('flunking you')); |
| } |
| return origId.apply(remote, arguments); |
| }; |
| |
| return remote.post({}).then(function () { |
| return new Promise(function (resolve, reject) { |
| var rep = db.replicate.from(remote, { |
| live: true |
| }); |
| rep.on('error', reject); |
| }).then(function () { |
| throw new Error('should have thrown error'); |
| }, function (err) { |
| should.exist(err); |
| }); |
| }); |
| }); |
| |
| it('Replicates deleted docs (issue #2636)', function () { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| |
| var replication = db.replicate.to(remote, { |
| live: true |
| }); |
| |
| return db.post({}).then(function (res) { |
| var doc = { |
| _id: res.id, |
| _rev: res.rev |
| }; |
| return db.remove(doc); |
| }).then(function () { |
| return db.allDocs(); |
| }).then(function (res) { |
| res.rows.should.have.length(0, 'deleted locally'); |
| }).then(function () { |
| return waitForChange(remote, function (change) { |
| return change.deleted === true; |
| }); |
| }).then(function () { |
| return remote.allDocs(); |
| }).then(function (res) { |
| replication.cancel(); |
| res.rows.should.have.length(0, 'deleted in remote'); |
| }); |
| }); |
| |
| it('Replicates deleted docs w/ delay (issue #2636)', function () { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| |
| var replication = db.replicate.to(remote, { |
| live: true |
| }); |
| |
| var doc; |
| return db.post({}).then(function (res) { |
| doc = {_id: res.id, _rev: res.rev}; |
| return waitForChange(remote, function () { return true; }); |
| }).then(function () { |
| return db.remove(doc); |
| }).then(function () { |
| return db.allDocs(); |
| }).then(function (res) { |
| res.rows.should.have.length(0, 'deleted locally'); |
| }).then(function () { |
| return waitForChange(remote, function (c) { |
| return c.id === doc._id && c.deleted; |
| }); |
| }).then(function () { |
| return remote.allDocs(); |
| }).then(function (res) { |
| replication.cancel(); |
| res.rows.should.have.length(0, 'deleted in remote'); |
| }); |
| }); |
| |
| it('Replicates deleted docs w/ compaction', function () { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| |
| var doc = {_id: 'foo'}; |
| return db.put(doc).then(function (res) { |
| doc._rev = res.rev; |
| return db.replicate.to(remote); |
| }).then(function () { |
| return db.put(doc); |
| }).then(function (res) { |
| doc._rev = res.rev; |
| return db.remove(doc); |
| }).then(function () { |
| return db.compact(); |
| }).then(function () { |
| return db.allDocs(); |
| }).then(function (res) { |
| res.rows.should.have.length(0, 'deleted locally'); |
| }).then(function () { |
| return db.replicate.to(remote); |
| }).then(function () { |
| return remote.allDocs(); |
| }).then(function (res) { |
| res.rows.should.have.length(0, 'deleted in remote'); |
| }); |
| }); |
| |
| it('Replicates modified docs (issue #2636)', function () { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| |
| var replication = db.replicate.to(remote, { |
| live: true |
| }); |
| |
| return db.post({}).then(function (res) { |
| var doc = { |
| _id: res.id, |
| _rev: res.rev, |
| modified: 'yep' |
| }; |
| |
| return db.put(doc); |
| }).then(function () { |
| return db.allDocs({include_docs: true}); |
| }).then(function (res) { |
| res.rows.should.have.length(1, 'one doc synced locally'); |
| res.rows[0].doc.modified.should.equal('yep', 'modified locally'); |
| }).then(function () { |
| return waitForChange(remote, function (change) { |
| return change.doc.modified === 'yep'; |
| }); |
| }).then(function () { |
| return remote.allDocs({include_docs: true}); |
| }).then(function (res) { |
| replication.cancel(); |
| res.rows.should.have.length(1, '1 doc in remote'); |
| res.rows[0].doc.modified.should.equal('yep', 'modified in remote'); |
| }); |
| }); |
| |
| it('Replication of multiple remote conflicts (#789)', function (done) { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| var doc = {_id: '789', _rev: '1-a', value: 'test'}; |
| function createConflicts(db, callback) { |
| db.put(doc, { new_edits: false }, function () { |
| testUtils.putAfter(db, { |
| _id: '789', |
| _rev: '2-a', |
| value: 'v1' |
| }, '1-a', function () { |
| testUtils.putAfter(db, { |
| _id: '789', |
| _rev: '2-b', |
| value: 'v2' |
| }, '1-a', function () { |
| testUtils.putAfter(db, { |
| _id: '789', |
| _rev: '2-c', |
| value: 'v3' |
| }, '1-a', function () { |
| callback(); |
| }); |
| }); |
| }); |
| }); |
| } |
| createConflicts(remote, function () { |
| db.replicate.from(remote, function (err, result) { |
| result.ok.should.equal(true); |
| // in this situation, all the conflicting revisions should be read and |
| // written to the target database (this is consistent with CouchDB) |
| result.docs_written.should.equal(3); |
| result.docs_read.should.equal(3); |
| db.info(function (err, info) { |
| if (!testUtils.isCouchMaster()) { |
| info.update_seq.should.be.above(0); |
| } |
| info.doc_count.should.equal(1); |
| done(); |
| }); |
| }); |
| }); |
| }); |
| |
| it('Replicate large number of docs', function (done) { |
| if ('saucelabs' in testUtils.params()) { |
| return done(); |
| } |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| var docs = []; |
| var num = 30; |
| for (var i = 0; i < num; i++) { |
| docs.push({ |
| _id: 'doc_' + i, |
| foo: 'bar_' + i |
| }); |
| } |
| remote.bulkDocs({ docs: docs }, function () { |
| db.replicate.from(remote, {}, function () { |
| db.allDocs(function (err, res) { |
| res.total_rows.should.equal(num); |
| db.info(function (err, info) { |
| verifyInfo(info, { |
| update_seq: 30, |
| doc_count: 30 |
| }); |
| done(); |
| }); |
| }); |
| }); |
| }); |
| }); |
| |
| it('Ensure checkpoint after deletion', function (done) { |
| var db1name = dbs.name; |
| var adoc = { '_id': 'adoc' }; |
| var newdoc = { '_id': 'newdoc' }; |
| var db1 = new PouchDB(dbs.name); |
| var db2 = new PouchDB(dbs.remote); |
| db1.post(adoc, function () { |
| PouchDB.replicate(db1, db2).on('complete', function () { |
| db1.destroy(function () { |
| var fresh = new PouchDB(db1name); |
| fresh.post(newdoc, function () { |
| PouchDB.replicate(fresh, db2).on('complete', function () { |
| db2.allDocs(function (err, docs) { |
| docs.rows.length.should.equal(2); |
| done(); |
| }); |
| }); |
| }); |
| }); |
| }); |
| }); |
| }); |
| |
| it('issue #1001 cb as 3rd argument', function (done) { |
| PouchDB.replicate('http://example.com', dbs.name, function (err) { |
| should.exist(err); |
| done(); |
| }); |
| }); |
| |
| it('issue #1001 cb as 4th argument', function (done) { |
| var url = 'http://example.com'; |
| PouchDB.replicate(url, dbs.name, {}, function (err) { |
| should.exist(err); |
| done(); |
| }); |
| }); |
| |
| it('issue #909 Filtered replication bails at paging limit', |
| function (done) { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| var docs = []; |
| var num = 100; |
| for (var i = 0; i < num; i++) { |
| docs.push({ |
| _id: 'doc_' + i, |
| foo: 'bar_' + i |
| }); |
| } |
| num = 100; |
| var docList = []; |
| for (i = 0; i < num; i += 5) { |
| docList.push('doc_' + i); |
| } |
| // uncomment this line to test only docs higher than paging limit |
| docList = [ |
| 'doc_33', |
| 'doc_60', |
| 'doc_90' |
| ]; |
| remote.bulkDocs({ docs: docs }, {}, function () { |
| db.replicate.from(dbs.remote, { |
| live: false, |
| doc_ids: docList |
| }, function (err, result) { |
| result.docs_written.should.equal(docList.length); |
| db.info(function (err, info) { |
| verifyInfo(info, { |
| update_seq: 3, |
| doc_count: 3 |
| }); |
| done(); |
| }); |
| }); |
| }); |
| }); |
| |
| it('(#4963) Ensure successful docs are saved but seq not updated if single doc fails to replicate', function (done) { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| var Promise = testUtils.Promise; |
| |
| // 10 test documents |
| var num = 10; |
| var docs = []; |
| for (var i = 0; i < num; i++) { |
| docs.push({ |
| _id: 'doc_' + i, |
| foo: 'bar_' + i, |
| // needed to cause the code to fetch using get |
| _attachments: { |
| text: { |
| content_type: 'text/plain', |
| data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ=" |
| } |
| } |
| }); |
| } |
| // Initialize remote with test documents |
| remote.bulkDocs({ docs: docs }, {}, function (err, results) { |
| var bulkGet = remote.bulkGet; |
| function first_replicate() { |
| remote.bulkGet = function () { |
| var getResults = []; |
| for (var i = 0; i < docs.length; i++) { |
| var doc = docs[i]; |
| getResults.push({ |
| id: doc._id, |
| docs: [ { |
| ok: { |
| _id: doc._id, |
| foo: doc.foo, |
| _attachments: doc._attachments, |
| _rev: results[i].rev |
| } |
| } ] |
| }); |
| } |
| // Mock remote.get to fail writing doc_3 (fourth doc) |
| getResults[3].docs[0] = { error: new Error('timeout') }; |
| return Promise.resolve({ results: getResults }); |
| }; |
| // Replicate and confirm failure, docs_written and target docs |
| db.replicate.from(remote).then(function () { |
| done(new Error('First replication should fail')); |
| }).catch(function (err) { |
| // We expect that first replication should fail |
| should.exist(err); |
| |
| err.result.ok.should.equal(false); |
| err.result.docs_written.should.equal(9); |
| if (!testUtils.isCouchMaster()) { |
| err.result.last_seq.should.equal(0); |
| } |
| |
| var docs = [ |
| [ 'doc_0', true ], |
| [ 'doc_1', true ], |
| [ 'doc_2', true ], |
| [ 'doc_3', false ], |
| [ 'doc_4', true ], |
| [ 'doc_5', true ], |
| [ 'doc_6', true ], |
| [ 'doc_7', true ], |
| [ 'doc_8', true ], |
| [ 'doc_9', true ] |
| ]; |
| |
| function check_docs(id, exists) { |
| if (!id) { |
| db.info(function (err, info) { |
| verifyInfo(info, { |
| update_seq: 9, |
| doc_count: 9 |
| }); |
| |
| second_replicate(); |
| }); |
| return; |
| } |
| db.get(id, function (err) { |
| if (exists) { |
| should.not.exist(err); |
| } else { |
| should.exist(err); |
| } |
| check_docs.apply(this, docs.shift()); |
| }); |
| } |
| |
| check_docs.apply(this, docs.shift()); |
| }); |
| } |
| function second_replicate() { |
| // Restore remote.bulkGet to original |
| remote.bulkGet = bulkGet; |
| // Replicate and confirm success, docs_written and target docs |
| db.replicate.from(remote).then(function (result) { |
| should.exist(result); |
| result.docs_written.should.equal(1); |
| if (!testUtils.isCouchMaster()) { |
| result.last_seq.should.equal(10); |
| } |
| |
| var docs = [ 'doc_0', 'doc_1', 'doc_2', 'doc_3', 'doc_4', 'doc_5', 'doc_6', 'doc_7', 'doc_8', 'doc_9' ]; |
| |
| function check_docs(id) { |
| if (!id) { |
| db.info(function (err, info) { |
| verifyInfo(info, { |
| update_seq: 10, |
| doc_count: 10 |
| }); |
| done(); |
| }); |
| return; |
| } |
| db.get(id, function (err) { |
| should.not.exist(err); |
| check_docs(docs.shift()); |
| }); |
| } |
| |
| check_docs(docs.shift()); |
| }).catch(done); |
| } |
| // Done the test |
| first_replicate(); |
| }); |
| }); |
| |
| // Should not start replication over if last_seq mismatches in checkpoints |
| // and it can be resolved some other way |
| it('#3999-1 should not start over if last_seq mismatches', function () { |
| |
| var source = new PouchDB(dbs.remote); |
| var mismatch = false; |
| var failWrite = false; |
| var checkpoint; |
| var checkpointCount = 0; |
| |
| // 1. This is where we fake the mismatch: |
| var putte = source.put; |
| |
| source.put = function (doc) { |
| |
| // We need the checkpoint id so we can inspect it later |
| if (/local/.test(doc._id)) { |
| checkpointCount++; |
| checkpoint = doc._id; |
| } |
| |
| if (failWrite && checkpointCount > 1) { |
| return testUtils.Promise.reject({ |
| status: 0, |
| message: 'Database encountered an unknown error' |
| }); |
| } |
| |
| return putte.apply(this, arguments); |
| }; |
| |
| // 2. We measure that the replication starts in the expected |
| // place in the 'changes' function |
| interceptChanges(source, function (opts) { |
| if (mismatch) { |
| opts.since.should.not.equal(0); |
| } |
| }); |
| |
| |
| var doc = { _id: '3', count: 0 }; |
| var put; |
| |
| return source.put({ _id: '4', count: 1 }, {}).then(function () { |
| return source.put(doc, {}); |
| }).then(function (_put) { |
| put = _put; |
| // Do one replication, this replication |
| // will fail writing one checkpoint |
| failWrite = true; |
| return source.replicate.to(dbs.name, { batch_size: 1 }); |
| }).catch(function () { |
| failWrite = false; |
| }).then(function () { |
| // Verify that checkpoints are indeed mismatching: |
| should.exist(checkpoint); |
| var target = new PouchDB(dbs.name); |
| return testUtils.Promise.all([ |
| target.get(checkpoint), |
| source.get(checkpoint) |
| ]); |
| }).then(function (res) { |
| res[0].session_id.should.equal(res[1].session_id); |
| res[0].last_seq.should.not.equal(res[1].last_seq); |
| |
| doc._rev = put.rev; |
| doc.count++; |
| return source.put(doc, {}); |
| }).then(function () { |
| // Trigger the mismatch on the 2nd replication |
| mismatch = true; |
| return source.replicate.to(dbs.name); |
| }); |
| }); |
| |
| it('#3999-2 should start over if no common session is found', function () { |
| |
| var source = new PouchDB(dbs.remote); |
| var mismatch = false; |
| var writeStrange = false; |
| var checkpoint; |
| var checkpointCount = 0; |
| |
| // 1. This is where we fake the mismatch: |
| var putte = source.put; |
| source.put = function (doc) { |
| |
| // We need the checkpoint id so we can inspect it later |
| if (/local/.test(doc._id)) { |
| checkpointCount++; |
| checkpoint = doc._id; |
| } |
| |
| if (!writeStrange || checkpointCount < 1) { |
| return putte.apply(this, arguments); |
| } |
| |
| // Change session id of source checkpoint to mismatch |
| doc.session_id = "aaabbbbb"; |
| doc.history[0].session_id = "aaabbbbb"; |
| return putte.apply(this, arguments); |
| }; |
| |
| // 2. We measure that the replication starts in the expected |
| // place in the 'changes' function |
| interceptChanges(function (opts) { |
| if (mismatch) { |
| // We expect this replication to start over, |
| // so the correct value of since is 0 |
| // if it's higher, the replication read the checkpoint |
| // without caring for session id |
| opts.since.should.equal(0); |
| mismatch = false; |
| } |
| }); |
| |
| var doc = { _id: '3', count: 0 }; |
| var put; |
| |
| return source.put(doc, {}).then(function (_put) { |
| put = _put; |
| writeStrange = true; |
| // Do one replication, to not start from 0 |
| return source.replicate.to(dbs.name); |
| }).then(function () { |
| writeStrange = false; |
| |
| // Verify that checkpoints are indeed mismatching: |
| should.exist(checkpoint); |
| var target = new PouchDB(dbs.name); |
| return testUtils.Promise.all([ |
| target.get(checkpoint), |
| source.get(checkpoint) |
| ]); |
| }).then(function (res) { |
| // [0] = target checkpoint, [1] = source checkpoint |
| res[0].session_id.should.not.equal(res[1].session_id); |
| |
| doc._rev = put.rev; |
| doc.count++; |
| return source.put(doc, {}); |
| }).then(function () { |
| // Trigger the mismatch on the 2nd replication |
| mismatch = true; |
| return source.replicate.to(dbs.name); |
| }); |
| }); |
| |
| it('#3999-3 should not start over if common session is found', function () { |
| |
| var source = new PouchDB(dbs.remote); |
| var mismatch = false; |
| var writeStrange = false; |
| var checkpoint; |
| var checkpointCount = 0; |
| |
| // 1. This is where we fake the mismatch: |
| var putte = source.put; |
| source.put = function (doc) { |
| |
| // We need the checkpoint id so we can inspect it later |
| if (/local/.test(doc._id)) { |
| checkpointCount++; |
| checkpoint = doc._id; |
| } |
| |
| if (!writeStrange || checkpointCount < 1) { |
| return putte.apply(this, arguments); |
| } |
| |
| // Change session id of source checkpoint to mismatch |
| var session = doc.session_id; |
| |
| doc.session_id = "aaabbbbb"; |
| doc.history[0].session_id = "aaabbbbb"; |
| // put a working session id in the history: |
| doc.history.push({ |
| session_id: session, |
| last_seq: doc.last_seq |
| }); |
| return putte.apply(this, arguments); |
| }; |
| |
| // 2. We measure that the replication starts in the expected |
| // place in the 'changes' function |
| interceptChanges(function (opts) { |
| if (mismatch) { |
| // If we resolve to 0, the checkpoint resolver has not |
| // been going through the sessions |
| opts.since.should.not.equal(0); |
| |
| mismatch = false; |
| } |
| }); |
| |
| |
| var doc = { _id: '3', count: 0 }; |
| var put; |
| |
| return source.put(doc, {}).then(function (_put) { |
| put = _put; |
| // Do one replication, to not start from 0 |
| writeStrange = true; |
| return source.replicate.to(dbs.name); |
| }).then(function () { |
| writeStrange = false; |
| // Verify that checkpoints are indeed mismatching: |
| should.exist(checkpoint); |
| var target = new PouchDB(dbs.name); |
| return testUtils.Promise.all([ |
| target.get(checkpoint), |
| source.get(checkpoint) |
| ]); |
| }).then(function (res) { |
| // [0] = target checkpoint, [1] = source checkpoint |
| res[0].session_id.should.not.equal(res[1].session_id); |
| |
| doc._rev = put.rev; |
| doc.count++; |
| return source.put(doc, {}); |
| }).then(function () { |
| // Trigger the mismatch on the 2nd replication |
| mismatch = true; |
| return source.replicate.to(dbs.name); |
| }); |
| }); |
| |
| it('#3999-4 should "upgrade" an old checkpoint', function () { |
| |
| var secondRound = false; |
| var writeStrange = false; |
| var checkpoint; |
| var checkpointCount = 0; |
| var source = new PouchDB(dbs.remote); |
| var target = new PouchDB(dbs.name); |
| |
| // 1. This is where we fake the mismatch: |
| var putter = function (doc) { |
| |
| // We need the checkpoint id so we can inspect it later |
| if (/local/.test(doc._id)) { |
| checkpointCount++; |
| checkpoint = doc._id; |
| } |
| |
| var args = [].slice.call(arguments, 0); |
| |
| // Write an old-style checkpoint on the first replication: |
| if (writeStrange && checkpointCount >= 1) { |
| var newDoc = { |
| _id: doc._id, |
| last_seq: doc.last_seq |
| }; |
| |
| args.shift(); |
| args.unshift(newDoc); |
| } |
| |
| if (this === source) { |
| return sourcePut.apply(this, args); |
| } |
| |
| return targetPut.apply(this, args); |
| }; |
| |
| var sourcePut = source.put; |
| source.put = putter; |
| var targetPut = target.put; |
| target.put = putter; |
| |
| interceptChanges(source, function (opts) { |
| if (secondRound) { |
| // Test 1: Check that we read the old style local doc |
| // and didn't start from 0 |
| opts.since.should.not.equal(0); |
| } |
| }); |
| |
| var doc = { _id: '3', count: 0 }; |
| |
| return source.put({ _id: '4', count: 1 }, {}).then(function () { |
| writeStrange = true; |
| return source.replicate.to(target); |
| }).then(function () { |
| writeStrange = false; |
| // Verify that we have old checkpoints: |
| should.exist(checkpoint); |
| var target = new PouchDB(dbs.name); |
| return testUtils.Promise.all([ |
| target.get(checkpoint), |
| source.get(checkpoint) |
| ]); |
| }).then(function (res) { |
| // [0] = target checkpoint, [1] = source checkpoint |
| should.not.exist(res[0].session_id); |
| should.not.exist(res[1].session_id); |
| |
| return source.put(doc, {}); |
| }).then(function () { |
| // Do one replication, check that we start from expected last_seq |
| secondRound = true; |
| return source.replicate.to(target); |
| }).then(function () { |
| should.exist(checkpoint); |
| return source.get(checkpoint); |
| }).then(function (res) { |
| should.exist(res.version); |
| should.exist(res.replicator); |
| should.exist(res.session_id); |
| res.version.should.equal(1); |
| res.session_id.should.be.a('string'); |
| }); |
| }); |
| |
| it('(#1307) - replicate empty database', function (done) { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| db.replicate.from(remote, function (err, result) { |
| should.not.exist(err); |
| should.exist(result); |
| result.docs_written.should.equal(0); |
| db.info(function (err, info) { |
| verifyInfo(info, { |
| update_seq: 0, |
| doc_count: 0 |
| }); |
| done(); |
| }); |
| }); |
| }); |
| |
| |
| // This fails as it somehow triggers an xhr abort in the http adapter in |
| // node which doesnt have xhr.... |
| it.skip('Syncing should stop if one replication fails (issue 838)', |
| function (done) { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| var doc1 = {_id: 'adoc', foo: 'bar'}; |
| var doc2 = {_id: 'anotherdoc', foo: 'baz'}; |
| var finished = false; |
| var replications = db.replicate.sync(remote, { |
| live: true |
| }).on('complete', function () { |
| if (finished) { |
| return; |
| } |
| finished = true; |
| remote.put(doc2, function () { |
| setTimeout(function () { |
| db.allDocs(function (err, res) { |
| res.total_rows.should.be.below(2); |
| done(); |
| }); |
| }, 100); |
| }); |
| }); |
| db.put(doc1, function () { |
| replications.pull.cancel(); |
| }); |
| }); |
| |
| it("Reporting write failures (#942)", function (done) { |
| var docs = [{_id: 'a', _rev: '1-a'}, {_id: 'b', _rev: '1-b'}]; |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| db.bulkDocs({docs: docs}, {new_edits: false}, function () { |
| var bulkDocs = remote.bulkDocs; |
| var bulkDocsCallCount = 0; |
| remote.bulkDocs = function (content, opts, callback) { |
| return new testUtils.Promise(function (fulfill, reject) { |
| if (typeof callback !== 'function') { |
| callback = function (err, resp) { |
| if (err) { |
| reject(err); |
| } else { |
| fulfill(resp); |
| } |
| }; |
| } |
| |
| // mock a successful write for the first |
| // document and a failed write for the second |
| var doc = content.docs[0]; |
| |
| if (/^_local/.test(doc._id)) { |
| return bulkDocs.apply(remote, [content, opts, callback]); |
| } |
| |
| if (bulkDocsCallCount === 0) { |
| bulkDocsCallCount++; |
| callback(null, [{ok: true, id: doc._id, rev: doc._rev}]); |
| } else if (bulkDocsCallCount === 1) { |
| bulkDocsCallCount++; |
| callback(null, [{ |
| id: doc._id, |
| error: 'internal server error', |
| reason: 'test document write error' |
| }]); |
| } else { |
| bulkDocs.apply(remote, [content, opts, callback]); |
| } |
| }); |
| }; |
| |
| db.replicate.to(remote, {batch_size: 1, retry: false}, |
| function (err, result) { |
| should.not.exist(result); |
| should.exist(err); |
| err.result.docs_read.should.equal(2, 'docs_read'); |
| err.result.docs_written.should.equal(1, 'docs_written'); |
| err.result.doc_write_failures.should.equal(1, 'doc_write_failures'); |
| remote.bulkDocs = bulkDocs; |
| db.replicate.to(remote, {batch_size: 1, retry: false}, |
| function (err, result) { |
| // checkpoint should not be moved past first doc |
| // should continue from this point and retry second doc |
| result.docs_read.should.equal(1, 'second replication, docs_read'); |
| result.docs_written.should |
| .equal(1, 'second replication, docs_written'); |
| result.doc_write_failures.should |
| .equal(0, 'second replication, doc_write_failures'); |
| db.info(function (err, info) { |
| verifyInfo(info, { |
| update_seq: 2, |
| doc_count: 2 |
| }); |
| done(); |
| }); |
| }); |
| }); |
| }); |
| }); |
| |
| it("Reporting write failures if whole saving fails (#942)", |
| function (done) { |
| var docs = [{_id: 'a', _rev: '1-a'}, {_id: 'b', _rev: '1-b'}]; |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| db.bulkDocs({docs: docs}, {new_edits: false}, function () { |
| var bulkDocs = remote.bulkDocs; |
| remote.bulkDocs = function (docs, opts, callback) { |
| if (typeof callback !== 'function') { |
| return testUtils.Promise.reject(new Error()); |
| } |
| callback(new Error()); |
| }; |
| |
| db.replicate.to(remote, {batch_size: 1, retry: false}, |
| function (err, result) { |
| should.not.exist(result); |
| should.exist(err); |
| err.result.docs_read.should.equal(1, 'docs_read'); |
| err.result.docs_written.should.equal(0, 'docs_written'); |
| err.result.doc_write_failures.should.equal(1, 'doc_write_failures'); |
| err.result.last_seq.should.equal(0, 'last_seq'); |
| remote.bulkDocs = bulkDocs; |
| db.replicate.to(remote, {batch_size: 1, retry: false}, |
| function (err, result) { |
| result.doc_write_failures.should |
| .equal(0, 'second replication, doc_write_failures'); |
| result.docs_written.should |
| .equal(2, 'second replication, docs_written'); |
| done(); |
| }); |
| }); |
| }); |
| }); |
| |
| it('Test consecutive replications with different query_params', |
| function (done) { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| var myDocs = [ |
| {_id: '0', integer: 0, string: '0'}, |
| {_id: '1', integer: 1, string: '1'}, |
| {_id: '2', integer: 2, string: '2'}, |
| {_id: '3', integer: 3, string: '3'}, |
| {_id: '4', integer: 5, string: '5'} |
| ]; |
| remote.bulkDocs({ docs: myDocs }, {}, function () { |
| var filterFun = function (doc, req) { |
| if (req.query.even) { |
| return doc.integer % 2 === 0; |
| } else { |
| return true; |
| } |
| }; |
| db.replicate.from(dbs.remote, { |
| filter: filterFun, |
| query_params: { 'even': true } |
| }, function (err, result) { |
| result.docs_written.should.equal(2); |
| db.replicate.from(dbs.remote, { |
| filter: filterFun, |
| query_params: { 'even': false } |
| }, function (err, result) { |
| result.docs_written.should.equal(3); |
| done(); |
| }); |
| }); |
| }); |
| }); |
| |
| it('Test consecutive replications with different query_params and promises', |
| function (done) { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| var myDocs = [ |
| {_id: '0', integer: 0, string: '0'}, |
| {_id: '1', integer: 1, string: '1'}, |
| {_id: '2', integer: 2, string: '2'}, |
| {_id: '3', integer: 3, string: '3'}, |
| {_id: '4', integer: 5, string: '5'} |
| ]; |
| var filterFun; |
| remote.bulkDocs({ docs: myDocs }).then(function () { |
| filterFun = function (doc, req) { |
| if (req.query.even) { |
| return doc.integer % 2 === 0; |
| } else { |
| return true; |
| } |
| }; |
| return db.replicate.from(dbs.remote, { |
| filter: filterFun, |
| query_params: { 'even': true } |
| }); |
| }).then(function (result) { |
| result.docs_written.should.equal(2); |
| return db.replicate.from(dbs.remote, { |
| filter: filterFun, |
| query_params: { 'even': false } |
| }); |
| }).then(function (result) { |
| result.docs_written.should.equal(3); |
| done(); |
| }).catch(done); |
| }); |
| |
| it('Test consecutive replications with different doc_ids', function (done) { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| var myDocs = [ |
| {_id: '0', integer: 0, string: '0'}, |
| {_id: '1', integer: 1, string: '1'}, |
| {_id: '2', integer: 2, string: '2'}, |
| {_id: '3', integer: 3, string: '3'}, |
| {_id: '4', integer: 5, string: '5'} |
| ]; |
| remote.bulkDocs({ docs: myDocs }, {}, function () { |
| db.replicate.from(dbs.remote, { |
| doc_ids: ['0', '4'] |
| }, function (err, result) { |
| result.docs_written.should.equal(2); |
| db.replicate.from(dbs.remote, { |
| doc_ids: ['1', '2', '3'] |
| }, function (err, result) { |
| result.docs_written.should.equal(3); |
| db.replicate.from(dbs.remote, { |
| doc_ids: ['5'] |
| }, function (err, result) { |
| result.docs_written.should.equal(0); |
| db.info(function (err, info) { |
| verifyInfo(info, { |
| update_seq: 5, |
| doc_count: 5 |
| }); |
| done(); |
| }); |
| }); |
| }); |
| }); |
| }); |
| }); |
| |
| it('#3962 - Test many attachments', function () { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| var doc = {_id: 'foo', _attachments: {}}; |
| var num = 50; |
| |
| Array.apply(null, {length: num}).forEach(function (_, i) { |
| doc._attachments['file_' + i] = { |
| content_type: 'text/plain', |
| data: testUtils.makeBlob('Some text: ' + i) |
| }; |
| }); |
| |
| return remote.put(doc).then(function () { |
| return db.replicate.from(dbs.remote); |
| }).then(function () { |
| return db.get('foo'); |
| }).then(function (res) { |
| Object.keys(res._attachments).length.should.equal(num); |
| }); |
| }); |
| |
| it('doc count after multiple replications', function (done) { |
| var runs = 2; |
| // helper. remove each document in db and bulk load docs into same |
| function rebuildDocuments(db, docs, callback) { |
| db.allDocs({ include_docs: true }, function (err, response) { |
| var count = 0; |
| var limit = response.rows.length; |
| if (limit === 0) { |
| bulkLoad(db, docs, callback); |
| } |
| response.rows.forEach(function (doc) { |
| db.remove(doc, function () { |
| ++count; |
| if (count === limit) { |
| bulkLoad(db, docs, callback); |
| } |
| }); |
| }); |
| }); |
| } |
| |
| // helper. |
| function bulkLoad(db, docs, callback) { |
| db.bulkDocs({ docs: docs }, function (err, results) { |
| if (err) { |
| console.error('Unable to bulk load docs. Err: ' + |
| JSON.stringify(err)); |
| return; |
| } |
| callback(results); |
| }); |
| } |
| |
| // The number of workflow cycles to perform. 2+ was always failing |
| // reason for this test. |
| var workflow = function (name, remote, x) { |
| // some documents. note that the variable Date component, |
| //thisVaries, makes a difference. |
| // when the document is otherwise static, couch gets the same hash |
| // when calculating revision. |
| // and the revisions get messed up in pouch |
| var docs = [ |
| { |
| _id: '0', |
| integer: 0, |
| thisVaries: new Date(), |
| common: true |
| }, |
| { |
| _id: '1', |
| integer: 1, |
| thisVaries: new Date(), |
| common: true |
| }, |
| { |
| _id: '2', |
| integer: 2, |
| thisVaries: new Date(), |
| common: true |
| }, |
| { |
| _id: '3', |
| integer: 3, |
| thisVaries: new Date(), |
| common: true |
| }, |
| { |
| "_id": "_design/common", |
| views: { |
| common: { |
| map: function (doc) { |
| if (doc.common) { |
| emit(doc._id, doc._rev); |
| } |
| }.toString() |
| } |
| } |
| } |
| ]; |
| var dbr = new PouchDB(remote); |
| // Test invalid if adapter doesnt support mapreduce |
| if (!dbr.query) { |
| return done(); |
| } |
| |
| rebuildDocuments(dbr, docs, function () { |
| var db = new PouchDB(name); |
| db.replicate.from(remote, function () { |
| db.query('common/common', { reduce: false }, |
| function (err, result) { |
| // -1 for the design doc |
| result.rows.length.should.equal(docs.length - 1); |
| if (--x) { |
| workflow(name, remote, x); |
| } else { |
| db.info(function (err, info) { |
| verifyInfo(info, { |
| update_seq: 5, |
| doc_count: 5 |
| }); |
| done(); |
| }); |
| } |
| } |
| ); |
| }); |
| }); |
| }; |
| |
| workflow(dbs.name, dbs.remote, runs); |
| }); |
| |
| it('issue #300 rev id unique per doc', function (done) { |
| var remote = new PouchDB(dbs.remote); |
| var db = new PouchDB(dbs.name); |
| var docs = [{ _id: 'a' }, { _id: 'b' }]; |
| remote.bulkDocs({ docs: docs }, {}, function () { |
| db.replicate.from(dbs.remote, function () { |
| db.allDocs(function (err, result) { |
| result.rows.length.should.equal(2); |
| result.rows[0].id.should.equal('a'); |
| result.rows[1].id.should.equal('b'); |
| db.info(function (err, info) { |
| verifyInfo(info, { |
| update_seq: 2, |
| doc_count: 2 |
| }); |
| done(); |
| }); |
| }); |
| }); |
| }); |
| }); |
| |
| it('issue #585 Store checkpoint on target db.', function (done) { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| var docs = [{ _id: 'a' }, { _id: 'b' }]; |
| db.bulkDocs({ docs: docs }, {}, function () { |
| db.replicate.to(dbs.remote, function (err, result) { |
| result.docs_written.should.equal(docs.length); |
| remote.destroy(function () { |
| db.replicate.to(dbs.remote, function (err, result) { |
| result.docs_written.should.equal(docs.length); |
| db.info(function (err, info) { |
| verifyInfo(info, { |
| update_seq: 2, |
| doc_count: 2 |
| }); |
| done(); |
| }); |
| }); |
| }); |
| }); |
| }); |
| }); |
| it('should work with a read only source', function (done) { |
| var src = new PouchDB(dbs.name); |
| var target = new PouchDB(dbs.remote); |
| var err = { |
| "message": "_writer access is required for this request", |
| "name": "unauthorized", |
| "status": 401 |
| }; |
| src.bulkDocs({docs: [ |
| {_id: '0', integer: 0, string: '0'}, |
| {_id: '1', integer: 1, string: '1'}, |
| {_id: '2', integer: 2, string: '2'} |
| ]}).then(function () { |
| src.put = function () { |
| if (typeof arguments[arguments.length - 1] === 'function') { |
| arguments[arguments.length - 1](err); |
| } else { |
| return testUtils.Promise.reject(err); |
| } |
| }; |
| return src.replicate.to(target); |
| }).then(function () { |
| target.info(function (err, info) { |
| verifyInfo(info, { |
| update_seq: 3, |
| doc_count: 3 |
| }); |
| done(); |
| }); |
| }, function (a) { |
| done(JSON.stringify(a, false, 4)); |
| }); |
| }); |
| |
| it('issue #2342 update_seq after replication', function (done) { |
| var docs = []; |
| for (var i = 0; i < 10; i++) { |
| docs.push({_id: i.toString()}); |
| } |
| |
| var remote = new PouchDB(dbs.remote); |
| var db = new PouchDB(dbs.name); |
| |
| remote.bulkDocs({ docs: docs }, {}, function (err, res) { |
| res.forEach(function (row, i) { |
| docs[i]._rev = row.rev; |
| if (i % 2 === 0) { |
| docs[i]._deleted = true; |
| } |
| }); |
| remote.bulkDocs({docs: docs}, {}, function () { |
| db.replicate.from(dbs.remote, function () { |
| db.info(function (err, info) { |
| db.changes({ |
| descending: true, |
| limit: 1 |
| }).on('change', function (change) { |
| change.changes.should.have.length(1); |
| |
| // not a valid assertion in CouchDB 2.0 |
| if (!testUtils.isCouchMaster()) { |
| change.seq.should.equal(info.update_seq); |
| } |
| done(); |
| }).on('error', done); |
| }); |
| }); |
| }); |
| }); |
| }); |
| |
| it('issue #2393 update_seq after new_edits + replication', function (done) { |
| // the assertions below do not hold in a clustered CouchDB |
| if (testUtils.isCouchMaster()) { |
| return done(); |
| } |
| |
| var docs = [{ |
| '_id': 'foo', |
| '_rev': '1-x', |
| '_revisions': { |
| 'start': 1, |
| 'ids': ['x'] |
| } |
| }]; |
| |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| |
| remote.bulkDocs({docs: docs, new_edits: false}, function (err) { |
| should.not.exist(err); |
| remote.bulkDocs({docs: docs, new_edits: false}, function (err) { |
| should.not.exist(err); |
| db.replicate.from(dbs.remote, function () { |
| db.info(function (err, info) { |
| var changes = db.changes({ |
| descending: true, |
| limit: 1 |
| }).on('change', function (change) { |
| change.changes.should.have.length(1); |
| change.seq.should.equal(info.update_seq); |
| changes.cancel(); |
| }).on('complete', function () { |
| remote.info(function (err, info) { |
| var rchanges = remote.changes({ |
| descending: true, |
| limit: 1 |
| }).on('change', function (change) { |
| change.changes.should.have.length(1); |
| change.seq.should.equal(info.update_seq); |
| rchanges.cancel(); |
| }).on('complete', function () { |
| done(); |
| }).on('error', done); |
| }); |
| }).on('error', done); |
| }); |
| }); |
| }); |
| }); |
| }); |
| |
| it('should cancel for live replication', function (done) { |
| var remote = new PouchDB(dbs.remote); |
| var db = new PouchDB(dbs.name); |
| var rep = db.replicate.from(remote, {live: true}); |
| var called = false; |
| rep.on('change', function () { |
| if (called) { |
| done(new Error('called too many times!')); |
| } else { |
| called = true; |
| rep.cancel(); |
| remote.put({_id: 'foo'}).then(function () { |
| return remote.put({_id: 'bar'}); |
| }).then(function () { |
| setTimeout(function () { |
| done(); |
| }, 500); |
| }); |
| } |
| }); |
| remote.put({_id: 'hazaa'}); |
| }); |
| |
| it('#2970 replicate database w/ deleted conflicted revs', function () { |
| var local = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| var docid = "mydoc"; |
| |
| // create a bunch of rando, good revisions |
| var numRevs = 5; |
| var uuids = []; |
| for (var i = 0; i < numRevs - 1; i++) { |
| uuids.push(testUtils.rev()); |
| } |
| |
| // good branch |
| // this branch is one revision ahead of the conflicted branch |
| var a_conflict = testUtils.rev(); |
| var a_burner = testUtils.rev(); |
| var a_latest = testUtils.rev(); |
| var a_rev_num = numRevs + 2; |
| var a_doc = { |
| _id: docid, |
| _rev: a_rev_num + '-' + a_latest, |
| _revisions: { |
| start: a_rev_num, |
| ids: [ a_latest, a_burner, a_conflict ].concat(uuids) |
| } |
| }; |
| |
| // conflicted deleted branch |
| var b_conflict = testUtils.rev(); |
| var b_deleted = testUtils.rev(); |
| var b_rev_num = numRevs + 1; |
| var b_doc = { |
| _id: docid, |
| _rev: b_rev_num + '-' + b_deleted, |
| _deleted: true, |
| _revisions: { |
| start: b_rev_num, |
| ids: [ b_deleted, b_conflict ].concat(uuids) |
| } |
| }; |
| |
| // push the conflicted documents |
| return remote.bulkDocs([ a_doc, b_doc ], { |
| new_edits: false |
| }).then(function () { |
| return remote.get(docid, { open_revs: 'all' }).then(function (revs) { |
| revs.length.should.equal(2, 'correct number of open revisions'); |
| revs[0].ok._id.should.equal(docid, 'rev 1, correct document id'); |
| revs[1].ok._id.should.equal(docid, 'rev 2, correct document id'); |
| // order of revisions is not specified |
| (( |
| revs[0].ok._rev === a_doc._rev && |
| revs[1].ok._rev === b_doc._rev) || |
| ( |
| revs[0].ok._rev === b_doc._rev && |
| revs[1].ok._rev === a_doc._rev) |
| ).should.equal(true); |
| }); |
| }) |
| |
| // attempt to replicate |
| .then(function () { |
| return local.replicate.from(remote).then(function (result) { |
| result.ok.should.equal(true, 'replication result was ok'); |
| // # of documents is 2 because deleted |
| // conflicted revision counts as one |
| result.docs_written.should.equal(2, |
| 'replicated the correct number of documents'); |
| }); |
| }); |
| }); |
| |
| |
| // test validate_doc_update, which is a reasonable substitute |
| // for testing design doc replication of non-admin users, since we |
| // always test in admin party |
| it('#2268 dont stop replication if single forbidden', function (done) { |
| |
| testUtils.isCouchDB(function (isCouchDB) { |
| if (adapters[1] !== 'http' || !isCouchDB) { |
| return done(); |
| } |
| |
| var ddoc = { |
| "_id": "_design/validate", |
| "validate_doc_update": function (newDoc) { |
| if (newDoc.foo === undefined) { |
| throw {forbidden: 'Document must have a foo.'}; |
| } |
| }.toString() |
| }; |
| |
| var remote = new PouchDB(dbs.remote); |
| var db = new PouchDB(dbs.name); |
| |
| remote.put(ddoc).then(function () { |
| var docs = [{foo: 'bar'}, {foo: 'baz'}, {}, {foo: 'quux'}]; |
| return db.bulkDocs({docs: docs}); |
| }).then(function () { |
| return db.replicate.to(dbs.remote); |
| }).then(function (res) { |
| res.ok.should.equal(true); |
| res.docs_read.should.equal(4); |
| res.docs_written.should.equal(3); |
| res.doc_write_failures.should.equal(1); |
| res.errors.should.have.length(1); |
| |
| return remote.allDocs({limit: 0}); |
| }).then(function (res) { |
| res.total_rows.should.equal(4); // 3 plus the validate doc |
| return db.allDocs({limit: 0}); |
| }).then(function (res) { |
| res.total_rows.should.equal(4); // 3 plus the invalid doc |
| }).then(done); |
| }); |
| }); |
| |
| it('#2268 dont stop replication if single unauth', function (done) { |
| |
| testUtils.isCouchDB(function (isCouchDB) { |
| if (adapters[1] !== 'http' || !isCouchDB) { |
| return done(); |
| } |
| |
| var ddoc = { |
| "_id": "_design/validate", |
| "validate_doc_update": function (newDoc) { |
| if (newDoc.foo === undefined) { |
| throw {unauthorized: 'Document must have a foo.'}; |
| } |
| }.toString() |
| }; |
| |
| var remote = new PouchDB(dbs.remote); |
| var db = new PouchDB(dbs.name); |
| |
| return remote.put(ddoc).then(function () { |
| var docs = [{foo: 'bar'}, {foo: 'baz'}, {}, {foo: 'quux'}]; |
| return db.bulkDocs({docs: docs}); |
| }).then(function () { |
| return db.replicate.to(dbs.remote); |
| }).then(function (res) { |
| res.ok.should.equal(true); |
| res.docs_read.should.equal(4); |
| res.docs_written.should.equal(3); |
| res.doc_write_failures.should.equal(1); |
| res.errors.should.have.length(1); |
| |
| return remote.allDocs({limit: 0}); |
| }).then(function (res) { |
| res.total_rows.should.equal(4); // 3 plus the validate doc |
| return db.allDocs({limit: 0}); |
| }).then(function (res) { |
| res.total_rows.should.equal(4); // 3 plus the invalid doc |
| }).then(done); |
| }); |
| }); |
| |
| it('#2268 dont stop replication if many unauth', function (done) { |
| |
| testUtils.isCouchDB(function (isCouchDB) { |
| if (adapters[1] !== 'http' || !isCouchDB) { |
| return done(); |
| } |
| |
| var ddoc = { |
| "_id": "_design/validate", |
| "validate_doc_update": function (newDoc) { |
| if (newDoc.foo === undefined) { |
| throw {unauthorized: 'Document must have a foo.'}; |
| } |
| }.toString() |
| }; |
| |
| var remote = new PouchDB(dbs.remote); |
| var db = new PouchDB(dbs.name); |
| |
| return remote.put(ddoc).then(function () { |
| var docs = [{foo: 'bar'}, {foo: 'baz'}, {}, {foo: 'quux'}, {}, {}, |
| {foo: 'toto'}, {}]; |
| return db.bulkDocs({docs: docs}); |
| }).then(function () { |
| return db.replicate.to(dbs.remote); |
| }).then(function (res) { |
| res.ok.should.equal(true); |
| res.docs_read.should.equal(8); |
| res.docs_written.should.equal(4); |
| res.doc_write_failures.should.equal(4); |
| res.errors.should.have.length(4); |
| |
| return remote.allDocs({limit: 0}); |
| }).then(function (res) { |
| res.total_rows.should.equal(5); // 4 plus the validate doc |
| return db.allDocs({limit: 0}); |
| }).then(function (res) { |
| res.total_rows.should.equal(8); // 4 valid and 4 invalid |
| }).then(done); |
| }); |
| }); |
| |
| // Errors from validate_doc_update should have the message |
| // defined in PourchDB.Errors instead of the thrown value. |
| it('#3171 Forbidden validate_doc_update error message', |
| function (done) { |
| testUtils.isCouchDB(function (isCouchDB) { |
| if (adapters[1] !== 'http' || !isCouchDB) { |
| return done(); |
| } |
| |
| var ddoc = { |
| "_id": "_design/validate", |
| "validate_doc_update": function (newDoc) { |
| if (newDoc.foo === 'object') { |
| throw { forbidden: { foo: 'is object' } }; |
| } else if (newDoc.foo === 'string') { |
| throw { forbidden: 'Document foo is string' }; |
| } |
| }.toString() |
| }; |
| |
| var remote = new PouchDB(dbs.remote); |
| var db = new PouchDB(dbs.name); |
| |
| return remote.put(ddoc).then(function () { |
| var docs = [{foo: 'string'}, {}, {foo: 'object'}]; |
| return db.bulkDocs({docs: docs}); |
| }).then(function () { |
| return db.replicate.to(dbs.remote); |
| }).then(function (res) { |
| res.ok.should.equal(true); |
| res.docs_read.should.equal(3); |
| res.docs_written.should.equal(1); |
| res.doc_write_failures.should.equal(2); |
| res.errors.should.have.length(2); |
| res.errors.forEach(function (e) { |
| e.name.should.equal(testUtils.errors.FORBIDDEN.name, |
| 'correct error name returned'); |
| }); |
| |
| return remote.allDocs({limit: 0}); |
| }).then(function (res) { |
| res.total_rows.should.equal(2); // 1 plus the validate doc |
| return db.allDocs({limit: 0}); |
| }).then(function (res) { |
| res.total_rows.should.equal(3); // 1 valid and 2 invalid |
| }).then(done); |
| }); |
| }); |
| |
| it.skip('Test immediate replication canceling', function (done) { |
| //See http://pouchdb.com/guides/replication.html : Cancelling replication |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| var replicationHandler = remote.replicate.to(db, { |
| live: true, |
| retry: true |
| }); |
| |
| replicationHandler.on('complete', function () { |
| done(); |
| }).on('error', done); |
| |
| replicationHandler.cancel(); |
| }); |
| |
| it('#3171 Unauthorized validate_doc_update error message', |
| function (done) { |
| testUtils.isCouchDB(function (isCouchDB) { |
| if (adapters[1] !== 'http' || !isCouchDB) { |
| return done(); |
| } |
| |
| var ddoc = { |
| "_id": "_design/validate", |
| "validate_doc_update": function (newDoc) { |
| if (newDoc.foo === 'object') { |
| throw { unauthorized: { foo: 'is object' } }; |
| } else if (newDoc.foo === 'string') { |
| throw { unauthorized: 'Document foo is string' }; |
| } |
| }.toString() |
| }; |
| |
| var remote = new PouchDB(dbs.remote); |
| var db = new PouchDB(dbs.name); |
| |
| return remote.put(ddoc).then(function () { |
| var docs = [{foo: 'string'}, {}, {foo: 'object'}]; |
| return db.bulkDocs({docs: docs}); |
| }).then(function () { |
| return db.replicate.to(dbs.remote); |
| }).then(function (res) { |
| res.ok.should.equal(true); |
| res.docs_read.should.equal(3); |
| res.docs_written.should.equal(1); |
| res.doc_write_failures.should.equal(2); |
| res.errors.should.have.length(2); |
| res.errors.forEach(function (e) { |
| e.name.should.equal(testUtils.errors.UNAUTHORIZED.name, |
| 'correct error name returned'); |
| }); |
| |
| return remote.allDocs({limit: 0}); |
| }).then(function (res) { |
| res.total_rows.should.equal(2); // 1 plus the validate doc |
| return db.allDocs({limit: 0}); |
| }).then(function (res) { |
| res.total_rows.should.equal(3); // 1 valid and 2 invalid |
| }).then(done); |
| }); |
| }); |
| |
| it('#3070 Doc IDs with validate_doc_update errors', |
| function (done) { |
| testUtils.isCouchDB(function (isCouchDB) { |
| if (adapters[1] !== 'http' || !isCouchDB) { |
| return done(); |
| } |
| |
| var ddoc = { |
| "_id": "_design/validate", |
| "validate_doc_update": function (newDoc) { |
| if (newDoc.foo) { |
| throw { unauthorized: 'go away, no picture' }; |
| } |
| }.toString() |
| }; |
| |
| var remote = new PouchDB(dbs.remote); |
| var db = new PouchDB(dbs.name); |
| |
| return remote.put(ddoc).then(function () { |
| var docs = [{foo: 'string'}, {}, {foo: 'object'}]; |
| return db.bulkDocs({docs: docs}); |
| }).then(function () { |
| return db.replicate.to(dbs.remote); |
| }).then(function (res) { |
| var ids = []; |
| res.ok.should.equal(true); |
| res.docs_read.should.equal(3); |
| res.docs_written.should.equal(1); |
| res.doc_write_failures.should.equal(2); |
| res.errors.should.have.length(2); |
| res.errors.forEach(function (e) { |
| should.exist(e.id, 'get doc id with error message'); |
| ids.push(e.id); |
| }); |
| ids = ids.filter(function (id) { |
| return ids.indexOf(id) === ids.lastIndexOf(id); |
| }); |
| ids.length.should.equal(res.errors.length, |
| 'doc ids are unique'); |
| return remote.allDocs({limit: 0}); |
| }).then(function (res) { |
| res.total_rows.should.equal(2); // 1 plus the validate doc |
| return db.allDocs({limit: 0}); |
| }).then(function (res) { |
| res.total_rows.should.equal(3); // 1 valid and 2 invalid |
| }).then(done); |
| }); |
| }); |
| |
| it('#3270 triggers "denied" events', function (done) { |
| testUtils.isCouchDB(function (isCouchDB) { |
| if (/*adapters[1] !== 'http' || */!isCouchDB) { |
| return done(); |
| } |
| if (adapters[0] !== 'local' || adapters[1] !== 'http') { |
| return done(); |
| } |
| |
| var deniedErrors = []; |
| var ddoc = { |
| "_id": "_design/validate", |
| "validate_doc_update": function (newDoc) { |
| if (newDoc.foo) { |
| throw { unauthorized: 'go away, no picture' }; |
| } |
| }.toString() |
| }; |
| |
| var remote = new PouchDB(dbs.remote); |
| var db = new PouchDB(dbs.name); |
| |
| return remote.put(ddoc).then(function () { |
| var docs = [ |
| {_id: 'foo1', foo: 'string'}, |
| {_id: 'nofoo'}, |
| {_id: 'foo2', foo: 'object'} |
| ]; |
| return db.bulkDocs({docs: docs}); |
| }).then(function () { |
| var replication = db.replicate.to(dbs.remote); |
| replication.on('denied', function (error) { |
| deniedErrors.push(error); |
| }); |
| return replication; |
| }).then(function () { |
| deniedErrors.length.should.equal(2); |
| deniedErrors[0].name.should.equal('unauthorized'); |
| deniedErrors[1].name.should.equal('unauthorized'); |
| done(); |
| }).catch(done); |
| }); |
| }); |
| |
| it('#3606 - live replication with filtered ddoc', function () { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| var Promise = testUtils.Promise; |
| |
| return remote.bulkDocs([{ |
| _id: '_design/myddoc', |
| filters: { |
| myfilter: function (doc) { |
| return doc.name === 'barbara'; |
| }.toString() |
| } |
| }, |
| {_id: 'a', name: 'anna'}, |
| {_id: 'b', name: 'barbara'}, |
| {_id: 'c', name: 'charlie'} |
| ]).then(function () { |
| return new Promise(function (resolve, reject) { |
| var replicate = remote.replicate.to(db, { |
| filter: 'myddoc/myfilter', |
| live: true |
| }).on('change', function () { |
| replicate.cancel(); |
| }).on('complete', resolve) |
| .on('error', reject); |
| }); |
| }).then(function () { |
| return db.allDocs(); |
| }).then(function (res) { |
| res.rows.should.have.length(1); |
| res.rows[0].id.should.equal('b'); |
| }); |
| }); |
| |
| it('#3606 - live repl with filtered ddoc+query_params', function () { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| var Promise = testUtils.Promise; |
| |
| return remote.bulkDocs([{ |
| _id: '_design/myddoc', |
| filters: { |
| myfilter: function (doc, req) { |
| return doc.name === req.query.name; |
| }.toString() |
| } |
| }, |
| {_id: 'a', name: 'anna'}, |
| {_id: 'b', name: 'barbara'}, |
| {_id: 'c', name: 'charlie'} |
| ]).then(function () { |
| return new Promise(function (resolve, reject) { |
| var replicate = remote.replicate.to(db, { |
| filter: 'myddoc/myfilter', |
| query_params: {name: 'barbara'}, |
| live: true |
| }).on('change', function () { |
| replicate.cancel(); |
| }).on('complete', resolve) |
| .on('error', reject); |
| }); |
| }).then(function () { |
| return db.allDocs(); |
| }).then(function (res) { |
| res.rows.should.have.length(1); |
| res.rows[0].id.should.equal('b'); |
| }); |
| }); |
| |
| it('#3606 - live repl with doc_ids', function () { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| var Promise = testUtils.Promise; |
| |
| return remote.bulkDocs([{ |
| _id: '_design/myddoc', |
| filters: { |
| myfilter: function (doc, req) { |
| return doc.name === req.query.name; |
| }.toString() |
| } |
| }, |
| {_id: 'a', name: 'anna'}, |
| {_id: 'b', name: 'barbara'}, |
| {_id: 'c', name: 'charlie'} |
| ]).then(function () { |
| return new Promise(function (resolve, reject) { |
| var replicate = remote.replicate.to(db, { |
| doc_ids: ['b'], |
| live: true |
| }).on('change', function () { |
| replicate.cancel(); |
| }).on('complete', resolve) |
| .on('error', reject); |
| }); |
| }).then(function () { |
| return db.allDocs(); |
| }).then(function (res) { |
| res.rows.should.have.length(1); |
| res.rows[0].id.should.equal('b'); |
| }); |
| }); |
| |
| it('#3606 - live repl with view', function () { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| var Promise = testUtils.Promise; |
| |
| return remote.bulkDocs([{ |
| _id: '_design/myddoc', |
| views: { |
| mymap: { |
| map: function (doc) { |
| if (doc.name === 'barbara') { |
| emit(doc._id, null); |
| } |
| }.toString() |
| } |
| } |
| }, |
| {_id: 'a', name: 'anna'}, |
| {_id: 'b', name: 'barbara'}, |
| {_id: 'c', name: 'charlie'} |
| ]).then(function () { |
| return new Promise(function (resolve, reject) { |
| var replicate = remote.replicate.to(db, { |
| filter: '_view', |
| view: 'myddoc/mymap', |
| live: true |
| }).on('change', function () { |
| replicate.cancel(); |
| }).on('complete', resolve) |
| .on('error', reject); |
| }); |
| }).then(function () { |
| return db.allDocs(); |
| }).then(function (res) { |
| res.rows.should.have.length(1); |
| res.rows[0].id.should.equal('b'); |
| }); |
| }); |
| |
| it('#3569 - 409 during replication', function () { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| var Promise = testUtils.Promise; |
| |
| // we know we're easily going to go over that limit |
| // because of all the parallel replications we're doing |
| db.setMaxListeners(100); |
| |
| function timeoutPromise(delay, fun) { |
| return new Promise(function (resolve) { |
| setTimeout(resolve, delay); |
| }).then(fun); |
| } |
| |
| return Promise.all([ |
| db.put({_id: 'foo'}).then(function () { |
| return db.get('foo'); |
| }).then(function (doc) { |
| return db.remove(doc); |
| }).then(function () { |
| return db.replicate.to(remote); |
| }), |
| db.replicate.to(remote), |
| timeoutPromise(0, function () { |
| return db.replicate.to(remote); |
| }), |
| timeoutPromise(1, function () { |
| return db.replicate.to(remote); |
| }), |
| timeoutPromise(2, function () { |
| return db.replicate.to(remote); |
| }) |
| ]).then(function () { |
| return db.info(); |
| }).then(function (localInfo) { |
| return remote.info().then(function (remoteInfo) { |
| localInfo.doc_count.should.equal(remoteInfo.doc_count); |
| }); |
| }); |
| }); |
| |
| it('#3270 triggers "change" events with .docs property', function (done) { |
| var replicatedDocs = []; |
| var db = new PouchDB(dbs.name); |
| db.bulkDocs({ docs: docs }, {}).then(function () { |
| var replication = db.replicate.to(dbs.remote); |
| replication.on('change', function (change) { |
| replicatedDocs = replicatedDocs.concat(change.docs); |
| }); |
| return replication; |
| }) |
| .then(function () { |
| replicatedDocs.sort(function (a, b) { |
| return a._id > b._id ? 1 : -1; |
| }); |
| replicatedDocs.length.should.equal(3); |
| replicatedDocs[0]._id.should.equal('0'); |
| replicatedDocs[1]._id.should.equal('1'); |
| replicatedDocs[2]._id.should.equal('2'); |
| done(); |
| }) |
| .catch(done); |
| }); |
| |
| it('#3543 replication with a ddoc filter', function () { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| |
| return remote.bulkDocs([{ |
| _id: '_design/myddoc', |
| filters: { |
| myfilter: function (doc) { |
| return doc._id === 'a'; |
| }.toString() |
| } |
| }, |
| {_id: 'a'}, |
| {_id: 'b'}, |
| {_id: 'c'} |
| ]).then(function () { |
| return remote.replicate.to(db, {filter: 'myddoc/myfilter'}); |
| }).then(function () { |
| return db.allDocs(); |
| }).then(function (res) { |
| res.rows.should.have.length(1); |
| res.rows[0].id.should.equal('a'); |
| }); |
| }); |
| |
| it("#3578 replication with a ddoc filter w/ _deleted=true", function () { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| |
| return remote.bulkDocs([{ |
| _id: '_design/myddoc', |
| filters: { |
| myfilter: function (doc) { |
| return doc._id === 'a' || doc._id === 'b'; |
| }.toString() |
| } |
| }, |
| {_id: 'a'}, |
| {_id: 'b'}, |
| {_id: 'c'} |
| ]).then(function () { |
| return remote.replicate.to(db, {filter: 'myddoc/myfilter'}); |
| }).then(function () { |
| return db.allDocs(); |
| }).then(function (res) { |
| res.rows.should.have.length(2); |
| }).then(function () { |
| return remote.get('a'); |
| }).then(function (doc) { |
| doc._deleted = true; |
| return remote.put(doc); |
| }).then(function () { |
| return remote.replicate.to(db, {filter: 'myddoc/myfilter'}); |
| }).then(function () { |
| return db.allDocs(); |
| }).then(function (res) { |
| res.rows.should.have.length(1); |
| }); |
| }); |
| |
| it("#3578 replication with a ddoc filter w/ remove()", function () { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| |
| return remote.bulkDocs([{ |
| _id: '_design/myddoc', |
| filters: { |
| myfilter: function (doc) { |
| return doc._id === 'a' || doc._id === 'b'; |
| }.toString() |
| } |
| }, |
| {_id: 'a'}, |
| {_id: 'b'}, |
| {_id: 'c'} |
| ]).then(function () { |
| return remote.replicate.to(db, {filter: 'myddoc/myfilter'}); |
| }).then(function () { |
| return db.allDocs(); |
| }).then(function (res) { |
| res.rows.should.have.length(2); |
| }).then(function () { |
| return remote.get('a'); |
| }).then(function (doc) { |
| return remote.remove(doc); |
| }).then(function () { |
| return remote.replicate.to(db, {filter: 'myddoc/myfilter'}); |
| }).then(function () { |
| return db.allDocs(); |
| }).then(function (docs) { |
| docs.rows.should.have.length(1); |
| }); |
| }); |
| |
| it("#2454 info() call breaks taskqueue", function (done) { |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| |
| remote.bulkDocs(docs).then(function () { |
| |
| var repl = db.replicate.from(remote, {live: true}); |
| repl.on('complete', done.bind(null, null)); |
| |
| remote.info().then(function () { |
| repl.cancel(); |
| }).catch(done); |
| }).catch(done); |
| }); |
| |
| |
| it('4094 cant fetch server uuid', function (done) { |
| |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| |
| var ajax = remote._ajax; |
| |
| remote._ajax = function (opts, cb) { |
| var uri = testUtils.parseUri(opts.url); |
| if (uri.path === '/') { |
| cb(new Error('flunking you')); |
| } else { |
| ajax.apply(this, arguments); |
| } |
| }; |
| |
| var _complete = 0; |
| function complete() { |
| if (++_complete === 2) { |
| remote._ajax = ajax; |
| done(); |
| } |
| } |
| |
| var rep = db.replicate.from(remote, {live: true, retry: true}) |
| .on('complete', complete); |
| |
| var changes = db.changes({live: true}).on('change', function () { |
| rep.cancel(); |
| changes.cancel(); |
| }).on('complete', complete); |
| |
| remote.post({a: 'doc'}); |
| }); |
| |
| it('#4293 Triggers extra replication events', function (done) { |
| |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| |
| var hasChange = false; |
| function change() { |
| hasChange = true; |
| } |
| |
| var _complete = 0; |
| function complete() { |
| if (++_complete === 2) { |
| hasChange.should.equal(false); |
| done(); |
| } |
| } |
| |
| function paused() { |
| // Because every setTimeout should be justified :) |
| // We are testing a negative, that there are no extra events |
| // triggered from our replication, cancelling the replication will |
| // cancel the event anyway so we wait a short period and give it time |
| // to fire (since there is nothing to wait deteministically for) |
| // Without the setTimeout this will pass, just less likely to catch |
| // the failing case |
| setTimeout(function () { |
| push.cancel(); |
| pull.cancel(); |
| }, 100); |
| } |
| |
| var push = remote.replicate.from(db, {live: true}) |
| .on('paused', paused) |
| .on('complete', complete); |
| |
| var pull = db.replicate.from(remote, {live: true}) |
| .on('change', change) |
| .on('complete', complete); |
| |
| db.post({a: 'doc'}); |
| }); |
| |
| it('Heartbeat gets passed', function () { |
| |
| if (!(/http/.test(dbs.remote) && !/http/.test(dbs.name))) { |
| return true; |
| } |
| |
| var seenHeartBeat = false; |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote, { |
| fetch: function (url, opts) { |
| if (/heartbeat/.test(url)) { |
| seenHeartBeat = true; |
| } |
| return PouchDB.fetch(url, opts); |
| } |
| }); |
| |
| return remote.bulkDocs([{foo: 'bar'}]).then(function () { |
| return db.replicate.from(remote, {heartbeat: 10}); |
| }).then(function () { |
| seenHeartBeat.should.equal(true); |
| }); |
| }); |
| |
| it('Timeout gets passed', function () { |
| |
| if (!(/http/.test(dbs.remote) && !/http/.test(dbs.name))) { |
| return true; |
| } |
| |
| var seenTimeout; |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote, { |
| fetch: function (url, opts) { |
| if (/timeout=20000/.test(url)) { |
| seenTimeout = true; |
| } |
| return PouchDB.fetch(url, opts); |
| } |
| }); |
| |
| return remote.bulkDocs([{foo: 'bar'}]).then(function () { |
| return db.replicate.from(remote, {timeout: 20000}); |
| }).then(function () { |
| seenTimeout.should.equal(true); |
| }); |
| }); |
| |
| it('#5452 Cleanly fail with no unhandled promises on a bad connection', function (done) { |
| |
| if (!/http/.test(dbs.remote)) { |
| return done(); |
| } |
| |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB('http://localhost:9382/does_not_exist', {skip_setup: true}); |
| |
| remote.replicate.to(db, { |
| live: true, |
| since: 0, |
| timeout: 20000 |
| }).catch(function () { |
| done(); |
| }); |
| }); |
| |
| it('#2426 doc_ids dont prevent replication', function () { |
| |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| |
| var writes = []; |
| for (var i = 0; i < 20; i++) { |
| writes.push(remote.put({_id: i + ''})); |
| } |
| |
| return testUtils.Promise.all(writes).then(function () { |
| return db.sync(remote, {batch_size: 10, doc_ids: ['11', '12', '13']}); |
| }).then(function () { |
| return db.allDocs(); |
| }).then(function (allDocs) { |
| allDocs.total_rows.should.equal(3); |
| }); |
| }); |
| |
| it('Replication filter using selector', function (done) { |
| // only supported in CouchDB 2.x and later |
| if (!testUtils.isCouchMaster()) { |
| done(); |
| return; |
| } |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| var docs1 = [ |
| {_id: '0', user: 'foo'}, |
| {_id: '1', user: 'bar'}, |
| {_id: '2', user: 'foo'}, |
| {_id: '3', user: 'bar'} |
| ]; |
| remote.bulkDocs(docs1, function () { |
| db.replicate.from(remote, { |
| selector: {'user':'foo'} |
| }).on('error', done).on('complete', function () { |
| db.allDocs(function (err, docs) { |
| if (err) { done(err); } |
| docs.rows.length.should.equal(2); |
| db.info(function (err, info) { |
| verifyInfo(info, { |
| doc_count: 2 |
| }); |
| done(); |
| }); |
| }); |
| }); |
| }); |
| }); |
| |
| it('Invalid selector', function () { |
| // only supported in CouchDB 2.x and later or PouchDB |
| if (!testUtils.isCouchMaster()) { |
| return; |
| } |
| |
| var db = new PouchDB(dbs.name); |
| var remote = new PouchDB(dbs.remote); |
| var thedocs = [ |
| {_id: '3', integer: 3, string: '3'}, |
| {_id: '4', integer: 4, string: '4'}, |
| {_id: '5', integer: 5, string: '5'} |
| ]; |
| return remote.bulkDocs(thedocs).then(function () { |
| return db.replicate.from(remote, {selector: 'foo'}); |
| }).catch(function (err) { |
| err.name.should.equal('bad_request'); |
| err.reason.should.contain('expected a JSON object'); |
| }); |
| }); |
| }); |
| }); |
| |
| // This test only needs to run for one configuration, and it slows stuff |
| // down |
| downAdapters.map(function () { |
| |
| describe('suite2 test.replication.js-down-test', function () { |
| |
| var dbs = {}; |
| |
| beforeEach(function (done) { |
| dbs.name = testUtils.adapterUrl(adapters[0], 'testdb'); |
| testUtils.cleanup([dbs.name], done); |
| }); |
| |
| afterEach(function (done) { |
| testUtils.cleanup([dbs.name], done); |
| }); |
| |
| it('replicate from down server test', function (done) { |
| var source = new PouchDB('http://infiniterequest.com', { |
| ajax: {timeout: 10} |
| }); |
| var target = new PouchDB(dbs.name); |
| source.replicate.to(target, function (err) { |
| should.exist(err); |
| done(); |
| }); |
| }); |
| |
| }); |
| }); |
| |
| function interceptChanges(source, interceptFunction) { |
| var changes = source.changes; |
| source.changes = function (opts) { |
| interceptFunction(opts); |
| return changes.apply(source, arguments); |
| }; |
| } |
| |
| function assertSince(opts, expectedSince) { |
| if (expectedSince !== false) { |
| if (typeof opts.since === 'number') { |
| opts.since.should.equal(expectedSince); |
| } else if (typeof opts.since === 'string') { |
| opts.since.should.match(new RegExp('^' + expectedSince + '-')); |
| } else { |
| throw new Error('Can\'t handle type for opts.since: ' + (typeof opts.since) + ' (value=' + opts.since + ')'); |
| } |
| } |
| } |