blob: 48ca341b2bf1720f69ffa4fda761c7a43316533a [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.replicator_db = function(debug) {
if (debug) debugger;
var wait_rep_doc = 500; // number of millisecs to wait after saving a Rep Doc
var dbA = new CouchDB("test_suite_rep_db_a", {"X-Couch-Full-Commit":"false"});
var dbB = new CouchDB("test_suite_rep_db_b", {"X-Couch-Full-Commit":"false"});
var repDb = new CouchDB("test_suite_rep_db", {"X-Couch-Full-Commit":"false"});
var usersDb = new CouchDB("test_suite_auth", {"X-Couch-Full-Commit":"false"});
var docs1 = [
{
_id: "foo1",
value: 11
},
{
_id: "foo2",
value: 22
},
{
_id: "foo3",
value: 33
}
];
function waitForRep(repDb, repDoc, state) {
var newRep,
t0 = new Date(),
t1,
ms = 3000;
do {
newRep = repDb.open(repDoc._id);
t1 = new Date();
} while (((t1 - t0) <= ms) && newRep._replication_state !== state);
}
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 waitForDocPos(db, docId, pos) {
var doc, curPos, t0, t1,
maxWait = 3000;
doc = db.open(docId);
curPos = Number(doc._rev.split("-", 1));
t0 = t1 = new Date();
while ((curPos < pos) && ((t1 - t0) <= maxWait)) {
doc = db.open(docId);
curPos = Number(doc._rev.split("-", 1));
t1 = new Date();
}
return doc;
}
function wait(ms) {
var t0 = new Date(), t1;
do {
CouchDB.request("GET", "/");
t1 = new Date();
} while ((t1 - t0) <= ms);
}
function populate_db(db, docs) {
if (db.name !== usersDb.name) {
db.deleteDb();
db.createDb();
}
for (var i = 0; i < docs.length; i++) {
var d = docs[i];
delete d._rev;
T(db.save(d).ok);
}
}
function simple_replication() {
populate_db(dbA, docs1);
populate_db(dbB, []);
var repDoc = {
_id: "foo_simple_rep",
source: dbA.name,
target: dbB.name
};
T(repDb.save(repDoc).ok);
waitForRep(repDb, repDoc, "completed");
for (var i = 0; i < docs1.length; i++) {
var doc = docs1[i];
var copy = dbB.open(doc._id);
T(copy !== null);
T(copy.value === doc.value);
}
var repDoc1 = repDb.open(repDoc._id);
T(repDoc1 !== null);
T(repDoc1.source === repDoc.source);
T(repDoc1.target === repDoc.target);
T(repDoc1._replication_state === "completed", "simple");
T(typeof repDoc1._replication_state_time === "string");
T(typeof repDoc1._replication_id === "string");
T(typeof repDoc1._replication_stats === "object", "doc has stats");
var stats = repDoc1._replication_stats;
TEquals(docs1.length, stats.revisions_checked,
"right # of revisions_checked");
TEquals(docs1.length, stats.missing_revisions_found,
"right # of missing_revisions_found");
TEquals(docs1.length, stats.docs_read, "right # of docs_read");
TEquals(docs1.length, stats.docs_written, "right # of docs_written");
TEquals(0, stats.doc_write_failures, "right # of doc_write_failures");
TEquals(dbA.info().update_seq, stats.checkpointed_source_seq,
"right checkpointed_source_seq");
}
function filtered_replication() {
var docs2 = docs1.concat([
{
_id: "_design/mydesign",
language : "javascript",
filters : {
myfilter : (function(doc, req) {
return (doc.value % 2) !== Number(req.query.myparam);
}).toString()
}
}
]);
populate_db(dbA, docs2);
populate_db(dbB, []);
var repDoc = {
_id: "foo_filt_rep_doc",
source: "http://" + CouchDB.host + "/" + dbA.name,
target: dbB.name,
filter: "mydesign/myfilter",
query_params: {
myparam: 1
}
};
T(repDb.save(repDoc).ok);
waitForRep(repDb, repDoc, "completed");
for (var i = 0; i < docs2.length; i++) {
var doc = docs2[i];
var copy = dbB.open(doc._id);
if (typeof doc.value === "number") {
if ((doc.value % 2) !== 1) {
T(copy !== null);
T(copy.value === doc.value);
} else {
T(copy === null);
}
}
}
var repDoc1 = repDb.open(repDoc._id);
T(repDoc1 !== null);
T(repDoc1.source === repDoc.source);
T(repDoc1.target === repDoc.target);
T(repDoc1._replication_state === "completed", "filtered");
T(typeof repDoc1._replication_state_time === "string");
T(typeof repDoc1._replication_id === "string");
T(typeof repDoc1._replication_stats === "object", "doc has stats");
var stats = repDoc1._replication_stats;
TEquals(2, stats.revisions_checked, "right # of revisions_checked");
TEquals(2, stats.missing_revisions_found, "right # of missing_revisions_found");
TEquals(2, stats.docs_read, "right # of docs_read");
TEquals(1, stats.docs_written, "right # of docs_written");
TEquals(1, stats.doc_write_failures, "right # of doc_write_failures");
TEquals(dbA.info().update_seq, stats.checkpointed_source_seq,
"right checkpointed_source_seq");
}
function continuous_replication() {
populate_db(dbA, docs1);
populate_db(dbB, []);
var repDoc = {
_id: "foo_cont_rep_doc",
source: "http://" + CouchDB.host + "/" + dbA.name,
target: dbB.name,
continuous: true,
user_ctx: {
roles: ["_admin"]
}
};
T(repDb.save(repDoc).ok);
waitForSeq(dbA, dbB);
for (var i = 0; i < docs1.length; i++) {
var doc = docs1[i];
var copy = dbB.open(doc._id);
T(copy !== null);
T(copy.value === doc.value);
}
var tasks = JSON.parse(CouchDB.request("GET", "/_active_tasks").responseText);
TEquals(1, tasks.length, "1 active task");
TEquals(repDoc._id, tasks[0].doc_id, "replication doc id in active tasks");
// add another doc to source, it will be replicated to target
var docX = {
_id: "foo1000",
value: 1001
};
T(dbA.save(docX).ok);
waitForSeq(dbA, dbB);
var copy = dbB.open("foo1000");
T(copy !== null);
T(copy.value === 1001);
var repDoc1 = repDb.open(repDoc._id);
T(repDoc1 !== null);
T(repDoc1.source === repDoc.source);
T(repDoc1.target === repDoc.target);
T(repDoc1._replication_state === "triggered");
T(typeof repDoc1._replication_state_time === "string");
T(typeof repDoc1._replication_id === "string");
// Design documents are only replicated to local targets if the respective
// replication document has a user_ctx filed with the "_admin" role in it.
var ddoc = {
_id: "_design/foobar",
language: "javascript"
};
T(dbA.save(ddoc).ok);
waitForSeq(dbA, dbB);
var ddoc_copy = dbB.open("_design/foobar");
T(ddoc_copy !== null);
T(ddoc.language === "javascript");
// update the design doc on source, test that the new revision is replicated
ddoc.language = "erlang";
T(dbA.save(ddoc).ok);
T(ddoc._rev.indexOf("2-") === 0);
waitForSeq(dbA, dbB);
ddoc_copy = dbB.open("_design/foobar");
T(ddoc_copy !== null);
T(ddoc_copy._rev === ddoc._rev);
T(ddoc.language === "erlang");
// stop replication by deleting the replication document
T(repDb.deleteDoc(repDoc1).ok);
// add another doc to source, it will NOT be replicated to target
var docY = {
_id: "foo666",
value: 999
};
T(dbA.save(docY).ok);
wait(200); // is there a way to avoid wait here?
var copy = dbB.open("foo666");
T(copy === null);
}
function by_doc_ids_replication() {
// to test that we can replicate docs with slashes in their IDs
var docs2 = docs1.concat([
{
_id: "_design/mydesign",
language : "javascript"
}
]);
populate_db(dbA, docs2);
populate_db(dbB, []);
var repDoc = {
_id: "foo_cont_rep_doc",
source: "http://" + CouchDB.host + "/" + dbA.name,
target: dbB.name,
doc_ids: ["foo666", "foo3", "_design/mydesign", "foo999", "foo1"]
};
T(repDb.save(repDoc).ok);
waitForRep(repDb, repDoc, "completed");
var copy = dbB.open("foo1");
T(copy !== null);
T(copy.value === 11);
copy = dbB.open("foo2");
T(copy === null);
copy = dbB.open("foo3");
T(copy !== null);
T(copy.value === 33);
copy = dbB.open("foo666");
T(copy === null);
copy = dbB.open("foo999");
T(copy === null);
copy = dbB.open("_design/mydesign");
T(copy === null);
repDoc = repDb.open(repDoc._id);
T(typeof repDoc._replication_stats === "object", "doc has stats");
var stats = repDoc._replication_stats;
TEquals(3, stats.revisions_checked, "right # of revisions_checked");
TEquals(3, stats.missing_revisions_found, "right # of missing_revisions_found");
TEquals(3, stats.docs_read, "right # of docs_read");
TEquals(2, stats.docs_written, "right # of docs_written");
TEquals(1, stats.doc_write_failures, "right # of doc_write_failures");
TEquals(dbA.info().update_seq, stats.checkpointed_source_seq,
"right checkpointed_source_seq");
}
function successive_identical_replications() {
populate_db(dbA, docs1);
populate_db(dbB, []);
var repDoc1 = {
_id: "foo_ident_rep_1",
source: dbA.name,
target: dbB.name
};
T(repDb.save(repDoc1).ok);
waitForRep(repDb, repDoc1, "completed");
for (var i = 0; i < docs1.length; i++) {
var doc = docs1[i];
var copy = dbB.open(doc._id);
T(copy !== null);
T(copy.value === doc.value);
}
var repDoc1_copy = repDb.open(repDoc1._id);
T(repDoc1_copy !== null);
T(repDoc1_copy.source === repDoc1.source);
T(repDoc1_copy.target === repDoc1.target);
T(repDoc1_copy._replication_state === "completed");
T(typeof repDoc1_copy._replication_state_time === "string");
T(typeof repDoc1_copy._replication_id === "string");
T(typeof repDoc1_copy._replication_stats === "object", "doc has stats");
var stats = repDoc1_copy._replication_stats;
TEquals(docs1.length, stats.revisions_checked,
"right # of revisions_checked");
TEquals(docs1.length, stats.missing_revisions_found,
"right # of missing_revisions_found");
TEquals(docs1.length, stats.docs_read, "right # of docs_read");
TEquals(docs1.length, stats.docs_written, "right # of docs_written");
TEquals(0, stats.doc_write_failures, "right # of doc_write_failures");
TEquals(dbA.info().update_seq, stats.checkpointed_source_seq,
"right checkpointed_source_seq");
var newDoc = {
_id: "doc666",
value: 666
};
T(dbA.save(newDoc).ok);
wait(200);
var newDoc_copy = dbB.open(newDoc._id);
// not replicated because first replication is complete (not continuous)
T(newDoc_copy === null);
var repDoc2 = {
_id: "foo_ident_rep_2",
source: dbA.name,
target: dbB.name
};
T(repDb.save(repDoc2).ok);
waitForRep(repDb, repDoc2, "completed");
var newDoc_copy = dbB.open(newDoc._id);
T(newDoc_copy !== null);
T(newDoc_copy.value === newDoc.value);
var repDoc2_copy = repDb.open(repDoc2._id);
T(repDoc2_copy !== null);
T(repDoc2_copy.source === repDoc1.source);
T(repDoc2_copy.target === repDoc1.target);
T(repDoc2_copy._replication_state === "completed");
T(typeof repDoc2_copy._replication_state_time === "string");
T(typeof repDoc2_copy._replication_id === "string");
T(repDoc2_copy._replication_id === repDoc1_copy._replication_id);
T(typeof repDoc2_copy._replication_stats === "object", "doc has stats");
stats = repDoc2_copy._replication_stats;
TEquals(1, stats.revisions_checked, "right # of revisions_checked");
TEquals(1, stats.missing_revisions_found,
"right # of missing_revisions_found");
TEquals(1, stats.docs_read, "right # of docs_read");
TEquals(1, stats.docs_written, "right # of docs_written");
TEquals(0, stats.doc_write_failures, "right # of doc_write_failures");
TEquals(dbA.info().update_seq, stats.checkpointed_source_seq,
"right checkpointed_source_seq");
}
// test the case where multiple replication docs (different IDs)
// describe in fact the same replication (source, target, etc)
function identical_rep_docs() {
populate_db(dbA, docs1);
populate_db(dbB, []);
var repDoc1 = {
_id: "foo_dup_rep_doc_1",
source: "http://" + CouchDB.host + "/" + dbA.name,
target: dbB.name
};
var repDoc2 = {
_id: "foo_dup_rep_doc_2",
source: "http://" + CouchDB.host + "/" + dbA.name,
target: dbB.name
};
T(repDb.save(repDoc1).ok);
T(repDb.save(repDoc2).ok);
waitForRep(repDb, repDoc1, "completed");
for (var i = 0; i < docs1.length; i++) {
var doc = docs1[i];
var copy = dbB.open(doc._id);
T(copy !== null);
T(copy.value === doc.value);
}
repDoc1 = repDb.open("foo_dup_rep_doc_1");
T(repDoc1 !== null);
T(repDoc1._replication_state === "completed", "identical");
T(typeof repDoc1._replication_state_time === "string");
T(typeof repDoc1._replication_id === "string");
repDoc2 = repDb.open("foo_dup_rep_doc_2");
T(repDoc2 !== null);
T(typeof repDoc2._replication_state === "undefined");
T(typeof repDoc2._replication_state_time === "undefined");
T(repDoc2._replication_id === repDoc1._replication_id);
}
// test the case where multiple replication docs (different IDs)
// describe in fact the same continuous replication (source, target, etc)
function identical_continuous_rep_docs() {
populate_db(dbA, docs1);
populate_db(dbB, []);
var repDoc1 = {
_id: "foo_dup_cont_rep_doc_1",
source: "http://" + CouchDB.host + "/" + dbA.name,
target: dbB.name,
continuous: true
};
var repDoc2 = {
_id: "foo_dup_cont_rep_doc_2",
source: "http://" + CouchDB.host + "/" + dbA.name,
target: dbB.name,
continuous: true
};
T(repDb.save(repDoc1).ok);
T(repDb.save(repDoc2).ok);
waitForSeq(dbA, dbB);
for (var i = 0; i < docs1.length; i++) {
var doc = docs1[i];
var copy = dbB.open(doc._id);
T(copy !== null);
T(copy.value === doc.value);
}
repDoc1 = repDb.open("foo_dup_cont_rep_doc_1");
T(repDoc1 !== null);
T(repDoc1._replication_state === "triggered");
T(typeof repDoc1._replication_state_time === "string");
T(typeof repDoc1._replication_id === "string");
repDoc2 = repDb.open("foo_dup_cont_rep_doc_2");
T(repDoc2 !== null);
T(typeof repDoc2._replication_state === "undefined");
T(typeof repDoc2._replication_state_time === "undefined");
T(repDoc2._replication_id === repDoc1._replication_id);
var newDoc = {
_id: "foo666",
value: 999
};
T(dbA.save(newDoc).ok);
waitForSeq(dbA, dbB);
var copy = dbB.open("foo666");
T(copy !== null);
T(copy.value === 999);
// deleting second replication doc, doesn't affect the 1st one and
// neither it stops the replication
T(repDb.deleteDoc(repDoc2).ok);
repDoc1 = repDb.open("foo_dup_cont_rep_doc_1");
T(repDoc1 !== null);
T(repDoc1._replication_state === "triggered");
T(typeof repDoc1._replication_state_time === "string");
var newDoc2 = {
_id: "foo5000",
value: 5000
};
T(dbA.save(newDoc2).ok);
waitForSeq(dbA, dbB);
var copy = dbB.open("foo5000");
T(copy !== null);
T(copy.value === 5000);
// deleting the 1st replication document stops the replication
T(repDb.deleteDoc(repDoc1).ok);
var newDoc3 = {
_id: "foo1983",
value: 1983
};
T(dbA.save(newDoc3).ok);
wait(wait_rep_doc); //how to remove wait?
var copy = dbB.open("foo1983");
T(copy === null);
}
function test_replication_credentials_delegation() {
populate_db(usersDb, []);
var joeUserDoc = CouchDB.prepareUserDoc({
name: "joe",
roles: ["god", "erlanger"]
}, "erly");
T(usersDb.save(joeUserDoc).ok);
var ddoc = {
_id: "_design/beer",
language: "javascript"
};
populate_db(dbA, docs1.concat([ddoc]));
populate_db(dbB, []);
T(dbB.setSecObj({
admins: {
names: [],
roles: ["god"]
}
}).ok);
var server_admins_config = [
{
section: "couch_httpd_auth",
key: "iterations",
value: "1"
},
{
section: "admins",
key: "fdmanana",
value: "qwerty"
}
];
run_on_modified_server(server_admins_config, function() {
T(CouchDB.login("fdmanana", "qwerty").ok);
T(CouchDB.session().userCtx.name === "fdmanana");
T(CouchDB.session().userCtx.roles.indexOf("_admin") !== -1);
var repDoc = {
_id: "foo_rep_del_doc_1",
source: dbA.name,
target: dbB.name,
user_ctx: {
name: "joe",
roles: ["erlanger"]
}
};
T(repDb.save(repDoc).ok);
waitForRep(repDb, repDoc, "completed");
for (var i = 0; i < docs1.length; i++) {
var doc = docs1[i];
var copy = dbB.open(doc._id);
T(copy !== null);
T(copy.value === doc.value);
}
// design doc was not replicated, because joe is not an admin of db B
var doc = dbB.open(ddoc._id);
T(doc === null);
// now test the same replication but putting the role "god" in the
// delegation user context property
var repDoc2 = {
_id: "foo_rep_del_doc_2",
source: dbA.name,
target: dbB.name,
user_ctx: {
name: "joe",
roles: ["erlanger", "god"]
}
};
T(repDb.save(repDoc2).ok);
waitForRep(repDb, repDoc2, "completed");
for (var i = 0; i < docs1.length; i++) {
var doc = docs1[i];
var copy = dbB.open(doc._id);
T(copy !== null);
T(copy.value === doc.value);
}
// because anyone with a 'god' role is an admin of db B, a replication
// that is delegated to a 'god' role can write design docs to db B
doc = dbB.open(ddoc._id);
T(doc !== null);
T(doc.language === ddoc.language);
});
}
function continuous_replication_survives_restart() {
var origRepDbName = CouchDB.request(
"GET", "/_config/replicator/db").responseText;
repDb.deleteDb();
var xhr = CouchDB.request("PUT", "/_config/replicator/db", {
body : JSON.stringify(repDb.name),
headers: {"X-Couch-Persist": "false"}
});
T(xhr.status === 200);
populate_db(dbA, docs1);
populate_db(dbB, []);
var repDoc = {
_id: "foo_cont_rep_survives_doc",
source: dbA.name,
target: dbB.name,
continuous: true
};
T(repDb.save(repDoc).ok);
waitForSeq(dbA, dbB);
for (var i = 0; i < docs1.length; i++) {
var doc = docs1[i];
var copy = dbB.open(doc._id);
T(copy !== null);
T(copy.value === doc.value);
}
repDb.ensureFullCommit();
dbA.ensureFullCommit();
restartServer();
xhr = CouchDB.request("PUT", "/_config/replicator/db", {
body : JSON.stringify(repDb.name),
headers: {"X-Couch-Persist": "false"}
});
T(xhr.status === 200);
// add another doc to source, it will be replicated to target
var docX = {
_id: "foo1000",
value: 1001
};
T(dbA.save(docX).ok);
waitForSeq(dbA, dbB);
var copy = dbB.open("foo1000");
T(copy !== null);
T(copy.value === 1001);
repDoc = waitForDocPos(repDb, "foo_cont_rep_survives_doc", 3);
T(repDoc !== null);
T(repDoc.continuous === true);
// stop replication
T(repDb.deleteDoc(repDoc).ok);
xhr = CouchDB.request("PUT", "/_config/replicator/db", {
body : origRepDbName,
headers: {"X-Couch-Persist": "false"}
});
T(xhr.status === 200);
}
function rep_db_write_authorization() {
populate_db(dbA, docs1);
populate_db(dbB, []);
var server_admins_config = [
{
section: "admins",
key: "fdmanana",
value: "qwerty"
}
];
run_on_modified_server(server_admins_config, function() {
var repDoc = {
_id: "foo_rep_doc",
source: dbA.name,
target: dbB.name,
continuous: true
};
T(CouchDB.login("fdmanana", "qwerty").ok);
T(CouchDB.session().userCtx.name === "fdmanana");
T(CouchDB.session().userCtx.roles.indexOf("_admin") !== -1);
T(repDb.save(repDoc).ok);
waitForRep(repDb, repDoc, "completed");
for (var i = 0; i < docs1.length; i++) {
var doc = docs1[i];
var copy = dbB.open(doc._id);
T(copy !== null);
T(copy.value === doc.value);
}
repDoc = repDb.open("foo_rep_doc");
T(repDoc !== null);
repDoc.target = "test_suite_foo_db";
repDoc.create_target = true;
// Only the replicator can update replication documents.
// Admins can only add and delete replication documents.
try {
repDb.save(repDoc);
T(false && "Should have thrown an exception");
} catch (x) {
T(x["error"] === "forbidden");
}
});
}
function test_user_ctx_validation() {
populate_db(dbA, docs1);
populate_db(dbB, []);
populate_db(usersDb, []);
var joeUserDoc = CouchDB.prepareUserDoc({
name: "joe",
roles: ["erlanger", "bar"]
}, "erly");
var fdmananaUserDoc = CouchDB.prepareUserDoc({
name: "fdmanana",
roles: ["a", "b", "c"]
}, "qwerty");
TEquals(true, usersDb.save(joeUserDoc).ok);
TEquals(true, usersDb.save(fdmananaUserDoc).ok);
T(dbB.setSecObj({
admins: {
names: [],
roles: ["god"]
},
readers: {
names: [],
roles: ["foo"]
}
}).ok);
TEquals(true, CouchDB.login("joe", "erly").ok);
TEquals("joe", CouchDB.session().userCtx.name);
TEquals(-1, CouchDB.session().userCtx.roles.indexOf("_admin"));
var repDoc = {
_id: "foo_rep",
source: CouchDB.protocol + CouchDB.host + "/" + dbA.name,
target: dbB.name
};
try {
repDb.save(repDoc);
T(false, "Should have failed, user_ctx missing.");
} catch (x) {
TEquals("forbidden", x.error);
}
repDoc.user_ctx = {
name: "john",
roles: ["erlanger"]
};
try {
repDb.save(repDoc);
T(false, "Should have failed, wrong user_ctx.name.");
} catch (x) {
TEquals("forbidden", x.error);
}
repDoc.user_ctx = {
name: "joe",
roles: ["bar", "god", "erlanger"]
};
try {
repDb.save(repDoc);
T(false, "Should have failed, a bad role in user_ctx.roles.");
} catch (x) {
TEquals("forbidden", x.error);
}
// user_ctx.roles might contain only a subset of the user's roles
repDoc.user_ctx = {
name: "joe",
roles: ["erlanger"]
};
TEquals(true, repDb.save(repDoc).ok);
CouchDB.logout();
waitForRep(repDb, repDoc, "error");
var repDoc1 = repDb.open(repDoc._id);
T(repDoc1 !== null);
TEquals(repDoc.source, repDoc1.source);
TEquals(repDoc.target, repDoc1.target);
TEquals("error", repDoc1._replication_state);
TEquals("string", typeof repDoc1._replication_id);
TEquals("string", typeof repDoc1._replication_state_time);
TEquals(true, CouchDB.login("fdmanana", "qwerty").ok);
TEquals("fdmanana", CouchDB.session().userCtx.name);
TEquals(-1, CouchDB.session().userCtx.roles.indexOf("_admin"));
try {
T(repDb.deleteDoc(repDoc1).ok);
T(false, "Shouldn't be able to delete replication document.");
} catch (x) {
TEquals("forbidden", x.error);
}
CouchDB.logout();
TEquals(true, CouchDB.login("joe", "erly").ok);
TEquals("joe", CouchDB.session().userCtx.name);
TEquals(-1, CouchDB.session().userCtx.roles.indexOf("_admin"));
T(repDb.deleteDoc(repDoc1).ok);
CouchDB.logout();
for (var i = 0; i < docs1.length; i++) {
var doc = docs1[i];
var copy = dbB.open(doc._id);
TEquals(null, copy);
}
T(dbB.setSecObj({
admins: {
names: [],
roles: ["god", "erlanger"]
},
readers: {
names: [],
roles: ["foo"]
}
}).ok);
TEquals(true, CouchDB.login("joe", "erly").ok);
TEquals("joe", CouchDB.session().userCtx.name);
TEquals(-1, CouchDB.session().userCtx.roles.indexOf("_admin"));
repDoc = {
_id: "foo_rep_2",
source: CouchDB.protocol + CouchDB.host + "/" + dbA.name,
target: dbB.name,
user_ctx: {
name: "joe",
roles: ["erlanger"]
}
};
TEquals(true, repDb.save(repDoc).ok);
CouchDB.logout();
waitForRep(repDb, repDoc, "complete");
repDoc1 = repDb.open(repDoc._id);
T(repDoc1 !== null);
TEquals(repDoc.source, repDoc1.source);
TEquals(repDoc.target, repDoc1.target);
TEquals("completed", repDoc1._replication_state);
TEquals("string", typeof repDoc1._replication_id);
TEquals("string", typeof repDoc1._replication_state_time);
for (var i = 0; i < docs1.length; i++) {
var doc = docs1[i];
var copy = dbB.open(doc._id);
T(copy !== null);
TEquals(doc.value, copy.value);
}
// Admins don't need to supply a user_ctx property in replication docs.
// If they do not, the implicit user_ctx "user_ctx": {name: null, roles: []}
// is used, meaning that design documents will not be replicated into
// local targets
T(dbB.setSecObj({
admins: {
names: [],
roles: []
},
readers: {
names: [],
roles: []
}
}).ok);
var ddoc = { _id: "_design/foo" };
TEquals(true, dbA.save(ddoc).ok);
repDoc = {
_id: "foo_rep_3",
source: CouchDB.protocol + CouchDB.host + "/" + dbA.name,
target: dbB.name
};
TEquals(true, repDb.save(repDoc).ok);
waitForRep(repDb, repDoc, "complete");
repDoc1 = repDb.open(repDoc._id);
T(repDoc1 !== null);
TEquals(repDoc.source, repDoc1.source);
TEquals(repDoc.target, repDoc1.target);
TEquals("completed", repDoc1._replication_state);
TEquals("string", typeof repDoc1._replication_id);
TEquals("string", typeof repDoc1._replication_state_time);
var ddoc_copy = dbB.open(ddoc._id);
T(ddoc_copy === null);
repDoc = {
_id: "foo_rep_4",
source: CouchDB.protocol + CouchDB.host + "/" + dbA.name,
target: dbB.name,
user_ctx: {
roles: ["_admin"]
}
};
TEquals(true, repDb.save(repDoc).ok);
waitForRep(repDb, repDoc, "complete");
repDoc1 = repDb.open(repDoc._id);
T(repDoc1 !== null);
TEquals(repDoc.source, repDoc1.source);
TEquals(repDoc.target, repDoc1.target);
TEquals("completed", repDoc1._replication_state);
TEquals("string", typeof repDoc1._replication_id);
TEquals("string", typeof repDoc1._replication_state_time);
ddoc_copy = dbB.open(ddoc._id);
T(ddoc_copy !== null);
}
function rep_doc_with_bad_rep_id() {
populate_db(dbA, docs1);
populate_db(dbB, []);
var repDoc = {
_id: "foo_rep",
source: dbA.name,
target: dbB.name,
replication_id: "1234abc"
};
T(repDb.save(repDoc).ok);
waitForRep(repDb, repDoc, "completed");
for (var i = 0; i < docs1.length; i++) {
var doc = docs1[i];
var copy = dbB.open(doc._id);
T(copy !== null);
T(copy.value === doc.value);
}
var repDoc1 = repDb.open(repDoc._id);
T(repDoc1 !== null);
T(repDoc1.source === repDoc.source);
T(repDoc1.target === repDoc.target);
T(repDoc1._replication_state === "completed",
"replication document with bad replication id failed");
T(typeof repDoc1._replication_state_time === "string");
T(typeof repDoc1._replication_id === "string");
T(repDoc1._replication_id !== "1234abc");
}
function swap_rep_db() {
var repDb2 = new CouchDB("test_suite_rep_db_2");
var dbA = new CouchDB("test_suite_rep_db_a");
var dbA_copy = new CouchDB("test_suite_rep_db_a_copy");
var dbB = new CouchDB("test_suite_rep_db_b");
var dbB_copy = new CouchDB("test_suite_rep_db_b_copy");
var dbC = new CouchDB("test_suite_rep_db_c");
var dbC_copy = new CouchDB("test_suite_rep_db_c_copy");
var repDoc1, repDoc2, repDoc3;
var xhr, i, doc, copy, new_doc;
populate_db(dbA, docs1);
populate_db(dbB, docs1);
populate_db(dbC, docs1);
populate_db(dbA_copy, []);
populate_db(dbB_copy, []);
populate_db(dbC_copy, []);
populate_db(repDb2, []);
repDoc1 = {
_id: "rep1",
source: CouchDB.protocol + CouchDB.host + "/" + dbA.name,
target: dbA_copy.name,
continuous: true
};
repDoc2 = {
_id: "rep2",
source: CouchDB.protocol + CouchDB.host + "/" + dbB.name,
target: dbB_copy.name,
continuous: true
};
repDoc3 = {
_id: "rep3",
source: CouchDB.protocol + CouchDB.host + "/" + dbC.name,
target: dbC_copy.name,
continuous: true
};
TEquals(true, repDb.save(repDoc1).ok);
TEquals(true, repDb.save(repDoc2).ok);
waitForSeq(dbA, dbA_copy);
waitForSeq(dbB, dbB_copy);
xhr = CouchDB.request("PUT", "/_config/replicator/db",{
body : JSON.stringify(repDb2.name),
headers: {"X-Couch-Persist": "false"}
});
TEquals(200, xhr.status);
new_doc = {
_id: "foo666",
value: 666
};
TEquals(true, dbA.save(new_doc).ok);
TEquals(true, dbB.save(new_doc).ok);
waitForSeq(dbA, dbA_copy);
waitForSeq(dbB, dbB_copy);
TEquals(true, repDb2.save(repDoc3).ok);
waitForSeq(dbC, dbC_copy);
for (i = 0; i < docs1.length; i++) {
doc = docs1[i];
copy = dbA_copy.open(doc._id);
T(copy !== null);
TEquals(doc.value, copy.value);
copy = dbB_copy.open(doc._id);
T(copy !== null);
TEquals(doc.value, copy.value);
copy = dbC_copy.open(doc._id);
T(copy !== null);
TEquals(doc.value, copy.value);
}
// replications rep1 and rep2 should have been stopped when the replicator
// database was swapped
copy = dbA_copy.open(new_doc._id);
TEquals(null, copy);
copy = dbB_copy.open(new_doc._id);
TEquals(null, copy);
xhr = CouchDB.request("PUT", "/_config/replicator/db",{
body : JSON.stringify(repDb.name),
headers: {"X-Couch-Persist": "false"}
});
TEquals(200, xhr.status);
// after setting the replicator database to the former, replications rep1
// and rep2 should have been resumed, while rep3 was stopped
TEquals(true, dbC.save(new_doc).ok);
wait(1000);
waitForSeq(dbA, dbA_copy);
waitForSeq(dbB, dbB_copy);
copy = dbA_copy.open(new_doc._id);
T(copy !== null);
TEquals(new_doc.value, copy.value);
copy = dbB_copy.open(new_doc._id);
T(copy !== null);
TEquals(new_doc.value, copy.value);
copy = dbC_copy.open(new_doc._id);
TEquals(null, copy);
}
function compact_rep_db() {
var dbA_copy = new CouchDB("test_suite_rep_db_a_copy");
var dbB_copy = new CouchDB("test_suite_rep_db_b_copy");
var repDoc1, repDoc2;
var xhr, i, doc, copy, new_doc;
var docs = makeDocs(1, 50);
populate_db(dbA, docs);
populate_db(dbB, docs);
populate_db(dbA_copy, []);
populate_db(dbB_copy, []);
repDoc1 = {
_id: "rep1",
source: CouchDB.protocol + CouchDB.host + "/" + dbA.name,
target: dbA_copy.name,
continuous: true
};
repDoc2 = {
_id: "rep2",
source: CouchDB.protocol + CouchDB.host + "/" + dbB.name,
target: dbB_copy.name,
continuous: true
};
TEquals(true, repDb.save(repDoc1).ok);
TEquals(true, repDb.save(repDoc2).ok);
TEquals(true, repDb.compact().ok);
TEquals(202, repDb.last_req.status);
waitForSeq(dbA, dbA_copy);
waitForSeq(dbB, dbB_copy);
while (repDb.info().compact_running) {};
for (i = 0; i < docs.length; i++) {
copy = dbA_copy.open(docs[i]._id);
T(copy !== null);
copy = dbB_copy.open(docs[i]._id);
T(copy !== null);
}
new_doc = {
_id: "foo666",
value: 666
};
TEquals(true, dbA.save(new_doc).ok);
TEquals(true, dbB.save(new_doc).ok);
waitForSeq(dbA, dbA_copy);
waitForSeq(dbB, dbB_copy);
copy = dbA.open(new_doc._id);
T(copy !== null);
TEquals(666, copy.value);
copy = dbB.open(new_doc._id);
T(copy !== null);
TEquals(666, copy.value);
}
function error_state_replication() {
populate_db(dbA, docs1);
var repDoc = {
_id: "foo_error_rep",
source: dbA.name,
target: "nonexistent_test_db"
};
T(repDb.save(repDoc).ok);
waitForRep(repDb, repDoc, "error");
var repDoc1 = repDb.open(repDoc._id);
T(repDoc1 !== null);
T(repDoc1._replication_state === "error");
T(typeof repDoc1._replication_state_time === "string");
T(typeof repDoc1._replication_id === "string");
}
function rep_doc_field_validation() {
var docs = makeDocs(1, 5);
populate_db(dbA, docs);
populate_db(dbB, []);
var repDoc = {
_id: "rep1",
target: dbB.name
};
try {
repDb.save(repDoc);
T(false, "should have failed because source field is missing");
} catch (x) {
TEquals("forbidden", x.error);
}
repDoc = {
_id: "rep1",
source: 123,
target: dbB.name
};
try {
repDb.save(repDoc);
T(false, "should have failed because source field is a number");
} catch (x) {
TEquals("forbidden", x.error);
}
repDoc = {
_id: "rep1",
source: dbA.name
};
try {
repDb.save(repDoc);
T(false, "should have failed because target field is missing");
} catch (x) {
TEquals("forbidden", x.error);
}
repDoc = {
_id: "rep1",
source: dbA.name,
target: null
};
try {
repDb.save(repDoc);
T(false, "should have failed because target field is null");
} catch (x) {
TEquals("forbidden", x.error);
}
repDoc = {
_id: "rep1",
source: dbA.name,
target: { url: 123 }
};
try {
repDb.save(repDoc);
T(false, "should have failed because target.url field is not a string");
} catch (x) {
TEquals("forbidden", x.error);
}
repDoc = {
_id: "rep1",
source: dbA.name,
target: { url: dbB.name, auth: null }
};
try {
repDb.save(repDoc);
T(false, "should have failed because target.auth field is null");
} catch (x) {
TEquals("forbidden", x.error);
}
repDoc = {
_id: "rep1",
source: dbA.name,
target: { url: dbB.name, auth: "foo:bar" }
};
try {
repDb.save(repDoc);
T(false, "should have failed because target.auth field is not an object");
} catch (x) {
TEquals("forbidden", x.error);
}
repDoc = {
_id: "rep1",
source: dbA.name,
target: dbB.name,
continuous: "true"
};
try {
repDb.save(repDoc);
T(false, "should have failed because continuous is not a boolean");
} catch (x) {
TEquals("forbidden", x.error);
}
repDoc = {
_id: "rep1",
source: dbA.name,
target: dbB.name,
filter: 123
};
try {
repDb.save(repDoc);
T(false, "should have failed because filter is not a string");
} catch (x) {
TEquals("forbidden", x.error);
}
}
function test_invalid_filter() {
// COUCHDB-1199 - replication document with a filter field that was invalid
// crashed the CouchDB server.
var repDoc1 = {
_id: "rep1",
source: "couch_foo_test_db",
target: "couch_bar_test_db",
filter: "test/foofilter"
};
TEquals(true, repDb.save(repDoc1).ok);
waitForRep(repDb, repDoc1, "error");
repDoc1 = repDb.open(repDoc1._id);
TEquals("undefined", typeof repDoc1._replication_id);
TEquals("error", repDoc1._replication_state);
populate_db(dbA, docs1);
populate_db(dbB, []);
var repDoc2 = {
_id: "rep2",
source: dbA.name,
target: dbB.name,
filter: "test/foofilter"
};
TEquals(true, repDb.save(repDoc2).ok);
waitForRep(repDb, repDoc2, "error");
repDoc2 = repDb.open(repDoc2._id);
TEquals("undefined", typeof repDoc2._replication_id);
TEquals("error", repDoc2._replication_state);
var ddoc = {
_id: "_design/mydesign",
language : "javascript",
filters : {
myfilter : (function(doc, req) {
return true;
}).toString()
}
};
TEquals(true, dbA.save(ddoc).ok);
var repDoc3 = {
_id: "rep3",
source: dbA.name,
target: dbB.name,
filter: "mydesign/myfilter"
};
TEquals(true, repDb.save(repDoc3).ok);
waitForRep(repDb, repDoc3, "completed");
repDoc3 = repDb.open(repDoc3._id);
TEquals("string", typeof repDoc3._replication_id);
TEquals("completed", repDoc3._replication_state);
}
function test_rep_db_update_security() {
var dbA_copy = new CouchDB("test_suite_rep_db_a_copy");
var dbB_copy = new CouchDB("test_suite_rep_db_b_copy");
var repDoc1, repDoc2;
var xhr, i, doc, copy, new_doc;
var docs = makeDocs(1, 3);
populate_db(dbA, docs);
populate_db(dbB, docs);
populate_db(dbA_copy, []);
populate_db(dbB_copy, []);
repDoc1 = {
_id: "rep1",
source: CouchDB.protocol + CouchDB.host + "/" + dbA.name,
target: dbA_copy.name
};
repDoc2 = {
_id: "rep2",
source: CouchDB.protocol + CouchDB.host + "/" + dbB.name,
target: dbB_copy.name
};
TEquals(true, repDb.save(repDoc1).ok);
waitForRep(repDb, repDoc1, "completed");
T(repDb.setSecObj({
readers: {
names: ["joe"]
}
}).ok);
TEquals(true, repDb.save(repDoc2).ok);
waitForRep(repDb, repDoc2, "completed");
}
// run all the tests
var server_config = [
{
section: "couch_httpd_auth",
key: "iterations",
value: "1"
},
{
section: "replicator",
key: "db",
value: repDb.name
}
];
repDb.deleteDb();
run_on_modified_server(server_config, simple_replication);
repDb.deleteDb();
restartServer();
run_on_modified_server(server_config, filtered_replication);
repDb.deleteDb();
restartServer();
run_on_modified_server(server_config, continuous_replication);
repDb.deleteDb();
restartServer();
run_on_modified_server(server_config, by_doc_ids_replication);
repDb.deleteDb();
restartServer();
run_on_modified_server(server_config, successive_identical_replications);
repDb.deleteDb();
restartServer();
run_on_modified_server(server_config, identical_rep_docs);
repDb.deleteDb();
restartServer();
run_on_modified_server(server_config, identical_continuous_rep_docs);
repDb.deleteDb();
restartServer();
run_on_modified_server(server_config, rep_db_write_authorization);
repDb.deleteDb();
restartServer();
run_on_modified_server(server_config, rep_doc_with_bad_rep_id);
var server_config_2 = server_config.concat([
{
section: "couch_httpd_auth",
key: "authentication_db",
value: usersDb.name
}
]);
repDb.deleteDb();
usersDb.deleteDb();
restartServer();
run_on_modified_server(server_config_2, test_user_ctx_validation);
repDb.deleteDb();
usersDb.deleteDb();
restartServer();
run_on_modified_server(server_config_2, test_replication_credentials_delegation);
repDb.deleteDb();
restartServer();
continuous_replication_survives_restart();
repDb.deleteDb();
restartServer();
run_on_modified_server(server_config, swap_rep_db);
repDb.deleteDb();
restartServer();
run_on_modified_server(server_config, compact_rep_db);
repDb.deleteDb();
restartServer();
run_on_modified_server(server_config, rep_doc_field_validation);
repDb.deleteDb();
restartServer();
run_on_modified_server(server_config, test_invalid_filter);
repDb.deleteDb();
restartServer();
run_on_modified_server(server_config, test_rep_db_update_security);
/*
* Disabled, since error state would be set on the document only after
* the exponential backoff retry done by the replicator database listener
* terminates, which takes too much time for a unit test.
*/
/*
* repDb.deleteDb();
* restartServer();
* run_on_modified_server(server_config, error_state_replication);
*/
// cleanup
repDb.deleteDb();
usersDb.deleteDb();
dbA.deleteDb();
dbB.deleteDb();
(new CouchDB("test_suite_rep_db_2")).deleteDb();
(new CouchDB("test_suite_rep_db_c")).deleteDb();
(new CouchDB("test_suite_rep_db_a_copy")).deleteDb();
(new CouchDB("test_suite_rep_db_b_copy")).deleteDb();
(new CouchDB("test_suite_rep_db_c_copy")).deleteDb();
};