You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by ja...@apache.org on 2014/10/10 21:12:26 UTC
[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('<ul>');
+ var row, row_number = 0, prevKey, firstKey = null;
+ while (row = getRow()) {
+ row_number += 1;
+ if (!firstKey) firstKey = row.key;
+ prevKey = row.key;
+ send('\n<li>Key: '+row.key
+ +' Value: '+row.value
+ +' LineNo: '+row_number+'</li>');
+ }
+ return '</ul><p>FirstKey: '+ firstKey + ' LastKey: '+ prevKey+'</p>';
+ }),
+ },
+ 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);
+ }
+ });
+ }
+}