Return-Path: X-Original-To: apmail-couchdb-commits-archive@www.apache.org Delivered-To: apmail-couchdb-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id D55AC17DAD for ; Fri, 10 Oct 2014 19:12:17 +0000 (UTC) Received: (qmail 3451 invoked by uid 500); 10 Oct 2014 19:12:17 -0000 Delivered-To: apmail-couchdb-commits-archive@couchdb.apache.org Received: (qmail 3254 invoked by uid 500); 10 Oct 2014 19:12:17 -0000 Mailing-List: contact commits-help@couchdb.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@couchdb.apache.org Delivered-To: mailing list commits@couchdb.apache.org Received: (qmail 2304 invoked by uid 99); 10 Oct 2014 19:12:16 -0000 Received: from tyr.zones.apache.org (HELO tyr.zones.apache.org) (140.211.11.114) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 10 Oct 2014 19:12:16 +0000 Received: by tyr.zones.apache.org (Postfix, from userid 65534) id 473B08196CE; Fri, 10 Oct 2014 19:12:16 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: jan@apache.org To: commits@couchdb.apache.org Date: Fri, 10 Oct 2014 19:12:26 -0000 Message-Id: <4f776b3985174bb59b10fdda519e0a2a@git.apache.org> In-Reply-To: <13dd8dd1d4ba48489b3688509f181458@git.apache.org> References: <13dd8dd1d4ba48489b3688509f181458@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: [12/37] move JS tests into safety http://git-wip-us.apache.org/repos/asf/couchdb/blob/3ba4fc0b/share/test/replicator_db_identical_continuous.js ---------------------------------------------------------------------- diff --git a/share/test/replicator_db_identical_continuous.js b/share/test/replicator_db_identical_continuous.js new file mode 100644 index 0000000..240c531 --- /dev/null +++ b/share/test/replicator_db_identical_continuous.js @@ -0,0 +1,139 @@ +// 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); + } + + // Rather than a timeout we're just waiting to hear the + // fourth change to the database. Changes 1 and 2 were + // us storing repDoc1 and repDoc2. Changes 3 and 4 are + // the replicator manager updating each document. This + // just waits until the fourth change before continuing. + repDb.changes({"feed":"longpoll", "since":3}); + + 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 http://git-wip-us.apache.org/repos/asf/couchdb/blob/3ba4fc0b/share/test/replicator_db_invalid_filter.js ---------------------------------------------------------------------- diff --git a/share/test/replicator_db_invalid_filter.js b/share/test/replicator_db_invalid_filter.js new file mode 100644 index 0000000..7b6df82 --- /dev/null +++ b/share/test/replicator_db_invalid_filter.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_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); + TEquals("Could not open source database `couch_foo_test_db`: {db_not_found,<<\"couch_foo_test_db\">>}", + repDoc1._replication_state_reason); + + 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); + TEquals("Couldn't open document `_design/test` from source database `test_suite_rep_db_a`: {error,<<\"not_found\">>}", + repDoc2._replication_state_reason); + + 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 http://git-wip-us.apache.org/repos/asf/couchdb/blob/3ba4fc0b/share/test/replicator_db_security.js ---------------------------------------------------------------------- diff --git a/share/test/replicator_db_security.js b/share/test/replicator_db_security.js new file mode 100644 index 0000000..7a2bfd1 --- /dev/null +++ b/share/test/replicator_db_security.js @@ -0,0 +1,399 @@ +// 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_security = function(debug) { + + var reset_dbs = function(dbs) { + dbs.forEach(function(db) { + db.deleteDb(); + try { db.createDb() } catch (e) {}; + }); + }; + + var dbs = ["couch_test_rep_db", "couch_test_users_db", + "test_suite_db_a", "test_suite_db_b", "test_suite_db_c"] + .map(function(db_name) { + return new CouchDB(db_name, {"X-Couch-Full-Commit":"false"}); + }); + + var repDb = dbs[0]; + var usersDb = dbs[1]; + var dbA = dbs[2]; + var dbB = dbs[3]; + var dbC = dbs[4]; + + if (debug) debugger; + + var loginUser = function(username) { + var pws = { + jan: "apple", + jchris: "mp3", + fdmanana: "foobar", + benoitc: "test" + }; + T(CouchDB.login(username, pws[username]).ok); + }; + + var repChanges = function(username) { + var pws = { + jan: "apple", + jchris: "mp3", + fdmanana: "foobar", + benoitc: "test" + }; + T(CouchDB.login(username, pws[username]).ok); + var changes = CouchDB.request( + "GET", + "/" + repDb.name + "/_changes?include_docs=true" + + "&anti-cache=" + String(Math.round(Math.random() * 100000))); + return changes = JSON.parse(changes.responseText); + }; + + var save_as = function(db, doc, username) + { + loginUser(username); + try { + return db.save(doc); + } catch (ex) { + return ex; + } finally { + CouchDB.logout(); + } + }; + + var open_as = function(db, docId, username) { + loginUser(username); + try { + return db.open(docId); + } finally { + CouchDB.logout(); + } + }; + + // from test replicator_db.js + 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; + } + + var testFun = function() + { + reset_dbs(dbs); + + // _replicator db + // in admin party mode, anonymous should be able to create a replication + var repDoc = { + _id: "null-owner-rep", + source: dbA.name, + target: dbB.name + }; + var result = repDb.save(repDoc); + TEquals(true, result.ok, "should allow anonymous replication docs in admin party"); + // new docs should get an owner field enforced. In admin party mode owner is null + repDoc = repDb.open(repDoc._id); + TIsnull(repDoc.owner, "owner should be null in admin party"); + +// Uncomment when _users database security changes are implemented. +// +// var jchrisDoc = { +// _id: "org.couchdb.user:jchris", +// type: "user", +// name: "jchris", +// password: "mp3", +// roles: [] +// }; + var jchrisDoc = CouchDB.prepareUserDoc({ + name: "jchris", + roles: [] + }, "mp3"); + usersDb.save(jchrisDoc); // set up a non-admin user + +// Uncomment when _users database security changes are implemented. +// +// var jchrisDoc = { +// _id: "org.couchdb.user:fdmanana", +// type: "user", +// name: "fdmanana", +// password: "foobar", +// roles: [] +// }; + var fdmananaDoc = CouchDB.prepareUserDoc({ + name: "fdmanana", + roles: [] + }, "foobar"); + usersDb.save(fdmananaDoc); // set up a non-admin user + +// Uncomment when _users database security changes are implemented. +// +// var benoitcDoc = { +// _id: "org.couchdb.user:fdmanana", +// type: "user", +// name: "fdmanana", +// password: "foobar", +// roles: [] +// }; + var benoitcDoc = CouchDB.prepareUserDoc({ + name: "benoitc", + roles: [] + }, "test"); + usersDb.save(benoitcDoc); // set up a non-admin user + + T(repDb.setSecObj({ + "admins" : { + roles : [], + names : ["benoitc"] + } + }).ok); + + run_on_modified_server([ + { + section: "admins", + key: "jan", + value: "apple" + } + ], function() { + // replication docs from admin-party mode in non-admin party mode can not + // be edited by non-admins (non-server admins) + repDoc = repDb.open(repDoc._id); + repDoc.target = dbC.name; + var result = save_as(repDb, repDoc, "jchris"); + TEquals("forbidden", result.error, "should forbid editing null-owner docs"); + + // replication docs from admin-party mode in non-admin party mode can only + // be edited by admins (server admins) + repDoc = waitForDocPos(repDb, repDoc._id, 3); + repDoc.target = dbC.name; + var result = save_as(repDb, repDoc, "jan"); + repDoc = open_as(repDb, repDoc._id, "jchris"); + TEquals(true, result.ok, "should allow editing null-owner docs to admins"); + TEquals("jan", repDoc.owner, "owner should be the admin now"); + + // user can update their own replication docs (repDoc.owner) + var jchrisRepDoc = { + _id: "jchris-rep-doc", + source: dbC.name, + target: dbA.name, + user_ctx: { name: "jchris", roles: [] } + }; + + var result = save_as(repDb, jchrisRepDoc, "jchris"); + TEquals(true, result.ok, "should create rep doc"); + jchrisRepDoc = repDb.open(jchrisRepDoc._id); + TEquals("jchris", jchrisRepDoc.owner, "should assign correct owner"); + jchrisRepDoc = waitForDocPos(repDb, jchrisRepDoc._id, 3); + jchrisRepDoc = open_as(repDb, jchrisRepDoc._id, "jchris"); + jchrisRepDoc.target = dbB.name; + var result = save_as(repDb, jchrisRepDoc, "jchris"); + TEquals(true, result.ok, "should allow update of rep doc"); + + // user should not be able to read from any view + var ddoc = { + _id: "_design/reps", + views: { + test: { + map: "function(doc) {" + + "if (doc._replication_state) { " + + "emit(doc._id, doc._replication_state);" + + "}" + + "}" + } + } + }; + + save_as(repDb, ddoc, "jan"); + + try { + repDb.view("reps/test"); + T(false, "non-admin had view read access"); + } catch (ex) { + TEquals("forbidden", ex.error, + "non-admins should not be able to read a view"); + } + + // admin should be able to read from any view + TEquals(true, CouchDB.login("jan", "apple").ok); + var result = repDb.view("reps/test"); + CouchDB.logout(); + TEquals(2, result.total_rows, "should allow access and list two users"); + + // test _all_docs, only available for _admins + try { + repDb.allDocs({include_docs: true}); + T(false, "non-admin had _all_docs access"); + } catch (ex) { + TEquals("forbidden", ex.error, + "non-admins should not be able to access _all_docs"); + } + + TEquals(true, CouchDB.login("jan", "apple").ok); + try { + repDb.allDocs({include_docs: true}); + } catch (ex) { + T(false, "admin couldn't access _all_docs"); + } + CouchDB.logout(); + + try { + repDb.view("reps/test"); + T(false, "non-admin had view read access"); + } catch (ex) { + TEquals("forbidden", ex.error, + "non-admins should not be able to read a view"); + } + + // admin should be able to read from any view + TEquals(true, CouchDB.login("benoitc", "test").ok); + var result = repDb.view("reps/test"); + CouchDB.logout(); + TEquals(2, result.total_rows, "should allow access and list two users"); + + // test _all_docs, only available for _admins + try { + repDb.allDocs({include_docs: true}); + T(false, "non-admin had _all_docs access"); + } catch (ex) { + TEquals("forbidden", ex.error, + "non-admins should not be able to access _all_docs"); + } + + TEquals(true, CouchDB.login("benoitc", "test").ok); + try { + repDb.allDocs({include_docs: true}); + } catch (ex) { + T(false, "admin couldn't access _all_docs"); + } + CouchDB.logout(); + + // Verify that users can't access credentials in the "source" and + // "target" fields of replication documents owned by other users. + var fdmananaRepDoc = { + _id: "fdmanana-rep-doc", + source: "http://fdmanana:foobar@" + CouchDB.host + "/" + dbC.name, + target: dbA.name, + user_ctx: { name: "fdmanana", roles: [] } + }; + + var result = save_as(repDb, fdmananaRepDoc, "fdmanana"); + TEquals(true, result.ok, "should create rep doc"); + waitForDocPos(repDb, fdmananaRepDoc._id, 3); + fdmananaRepDoc = open_as(repDb, fdmananaRepDoc._id, "fdmanana"); + TEquals("fdmanana", fdmananaRepDoc.owner, "should assign correct owner"); + TEquals("http://fdmanana:foobar@" + CouchDB.host + "/" + dbC.name, + fdmananaRepDoc.source, "source field has credentials"); + + fdmananaRepDoc = open_as(repDb, fdmananaRepDoc._id, "jchris"); + TEquals("fdmanana", fdmananaRepDoc.owner, "should assign correct owner"); + TEquals("http://" + CouchDB.host + "/" + dbC.name, + fdmananaRepDoc.source, "source field doesn't contain credentials"); + + // _changes?include_docs=true, users shouldn't be able to see credentials + // in documents owned by other users. + var changes = repChanges("jchris"); + var doc = changes.results[changes.results.length - 1].doc; + TEquals(fdmananaRepDoc._id, doc._id, "Got the right doc from _changes"); + TEquals("http://" + CouchDB.host + "/" + dbC.name, + doc.source, "source field doesn't contain credentials (doc from _changes)"); + CouchDB.logout(); + + // _changes?include_docs=true, user should be able to see credentials + // in documents they own. + var changes = repChanges("fdmanana"); + var doc = changes.results[changes.results.length - 1].doc; + TEquals(fdmananaRepDoc._id, doc._id, "Got the right doc from _changes"); + TEquals("http://fdmanana:foobar@" + CouchDB.host + "/" + dbC.name, + doc.source, "source field contains credentials (doc from _changes)"); + CouchDB.logout(); + + // _changes?include_docs=true, admins should be able to see credentials + // from all documents. + var changes = repChanges("jan"); + var doc = changes.results[changes.results.length - 1].doc; + TEquals(fdmananaRepDoc._id, doc._id, "Got the right doc from _changes"); + TEquals("http://fdmanana:foobar@" + CouchDB.host + "/" + dbC.name, + doc.source, "source field contains credentials (doc from _changes)"); + CouchDB.logout(); + + // _changes?include_docs=true, db admins should be able to see credentials + // from all documents. + var changes = repChanges("benoitc"); + var doc = changes.results[changes.results.length - 1].doc; + TEquals(fdmananaRepDoc._id, doc._id, "Got the right doc from _changes"); + TEquals("http://fdmanana:foobar@" + CouchDB.host + "/" + dbC.name, + doc.source, "source field contains credentials (doc from _changes)"); + CouchDB.logout(); + + var fdmananaRepDocOAuth = { + _id: "fdmanana-rep-doc-oauth", + source: dbC.name, + target: { + url: "http://" + CouchDB.host + "/" + dbA.name, + oauth: { + token: "abc", + token_secret: "foo", + consumer_key: "123", + consumer_secret: "321" + } + }, + user_ctx: { name: "fdmanana", roles: [] } + }; + + var result = save_as(repDb, fdmananaRepDocOAuth, "fdmanana"); + TEquals(true, result.ok, "should create rep doc"); + waitForDocPos(repDb, fdmananaRepDocOAuth._id, 3); + fdmananaRepDocOAuth = open_as(repDb, fdmananaRepDocOAuth._id, "fdmanana"); + TEquals("fdmanana", fdmananaRepDocOAuth.owner, "should assign correct owner"); + TEquals("object", typeof fdmananaRepDocOAuth.target.oauth, + "target field has oauth credentials"); + + fdmananaRepDocOAuth = open_as(repDb, fdmananaRepDocOAuth._id, "jchris"); + TEquals("fdmanana", fdmananaRepDocOAuth.owner, "should assign correct owner"); + TEquals("undefined", typeof fdmananaRepDocOAuth.target.oauth, + "target field doesn't have oauth credentials"); + + // ensure "old" replicator docs still work + // done in replicator_db.js? + + // Login as admin so run_on_modified_server can do its cleanup. + TEquals(true, CouchDB.login("jan", "apple").ok); + }); + }; + + run_on_modified_server([ + { + section: "couch_httpd_auth", + key: "authentication_db", + value: usersDb.name + }, + { + section: "replicator", + key: "db", + value: repDb.name + }], + testFun + ); + + // cleanup + usersDb.deleteDb(); + repDb.deleteDb(); +}; http://git-wip-us.apache.org/repos/asf/couchdb/blob/3ba4fc0b/share/test/replicator_db_simple.js ---------------------------------------------------------------------- diff --git a/share/test/replicator_db_simple.js b/share/test/replicator_db_simple.js new file mode 100644 index 0000000..f7acedb --- /dev/null +++ b/share/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 http://git-wip-us.apache.org/repos/asf/couchdb/blob/3ba4fc0b/share/test/replicator_db_successive.js ---------------------------------------------------------------------- diff --git a/share/test/replicator_db_successive.js b/share/test/replicator_db_successive.js new file mode 100644 index 0000000..4898c33 --- /dev/null +++ b/share/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 http://git-wip-us.apache.org/repos/asf/couchdb/blob/3ba4fc0b/share/test/replicator_db_survives.js ---------------------------------------------------------------------- diff --git a/share/test/replicator_db_survives.js b/share/test/replicator_db_survives.js new file mode 100644 index 0000000..38273ca --- /dev/null +++ b/share/test/replicator_db_survives.js @@ -0,0 +1,126 @@ +// 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); + + 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(); +} http://git-wip-us.apache.org/repos/asf/couchdb/blob/3ba4fc0b/share/test/replicator_db_swap_rep_db.js ---------------------------------------------------------------------- diff --git a/share/test/replicator_db_swap_rep_db.js b/share/test/replicator_db_swap_rep_db.js new file mode 100644 index 0000000..04f4e9f --- /dev/null +++ b/share/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 http://git-wip-us.apache.org/repos/asf/couchdb/blob/3ba4fc0b/share/test/replicator_db_update_security.js ---------------------------------------------------------------------- diff --git a/share/test/replicator_db_update_security.js b/share/test/replicator_db_update_security.js new file mode 100644 index 0000000..4651514 --- /dev/null +++ b/share/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 http://git-wip-us.apache.org/repos/asf/couchdb/blob/3ba4fc0b/share/test/replicator_db_user_ctx.js ---------------------------------------------------------------------- diff --git a/share/test/replicator_db_user_ctx.js b/share/test/replicator_db_user_ctx.js new file mode 100644 index 0000000..570fc7d --- /dev/null +++ b/share/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 http://git-wip-us.apache.org/repos/asf/couchdb/blob/3ba4fc0b/share/test/replicator_db_write_auth.js ---------------------------------------------------------------------- diff --git a/share/test/replicator_db_write_auth.js b/share/test/replicator_db_write_auth.js new file mode 100644 index 0000000..697abf3 --- /dev/null +++ b/share/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 http://git-wip-us.apache.org/repos/asf/couchdb/blob/3ba4fc0b/share/test/rev_stemming.js ---------------------------------------------------------------------- diff --git a/share/test/rev_stemming.js b/share/test/rev_stemming.js new file mode 100644 index 0000000..954da79 --- /dev/null +++ b/share/test/rev_stemming.js @@ -0,0 +1,110 @@ +// 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.rev_stemming = function(debug) { + var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); + db.deleteDb(); + db.createDb(); + var db = new CouchDB("test_suite_db_a", {"X-Couch-Full-Commit":"false"}); + db.deleteDb(); + db.createDb(); + var dbB = new CouchDB("test_suite_db_b", {"X-Couch-Full-Commit":"false"}); + dbB.deleteDb(); + dbB.createDb(); + if (debug) debugger; + + var newLimit = 5; + + T(db.getDbProperty("_revs_limit") == 1000); + + // Make an invalid request to _revs_limit + // Should return 400 + var xhr = CouchDB.request("PUT", "/test_suite_db/_revs_limit", {body:"\"foo\""}); + T(xhr.status == 400); + var result = JSON.parse(xhr.responseText); + T(result.error == "bad_request"); + T(result.reason == "Rev limit has to be an integer"); + + var doc = {_id:"foo",foo:0} + for( var i=0; i < newLimit + 1; i++) { + doc.foo++; + T(db.save(doc).ok); + } + var doc0 = db.open("foo", {revs:true}); + T(doc0._revisions.ids.length == newLimit + 1); + + var docBar = {_id:"bar",foo:0} + for( var i=0; i < newLimit + 1; i++) { + docBar.foo++; + T(db.save(docBar).ok); + } + T(db.open("bar", {revs:true})._revisions.ids.length == newLimit + 1); + + T(db.setDbProperty("_revs_limit", newLimit).ok); + + for( var i=0; i < newLimit + 1; i++) { + doc.foo++; + T(db.save(doc).ok); + } + doc0 = db.open("foo", {revs:true}); + T(doc0._revisions.ids.length == newLimit); + + + // If you replicate after you make more edits than the limit, you'll + // cause a spurious edit conflict. + CouchDB.replicate("test_suite_db_a", "test_suite_db_b"); + var docB1 = dbB.open("foo",{conflicts:true}) + T(docB1._conflicts == null); + + for( var i=0; i < newLimit - 1; i++) { + doc.foo++; + T(db.save(doc).ok); + } + + // one less edit than limit, no conflict + CouchDB.replicate("test_suite_db_a", "test_suite_db_b"); + var docB1 = dbB.open("foo",{conflicts:true}) + T(docB1._conflicts == null); + + //now we hit the limit + for( var i=0; i < newLimit; i++) { + doc.foo++; + T(db.save(doc).ok); + } + + CouchDB.replicate("test_suite_db_a", "test_suite_db_b"); + + var docB2 = dbB.open("foo",{conflicts:true}); + + // we have a conflict, but the previous replicated rev is always the losing + // conflict + T(docB2._conflicts[0] == docB1._rev) + + // We having already updated bar before setting the limit, so it's still got + // a long rev history. compact to stem the revs. + + T(db.open("bar", {revs:true})._revisions.ids.length == newLimit + 1); + + T(db.compact().ok); + + // compaction isn't instantaneous, loop until done + while (db.info().compact_running) {}; + + // force reload because ETags don't honour compaction + var req = db.request("GET", "/test_suite_db_a/bar?revs=true", { + headers:{"if-none-match":"pommes"} + }); + + var finalDoc = JSON.parse(req.responseText); + TEquals(newLimit, finalDoc._revisions.ids.length, + "should return a truncated revision list"); +}; http://git-wip-us.apache.org/repos/asf/couchdb/blob/3ba4fc0b/share/test/rewrite.js ---------------------------------------------------------------------- diff --git a/share/test/rewrite.js b/share/test/rewrite.js new file mode 100644 index 0000000..5c56fa5 --- /dev/null +++ b/share/test/rewrite.js @@ -0,0 +1,505 @@ +// 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.rewrite = function(debug) { + if (debug) debugger; + var dbNames = ["test_suite_db", "test_suite_db/with_slashes"]; + for (var i=0; i < dbNames.length; i++) { + var db = new CouchDB(dbNames[i]); + var dbName = encodeURIComponent(dbNames[i]); + db.deleteDb(); + db.createDb(); + + + run_on_modified_server( + [{section: "httpd", + key: "authentication_handlers", + value: "{couch_httpd_auth, special_test_authentication_handler}"}, + {section:"httpd", + key: "WWW-Authenticate", + value: "X-Couch-Test-Auth"}], + + function(){ + var designDoc = { + _id:"_design/test", + language: "javascript", + _attachments:{ + "foo.txt": { + content_type:"text/plain", + data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ=" + } + }, + rewrites: [ + { + "from": "foo", + "to": "foo.txt" + }, + { + "from": "foo2", + "to": "foo.txt", + "method": "GET" + }, + { + "from": "hello/:id", + "to": "_update/hello/:id", + "method": "PUT" + }, + { + "from": "/welcome", + "to": "_show/welcome" + }, + { + "from": "/welcome/:name", + "to": "_show/welcome", + "query": { + "name": ":name" + } + }, + { + "from": "/welcome2", + "to": "_show/welcome", + "query": { + "name": "user" + } + }, + { + "from": "/welcome3/:name", + "to": "_update/welcome2/:name", + "method": "PUT" + }, + { + "from": "/welcome3/:name", + "to": "_show/welcome2/:name", + "method": "GET" + }, + { + "from": "/welcome4/*", + "to" : "_show/welcome3", + "query": { + "name": "*" + } + }, + { + "from": "/welcome5/*", + "to" : "_show/*", + "query": { + "name": "*" + } + }, + { + "from": "basicView", + "to": "_view/basicView", + }, + { + "from": "simpleForm/basicView", + "to": "_list/simpleForm/basicView", + }, + { + "from": "simpleForm/basicViewFixed", + "to": "_list/simpleForm/basicView", + "query": { + "startkey": 3, + "endkey": 8 + } + }, + { + "from": "simpleForm/basicViewPath/:start/:end", + "to": "_list/simpleForm/basicView", + "query": { + "startkey": ":start", + "endkey": ":end" + }, + "formats": { + "start": "int", + "end": "int" + } + }, + { + "from": "simpleForm/complexView", + "to": "_list/simpleForm/complexView", + "query": { + "key": [1, 2] + } + }, + { + "from": "simpleForm/complexView2", + "to": "_list/simpleForm/complexView", + "query": { + "key": ["test", {}] + } + }, + { + "from": "simpleForm/complexView3", + "to": "_list/simpleForm/complexView", + "query": { + "key": ["test", ["test", "essai"]] + } + }, + { + "from": "simpleForm/complexView4", + "to": "_list/simpleForm/complexView2", + "query": { + "key": {"c": 1} + } + }, + { + "from": "simpleForm/complexView5/:a/:b", + "to": "_list/simpleForm/complexView3", + "query": { + "key": [":a", ":b"] + } + }, + { + "from": "simpleForm/complexView6", + "to": "_list/simpleForm/complexView3", + "query": { + "key": [":a", ":b"] + } + }, + { + "from": "simpleForm/complexView7/:a/:b", + "to": "_view/complexView3", + "query": { + "key": [":a", ":b"], + "include_docs": ":doc" + }, + "format": { + "doc": "bool" + } + + }, + { + "from": "/", + "to": "_view/basicView", + }, + { + "from": "/db/*", + "to": "../../*" + } + ], + lists: { + simpleForm: stringFun(function(head, req) { + log("simpleForm"); + send('
    '); + var row, row_number = 0, prevKey, firstKey = null; + while (row = getRow()) { + row_number += 1; + if (!firstKey) firstKey = row.key; + prevKey = row.key; + send('\n
  • Key: '+row.key + +' Value: '+row.value + +' LineNo: '+row_number+'
  • '); + } + return '

