'use strict'; var PouchDB = require('./pouchdb'); var should = require('chai').should(); var testUtils = require('./test.utils.js'); var adapters = [ ['local', 'local'] ]; adapters.forEach(function (adapters) { describe('suite2 test.replication.js-' + adapters[0] + '-' + adapters[1], function () { var dbs = {}; beforeEach(function (done) { dbs.name = testUtils.adapterUrl(adapters[0], 'testdb'); dbs.remote = testUtils.adapterUrl(adapters[1], 'test_repl_remote'); testUtils.cleanup([dbs.name, dbs.remote], done); }); after(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) { if (testUtils.isSyncGateway() && change.doc && change.doc._conflicts) { // CSG does not render conflict metadata inline // in the document. Remove it for comparisons. delete change.doc._conflicts; } 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() || testUtils.isSyncGateway()) { changes.sort(function (a, b) { return a.id > b.id; }); } // CSG will send a change event when just the ACL changed if (testUtils.isSyncGateway()) { changes = changes.filter(function(change){ return change.id !== "_user/"; }); } return changes; } function verifyInfo(info, expected) { if (!testUtils.isCouchMaster()) { if (typeof info.doc_count === 'undefined') { // info is from Sync Gateway, which allocates an extra seqnum // for user access control purposes. info.update_seq.should.be.within(expected.update_seq, expected.update_seq + 1, 'update_seq'); } else { info.update_seq.should.equal(expected.update_seq, 'update_seq'); } } if (info.doc_count) { // info is NOT from Sync Gateway 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.skip('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 PouchDB.utils.uuid(32, 16).toLowerCase(); } 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) { if (!testUtils.isSyncGateway() || info.doc_count) { 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) { if (!testUtils.isSyncGateway() || info.doc_count) { 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) { if (testUtils.isSyncGateway() && !res.rows[0].value) { return remote.get('foo', {open_revs:'all'}).then(function(doc){ return db.put({_id: 'foo', _rev: doc[0].ok._rev}); }); } else { 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.skip('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-' + PouchDB.utils.uuid(32, 16).toLowerCase(); 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); }); }); }); }); 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'); } if (!testUtils.isSyncGateway() || info.doc_count) { 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 live push checkpoint', function (done) { var db = new PouchDB(dbs.name); var remote = new PouchDB(dbs.remote); function complete(details) { if (testUtils.isSyncGateway()) { // TODO investigate why Sync Gateway sometimes reads a // document. This seems to come up 1 more in the browser // and 0 more in node, but I've seen 1 in node. details.docs_read.should.be.within(0, 1); } else { 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('#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 = PouchDB.utils.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 = PouchDB.utils.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 = PouchDB.utils.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 = PouchDB.utils.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 PouchDB.utils.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.skip('Test _conflicts key', 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'}; 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) { new PouchDB(dbs.remote, function (err, 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('Replication with deleted doc', 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}, {_id: '4', integer: 4, _deleted: true} ]; remote.bulkDocs({ docs: docs1 }, function () { db.replicate.from(remote, function () { db.allDocs(function (err, res) { res.total_rows.should.equal(4); db.info(function (err, info) { verifyInfo(info, { update_seq: 5, doc_count: 4 }); done(); }); }); }); }); }); 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 PouchDB.utils.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 = PouchDB.utils.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('#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 var changes = source.changes; source.changes = 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; } return changes.apply(source, arguments); }; 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 PouchDB.utils.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 var changes = source.changes; source.changes = 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; } return changes.apply(source, arguments); }; 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 PouchDB.utils.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; var changes = source.changes; source.changes = 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); } return changes.apply(source, arguments); }; 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 PouchDB.utils.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(); }); }); }); 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 PouchDB.utils.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 PouchDB.utils.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.skip('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); 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.skip('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 PouchDB.utils.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({}, 'foo').then(function () { return remote.put({}, 'bar'); }).then(function () { setTimeout(function () { done(); }, 500); }); } }); remote.put({}, 'hazaa'); }); it('#2970 should replicate remote database w/ deleted conflicted revs', function (done) { var local = new PouchDB(dbs.name); var remote = new PouchDB(dbs.remote); var docid = "mydoc"; function uuid() { return PouchDB.utils.uuid(32, 16).toLowerCase(); } // create a bunch of rando, good revisions var numRevs = 5; var uuids = []; for (var i = 0; i < numRevs - 1; i++) { uuids.push(uuid()); } // good branch // this branch is one revision ahead of the conflicted branch var a_conflict = uuid(); var a_burner = uuid(); var a_latest = uuid(); 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 = uuid(); var b_deleted = uuid(); 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'); }); }) .then(function () { done(); }, done); }); // 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.skip('#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); 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.skip('#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.skip('#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.skip('#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.status.should.equal(PouchDB.Errors.FORBIDDEN.status, 'correct error status returned'); e.name.should.equal(PouchDB.Errors.FORBIDDEN.name, 'correct error name returned'); e.message.should.equal(PouchDB.Errors.FORBIDDEN.message, 'correct error message 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 = PouchDB.utils.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 = PouchDB.utils.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 = PouchDB.utils.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 = PouchDB.utils.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 = PouchDB.utils.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 ajax = PouchDB.utils.ajax; PouchDB.utils.ajax = function (opts, cb) { var uri = PouchDB.utils.parseUri(opts.url); if (uri.path === '/') { cb(new Error('flunking you')); } else { ajax.apply(this, arguments); } }; var db = new PouchDB(dbs.name); var remote = new PouchDB(dbs.remote); var _complete = 0; function complete() { if (++_complete === 2) { PouchDB.utils.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('#4276 Triggers paused error', function (done) { if (!(/http/.test(dbs.remote) && !/http/.test(dbs.name))) { return done(); } var err = { "message": "_writer access is required for this request", "name": "unauthorized", "status": 401 }; var ajax = PouchDB.utils.ajax; PouchDB.utils.ajax = function (opts, cb) { cb(err); }; var db = new PouchDB(dbs.name); var remote = new PouchDB(dbs.remote); db.bulkDocs([{foo: 'bar'}]).then(function() { var repl = db.replicate.to(remote, {live: true, retry: true}); repl.on('paused', function(err) { if (err) { repl.cancel(); } }); repl.on('complete', function() { PouchDB.utils.ajax = ajax; done(); }); }); }); it('Heartbeat gets passed', function (done) { if (!(/http/.test(dbs.remote) && !/http/.test(dbs.name))) { return done(); } var seenHeartBeat = false; var ajax = PouchDB.utils.ajax; PouchDB.utils.ajax = function (opts) { if (/heartbeat/.test(opts.url)) { seenHeartBeat = true; } ajax.apply(this, arguments); }; var db = new PouchDB(dbs.name); var remote = new PouchDB(dbs.remote); return remote.bulkDocs([{foo: 'bar'}]).then(function() { return db.replicate.from(remote, {heartbeat: 10}); }).then(function() { seenHeartBeat.should.equal(true); PouchDB.utils.ajax = ajax; done(); }); }); it('Timeout gets passed', function (done) { if (!(/http/.test(dbs.remote) && !/http/.test(dbs.name))) { return done(); } var seenTimeout = false; var ajax = PouchDB.utils.ajax; PouchDB.utils.ajax = function (opts) { // the http adapter takes 5s off the provided timeout if (/timeout=15000/.test(opts.url)) { seenTimeout = true; } ajax.apply(this, arguments); }; var db = new PouchDB(dbs.name); var remote = new PouchDB(dbs.remote); return remote.bulkDocs([{foo: 'bar'}]).then(function() { return db.replicate.from(remote, {timeout: 20000}); }).then(function() { seenTimeout.should.equal(true); PouchDB.utils.ajax = ajax; done(); }); }); }); });