diff options
author | Jan Lehnardt <jan@apache.org> | 2013-01-13 15:22:01 +0100 |
---|---|---|
committer | Jan Lehnardt <jan@apache.org> | 2013-02-08 19:17:43 +0100 |
commit | ae048910ebea89942116fe7734d36baaa7d6cfcf (patch) | |
tree | 2b977f5738e327a2b5cdd8b7859518981a74392a | |
parent | 3b103eb10b115b4b6a8f0c6e31dc92292c0aeb71 (diff) | |
download | couchdb-ae048910ebea89942116fe7734d36baaa7d6cfcf.tar.gz |
split replicator_db tests
23 files changed, 2347 insertions, 1562 deletions
@@ -1,6 +1,16 @@ Apache CouchDB CHANGES ====================== +Version 1.4.0 +------------- + +This version has not been released yet. + +Test Suite: + + * Split up replicator_db tests into multiple independent tests. + + Version 1.3.0 ------------- diff --git a/share/Makefile.am b/share/Makefile.am index 31373ee2a..876c70142 100644 --- a/share/Makefile.am +++ b/share/Makefile.am @@ -176,7 +176,25 @@ nobase_dist_localdata_DATA = \ www/script/test/reduce_false.js \ www/script/test/reduce_false_temp.js \ www/script/test/replication.js \ - www/script/test/replicator_db.js \ + www/script/replicator_db_inc.js \ + www/script/test/replicator_db_bad_rep_id.js \ + www/script/test/replicator_db_by_doc_id.js \ + www/script/test/replicator_db_compact_rep_db.js \ + www/script/test/replicator_db_continuous.js \ + www/script/test/replicator_db_credential_delegation.js \ + www/script/test/replicator_db_field_validation.js \ + www/script/test/replicator_db_filtered.js \ + www/script/test/replicator_db_identical.js \ + www/script/test/replicator_db_identical_continuous.js \ + www/script/test/replicator_db_invalid_filter.js \ + www/script/test/replicator_db_security.js \ + www/script/test/replicator_db_simple.js \ + www/script/test/replicator_db_successive.js \ + www/script/test/replicator_db_survives.js \ + www/script/test/replicator_db_swap_rep_db.js \ + www/script/test/replicator_db_update_security.js \ + www/script/test/replicator_db_user_ctx.js \ + www/script/test/replicator_db_write_auth.js \ www/script/test/replicator_db_security.js \ www/script/test/rev_stemming.js \ www/script/test/rewrite.js \ diff --git a/share/www/script/couch_tests.js b/share/www/script/couch_tests.js index c1cdf75db..99d63e745 100644 --- a/share/www/script/couch_tests.js +++ b/share/www/script/couch_tests.js @@ -68,7 +68,24 @@ loadTest("reduce_builtin.js"); loadTest("reduce_false.js"); loadTest("reduce_false_temp.js"); loadTest("replication.js"); -loadTest("replicator_db.js"); +loadScript("script/replicator_db_inc.js"); +loadTest("replicator_db_bad_rep_id.js"); +loadTest("replicator_db_by_doc_id.js"); +loadTest("replicator_db_compact_rep_db.js"); +loadTest("replicator_db_continuous.js"); +loadTest("replicator_db_credential_delegation.js"); +loadTest("replicator_db_field_validation.js"); +loadTest("replicator_db_filtered.js"); +loadTest("replicator_db_identical.js"); +loadTest("replicator_db_identical_continuous.js"); +loadTest("replicator_db_invalid_filter.js"); +loadTest("replicator_db_simple.js"); +loadTest("replicator_db_successive.js"); +loadTest("replicator_db_survives.js"); +loadTest("replicator_db_swap_rep_db.js"); +loadTest("replicator_db_update_security.js"); +loadTest("replicator_db_user_ctx.js"); +loadTest("replicator_db_write_auth.js"); loadTest("replicator_db_security.js"); loadTest("rev_stemming.js"); loadTest("rewrite.js"); diff --git a/share/www/script/replicator_db_inc.js b/share/www/script/replicator_db_inc.js new file mode 100644 index 000000000..23f858768 --- /dev/null +++ b/share/www/script/replicator_db_inc.js @@ -0,0 +1,96 @@ +// 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. + +var replicator_db = {}; +replicator_db.wait_rep_doc = 500; // number of millisecs to wait after saving a Rep Doc +replicator_db.dbA = new CouchDB("test_suite_rep_db_a", {"X-Couch-Full-Commit":"false"}); +replicator_db.dbB = new CouchDB("test_suite_rep_db_b", {"X-Couch-Full-Commit":"false"}); +replicator_db.repDb = new CouchDB("test_suite_rep_db", {"X-Couch-Full-Commit":"false"}); +replicator_db.usersDb = new CouchDB("test_suite_auth", {"X-Couch-Full-Commit":"false"}); + +replicator_db.docs1 = [ + { + _id: "foo1", + value: 11 + }, + { + _id: "foo2", + value: 22 + }, + { + _id: "foo3", + value: 33 + } +]; + +replicator_db.waitForRep = function waitForSeq(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); +} + +replicator_db.waitForSeq = 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); +} + +replicator_db.waitForDocPos = 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; +} + +replicator_db.wait = function wait(ms) { + var t0 = new Date(), t1; + do { + CouchDB.request("GET", "/"); + t1 = new Date(); + } while ((t1 - t0) <= ms); +} + + +replicator_db.populate_db = function populate_db(db, docs) { + if (db.name !== replicator_db.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); + } +}
\ No newline at end of file diff --git a/share/www/script/test/replicator_db.js b/share/www/script/test/replicator_db.js deleted file mode 100644 index edc85f49f..000000000 --- a/share/www/script/test/replicator_db.js +++ /dev/null @@ -1,1560 +0,0 @@ -// 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); - - // Temporary band-aid, give the replicator db some - // time to make the switch - wait(500); - - 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(); -}; diff --git a/share/www/script/test/replicator_db_bad_rep_id.js b/share/www/script/test/replicator_db_bad_rep_id.js new file mode 100644 index 000000000..285b863e7 --- /dev/null +++ b/share/www/script/test/replicator_db_bad_rep_id.js @@ -0,0 +1,77 @@ +// 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_bad_rep_id = function(debug) { + + if (debug) debugger; + + var populate_db = replicator_db.populate_db; + var docs1 = replicator_db.docs1; + var dbA = replicator_db.dbA; + var dbB = replicator_db.dbB; + var repDb = replicator_db.repDb; + var wait = replicator_db.wait; + var waitForRep = replicator_db.waitForRep; + var waitForSeq = replicator_db.waitForSeq; + + 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"); + } + + 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, rep_doc_with_bad_rep_id); + + // cleanup + repDb.deleteDb(); + dbA.deleteDb(); + dbB.deleteDb(); +}
\ No newline at end of file diff --git a/share/www/script/test/replicator_db_by_doc_id.js b/share/www/script/test/replicator_db_by_doc_id.js new file mode 100644 index 000000000..1e1a38521 --- /dev/null +++ b/share/www/script/test/replicator_db_by_doc_id.js @@ -0,0 +1,99 @@ +// 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_by_doc_id = function(debug) { + + if (debug) debugger; + + var populate_db = replicator_db.populate_db; + var docs1 = replicator_db.docs1; + var dbA = replicator_db.dbA; + var dbB = replicator_db.dbB; + var repDb = replicator_db.repDb; + var wait = replicator_db.wait; + var waitForRep = replicator_db.waitForRep; + var waitForSeq = replicator_db.waitForSeq; + + 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"); + } + + 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, by_doc_ids_replication); + + // cleanup + repDb.deleteDb(); + dbA.deleteDb(); + dbB.deleteDb(); +}
\ No newline at end of file diff --git a/share/www/script/test/replicator_db_compact_rep_db.js b/share/www/script/test/replicator_db_compact_rep_db.js new file mode 100644 index 000000000..0dea0ed66 --- /dev/null +++ b/share/www/script/test/replicator_db_compact_rep_db.js @@ -0,0 +1,119 @@ +// 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_compact_rep_db = function(debug) { + + if (debug) debugger; + + var populate_db = replicator_db.populate_db; + var docs1 = replicator_db.docs1; + var dbA = replicator_db.dbA; + var dbB = replicator_db.dbB; + var repDb = replicator_db.repDb; + var usersDb = replicator_db.usersDb; + var wait = replicator_db.wait; + var waitForRep = replicator_db.waitForRep; + var waitForSeq = replicator_db.waitForSeq; + var wait_rep_doc = replicator_db.wait_rep_doc; + + 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); + } + + var server_config = [ + { + section: "couch_httpd_auth", + key: "iterations", + value: "1" + }, + { + section: "replicator", + key: "db", + value: repDb.name + }, + { + section: "couch_httpd_auth", + key: "authentication_db", + value: usersDb.name + } + ]; + + repDb.deleteDb(); + run_on_modified_server(server_config, compact_rep_db); + + // cleanup + repDb.deleteDb(); + dbA.deleteDb(); + dbB.deleteDb(); + usersDb.deleteDb(); + (new CouchDB("test_suite_rep_db_a_copy")).deleteDb(); + (new CouchDB("test_suite_rep_db_b_copy")).deleteDb(); + +}
\ No newline at end of file diff --git a/share/www/script/test/replicator_db_continuous.js b/share/www/script/test/replicator_db_continuous.js new file mode 100644 index 000000000..a2b17d00a --- /dev/null +++ b/share/www/script/test/replicator_db_continuous.js @@ -0,0 +1,137 @@ +// 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_continuous = function(debug) { + + if (debug) debugger; + + var populate_db = replicator_db.populate_db; + var docs1 = replicator_db.docs1; + var dbA = replicator_db.dbA; + var dbB = replicator_db.dbB; + var repDb = replicator_db.repDb; + var wait = replicator_db.wait; + var waitForRep = replicator_db.waitForRep; + var waitForSeq = replicator_db.waitForSeq; + + 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); + } + + + 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, continuous_replication); + + // cleanup + repDb.deleteDb(); + dbA.deleteDb(); + dbB.deleteDb(); +}
\ No newline at end of file diff --git a/share/www/script/test/replicator_db_credential_delegation.js b/share/www/script/test/replicator_db_credential_delegation.js new file mode 100644 index 000000000..7dd9d1812 --- /dev/null +++ b/share/www/script/test/replicator_db_credential_delegation.js @@ -0,0 +1,149 @@ +// 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_credential_delegation = function(debug) { + + if (debug) debugger; + + var populate_db = replicator_db.populate_db; + var docs1 = replicator_db.docs1; + var dbA = replicator_db.dbA; + var dbB = replicator_db.dbB; + var repDb = replicator_db.repDb; + var usersDb = replicator_db.usersDb; + var wait = replicator_db.wait; + var waitForRep = replicator_db.waitForRep; + var waitForSeq = replicator_db.waitForSeq; + var wait_rep_doc = replicator_db.wait_rep_doc; + + 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); + }); + } + + var server_config = [ + { + section: "couch_httpd_auth", + key: "iterations", + value: "1" + }, + { + section: "replicator", + key: "db", + value: repDb.name + }, + { + section: "couch_httpd_auth", + key: "authentication_db", + value: usersDb.name + } + ]; + + repDb.deleteDb(); + run_on_modified_server(server_config, test_replication_credentials_delegation); + + // cleanup + repDb.deleteDb(); + dbA.deleteDb(); + dbB.deleteDb(); + usersDb.deleteDb(); +}
\ No newline at end of file diff --git a/share/www/script/test/replicator_db_field_validation.js b/share/www/script/test/replicator_db_field_validation.js new file mode 100644 index 000000000..167bdccb0 --- /dev/null +++ b/share/www/script/test/replicator_db_field_validation.js @@ -0,0 +1,178 @@ +// 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_field_validation = function(debug) { + + if (debug) debugger; + + var populate_db = replicator_db.populate_db; + var docs1 = replicator_db.docs1; + var dbA = replicator_db.dbA; + var dbB = replicator_db.dbB; + var repDb = replicator_db.repDb; + var usersDb = replicator_db.usersDb; + var wait = replicator_db.wait; + var waitForRep = replicator_db.waitForRep; + var waitForSeq = replicator_db.waitForSeq; + var wait_rep_doc = replicator_db.wait_rep_doc; + + 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); + } + } + + var server_config = [ + { + section: "couch_httpd_auth", + key: "iterations", + value: "1" + }, + { + section: "replicator", + key: "db", + value: repDb.name + }, + { + section: "couch_httpd_auth", + key: "authentication_db", + value: usersDb.name + } + ]; + + repDb.deleteDb(); + run_on_modified_server(server_config, rep_doc_field_validation); + + // cleanup + repDb.deleteDb(); + dbA.deleteDb(); + dbB.deleteDb(); + usersDb.deleteDb(); +}
\ No newline at end of file diff --git a/share/www/script/test/replicator_db_filtered.js b/share/www/script/test/replicator_db_filtered.js new file mode 100644 index 000000000..31c78a7c5 --- /dev/null +++ b/share/www/script/test/replicator_db_filtered.js @@ -0,0 +1,105 @@ +// 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_filtered = function(debug) { + + if (debug) debugger; + + var populate_db = replicator_db.populate_db; + var docs1 = replicator_db.docs1; + var dbA = replicator_db.dbA; + var dbB = replicator_db.dbB; + var repDb = replicator_db.repDb; + var waitForRep = replicator_db.waitForRep; + + 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"); + } + + + 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, filtered_replication); + + // cleanup + repDb.deleteDb(); + dbA.deleteDb(); + dbB.deleteDb(); +}
\ No newline at end of file diff --git a/share/www/script/test/replicator_db_identical.js b/share/www/script/test/replicator_db_identical.js new file mode 100644 index 000000000..cdf4592be --- /dev/null +++ b/share/www/script/test/replicator_db_identical.js @@ -0,0 +1,87 @@ +// 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_identical = function(debug) { + + if (debug) debugger; + + var populate_db = replicator_db.populate_db; + var docs1 = replicator_db.docs1; + var dbA = replicator_db.dbA; + var dbB = replicator_db.dbB; + var repDb = replicator_db.repDb; + var wait = replicator_db.wait; + var waitForRep = replicator_db.waitForRep; + var waitForSeq = replicator_db.waitForSeq; + + // 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); + } + + 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, identical_rep_docs); + + // cleanup + repDb.deleteDb(); + dbA.deleteDb(); + dbB.deleteDb(); +}
\ No newline at end of file diff --git a/share/www/script/test/replicator_db_identical_continuous.js b/share/www/script/test/replicator_db_identical_continuous.js new file mode 100644 index 000000000..5e7f15121 --- /dev/null +++ b/share/www/script/test/replicator_db_identical_continuous.js @@ -0,0 +1,132 @@ +// 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_identical_continuous = function(debug) { + + if (debug) debugger; + + var populate_db = replicator_db.populate_db; + var docs1 = replicator_db.docs1; + var dbA = replicator_db.dbA; + var dbB = replicator_db.dbB; + var repDb = replicator_db.repDb; + var wait = replicator_db.wait; + var waitForRep = replicator_db.waitForRep; + var waitForSeq = replicator_db.waitForSeq; + var wait_rep_doc = replicator_db.wait_rep_doc; + + // 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); + } + + 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, identical_continuous_rep_docs); + + // cleanup + repDb.deleteDb(); + dbA.deleteDb(); + dbB.deleteDb(); +}
\ No newline at end of file diff --git a/share/www/script/test/replicator_db_invalid_filter.js b/share/www/script/test/replicator_db_invalid_filter.js new file mode 100644 index 000000000..6438d4356 --- /dev/null +++ b/share/www/script/test/replicator_db_invalid_filter.js @@ -0,0 +1,115 @@ +// 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_invalid_filter = function(debug) { + + if (debug) debugger; + + var populate_db = replicator_db.populate_db; + var docs1 = replicator_db.docs1; + var dbA = replicator_db.dbA; + var dbB = replicator_db.dbB; + var repDb = replicator_db.repDb; + var usersDb = replicator_db.usersDb; + var wait = replicator_db.wait; + var waitForRep = replicator_db.waitForRep; + var waitForSeq = replicator_db.waitForSeq; + var wait_rep_doc = replicator_db.wait_rep_doc; + + 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); + } + + var server_config = [ + { + section: "couch_httpd_auth", + key: "iterations", + value: "1" + }, + { + section: "replicator", + key: "db", + value: repDb.name + }, + { + section: "couch_httpd_auth", + key: "authentication_db", + value: usersDb.name + } + ]; + + repDb.deleteDb(); + run_on_modified_server(server_config, test_invalid_filter); + + // cleanup + repDb.deleteDb(); + dbA.deleteDb(); + dbB.deleteDb(); + usersDb.deleteDb(); +}
\ No newline at end of file diff --git a/share/www/script/test/replicator_db_simple.js b/share/www/script/test/replicator_db_simple.js new file mode 100644 index 000000000..f7acedb37 --- /dev/null +++ b/share/www/script/test/replicator_db_simple.js @@ -0,0 +1,114 @@ +// 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_simple = function(debug) { + + if (debug) debugger; + + var populate_db = replicator_db.populate_db; + var docs1 = replicator_db.docs1; + var dbA = replicator_db.dbA; + var dbB = replicator_db.dbB; + var repDb = replicator_db.repDb; + var waitForRep = replicator_db.waitForRep; + + 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"); + } + + 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); + +/* + * 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. + */ + /* + 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"); + } + */ +/* + * repDb.deleteDb(); + * restartServer(); + * run_on_modified_server(server_config, error_state_replication); + */ + + + // cleanup + repDb.deleteDb(); + dbA.deleteDb(); + dbB.deleteDb(); +}
\ No newline at end of file diff --git a/share/www/script/test/replicator_db_successive.js b/share/www/script/test/replicator_db_successive.js new file mode 100644 index 000000000..4898c3336 --- /dev/null +++ b/share/www/script/test/replicator_db_successive.js @@ -0,0 +1,127 @@ +// 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_successive = function(debug) { + + if (debug) debugger; + + var populate_db = replicator_db.populate_db; + var docs1 = replicator_db.docs1; + var dbA = replicator_db.dbA; + var dbB = replicator_db.dbB; + var repDb = replicator_db.repDb; + var wait = replicator_db.wait; + var waitForRep = replicator_db.waitForRep; + var waitForSeq = replicator_db.waitForSeq; + + 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"); + } + + 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, successive_identical_replications); + + // cleanup + repDb.deleteDb(); + dbA.deleteDb(); + dbB.deleteDb(); +}
\ No newline at end of file diff --git a/share/www/script/test/replicator_db_survives.js b/share/www/script/test/replicator_db_survives.js new file mode 100644 index 000000000..dcaa1013f --- /dev/null +++ b/share/www/script/test/replicator_db_survives.js @@ -0,0 +1,128 @@ +// 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_survives = function(debug) { + + if (debug) debugger; + + var populate_db = replicator_db.populate_db; + var docs1 = replicator_db.docs1; + var dbA = replicator_db.dbA; + var dbB = replicator_db.dbB; + var repDb = replicator_db.repDb; + var usersDb = replicator_db.usersDb; + var wait = replicator_db.wait; + var waitForRep = replicator_db.waitForRep; + var waitForSeq = replicator_db.waitForSeq; + var waitForDocPos = replicator_db.waitForDocPos; + var wait_rep_doc = replicator_db.wait_rep_doc; + + 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); + + repDb.createDb(); // the config put above should create this db + + 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); + } + + var server_config = [ + { + section: "couch_httpd_auth", + key: "iterations", + value: "1" + }, + { + section: "replicator", + key: "db", + value: repDb.name + }, + { + section: "couch_httpd_auth", + key: "authentication_db", + value: usersDb.name + } + ]; + + repDb.deleteDb(); + run_on_modified_server(server_config, continuous_replication_survives_restart); + + // cleanup + repDb.deleteDb(); + dbA.deleteDb(); + dbB.deleteDb(); + usersDb.deleteDb(); +}
\ No newline at end of file diff --git a/share/www/script/test/replicator_db_swap_rep_db.js b/share/www/script/test/replicator_db_swap_rep_db.js new file mode 100644 index 000000000..04f4e9fa2 --- /dev/null +++ b/share/www/script/test/replicator_db_swap_rep_db.js @@ -0,0 +1,170 @@ +// 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_swap_rep_db = function(debug) { + + if (debug) debugger; + + var populate_db = replicator_db.populate_db; + var docs1 = replicator_db.docs1; + var dbA = replicator_db.dbA; + var dbB = replicator_db.dbB; + var repDb = replicator_db.repDb; + var usersDb = replicator_db.usersDb; + var wait = replicator_db.wait; + var waitForRep = replicator_db.waitForRep; + var waitForSeq = replicator_db.waitForSeq; + var wait_rep_doc = replicator_db.wait_rep_doc; + + 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); + + // Temporary band-aid, give the replicator db some + // time to make the switch + wait(500); + + 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); + } + var server_config = [ + { + section: "couch_httpd_auth", + key: "iterations", + value: "1" + }, + { + section: "replicator", + key: "db", + value: repDb.name + }, + { + section: "couch_httpd_auth", + key: "authentication_db", + value: usersDb.name + } + ]; + + repDb.deleteDb(); + run_on_modified_server(server_config, swap_rep_db); + + // cleanup + repDb.deleteDb(); + dbA.deleteDb(); + dbB.deleteDb(); + usersDb.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(); + +}
\ No newline at end of file diff --git a/share/www/script/test/replicator_db_update_security.js b/share/www/script/test/replicator_db_update_security.js new file mode 100644 index 000000000..465151468 --- /dev/null +++ b/share/www/script/test/replicator_db_update_security.js @@ -0,0 +1,92 @@ +// 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_update_security = function(debug) { + + if (debug) debugger; + + var populate_db = replicator_db.populate_db; + var docs1 = replicator_db.docs1; + var dbA = replicator_db.dbA; + var dbB = replicator_db.dbB; + var repDb = replicator_db.repDb; + var usersDb = replicator_db.usersDb; + var wait = replicator_db.wait; + var waitForRep = replicator_db.waitForRep; + var waitForSeq = replicator_db.waitForSeq; + var wait_rep_doc = replicator_db.wait_rep_doc; + + 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"); + } + + var server_config = [ + { + section: "couch_httpd_auth", + key: "iterations", + value: "1" + }, + { + section: "replicator", + key: "db", + value: repDb.name + }, + { + section: "couch_httpd_auth", + key: "authentication_db", + value: usersDb.name + } + ]; + + repDb.deleteDb(); + run_on_modified_server(server_config, test_rep_db_update_security); + + // cleanup + repDb.deleteDb(); + dbA.deleteDb(); + dbB.deleteDb(); + usersDb.deleteDb(); + (new CouchDB("test_suite_rep_db_a_copy")).deleteDb(); + (new CouchDB("test_suite_rep_db_b_copy")).deleteDb(); +}
\ No newline at end of file diff --git a/share/www/script/test/replicator_db_user_ctx.js b/share/www/script/test/replicator_db_user_ctx.js new file mode 100644 index 000000000..570fc7d55 --- /dev/null +++ b/share/www/script/test/replicator_db_user_ctx.js @@ -0,0 +1,272 @@ +// 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_user_ctx = function(debug) { + + if (debug) debugger; + + var populate_db = replicator_db.populate_db; + var docs1 = replicator_db.docs1; + var dbA = replicator_db.dbA; + var dbB = replicator_db.dbB; + var repDb = replicator_db.repDb; + var usersDb = replicator_db.usersDb; + var wait = replicator_db.wait; + var waitForRep = replicator_db.waitForRep; + var waitForSeq = replicator_db.waitForSeq; + var wait_rep_doc = replicator_db.wait_rep_doc; + + 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); + } + + var server_config = [ + { + section: "couch_httpd_auth", + key: "iterations", + value: "1" + }, + { + section: "replicator", + key: "db", + value: repDb.name + }, + { + section: "couch_httpd_auth", + key: "authentication_db", + value: usersDb.name + } + ]; + + repDb.deleteDb(); + run_on_modified_server(server_config, test_user_ctx_validation); + + // cleanup + repDb.deleteDb(); + dbA.deleteDb(); + dbB.deleteDb(); + usersDb.deleteDb(); +}
\ No newline at end of file diff --git a/share/www/script/test/replicator_db_write_auth.js b/share/www/script/test/replicator_db_write_auth.js new file mode 100644 index 000000000..697abf340 --- /dev/null +++ b/share/www/script/test/replicator_db_write_auth.js @@ -0,0 +1,102 @@ +// 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_survives = function(debug) { + + if (debug) debugger; + + var populate_db = replicator_db.populate_db; + var docs1 = replicator_db.docs1; + var dbA = replicator_db.dbA; + var dbB = replicator_db.dbB; + var repDb = replicator_db.repDb; + var usersDb = replicator_db.usersDb; + var wait = replicator_db.wait; + var waitForRep = replicator_db.waitForRep; + var waitForSeq = replicator_db.waitForSeq; + var waitForDocPos = replicator_db.waitForDocPos; + var wait_rep_doc = replicator_db.wait_rep_doc; + + 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"); + } + }); + } + + 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, rep_db_write_authorization); + + // cleanup + repDb.deleteDb(); + dbA.deleteDb(); + dbB.deleteDb(); + usersDb.deleteDb(); +}
\ No newline at end of file diff --git a/test/javascript/run.tpl b/test/javascript/run.tpl index 647a29a58..39a47a6b8 100644 --- a/test/javascript/run.tpl +++ b/test/javascript/run.tpl @@ -70,6 +70,7 @@ $COUCHJS -H -u $COUCH_URI_FILE \ $SCRIPT_DIR/couch.js \ $SCRIPT_DIR/couch_test_runner.js \ $SCRIPT_DIR/couch_tests.js \ + $SCRIPT_DIR/replicator_db_inc.js \ $TEST_SRC \ $JS_TEST_DIR/couch_http.js \ $JS_TEST_DIR/cli_runner.js |