FirstKey: '+ firstKey + ' LastKey: '+ prevKey+'

'; + }), + }, + shows: { + "welcome": stringFun(function(doc,req) { + return "Welcome " + req.query["name"]; + }), + "welcome2": stringFun(function(doc, req) { + return "Welcome " + doc.name; + }), + "welcome3": stringFun(function(doc,req) { + return "Welcome " + req.query["name"]; + }) + }, + updates: { + "hello" : stringFun(function(doc, req) { + if (!doc) { + if (req.id) { + return [{ + _id : req.id + }, "New World"] + } + return [null, "Empty World"]; + } + doc.world = "hello"; + doc.edited_by = req.userCtx; + return [doc, "hello doc"]; + }), + "welcome2": stringFun(function(doc, req) { + if (!doc) { + if (req.id) { + return [{ + _id: req.id, + name: req.id + }, "New World"] + } + return [null, "Empty World"]; + } + return [doc, "hello doc"]; + }) + }, + views : { + basicView : { + map : stringFun(function(doc) { + if (doc.integer) { + emit(doc.integer, doc.string); + } + + }) + }, + complexView: { + map: stringFun(function(doc) { + if (doc.type == "complex") { + emit([doc.a, doc.b], doc.string); + } + }) + }, + complexView2: { + map: stringFun(function(doc) { + if (doc.type == "complex") { + emit(doc.a, doc.string); + } + }) + }, + complexView3: { + map: stringFun(function(doc) { + if (doc.type == "complex") { + emit(doc.b, doc.string); + } + }) + } + } + } + + db.save(designDoc); + + var docs = makeDocs(0, 10); + db.bulkSave(docs); + + var docs2 = [ + {"a": 1, "b": 1, "string": "doc 1", "type": "complex"}, + {"a": 1, "b": 2, "string": "doc 2", "type": "complex"}, + {"a": "test", "b": {}, "string": "doc 3", "type": "complex"}, + {"a": "test", "b": ["test", "essai"], "string": "doc 4", "type": "complex"}, + {"a": {"c": 1}, "b": "", "string": "doc 5", "type": "complex"} + ]; + + db.bulkSave(docs2); + + // test simple rewriting + + req = CouchDB.request("GET", "/"+dbName+"/_design/test/_rewrite/foo"); + T(req.responseText == "This is a base64 encoded text"); + T(req.getResponseHeader("Content-Type") == "text/plain"); + + req = CouchDB.request("GET", "/"+dbName+"/_design/test/_rewrite/foo2"); + T(req.responseText == "This is a base64 encoded text"); + T(req.getResponseHeader("Content-Type") == "text/plain"); + + + // test POST + // hello update world + + var doc = {"word":"plankton", "name":"Rusty"} + var resp = db.save(doc); + T(resp.ok); + var docid = resp.id; + + xhr = CouchDB.request("PUT", "/"+dbName+"/_design/test/_rewrite/hello/"+docid); + T(xhr.status == 201); + T(xhr.responseText == "hello doc"); + T(/charset=utf-8/.test(xhr.getResponseHeader("Content-Type"))) + + doc = db.open(docid); + T(doc.world == "hello"); + + req = CouchDB.request("GET", "/"+dbName+"/_design/test/_rewrite/welcome?name=user"); + T(req.responseText == "Welcome user"); + + req = CouchDB.request("GET", "/"+dbName+"/_design/test/_rewrite/welcome/user"); + T(req.responseText == "Welcome user"); + + req = CouchDB.request("GET", "/"+dbName+"/_design/test/_rewrite/welcome2"); + T(req.responseText == "Welcome user"); + + xhr = CouchDB.request("PUT", "/"+dbName+"/_design/test/_rewrite/welcome3/test"); + T(xhr.status == 201); + T(xhr.responseText == "New World"); + T(/charset=utf-8/.test(xhr.getResponseHeader("Content-Type"))); + + xhr = CouchDB.request("GET", "/"+dbName+"/_design/test/_rewrite/welcome3/test"); + T(xhr.responseText == "Welcome test"); + + req = CouchDB.request("GET", "/"+dbName+"/_design/test/_rewrite/welcome4/user"); + T(req.responseText == "Welcome user"); + + req = CouchDB.request("GET", "/"+dbName+"/_design/test/_rewrite/welcome5/welcome3"); + T(req.responseText == "Welcome welcome3"); + + xhr = CouchDB.request("GET", "/"+dbName+"/_design/test/_rewrite/basicView"); + T(xhr.status == 200, "view call"); + T(/{"total_rows":9/.test(xhr.responseText)); + + xhr = CouchDB.request("GET", "/"+dbName+"/_design/test/_rewrite/"); + T(xhr.status == 200, "view call"); + T(/{"total_rows":9/.test(xhr.responseText)); + + + // get with query params + xhr = CouchDB.request("GET", "/"+dbName+"/_design/test/_rewrite/simpleForm/basicView?startkey=3&endkey=8"); + T(xhr.status == 200, "with query params"); + T(!(/Key: 1/.test(xhr.responseText))); + T(/FirstKey: 3/.test(xhr.responseText)); + T(/LastKey: 8/.test(xhr.responseText)); + + xhr = CouchDB.request("GET", "/"+dbName+"/_design/test/_rewrite/simpleForm/basicViewFixed"); + T(xhr.status == 200, "with query params"); + T(!(/Key: 1/.test(xhr.responseText))); + T(/FirstKey: 3/.test(xhr.responseText)); + T(/LastKey: 8/.test(xhr.responseText)); + + // get with query params + xhr = CouchDB.request("GET", "/"+dbName+"/_design/test/_rewrite/simpleForm/basicViewFixed?startkey=4"); + T(xhr.status == 200, "with query params"); + T(!(/Key: 1/.test(xhr.responseText))); + T(/FirstKey: 3/.test(xhr.responseText)); + T(/LastKey: 8/.test(xhr.responseText)); + + // get with query params + xhr = CouchDB.request("GET", "/"+dbName+"/_design/test/_rewrite/simpleForm/basicViewPath/3/8"); + T(xhr.status == 200, "with query params"); + T(!(/Key: 1/.test(xhr.responseText))); + T(/FirstKey: 3/.test(xhr.responseText)); + T(/LastKey: 8/.test(xhr.responseText)); + + // get with query params + xhr = CouchDB.request("GET", "/"+dbName+"/_design/test/_rewrite/simpleForm/complexView"); + T(xhr.status == 200, "with query params"); + T(/FirstKey: [1, 2]/.test(xhr.responseText)); + + xhr = CouchDB.request("GET", "/"+dbName+"/_design/test/_rewrite/simpleForm/complexView2"); + T(xhr.status == 200, "with query params"); + T(/Value: doc 3/.test(xhr.responseText)); + + xhr = CouchDB.request("GET", "/"+dbName+"/_design/test/_rewrite/simpleForm/complexView3"); + T(xhr.status == 200, "with query params"); + T(/Value: doc 4/.test(xhr.responseText)); + + xhr = CouchDB.request("GET", "/"+dbName+"/_design/test/_rewrite/simpleForm/complexView4"); + T(xhr.status == 200, "with query params"); + T(/Value: doc 5/.test(xhr.responseText)); + + xhr = CouchDB.request("GET", "/"+dbName+"/_design/test/_rewrite/simpleForm/complexView5/test/essai"); + T(xhr.status == 200, "with query params"); + T(/Value: doc 4/.test(xhr.responseText)); + + xhr = CouchDB.request("GET", "/"+dbName+"/_design/test/_rewrite/simpleForm/complexView6?a=test&b=essai"); + T(xhr.status == 200, "with query params"); + T(/Value: doc 4/.test(xhr.responseText)); + + xhr = CouchDB.request("GET", "/"+dbName+"/_design/test/_rewrite/simpleForm/complexView7/test/essai?doc=true"); + T(xhr.status == 200, "with query params"); + var result = JSON.parse(xhr.responseText); + T(typeof(result.rows[0].doc) === "object"); + + // COUCHDB-2031 - path normalization versus qs params + xhr = CouchDB.request("GET", "/"+dbName+"/_design/test/_rewrite/db/_design/test?meta=true"); + T(xhr.status == 200, "path normalization works with qs params"); + var result = JSON.parse(xhr.responseText); + T(result['_id'] == "_design/test"); + T(typeof(result['_revs_info']) === "object"); + + // test path relative to server + designDoc.rewrites.push({ + "from": "uuids", + "to": "../../../_uuids" + }); + T(db.save(designDoc).ok); + + var xhr = CouchDB.request("GET", "/"+dbName+"/_design/test/_rewrite/uuids"); + T(xhr.status == 500); + var result = JSON.parse(xhr.responseText); + T(result.error == "insecure_rewrite_rule"); + + run_on_modified_server( + [{section: "httpd", + key: "secure_rewrites", + value: "false"}], + function() { + var xhr = CouchDB.request("GET", "/"+dbName+"/_design/test/_rewrite/uuids?cache=bust"); + T(xhr.status == 200); + var result = JSON.parse(xhr.responseText); + T(result.uuids.length == 1); + var first = result.uuids[0]; + }); + }); + + // test invalid rewrites + // string + var ddoc = { + _id: "_design/invalid", + rewrites: "[{\"from\":\"foo\",\"to\":\"bar\"}]" + } + db.save(ddoc); + var res = CouchDB.request("GET", "/"+dbName+"/_design/invalid/_rewrite/foo"); + TEquals(400, res.status, "should return 400"); + + var ddoc_requested_path = { + _id: "_design/requested_path", + rewrites:[ + {"from": "show", "to": "_show/origin/0"}, + {"from": "show_rewritten", "to": "_rewrite/show"} + ], + shows: { + origin: stringFun(function(doc, req) { + return req.headers["x-couchdb-requested-path"]; + })} + }; + + db.save(ddoc_requested_path); + var url = "/"+dbName+"/_design/requested_path/_rewrite/show"; + var res = CouchDB.request("GET", url); + TEquals(url, res.responseText, "should return the original url"); + + var url = "/"+dbName+"/_design/requested_path/_rewrite/show_rewritten"; + var res = CouchDB.request("GET", url); + TEquals(url, res.responseText, "returned the original url"); + + var ddoc_loop = { + _id: "_design/loop", + rewrites: [{ "from": "loop", "to": "_rewrite/loop"}] + }; + db.save(ddoc_loop); + + // Assert loop detection + run_on_modified_server( + [{section: "httpd", + key: "rewrite_limit", + value: "2"}], + function(){ + var url = "/"+dbName+"/_design/loop/_rewrite/loop"; + var xhr = CouchDB.request("GET", url); + TEquals(400, xhr.status); + }); + + // Assert serial execution is not spuriously counted as loop + run_on_modified_server( + [{section: "httpd", + key: "rewrite_limit", + value: "2"}, + {section: "httpd", + key: "secure_rewrites", + value: "false"}], + function(){ + var url = "/"+dbName+"/_design/test/_rewrite/foo"; + for (var i=0; i < 5; i++) { + var xhr = CouchDB.request("GET", url); + TEquals(200, xhr.status); + } + }); + } +}