3667 lines
114 KiB
JavaScript
3667 lines
114 KiB
JavaScript
|
'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();
|
||
|
});
|
||
|
});
|
||
|
|
||
|
});
|
||
|
});
|