blob: fd60dd41b9ed819751cb72ef24ef168050197925 [file] [log] [blame]
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
couchTests.replication = function(debug) {
if (debug) debugger;
var host = CouchDB.host;
var sourceDb = new CouchDB("test_suite_db_a",{"X-Couch-Full-Commit":"false"});
var targetDb = new CouchDB("test_suite_db_b",{"X-Couch-Full-Commit":"false"});
var dbPairs = [
{
source: sourceDb.name,
target: targetDb.name
},
{
source: CouchDB.protocol + host + "/" + sourceDb.name,
target: targetDb.name
},
{
source: sourceDb.name,
target: CouchDB.protocol + host + "/" + targetDb.name
},
{
source: CouchDB.protocol + host + "/" + sourceDb.name,
target: CouchDB.protocol + host + "/" + targetDb.name
}
];
var att1_data = CouchDB.request("GET", "/_utils/script/test/lorem.txt");
att1_data = att1_data.responseText;
var att2_data = CouchDB.request("GET", "/_utils/script/test/lorem_b64.txt");
att2_data = att2_data.responseText;
var sourceInfo, targetInfo;
var docs, doc, copy;
var repResult;
var i, j, k;
function makeAttData(minSize) {
var data = att1_data;
while (data.length < minSize) {
data = data + att1_data;
}
return data;
}
function enableAttCompression(level, types) {
var xhr = CouchDB.request(
"PUT",
"/_config/attachments/compression_level",
{
body: JSON.stringify(level),
headers: {"X-Couch-Persist": "false"}
}
);
T(xhr.status === 200);
xhr = CouchDB.request(
"PUT",
"/_config/attachments/compressible_types",
{
body: JSON.stringify(types),
headers: {"X-Couch-Persist": "false"}
}
);
T(xhr.status === 200);
}
function disableAttCompression() {
var xhr = CouchDB.request(
"PUT",
"/_config/attachments/compression_level",
{
body: JSON.stringify("0"),
headers: {"X-Couch-Persist": "false"}
}
);
T(xhr.status === 200);
}
function populateDb(db, docs, dontRecreateDb) {
if (dontRecreateDb !== true) {
db.deleteDb();
wait(100);
db.createDb();
}
for (var i = 0; i < docs.length; i++) {
var doc = docs[i];
delete doc._rev;
}
if (docs.length > 0) {
db.bulkSave(docs);
}
}
function addAtt(db, doc, attName, attData, type) {
var uri = "/" + db.name + "/" + encodeURIComponent(doc._id) + "/" + attName;
if (doc._rev) {
uri += "?rev=" + doc._rev;
}
var xhr = CouchDB.request("PUT", uri, {
headers: {
"Content-Type": type
},
body: attData
});
T(xhr.status === 201);
doc._rev = JSON.parse(xhr.responseText).rev;
}
function compareObjects(o1, o2) {
for (var p in o1) {
if (o1[p] === null && o2[p] !== null) {
return false;
} else if (typeof o1[p] === "object") {
if ((typeof o2[p] !== "object") || o2[p] === null) {
return false;
}
if (!arguments.callee(o1[p], o2[p])) {
return false;
}
} else {
if (o1[p] !== o2[p]) {
return false;
}
}
}
return true;
}
function waitForSeq(sourceDb, targetDb) {
var targetSeq,
sourceSeq = sourceDb.info().update_seq,
t0 = new Date(),
t1,
ms = 3000;
do {
targetSeq = targetDb.info().update_seq;
t1 = new Date();
} while (((t1 - t0) <= ms) && targetSeq < sourceSeq);
}
function wait(ms) {
var t0 = new Date(), t1;
do {
CouchDB.request("GET", "/");
t1 = new Date();
} while ((t1 - t0) <= ms);
}
// test simple replications (not continuous, not filtered), including
// conflict creation
docs = makeDocs(1, 21);
docs.push({
_id: "_design/foo",
language: "javascript",
value: "ddoc"
});
for (i = 0; i < dbPairs.length; i++) {
populateDb(sourceDb, docs);
populateDb(targetDb, []);
// add some attachments
for (j = 10; j < 15; j++) {
addAtt(sourceDb, docs[j], "readme.txt", att1_data, "text/plain");
}
repResult = CouchDB.replicate(dbPairs[i].source, dbPairs[i].target);
TEquals(true, repResult.ok);
sourceInfo = sourceDb.info();
targetInfo = targetDb.info();
TEquals(sourceInfo.doc_count, targetInfo.doc_count);
TEquals('string', typeof repResult.session_id);
TEquals(repResult.source_last_seq, sourceInfo.update_seq);
TEquals(true, repResult.history instanceof Array);
TEquals(1, repResult.history.length);
TEquals(repResult.history[0].session_id, repResult.session_id);
TEquals('string', typeof repResult.history[0].start_time);
TEquals('string', typeof repResult.history[0].end_time);
TEquals(0, repResult.history[0].start_last_seq);
TEquals(sourceInfo.update_seq, repResult.history[0].end_last_seq);
TEquals(sourceInfo.update_seq, repResult.history[0].recorded_seq);
TEquals(sourceInfo.doc_count, repResult.history[0].missing_checked);
TEquals(sourceInfo.doc_count, repResult.history[0].missing_found);
TEquals(sourceInfo.doc_count, repResult.history[0].docs_read);
TEquals(sourceInfo.doc_count, repResult.history[0].docs_written);
TEquals(0, repResult.history[0].doc_write_failures);
for (j = 0; j < docs.length; j++) {
doc = docs[j];
copy = targetDb.open(doc._id);
T(copy !== null);
TEquals(true, compareObjects(doc, copy));
if (j >= 10 && j < 15) {
var atts = copy._attachments;
TEquals('object', typeof atts);
TEquals('object', typeof atts["readme.txt"]);
TEquals(2, atts["readme.txt"].revpos);
TEquals(0, atts["readme.txt"].content_type.indexOf("text/plain"));
TEquals(true, atts["readme.txt"].stub);
var att_copy = CouchDB.request(
"GET", "/" + targetDb.name + "/" + copy._id + "/readme.txt"
).responseText;
TEquals(att1_data.length, att_copy.length);
TEquals(att1_data, att_copy);
}
}
// add one more doc to source, more attachments to some existing docs
// and replicate again
var newDoc = {
_id: "foo666",
value: "d"
};
TEquals(true, sourceDb.save(newDoc).ok);
// add some more attachments
for (j = 10; j < 15; j++) {
addAtt(sourceDb, docs[j], "data.dat", att2_data, "application/binary");
}
repResult = CouchDB.replicate(dbPairs[i].source, dbPairs[i].target);
TEquals(true, repResult.ok);
sourceInfo = sourceDb.info();
targetInfo = targetDb.info();
TEquals(targetInfo.doc_count, sourceInfo.doc_count);
TEquals('string', typeof repResult.session_id);
TEquals(sourceInfo.update_seq, repResult.source_last_seq);
TEquals(true, repResult.history instanceof Array);
TEquals(2, repResult.history.length);
TEquals(repResult.history[0].session_id, repResult.session_id);
TEquals('string', typeof repResult.history[0].start_time);
TEquals('string', typeof repResult.history[0].end_time);
TEquals((sourceInfo.update_seq - 6), repResult.history[0].start_last_seq);
TEquals(sourceInfo.update_seq, repResult.history[0].end_last_seq);
TEquals(sourceInfo.update_seq, repResult.history[0].recorded_seq);
TEquals(6, repResult.history[0].missing_checked);
TEquals(6, repResult.history[0].missing_found);
TEquals(6, repResult.history[0].docs_read);
TEquals(6, repResult.history[0].docs_written);
TEquals(0, repResult.history[0].doc_write_failures);
copy = targetDb.open(newDoc._id);
T(copy !== null);
TEquals(newDoc._id, copy._id);
TEquals(newDoc.value, copy.value);
for (j = 10; j < 15; j++) {
doc = docs[j];
copy = targetDb.open(doc._id);
T(copy !== null);
TEquals(true, compareObjects(doc, copy));
var atts = copy._attachments;
TEquals('object', typeof atts);
TEquals('object', typeof atts["readme.txt"]);
TEquals(2, atts["readme.txt"].revpos);
TEquals(0, atts["readme.txt"].content_type.indexOf("text/plain"));
TEquals(true, atts["readme.txt"].stub);
var att1_copy = CouchDB.request(
"GET", "/" + targetDb.name + "/" + copy._id + "/readme.txt"
).responseText;
TEquals(att1_data.length, att1_copy.length);
TEquals(att1_data, att1_copy);
TEquals('object', typeof atts["data.dat"]);
TEquals(3, atts["data.dat"].revpos);
TEquals(0, atts["data.dat"].content_type.indexOf("application/binary"));
TEquals(true, atts["data.dat"].stub);
var att2_copy = CouchDB.request(
"GET", "/" + targetDb.name + "/" + copy._id + "/data.dat"
).responseText;
TEquals(att2_data.length, att2_copy.length);
TEquals(att2_data, att2_copy);
}
// test deletion is replicated
doc = sourceDb.open(docs[1]._id);
TEquals(true, sourceDb.deleteDoc(doc).ok);
repResult = CouchDB.replicate(dbPairs[i].source, dbPairs[i].target);
TEquals(true, repResult.ok);
sourceInfo = sourceDb.info();
targetInfo = targetDb.info();
TEquals(targetInfo.doc_count, sourceInfo.doc_count);
TEquals(targetInfo.doc_del_count, sourceInfo.doc_del_count);
TEquals(1, targetInfo.doc_del_count);
TEquals(true, repResult.history instanceof Array);
TEquals(3, repResult.history.length);
TEquals((sourceInfo.update_seq - 1), repResult.history[0].start_last_seq);
TEquals(sourceInfo.update_seq, repResult.history[0].end_last_seq);
TEquals(sourceInfo.update_seq, repResult.history[0].recorded_seq);
TEquals(1, repResult.history[0].missing_checked);
TEquals(1, repResult.history[0].missing_found);
TEquals(1, repResult.history[0].docs_read);
TEquals(1, repResult.history[0].docs_written);
TEquals(0, repResult.history[0].doc_write_failures);
copy = targetDb.open(docs[1]._id);
TEquals(null, copy);
var changes = targetDb.changes({since: 0});
var idx = changes.results.length - 1;
TEquals(docs[1]._id, changes.results[idx].id);
TEquals(true, changes.results[idx].deleted);
// test conflict
doc = sourceDb.open(docs[0]._id);
doc.value = "white";
TEquals(true, sourceDb.save(doc).ok);
copy = targetDb.open(docs[0]._id);
copy.value = "black";
TEquals(true, targetDb.save(copy).ok);
repResult = CouchDB.replicate(dbPairs[i].source, dbPairs[i].target);
TEquals(true, repResult.ok);
sourceInfo = sourceDb.info();
targetInfo = targetDb.info();
TEquals(sourceInfo.doc_count, targetInfo.doc_count);
TEquals(true, repResult.history instanceof Array);
TEquals(4, repResult.history.length);
TEquals((sourceInfo.update_seq - 1), repResult.history[0].start_last_seq);
TEquals(sourceInfo.update_seq, repResult.history[0].end_last_seq);
TEquals(sourceInfo.update_seq, repResult.history[0].recorded_seq);
TEquals(1, repResult.history[0].missing_checked);
TEquals(1, repResult.history[0].missing_found);
TEquals(1, repResult.history[0].docs_read);
TEquals(1, repResult.history[0].docs_written);
TEquals(0, repResult.history[0].doc_write_failures);
copy = targetDb.open(docs[0]._id, {conflicts: true});
TEquals(0, copy._rev.indexOf("2-"));
TEquals(true, copy._conflicts instanceof Array);
TEquals(1, copy._conflicts.length);
TEquals(0, copy._conflicts[0].indexOf("2-"));
// replicate again with conflict
doc.value = "yellow";
TEquals(true, sourceDb.save(doc).ok);
repResult = CouchDB.replicate(dbPairs[i].source, dbPairs[i].target);
TEquals(true, repResult.ok);
sourceInfo = sourceDb.info();
targetInfo = targetDb.info();
TEquals(sourceInfo.doc_count, targetInfo.doc_count);
TEquals(true, repResult.history instanceof Array);
TEquals(5, repResult.history.length);
TEquals((sourceInfo.update_seq - 1), repResult.history[0].start_last_seq);
TEquals(sourceInfo.update_seq, repResult.history[0].end_last_seq);
TEquals(sourceInfo.update_seq, repResult.history[0].recorded_seq);
TEquals(1, repResult.history[0].missing_checked);
TEquals(1, repResult.history[0].missing_found);
TEquals(1, repResult.history[0].docs_read);
TEquals(1, repResult.history[0].docs_written);
TEquals(0, repResult.history[0].doc_write_failures);
copy = targetDb.open(docs[0]._id, {conflicts: true});
TEquals(0, copy._rev.indexOf("3-"));
TEquals(true, copy._conflicts instanceof Array);
TEquals(1, copy._conflicts.length);
TEquals(0, copy._conflicts[0].indexOf("2-"));
// resolve the conflict
TEquals(true, targetDb.deleteDoc({_id: copy._id, _rev: copy._conflicts[0]}).ok);
// replicate again, check there are no more conflicts
doc.value = "rainbow";
TEquals(true, sourceDb.save(doc).ok);
repResult = CouchDB.replicate(dbPairs[i].source, dbPairs[i].target);
TEquals(true, repResult.ok);
sourceInfo = sourceDb.info();
targetInfo = targetDb.info();
TEquals(sourceInfo.doc_count, targetInfo.doc_count);
TEquals(true, repResult.history instanceof Array);
TEquals(6, repResult.history.length);
TEquals((sourceInfo.update_seq - 1), repResult.history[0].start_last_seq);
TEquals(sourceInfo.update_seq, repResult.history[0].end_last_seq);
TEquals(sourceInfo.update_seq, repResult.history[0].recorded_seq);
TEquals(1, repResult.history[0].missing_checked);
TEquals(1, repResult.history[0].missing_found);
TEquals(1, repResult.history[0].docs_read);
TEquals(1, repResult.history[0].docs_written);
TEquals(0, repResult.history[0].doc_write_failures);
copy = targetDb.open(docs[0]._id, {conflicts: true});
TEquals(0, copy._rev.indexOf("4-"));
TEquals('undefined', typeof copy._conflicts);
// test that revisions already in a target are not copied
TEquals(true, sourceDb.save({_id: "foo1", value: 111}).ok);
TEquals(true, targetDb.save({_id: "foo1", value: 111}).ok);
TEquals(true, sourceDb.save({_id: "foo2", value: 222}).ok);
TEquals(true, sourceDb.save({_id: "foo3", value: 333}).ok);
TEquals(true, targetDb.save({_id: "foo3", value: 333}).ok);
repResult = CouchDB.replicate(dbPairs[i].source, dbPairs[i].target);
TEquals(true, repResult.ok);
sourceInfo = sourceDb.info();
TEquals(sourceInfo.update_seq, repResult.source_last_seq);
TEquals(sourceInfo.update_seq - 3, repResult.history[0].start_last_seq);
TEquals(sourceInfo.update_seq, repResult.history[0].end_last_seq);
TEquals(sourceInfo.update_seq, repResult.history[0].recorded_seq);
TEquals(3, repResult.history[0].missing_checked);
TEquals(1, repResult.history[0].missing_found);
TEquals(1, repResult.history[0].docs_read);
TEquals(1, repResult.history[0].docs_written);
TEquals(0, repResult.history[0].doc_write_failures);
TEquals(true, sourceDb.save({_id: "foo4", value: 444}).ok);
TEquals(true, targetDb.save({_id: "foo4", value: 444}).ok);
TEquals(true, sourceDb.save({_id: "foo5", value: 555}).ok);
TEquals(true, targetDb.save({_id: "foo5", value: 555}).ok);
repResult = CouchDB.replicate(dbPairs[i].source, dbPairs[i].target);
TEquals(true, repResult.ok);
sourceInfo = sourceDb.info();
TEquals(sourceInfo.update_seq, repResult.source_last_seq);
TEquals(sourceInfo.update_seq - 2, repResult.history[0].start_last_seq);
TEquals(sourceInfo.update_seq, repResult.history[0].end_last_seq);
TEquals(sourceInfo.update_seq, repResult.history[0].recorded_seq);
TEquals(2, repResult.history[0].missing_checked);
TEquals(0, repResult.history[0].missing_found);
TEquals(0, repResult.history[0].docs_read);
TEquals(0, repResult.history[0].docs_written);
TEquals(0, repResult.history[0].doc_write_failures);
repResult = CouchDB.replicate(dbPairs[i].source, dbPairs[i].target);
TEquals(true, repResult.ok);
TEquals(true, repResult.no_changes);
sourceInfo = sourceDb.info();
TEquals(sourceInfo.update_seq, repResult.source_last_seq);
}
// test error when source database does not exist
try {
CouchDB.replicate("foobar", "test_suite_db");
T(false, "should have failed with db_not_found error");
} catch (x) {
TEquals("db_not_found", x.error);
}
// validate COUCHDB-317
try {
CouchDB.replicate("/foobar", "test_suite_db");
T(false, "should have failed with db_not_found error");
} catch (x) {
TEquals("db_not_found", x.error);
}
try {
CouchDB.replicate(CouchDB.protocol + host + "/foobar", "test_suite_db");
T(false, "should have failed with db_not_found error");
} catch (x) {
TEquals("db_not_found", x.error);
}
// test since_seq parameter
docs = makeDocs(1, 6);
for (i = 0; i < dbPairs.length; i++) {
var since_seq = 3;
populateDb(sourceDb, docs);
populateDb(targetDb, []);
var expected_ids = [];
var changes = sourceDb.changes({since: since_seq});
for (j = 0; j < changes.results.length; j++) {
expected_ids.push(changes.results[j].id);
}
TEquals(2, expected_ids.length, "2 documents since since_seq");
// For OTP < R14B03, temporary child specs are kept in the supervisor
// after the child terminates, so cancel the replication to delete the
// child spec in those OTP releases, otherwise since_seq will have no
// effect.
try {
CouchDB.replicate(
dbPairs[i].source,
dbPairs[i].target,
{body: {cancel: true}}
);
} catch (x) {
// OTP R14B03 onwards
TEquals("not found", x.error);
}
repResult = CouchDB.replicate(
dbPairs[i].source,
dbPairs[i].target,
{body: {since_seq: since_seq}}
);
// Same reason as before. But here we don't want since_seq to affect
// subsequent replications, so we need to delete the child spec from the
// supervisor (since_seq is not used to calculate the replication ID).
try {
CouchDB.replicate(
dbPairs[i].source,
dbPairs[i].target,
{body: {cancel: true}}
);
} catch (x) {
// OTP R14B03 onwards
TEquals("not found", x.error);
}
TEquals(true, repResult.ok);
TEquals(2, repResult.history[0].missing_checked);
TEquals(2, repResult.history[0].missing_found);
TEquals(2, repResult.history[0].docs_read);
TEquals(2, repResult.history[0].docs_written);
TEquals(0, repResult.history[0].doc_write_failures);
for (j = 0; j < docs.length; j++) {
doc = docs[j];
copy = targetDb.open(doc._id);
if (expected_ids.indexOf(doc._id) === -1) {
T(copy === null);
} else {
T(copy !== null);
TEquals(true, compareObjects(doc, copy));
}
}
}
// test errors due to doc validate_doc_update functions in the target endpoint
docs = makeDocs(1, 8);
docs[2]["_attachments"] = {
"hello.txt": {
"content_type": "text/plain",
"data": "aGVsbG8gd29ybGQ=" // base64:encode("hello world")
}
};
var ddoc = {
_id: "_design/test",
language: "javascript",
validate_doc_update: (function(newDoc, oldDoc, userCtx, secObj) {
if ((newDoc.integer % 2) !== 0) {
throw {forbidden: "I only like multiples of 2."};
}
}).toString()
};
for (i = 0; i < dbPairs.length; i++) {
populateDb(sourceDb, docs);
populateDb(targetDb, [ddoc]);
repResult = CouchDB.replicate(
dbPairs[i].source,
dbPairs[i].target
);
TEquals(true, repResult.ok);
TEquals(7, repResult.history[0].missing_checked);
TEquals(7, repResult.history[0].missing_found);
TEquals(7, repResult.history[0].docs_read);
TEquals(3, repResult.history[0].docs_written);
TEquals(4, repResult.history[0].doc_write_failures);
for (j = 0; j < docs.length; j++) {
doc = docs[j];
copy = targetDb.open(doc._id);
if (doc.integer % 2 === 0) {
T(copy !== null);
TEquals(copy.integer, doc.integer);
} else {
T(copy === null);
}
}
}
// test create_target option
docs = makeDocs(1, 2);
for (i = 0; i < dbPairs.length; i++) {
populateDb(sourceDb, docs);
targetDb.deleteDb();
repResult = CouchDB.replicate(
dbPairs[i].source,
dbPairs[i].target,
{body: {create_target: true}}
);
TEquals(true, repResult.ok);
sourceInfo = sourceDb.info();
targetInfo = targetDb.info();
TEquals(sourceInfo.doc_count, targetInfo.doc_count);
TEquals(sourceInfo.update_seq, targetInfo.update_seq);
}
// test filtered replication
docs = makeDocs(1, 31);
docs.push({
_id: "_design/mydesign",
language: "javascript",
filters: {
myfilter: (function(doc, req) {
var modulus = Number(req.query.modulus);
var special = req.query.special;
return (doc.integer % modulus === 0) || (doc.string === special);
}).toString()
}
});
for (i = 0; i < dbPairs.length; i++) {
populateDb(sourceDb, docs);
populateDb(targetDb, []);
repResult = CouchDB.replicate(
dbPairs[i].source,
dbPairs[i].target,
{
body: {
filter: "mydesign/myfilter",
query_params: {
modulus: "2",
special: "7"
}
}
}
);
TEquals(true, repResult.ok);
for (j = 0; j < docs.length; j++) {
doc = docs[j];
copy = targetDb.open(doc._id);
if ((doc.integer && (doc.integer % 2 === 0)) || (doc.string === "7")) {
T(copy !== null);
TEquals(true, compareObjects(doc, copy));
} else {
TEquals(null, copy);
}
}
TEquals(true, repResult.history instanceof Array);
TEquals(1, repResult.history.length);
// NOT 31 (31 is db seq for last doc - the ddoc, which was not replicated)
TEquals(30, repResult.source_last_seq);
TEquals(0, repResult.history[0].start_last_seq);
TEquals(30, repResult.history[0].end_last_seq);
TEquals(30, repResult.history[0].recorded_seq);
// 16 => 15 docs with even integer field + 1 doc with string field "7"
TEquals(16, repResult.history[0].missing_checked);
TEquals(16, repResult.history[0].missing_found);
TEquals(16, repResult.history[0].docs_read);
TEquals(16, repResult.history[0].docs_written);
TEquals(0, repResult.history[0].doc_write_failures);
// add new docs to source and resume the same replication
var newDocs = makeDocs(50, 56);
populateDb(sourceDb, newDocs, true);
repResult = CouchDB.replicate(
dbPairs[i].source,
dbPairs[i].target,
{
body: {
filter: "mydesign/myfilter",
query_params: {
modulus: "2",
special: "7"
}
}
}
);
TEquals(true, repResult.ok);
for (j = 0; j < newDocs.length; j++) {
doc = newDocs[j];
copy = targetDb.open(doc._id);
if (doc.integer && (doc.integer % 2 === 0)) {
T(copy !== null);
TEquals(true, compareObjects(doc, copy));
} else {
TEquals(null, copy);
}
}
// last doc has even integer field, so last replicated seq is 36
TEquals(36, repResult.source_last_seq);
TEquals(true, repResult.history instanceof Array);
TEquals(2, repResult.history.length);
TEquals(30, repResult.history[0].start_last_seq);
TEquals(36, repResult.history[0].end_last_seq);
TEquals(36, repResult.history[0].recorded_seq);
TEquals(3, repResult.history[0].missing_checked);
TEquals(3, repResult.history[0].missing_found);
TEquals(3, repResult.history[0].docs_read);
TEquals(3, repResult.history[0].docs_written);
TEquals(0, repResult.history[0].doc_write_failures);
}
// test filtered replication works as expected after changing the filter's
// code (ticket COUCHDB-892)
var filterFun1 = (function(doc, req) {
if (doc.value < Number(req.query.maxvalue)) {
return true;
} else {
return false;
}
}).toString();
var filterFun2 = (function(doc, req) {
return true;
}).toString();
for (i = 0; i < dbPairs.length; i++) {
populateDb(targetDb, []);
populateDb(sourceDb, []);
TEquals(true, sourceDb.save({_id: "foo1", value: 1}).ok);
TEquals(true, sourceDb.save({_id: "foo2", value: 2}).ok);
TEquals(true, sourceDb.save({_id: "foo3", value: 3}).ok);
TEquals(true, sourceDb.save({_id: "foo4", value: 4}).ok);
var ddoc = {
"_id": "_design/mydesign",
"language": "javascript",
"filters": {
"myfilter": filterFun1
}
};
TEquals(true, sourceDb.save(ddoc).ok);
repResult = CouchDB.replicate(
dbPairs[i].source,
dbPairs[i].target,
{
body: {
filter: "mydesign/myfilter",
query_params: {
maxvalue: "3"
}
}
}
);
TEquals(true, repResult.ok);
TEquals(true, repResult.history instanceof Array);
TEquals(1, repResult.history.length);
TEquals(2, repResult.history[0].docs_written);
TEquals(2, repResult.history[0].docs_read);
TEquals(0, repResult.history[0].doc_write_failures);
var docFoo1 = targetDb.open("foo1");
T(docFoo1 !== null);
TEquals(1, docFoo1.value);
var docFoo2 = targetDb.open("foo2");
T(docFoo2 !== null);
TEquals(2, docFoo2.value);
var docFoo3 = targetDb.open("foo3");
TEquals(null, docFoo3);
var docFoo4 = targetDb.open("foo4");
TEquals(null, docFoo4);
// replication should start from scratch after the filter's code changed
ddoc.filters.myfilter = filterFun2;
TEquals(true, sourceDb.save(ddoc).ok);
repResult = CouchDB.replicate(
dbPairs[i].source,
dbPairs[i].target,
{
body: {
filter: "mydesign/myfilter",
query_params : {
maxvalue: "3"
}
}
}
);
TEquals(true, repResult.ok);
TEquals(true, repResult.history instanceof Array);
TEquals(1, repResult.history.length);
TEquals(3, repResult.history[0].docs_written);
TEquals(3, repResult.history[0].docs_read);
TEquals(0, repResult.history[0].doc_write_failures);
docFoo1 = targetDb.open("foo1");
T(docFoo1 !== null);
TEquals(1, docFoo1.value);
docFoo2 = targetDb.open("foo2");
T(docFoo2 !== null);
TEquals(2, docFoo2.value);
docFoo3 = targetDb.open("foo3");
T(docFoo3 !== null);
TEquals(3, docFoo3.value);
docFoo4 = targetDb.open("foo4");
T(docFoo4 !== null);
TEquals(4, docFoo4.value);
T(targetDb.open("_design/mydesign") !== null);
}
// test replication by doc IDs
docs = makeDocs(1, 11);
docs.push({
_id: "_design/foo",
language: "javascript",
integer: 1
});
var target_doc_ids = [
{ initial: ["1", "2", "10"], after: [], conflict_id: "2" },
{ initial: ["1", "2"], after: ["7"], conflict_id: "1" },
{ initial: ["1", "foo_666", "10"], after: ["7"], conflict_id: "10" },
{ initial: ["_design/foo", "8"], after: ["foo_5"], conflict_id: "8" },
{ initial: ["_design%2Ffoo", "8"], after: ["foo_5"], conflict_id: "8" },
{ initial: [], after: ["foo_1000", "_design/foo", "1"], conflict_id: "1" }
];
var doc_ids, after_doc_ids;
var id, num_inexistent_docs, after_num_inexistent_docs;
var total, after_total;
for (i = 0; i < dbPairs.length; i++) {
for (j = 0; j < target_doc_ids.length; j++) {
doc_ids = target_doc_ids[j].initial;
num_inexistent_docs = 0;
for (k = 0; k < doc_ids.length; k++) {
id = doc_ids[k];
if (id.indexOf("foo_") === 0) {
num_inexistent_docs += 1;
}
}
populateDb(sourceDb, docs);
populateDb(targetDb, []);
repResult = CouchDB.replicate(
dbPairs[i].source,
dbPairs[i].target,
{
body: {
doc_ids: doc_ids
}
}
);
total = doc_ids.length - num_inexistent_docs;
TEquals(true, repResult.ok);
if (total === 0) {
TEquals(true, repResult.no_changes);
} else {
TEquals('string', typeof repResult.start_time);
TEquals('string', typeof repResult.end_time);
TEquals(total, repResult.docs_read);
TEquals(total, repResult.docs_written);
TEquals(0, repResult.doc_write_failures);
}
for (k = 0; k < doc_ids.length; k++) {
id = decodeURIComponent(doc_ids[k]);
doc = sourceDb.open(id);
copy = targetDb.open(id);
if (id.indexOf("foo_") === 0) {
TEquals(null, doc);
TEquals(null, copy);
} else {
T(doc !== null);
T(copy !== null);
TEquals(true, compareObjects(doc, copy));
}
}
// be absolutely sure that other docs were not replicated
for (k = 0; k < docs.length; k++) {
var base_id = docs[k]._id;
id = encodeURIComponent(base_id);
doc = targetDb.open(base_id);
if ((doc_ids.indexOf(id) >= 0) || (doc_ids.indexOf(base_id) >= 0)) {
T(doc !== null);
} else {
TEquals(null, doc);
}
}
targetInfo = targetDb.info();
TEquals(total, targetInfo.doc_count);
// add more docs throught replication by doc IDs
after_doc_ids = target_doc_ids[j].after;
after_num_inexistent_docs = 0;
for (k = 0; k < after_doc_ids.length; k++) {
id = after_doc_ids[k];
if (id.indexOf("foo_") === 0) {
after_num_inexistent_docs += 1;
}
}
repResult = CouchDB.replicate(
dbPairs[i].source,
dbPairs[i].target,
{
body: {
doc_ids: after_doc_ids
}
}
);
after_total = after_doc_ids.length - after_num_inexistent_docs;
TEquals(true, repResult.ok);
if (after_total === 0) {
TEquals(true, repResult.no_changes);
} else {
TEquals('string', typeof repResult.start_time);
TEquals('string', typeof repResult.end_time);
TEquals(after_total, repResult.docs_read);
TEquals(after_total, repResult.docs_written);
TEquals(0, repResult.doc_write_failures);
}
for (k = 0; k < after_doc_ids.length; k++) {
id = after_doc_ids[k];
doc = sourceDb.open(id);
copy = targetDb.open(id);
if (id.indexOf("foo_") === 0) {
TEquals(null, doc);
TEquals(null, copy);
} else {
T(doc !== null);
T(copy !== null);
TEquals(true, compareObjects(doc, copy));
}
}
// be absolutely sure that other docs were not replicated
for (k = 0; k < docs.length; k++) {
var base_id = docs[k]._id;
id = encodeURIComponent(base_id);
doc = targetDb.open(base_id);
if ((doc_ids.indexOf(id) >= 0) || (after_doc_ids.indexOf(id) >= 0) ||
(doc_ids.indexOf(base_id) >= 0) ||
(after_doc_ids.indexOf(base_id) >= 0)) {
T(doc !== null);
} else {
TEquals(null, doc);
}
}
targetInfo = targetDb.info();
TEquals((total + after_total), targetInfo.doc_count);
// replicate again the same doc after updated on source (no conflict)
id = target_doc_ids[j].conflict_id;
doc = sourceDb.open(id);
T(doc !== null);
doc.integer = 666;
TEquals(true, sourceDb.save(doc).ok);
addAtt(sourceDb, doc, "readme.txt", att1_data, "text/plain");
addAtt(sourceDb, doc, "data.dat", att2_data, "application/binary");
repResult = CouchDB.replicate(
dbPairs[i].source,
dbPairs[i].target,
{
body: {
doc_ids: [id]
}
}
);
TEquals(true, repResult.ok);
TEquals(1, repResult.docs_read);
TEquals(1, repResult.docs_written);
TEquals(0, repResult.doc_write_failures);
copy = targetDb.open(id, {conflicts: true});
TEquals(666, copy.integer);
TEquals(0, copy._rev.indexOf("4-"));
TEquals('undefined', typeof copy._conflicts);
var atts = copy._attachments;
TEquals('object', typeof atts);
TEquals('object', typeof atts["readme.txt"]);
TEquals(3, atts["readme.txt"].revpos);
TEquals(0, atts["readme.txt"].content_type.indexOf("text/plain"));
TEquals(true, atts["readme.txt"].stub);
var att1_copy = CouchDB.request(
"GET", "/" + targetDb.name + "/" + copy._id + "/readme.txt"
).responseText;
TEquals(att1_data.length, att1_copy.length);
TEquals(att1_data, att1_copy);
TEquals('object', typeof atts["data.dat"]);
TEquals(4, atts["data.dat"].revpos);
TEquals(0, atts["data.dat"].content_type.indexOf("application/binary"));
TEquals(true, atts["data.dat"].stub);
var att2_copy = CouchDB.request(
"GET", "/" + targetDb.name + "/" + copy._id + "/data.dat"
).responseText;
TEquals(att2_data.length, att2_copy.length);
TEquals(att2_data, att2_copy);
// generate a conflict throught replication by doc IDs
id = target_doc_ids[j].conflict_id;
doc = sourceDb.open(id);
copy = targetDb.open(id);
T(doc !== null);
T(copy !== null);
doc.integer += 100;
copy.integer += 1;
TEquals(true, sourceDb.save(doc).ok);
TEquals(true, targetDb.save(copy).ok);
repResult = CouchDB.replicate(
dbPairs[i].source,
dbPairs[i].target,
{
body: {
doc_ids: [id]
}
}
);
TEquals(true, repResult.ok);
TEquals(1, repResult.docs_read);
TEquals(1, repResult.docs_written);
TEquals(0, repResult.doc_write_failures);
copy = targetDb.open(id, {conflicts: true});
TEquals(0, copy._rev.indexOf("5-"));
TEquals(true, copy._conflicts instanceof Array);
TEquals(1, copy._conflicts.length);
TEquals(0, copy._conflicts[0].indexOf("5-"));
}
}
docs = makeDocs(1, 25);
docs.push({
_id: "_design/foo",
language: "javascript",
filters: {
myfilter: (function(doc, req) { return true; }).toString()
}
});
for (i = 0; i < dbPairs.length; i++) {
populateDb(sourceDb, docs);
populateDb(targetDb, []);
// add some attachments
for (j = 10; j < 15; j++) {
addAtt(sourceDb, docs[j], "readme.txt", att1_data, "text/plain");
}
repResult = CouchDB.replicate(
dbPairs[i].source,
dbPairs[i].target,
{
body: {
continuous: true
}
}
);
TEquals(true, repResult.ok);
TEquals('string', typeof repResult._local_id);
var rep_id = repResult._local_id;
waitForSeq(sourceDb, targetDb);
for (j = 0; j < docs.length; j++) {
doc = docs[j];
copy = targetDb.open(doc._id);
T(copy !== null);
TEquals(true, compareObjects(doc, copy));
if (j >= 10 && j < 15) {
var atts = copy._attachments;
TEquals('object', typeof atts);
TEquals('object', typeof atts["readme.txt"]);
TEquals(2, atts["readme.txt"].revpos);
TEquals(0, atts["readme.txt"].content_type.indexOf("text/plain"));
TEquals(true, atts["readme.txt"].stub);
var att_copy = CouchDB.request(
"GET", "/" + targetDb.name + "/" + copy._id + "/readme.txt"
).responseText;
TEquals(att1_data.length, att_copy.length);
TEquals(att1_data, att_copy);
}
}
sourceInfo = sourceDb.info();
targetInfo = targetDb.info();
TEquals(sourceInfo.doc_count, targetInfo.doc_count);
// add attachments to docs in source
for (j = 10; j < 15; j++) {
addAtt(sourceDb, docs[j], "data.dat", att2_data, "application/binary");
}
var ddoc = docs[docs.length - 1]; // design doc
addAtt(sourceDb, ddoc, "readme.txt", att1_data, "text/plain");
waitForSeq(sourceDb, targetDb);
var modifDocs = docs.slice(10, 15).concat([ddoc]);
for (j = 0; j < modifDocs.length; j++) {
doc = modifDocs[j];
copy = targetDb.open(doc._id);
T(copy !== null);
TEquals(true, compareObjects(doc, copy));
var atts = copy._attachments;
TEquals('object', typeof atts);
TEquals('object', typeof atts["readme.txt"]);
TEquals(2, atts["readme.txt"].revpos);
TEquals(0, atts["readme.txt"].content_type.indexOf("text/plain"));
TEquals(true, atts["readme.txt"].stub);
var att1_copy = CouchDB.request(
"GET", "/" + targetDb.name + "/" + copy._id + "/readme.txt"
).responseText;
TEquals(att1_data.length, att1_copy.length);
TEquals(att1_data, att1_copy);
if (doc._id.indexOf("_design/") === -1) {
TEquals('object', typeof atts["data.dat"]);
TEquals(3, atts["data.dat"].revpos);
TEquals(0, atts["data.dat"].content_type.indexOf("application/binary"));
TEquals(true, atts["data.dat"].stub);
var att2_copy = CouchDB.request(
"GET", "/" + targetDb.name + "/" + copy._id + "/data.dat"
).responseText;
TEquals(att2_data.length, att2_copy.length);
TEquals(att2_data, att2_copy);
}
}
sourceInfo = sourceDb.info();
targetInfo = targetDb.info();
TEquals(sourceInfo.doc_count, targetInfo.doc_count);
// add another attachment to the ddoc on source
addAtt(sourceDb, ddoc, "data.dat", att2_data, "application/binary");
waitForSeq(sourceDb, targetDb);
copy = targetDb.open(ddoc._id);
var atts = copy._attachments;
TEquals('object', typeof atts);
TEquals('object', typeof atts["readme.txt"]);
TEquals(2, atts["readme.txt"].revpos);
TEquals(0, atts["readme.txt"].content_type.indexOf("text/plain"));
TEquals(true, atts["readme.txt"].stub);
var att1_copy = CouchDB.request(
"GET", "/" + targetDb.name + "/" + copy._id + "/readme.txt"
).responseText;
TEquals(att1_data.length, att1_copy.length);
TEquals(att1_data, att1_copy);
TEquals('object', typeof atts["data.dat"]);
TEquals(3, atts["data.dat"].revpos);
TEquals(0, atts["data.dat"].content_type.indexOf("application/binary"));
TEquals(true, atts["data.dat"].stub);
var att2_copy = CouchDB.request(
"GET", "/" + targetDb.name + "/" + copy._id + "/data.dat"
).responseText;
TEquals(att2_data.length, att2_copy.length);
TEquals(att2_data, att2_copy);
sourceInfo = sourceDb.info();
targetInfo = targetDb.info();
TEquals(sourceInfo.doc_count, targetInfo.doc_count);
// add more docs to source
var newDocs = makeDocs(25, 35);
populateDb(sourceDb, newDocs, true);
waitForSeq(sourceDb, targetDb);
for (j = 0; j < newDocs.length; j++) {
doc = newDocs[j];
copy = targetDb.open(doc._id);
T(copy !== null);
TEquals(true, compareObjects(doc, copy));
}
sourceInfo = sourceDb.info();
targetInfo = targetDb.info();
TEquals(sourceInfo.doc_count, targetInfo.doc_count);
// delete docs from source
TEquals(true, sourceDb.deleteDoc(newDocs[0]).ok);
wait(1000);
TEquals(true, sourceDb.deleteDoc(newDocs[6]).ok);
waitForSeq(sourceDb, targetDb);
copy = targetDb.open(newDocs[0]._id);
TEquals(null, copy);
copy = targetDb.open(newDocs[6]._id);
TEquals(null, copy);
var changes = targetDb.changes({since: targetInfo.update_seq});
var line1 = changes.results[changes.results.length - 2];
var line2 = changes.results[changes.results.length - 1];
TEquals(newDocs[0]._id, line1.id);
TEquals(true, line1.deleted);
TEquals(newDocs[6]._id, line2.id);
TEquals(true, line2.deleted);
// cancel the replication
repResult = CouchDB.replicate(
dbPairs[i].source,
dbPairs[i].target,
{
body: {
continuous: true,
cancel: true
}
}
);
TEquals(true, repResult.ok);
TEquals(rep_id, repResult._local_id);
doc = {
_id: 'foobar',
value: 666
};
TEquals(true, sourceDb.save(doc).ok);
wait(2000);
copy = targetDb.open(doc._id);
TEquals(null, copy);
}
// COUCHDB-1093 - filtered and continuous _changes feed dies when the
// database is compacted
docs = makeDocs(1, 10);
docs.push({
_id: "_design/foo",
language: "javascript",
filters: {
myfilter: (function(doc, req) { return true; }).toString()
}
});
populateDb(sourceDb, docs);
populateDb(targetDb, []);
repResult = CouchDB.replicate(
CouchDB.protocol + host + "/" + sourceDb.name,
targetDb.name,
{
body: {
continuous: true,
filter: "foo/myfilter"
}
}
);
TEquals(true, repResult.ok);
TEquals('string', typeof repResult._local_id);
var xhr = CouchDB.request("GET", "/_active_tasks");
var tasks = JSON.parse(xhr.responseText);
TEquals(true, sourceDb.compact().ok);
while (sourceDb.info().compact_running) {};
TEquals(true, sourceDb.save(makeDocs(30, 31)[0]).ok);
xhr = CouchDB.request("GET", "/_active_tasks");
var tasksAfter = JSON.parse(xhr.responseText);
TEquals(tasks.length, tasksAfter.length);
waitForSeq(sourceDb, targetDb);
T(sourceDb.open("30") !== null);
// cancel replication
repResult = CouchDB.replicate(
CouchDB.protocol + host + "/" + sourceDb.name,
targetDb.name,
{
body: {
continuous: true,
filter: "foo/myfilter",
cancel: true
}
}
);
TEquals(true, repResult.ok);
TEquals('string', typeof repResult._local_id);
//
// test replication of compressed attachments
//
doc = {
_id: "foobar"
};
var bigTextAtt = makeAttData(128 * 1024);
var attName = "readme.txt";
var xhr = CouchDB.request("GET", "/_config/attachments/compression_level");
var compressionLevel = JSON.parse(xhr.responseText);
xhr = CouchDB.request("GET", "/_config/attachments/compressible_types");
var compressibleTypes = JSON.parse(xhr.responseText);
for (i = 0; i < dbPairs.length; i++) {
populateDb(sourceDb, [doc]);
populateDb(targetDb, []);
// enable compression of text types
enableAttCompression("8", "text/*");
// add text attachment to foobar doc
xhr = CouchDB.request(
"PUT",
"/" + sourceDb.name + "/" + doc._id + "/" + attName + "?rev=" + doc._rev,
{
body: bigTextAtt,
headers: {"Content-Type": "text/plain"}
}
);
TEquals(201, xhr.status);
// disable compression and replicate
disableAttCompression();
repResult = CouchDB.replicate(dbPairs[i].source, dbPairs[i].target);
TEquals(true, repResult.ok);
TEquals(true, repResult.history instanceof Array);
TEquals(1, repResult.history.length);
TEquals(1, repResult.history[0].missing_checked);
TEquals(1, repResult.history[0].missing_found);
TEquals(1, repResult.history[0].docs_read);
TEquals(1, repResult.history[0].docs_written);
TEquals(0, repResult.history[0].doc_write_failures);
copy = targetDb.open(
doc._id,
{att_encoding_info: true, bypass_cache: Math.round(Math.random() * 1000)}
);
T(copy !== null);
T(attName in copy._attachments);
TEquals("gzip", copy._attachments[attName].encoding);
TEquals("number", typeof copy._attachments[attName].length);
TEquals("number", typeof copy._attachments[attName].encoded_length);
T(copy._attachments[attName].encoded_length < copy._attachments[attName].length);
}
delete bigTextAtt;
// restore original settings
enableAttCompression(compressionLevel, compressibleTypes);
//
// test replication triggered by non admins
//
// case 1) user triggering the replication is not a DB admin of the target DB
var joeUserDoc = CouchDB.prepareUserDoc({
name: "joe",
roles: ["erlanger"]
}, "erly");
var usersDb = new CouchDB("test_suite_auth", {"X-Couch-Full-Commit":"false"});
var server_config = [
{
section: "couch_httpd_auth",
key: "authentication_db",
value: usersDb.name
}
];
docs = makeDocs(1, 6);
docs.push({
_id: "_design/foo",
language: "javascript"
});
dbPairs = [
{
source: sourceDb.name,
target: targetDb.name
},
{
source: CouchDB.protocol + host + "/" + sourceDb.name,
target: targetDb.name
},
{
source: sourceDb.name,
target: CouchDB.protocol + "joe:erly@" + host + "/" + targetDb.name
},
{
source: CouchDB.protocol + host + "/" + sourceDb.name,
target: CouchDB.protocol + "joe:erly@" + host + "/" + targetDb.name
}
];
for (i = 0; i < dbPairs.length; i++) {
usersDb.deleteDb();
populateDb(sourceDb, docs);
populateDb(targetDb, []);
TEquals(true, targetDb.setSecObj({
admins: {
names: ["superman"],
roles: ["god"]
}
}).ok);
run_on_modified_server(server_config, function() {
delete joeUserDoc._rev;
TEquals(true, usersDb.save(joeUserDoc).ok);
TEquals(true, CouchDB.login("joe", "erly").ok);
TEquals('joe', CouchDB.session().userCtx.name);
repResult = CouchDB.replicate(dbPairs[i].source, dbPairs[i].target);
TEquals(true, CouchDB.logout().ok);
TEquals(true, repResult.ok);
TEquals(docs.length, repResult.history[0].docs_read);
TEquals((docs.length - 1), repResult.history[0].docs_written); // 1 ddoc
TEquals(1, repResult.history[0].doc_write_failures);
});
for (j = 0; j < docs.length; j++) {
doc = docs[j];
copy = targetDb.open(doc._id);
if (doc._id.indexOf("_design/") === 0) {
TEquals(null, copy);
} else {
T(copy !== null);
TEquals(true, compareObjects(doc, copy));
}
}
}
// case 2) user triggering the replication is not a reader (nor admin) of the
// source DB
dbPairs = [
{
source: sourceDb.name,
target: targetDb.name
},
{
source: CouchDB.protocol + "joe:erly@" + host + "/" + sourceDb.name,
target: targetDb.name
},
{
source: sourceDb.name,
target: CouchDB.protocol + host + "/" + targetDb.name
},
{
source: CouchDB.protocol + "joe:erly@" + host + "/" + sourceDb.name,
target: CouchDB.protocol + host + "/" + targetDb.name
}
];
for (i = 0; i < dbPairs.length; i++) {
usersDb.deleteDb();
populateDb(sourceDb, docs);
populateDb(targetDb, []);
TEquals(true, sourceDb.setSecObj({
admins: {
names: ["superman"],
roles: ["god"]
},
readers: {
names: ["john"],
roles: ["secret"]
}
}).ok);
run_on_modified_server(server_config, function() {
delete joeUserDoc._rev;
TEquals(true, usersDb.save(joeUserDoc).ok);
TEquals(true, CouchDB.login("joe", "erly").ok);
TEquals('joe', CouchDB.session().userCtx.name);
try {
CouchDB.replicate(dbPairs[i].source, dbPairs[i].target);
T(false, "should have raised an exception");
} catch (x) {
TEquals("unauthorized", x.error);
}
TEquals(true, CouchDB.logout().ok);
});
for (j = 0; j < docs.length; j++) {
doc = docs[j];
copy = targetDb.open(doc._id);
TEquals(null, copy);
}
}
// COUCHDB-885 - push replication of a doc with attachment causes a
// conflict in the target.
sourceDb = new CouchDB("test_suite_db_a");
targetDb = new CouchDB("test_suite_db_b");
sourceDb.deleteDb();
sourceDb.createDb();
targetDb.deleteDb();
targetDb.createDb();
doc = {
_id: "doc1"
};
TEquals(true, sourceDb.save(doc).ok);
repResult = CouchDB.replicate(
sourceDb.name,
CouchDB.protocol + host + "/" + targetDb.name
);
TEquals(true, repResult.ok);
TEquals(true, repResult.history instanceof Array);
TEquals(1, repResult.history.length);
TEquals(1, repResult.history[0].docs_written);
TEquals(1, repResult.history[0].docs_read);
TEquals(0, repResult.history[0].doc_write_failures);
doc["_attachments"] = {
"hello.txt": {
"content_type": "text/plain",
"data": "aGVsbG8gd29ybGQ=" // base64:encode("hello world")
},
"foo.dat": {
"content_type": "not/compressible",
"data": "aSBhbSBub3QgZ3ppcGVk" // base64:encode("i am not gziped")
}
};
TEquals(true, sourceDb.save(doc).ok);
repResult = CouchDB.replicate(
sourceDb.name,
CouchDB.protocol + host + "/" + targetDb.name
);
TEquals(true, repResult.ok);
TEquals(true, repResult.history instanceof Array);
TEquals(2, repResult.history.length);
TEquals(1, repResult.history[0].docs_written);
TEquals(1, repResult.history[0].docs_read);
TEquals(0, repResult.history[0].doc_write_failures);
copy = targetDb.open(doc._id, {
conflicts: true, deleted_conflicts: true,
attachments: true, att_encoding_info: true});
T(copy !== null);
TEquals("undefined", typeof copy._conflicts);
TEquals("undefined", typeof copy._deleted_conflicts);
TEquals("text/plain", copy._attachments["hello.txt"]["content_type"]);
TEquals("aGVsbG8gd29ybGQ=", copy._attachments["hello.txt"]["data"]);
TEquals("gzip", copy._attachments["hello.txt"]["encoding"]);
TEquals("not/compressible", copy._attachments["foo.dat"]["content_type"]);
TEquals("aSBhbSBub3QgZ3ppcGVk", copy._attachments["foo.dat"]["data"]);
TEquals("undefined", typeof copy._attachments["foo.dat"]["encoding"]);
// end of test for COUCHDB-885
// Test for COUCHDB-1242 (reject non-string query_params)
try {
CouchDB.replicate(sourceDb, targetDb, {
body: {
filter : "mydesign/myfilter",
query_params : {
"maxvalue": 4
}
}
});
} catch (e) {
TEquals("bad_request", e.error);
}
// Test that we can cancel a replication just by POSTing an object
// like {"replication_id": Id, "cancel": true}. The replication ID
// can be obtained from a continuous replication request response
// (_local_id field), from _active_tasks or from the log
populateDb(sourceDb, makeDocs(1, 6));
populateDb(targetDb, []);
repResult = CouchDB.replicate(
CouchDB.protocol + host + "/" + sourceDb.name,
targetDb.name,
{
body: {
continuous: true,
create_target: true
}
}
);
TEquals(true, repResult.ok);
TEquals('string', typeof repResult._local_id);
xhr = CouchDB.request("GET", "/_active_tasks");
tasks = JSON.parse(xhr.responseText);
var repId;
for (j = 0; j < tasks.length; j++) {
if (tasks[j].replication_id === repResult._local_id) {
repId = tasks[j].replication_id;
}
}
TEquals(repResult._local_id, repId, "Replication found in _active_tasks");
xhr = CouchDB.request(
"POST", "/_replicate", {
body: JSON.stringify({"replication_id": repId, "cancel": true}),
headers: {"Content-Type": "application/json"}
});
TEquals(200, xhr.status, "Replication cancel request success");
xhr = CouchDB.request("GET", "/_active_tasks");
tasks = JSON.parse(xhr.responseText);
repId = null;
for (j = 0; j < tasks.length; j++) {
if (tasks[j].replication_id === repResult._local_id) {
repId = tasks[j].replication_id;
}
}
TEquals(null, repId, "Replication was canceled");
xhr = CouchDB.request(
"POST", "/_replicate", {
body: JSON.stringify({"replication_id": repResult._local_id, "cancel": true}),
headers: {"Content-Type": "application/json"}
});
TEquals(404, xhr.status, "2nd replication cancel failed");
// Non-admin user can not cancel replications triggered by other users
var userDoc = CouchDB.prepareUserDoc({
name: "tony",
roles: ["mafia"]
}, "soprano");
usersDb = new CouchDB("test_suite_auth", {"X-Couch-Full-Commit":"false"});
server_config = [
{
section: "couch_httpd_auth",
key: "authentication_db",
value: usersDb.name
}
];
run_on_modified_server(server_config, function() {
populateDb(sourceDb, makeDocs(1, 6));
populateDb(targetDb, []);
TEquals(true, usersDb.save(userDoc).ok);
repResult = CouchDB.replicate(
CouchDB.protocol + host + "/" + sourceDb.name,
targetDb.name,
{
body: {
continuous: true
}
}
);
TEquals(true, repResult.ok);
TEquals('string', typeof repResult._local_id);
TEquals(true, CouchDB.login("tony", "soprano").ok);
TEquals('tony', CouchDB.session().userCtx.name);
xhr = CouchDB.request(
"POST", "/_replicate", {
body: JSON.stringify({"replication_id": repResult._local_id, "cancel": true}),
headers: {"Content-Type": "application/json"}
});
TEquals(401, xhr.status, "Unauthorized to cancel replication");
TEquals("unauthorized", JSON.parse(xhr.responseText).error);
TEquals(true, CouchDB.logout().ok);
xhr = CouchDB.request(
"POST", "/_replicate", {
body: JSON.stringify({"replication_id": repResult._local_id, "cancel": true}),
headers: {"Content-Type": "application/json"}
});
TEquals(200, xhr.status, "Authorized to cancel replication");
});
// cleanup
usersDb.deleteDb();
sourceDb.deleteDb();
targetDb.deleteDb();
};