couchdb-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From kxe...@apache.org
Subject [03/46] couchdb commit: updated refs/heads/master to 6f41698
Date Wed, 28 Jan 2015 15:51:50 GMT
http://git-wip-us.apache.org/repos/asf/couchdb/blob/e2d9c9b1/test/javascript/tests/users_db_security.js
----------------------------------------------------------------------
diff --git a/test/javascript/tests/users_db_security.js b/test/javascript/tests/users_db_security.js
new file mode 100644
index 0000000..f2ca8bc
--- /dev/null
+++ b/test/javascript/tests/users_db_security.js
@@ -0,0 +1,423 @@
+// 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.users_db_security = function(debug) {
+  var usersDb = new CouchDB("test_suite_users", {"X-Couch-Full-Commit":"false"});
+  if (debug) debugger;
+
+  function wait(ms) {
+    var t0 = new Date(), t1;
+    do {
+      CouchDB.request("GET", "/");
+      t1 = new Date();
+    } while ((t1 - t0) <= ms);
+  }
+
+  var loginUser = function(username) {
+    var pws = {
+      jan: "apple",
+      jchris: "mp3",
+      jchris1: "couch",
+      fdmanana: "foobar",
+      benoitc: "test"
+    };
+    var username1 = username.replace(/[0-9]$/, "");
+    var password = pws[username];
+    T(CouchDB.login(username1, pws[username]).ok);
+  };
+
+  var open_as = function(db, docId, username) {
+    loginUser(username);
+    try {
+      return db.open(docId, {"anti-cache": Math.round(Math.random() * 100000)});
+    } finally {
+      CouchDB.logout();
+    }
+  };
+
+  var view_as = function(db, viewname, username) {
+    loginUser(username);
+    try {
+      return db.view(viewname);
+    } finally {
+      CouchDB.logout();
+    }
+  };
+
+  var save_as = function(db, doc, username)
+  {
+    loginUser(username);
+    try {
+      return db.save(doc);
+    } catch (ex) {
+      return ex;
+    } finally {
+      CouchDB.logout();
+    }
+  };
+
+  var changes_as = function(db, username)
+  {
+    loginUser(username);
+    try {
+      return db.changes();
+    } catch(ex) {
+      return ex;
+    } finally {
+      CouchDB.logout();
+    }
+  };
+
+  var testFun = function()
+  {
+
+    // _users db
+    // a doc with a field 'password' should be hashed to 'derived_key'
+    //  with salt and salt stored in 'salt', 'password' is set to null.
+    //  Exising 'derived_key' and 'salt' fields are overwritten with new values
+    //  when a non-null 'password' field exists.
+    // anonymous should be able to create a user document
+    var userDoc = {
+      _id: "org.couchdb.user:jchris",
+      type: "user",
+      name: "jchris",
+      password: "mp3",
+      roles: []
+    };
+
+    // jan's gonna be admin as he's the first user
+    TEquals(true, usersDb.save(userDoc).ok, "should save document");
+    userDoc = usersDb.open("org.couchdb.user:jchris");
+    TEquals(undefined, userDoc.password, "password field should be null 1");
+    TEquals(40, userDoc.derived_key.length, "derived_key should exist");
+    TEquals(32, userDoc.salt.length, "salt should exist");
+
+    // create server admin
+    run_on_modified_server([
+        {
+          section: "couch_httpd_auth",
+          key: "iterations",
+          value: "1"
+        },
+        {
+          section: "admins",
+          key: "jan",
+          value: "apple"
+        }
+      ], function() {
+
+      // anonymous should not be able to read an existing user's user document
+      var res = usersDb.open("org.couchdb.user:jchris");
+      TEquals(null, res, "anonymous user doc read should be not found");
+
+      // anonymous should not be able to read /_users/_changes
+      try {
+        var ch = usersDb.changes();
+        T(false, "anonymous can read _changes");
+      } catch(e) {
+        TEquals("unauthorized", e.error, "anoymous can't read _changes");
+      }
+
+      // user should be able to read their own document
+      var jchrisDoc = open_as(usersDb, "org.couchdb.user:jchris", "jchris");
+      TEquals("org.couchdb.user:jchris", jchrisDoc._id);
+
+      // user should not be able to read /_users/_changes
+      var changes = changes_as(usersDb, "jchris");
+      TEquals("unauthorized", changes.error, "user can't read _changes");
+
+      // new 'password' fields should trigger new hashing routine
+      jchrisDoc.password = "couch";
+
+      TEquals(true, save_as(usersDb, jchrisDoc, "jchris").ok);
+      wait(100);
+      var jchrisDoc = open_as(usersDb, "org.couchdb.user:jchris", "jchris1");
+
+      TEquals(undefined, jchrisDoc.password, "password field should be null 2");
+      TEquals(40, jchrisDoc.derived_key.length, "derived_key should exist");
+      TEquals(32, jchrisDoc.salt.length, "salt should exist");
+
+      TEquals(true, userDoc.salt != jchrisDoc.salt, "should have new salt");
+      TEquals(true, userDoc.derived_key != jchrisDoc.derived_key,
+        "should have new derived_key");
+
+      // SHA-1 password hashes are upgraded to PBKDF2 on successful
+      // authentication
+      var rnewsonDoc = {
+        _id: "org.couchdb.user:rnewson",
+        type: "user",
+        name: "rnewson",
+        // password: "plaintext_password",
+        password_sha: "e29dc3aeed5abf43185c33e479f8998558c59474",
+        salt: "24f1e0a87c2e374212bda1073107e8ae",
+        roles: []
+      };
+
+      var password_sha = rnewsonDoc.password_sha,
+        salt = rnewsonDoc.salt,
+        derived_key,
+        iterations;
+
+      usersDb.save(rnewsonDoc);
+      rnewsonDoc = open_as(usersDb, rnewsonDoc._id, "jan");
+      T(!rnewsonDoc.password_scheme);
+      T(!rnewsonDoc.derived_key);
+      T(!rnewsonDoc.iterations);
+
+      // check that we don't upgrade when the password is wrong
+      TEquals("unauthorized", CouchDB.login("rnewson", "wrong_password").error);
+      rnewsonDoc = open_as(usersDb, rnewsonDoc._id, "jan");
+      TEquals(salt, rnewsonDoc.salt);
+      TEquals(password_sha, rnewsonDoc.password_sha);
+      T(!rnewsonDoc.password_scheme);
+      T(!rnewsonDoc.derived_key);
+      T(!rnewsonDoc.iterations);
+
+      TEquals(true, CouchDB.login("rnewson", "plaintext_password").ok);
+      rnewsonDoc = usersDb.open(rnewsonDoc._id);
+      TEquals("pbkdf2", rnewsonDoc.password_scheme);
+      T(rnewsonDoc.salt != salt);
+      T(!rnewsonDoc.password_sha);
+      T(rnewsonDoc.derived_key);
+      T(rnewsonDoc.iterations);
+
+      salt = rnewsonDoc.salt,
+      derived_key = rnewsonDoc.derived_key,
+      iterations = rnewsonDoc.iterations;
+
+      // check that authentication is still working
+      // and everything is staying the same now
+      CouchDB.logout();
+      TEquals(true, CouchDB.login("rnewson", "plaintext_password").ok);
+      rnewsonDoc = usersDb.open(rnewsonDoc._id);
+      TEquals("pbkdf2", rnewsonDoc.password_scheme);
+      TEquals(salt, rnewsonDoc.salt);
+      T(!rnewsonDoc.password_sha);
+      TEquals(derived_key, rnewsonDoc.derived_key);
+      TEquals(iterations, rnewsonDoc.iterations);
+
+      CouchDB.logout();
+
+      // user should not be able to read another user's user document
+      var fdmananaDoc = {
+        _id: "org.couchdb.user:fdmanana",
+        type: "user",
+        name: "fdmanana",
+        password: "foobar",
+        roles: []
+      };
+
+      usersDb.save(fdmananaDoc);
+
+      var fdmananaDocAsReadByjchris =
+        open_as(usersDb, "org.couchdb.user:fdmanana", "jchris1");
+      TEquals(null, fdmananaDocAsReadByjchris,
+        "should not_found opening another user's user doc");
+
+
+      // save a db admin
+      var benoitcDoc = {
+        _id: "org.couchdb.user:benoitc",
+        type: "user",
+        name: "benoitc",
+        password: "test",
+        roles: ["user_admin"]
+      };
+      save_as(usersDb, benoitcDoc, "jan");
+
+      TEquals(true, CouchDB.login("jan", "apple").ok);
+      T(usersDb.setSecObj({
+        "admins" : {
+          roles : [],
+          names : ["benoitc"]
+        }
+      }).ok);
+      CouchDB.logout();
+
+      // user should not be able to read from any view
+      var ddoc = {
+        _id: "_design/user_db_auth",
+        views: {
+          test: {
+            map: "function(doc) { emit(doc._id, null); }"
+          }
+        }
+      };
+
+      save_as(usersDb, ddoc, "jan");
+
+      try {
+        usersDb.view("user_db_auth/test");
+        T(false, "user had access to view in admin db");
+      } catch(e) {
+        TEquals("forbidden", e.error,
+        "non-admins should not be able to read a view");
+      }
+
+      // admin should be able to read from any view
+      var result = view_as(usersDb, "user_db_auth/test", "jan");
+      TEquals(4, result.total_rows, "should allow access and list four users to admin");
+
+      // db admin should be able to read from any view
+      var result = view_as(usersDb, "user_db_auth/test", "benoitc");
+      TEquals(4, result.total_rows, "should allow access and list four users to db admin");
+
+
+      // non-admins can't read design docs
+      try {
+        open_as(usersDb, "_design/user_db_auth", "jchris1");
+        T(false, "non-admin read design doc, should not happen");
+      } catch(e) {
+        TEquals("forbidden", e.error, "non-admins can't read design docs");
+      }
+
+      // admin should be able to read and edit any user doc
+      fdmananaDoc.password = "mobile";
+      var result = save_as(usersDb, fdmananaDoc, "jan");
+      TEquals(true, result.ok, "admin should be able to update any user doc");
+
+      // admin should be able to read and edit any user doc
+      fdmananaDoc.password = "mobile1";
+      var result = save_as(usersDb, fdmananaDoc, "benoitc");
+      TEquals(true, result.ok, "db admin by role should be able to update any user doc");
+
+      TEquals(true, CouchDB.login("jan", "apple").ok);
+      T(usersDb.setSecObj({
+        "admins" : {
+          roles : ["user_admin"],
+          names : []
+        }
+      }).ok);
+      CouchDB.logout();
+
+      // db admin should be able to read and edit any user doc
+      fdmananaDoc.password = "mobile2";
+      var result = save_as(usersDb, fdmananaDoc, "benoitc");
+      TEquals(true, result.ok, "db admin should be able to update any user doc");
+
+      // ensure creation of old-style docs still works
+      var robertDoc = CouchDB.prepareUserDoc({ name: "robert" }, "anchovy");
+      var result = usersDb.save(robertDoc);
+      TEquals(true, result.ok, "old-style user docs should still be accepted");
+
+      // log in one last time so run_on_modified_server can clean up the admin account
+      TEquals(true, CouchDB.login("jan", "apple").ok);
+    });
+
+    run_on_modified_server([
+        {
+          section: "couch_httpd_auth",
+          key: "iterations",
+          value: "1"
+        },
+        {
+          section: "couch_httpd_auth",
+          key: "public_fields",
+          value: "name,type"
+        },
+        {
+          section: "couch_httpd_auth",
+          key: "users_db_public",
+          value: "true"
+        },
+        {
+          section: "admins",
+          key: "jan",
+          value: "apple"
+        }
+      ], function() {
+        var res = usersDb.open("org.couchdb.user:jchris");
+        TEquals("jchris", res.name);
+        TEquals("user", res.type);
+        TEquals(undefined, res.roles);
+        TEquals(undefined, res.salt);
+        TEquals(undefined, res.password_scheme);
+        TEquals(undefined, res.derived_key);
+
+        TEquals(true, CouchDB.login("jchris", "couch").ok);
+
+        var all = usersDb.allDocs({ include_docs: true });
+        T(all.rows);
+        if (all.rows) {
+          T(all.rows.every(function(row) {
+            if (row.doc) {
+              return Object.keys(row.doc).every(function(key) {
+                return key === 'name' || key === 'type';
+              });
+            } else {
+              if(row.id[0] == "_") {
+                // ignore design docs
+                return true
+              } else {
+                return false;
+              }
+            }
+          }));
+        }
+      // log in one last time so run_on_modified_server can clean up the admin account
+      TEquals(true, CouchDB.login("jan", "apple").ok);
+    });
+
+    run_on_modified_server([
+      {
+        section: "couch_httpd_auth",
+        key: "iterations",
+        value: "1"
+      },
+      {
+        section: "couch_httpd_auth",
+        key: "public_fields",
+        value: "name"
+      },
+      {
+        section: "couch_httpd_auth",
+        key: "users_db_public",
+        value: "false"
+      },
+      {
+        section: "admins",
+        key: "jan",
+        value: "apple"
+      }
+    ], function() {
+      TEquals(true, CouchDB.login("jchris", "couch").ok);
+
+      try {
+        var all = usersDb.allDocs({ include_docs: true });
+        T(false); // should never hit
+      } catch(e) {
+        TEquals("forbidden", e.error, "should throw");
+      }
+
+      // COUCHDB-1888 make sure admins always get all fields
+      TEquals(true, CouchDB.login("jan", "apple").ok);
+      var all_admin = usersDb.allDocs({ include_docs: "true" });
+      TEquals("user", all_admin.rows[2].doc.type,
+          "should return type");
+
+
+      // log in one last time so run_on_modified_server can clean up the admin account
+      TEquals(true, CouchDB.login("jan", "apple").ok);
+    });
+  };
+
+  usersDb.deleteDb();
+  run_on_modified_server(
+    [{section: "couch_httpd_auth",
+      key: "iterations", value: "1"},
+     {section: "couch_httpd_auth",
+      key: "authentication_db", value: usersDb.name}],
+    testFun
+  );
+  usersDb.deleteDb(); // cleanup
+
+};

http://git-wip-us.apache.org/repos/asf/couchdb/blob/e2d9c9b1/test/javascript/tests/utf8.js
----------------------------------------------------------------------
diff --git a/test/javascript/tests/utf8.js b/test/javascript/tests/utf8.js
new file mode 100644
index 0000000..04f6313
--- /dev/null
+++ b/test/javascript/tests/utf8.js
@@ -0,0 +1,42 @@
+// 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.utf8 = function(debug) {
+  var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
+  db.deleteDb();
+  db.createDb();
+  if (debug) debugger;
+
+  var texts = [];
+
+  texts[0] = "1. Ascii: hello"
+  texts[1] = "2. Russian: На берегу пустынных волн"
+  texts[2] = "3. Math: ∮ E⋅da = Q,  n → ∞, ∑ f(i) = ∏ g(i),"
+  texts[3] = "4. Geek: STARGΛ̊TE SG-1"
+  texts[4] = "5. Braille: ⡌⠁⠧⠑ ⠼⠁⠒  ⡍⠜⠇⠑⠹⠰⠎ ⡣⠕⠌"
+  texts[5] = "6. null \u0000 byte" 
+
+  // check that we can save a reload with full fidelity
+  for (var i=0; i<texts.length; i++) {
+    T(db.save({_id:i.toString(), text:texts[i]}).ok);
+  }
+
+  for (var i=0; i<texts.length; i++) {
+    T(db.open(i.toString()).text == texts[i]);
+  }
+
+  // check that views and key collation don't blow up
+  var rows = db.query(function(doc) { emit(null, doc.text) }).rows;
+  for (var i=0; i<texts.length; i++) {
+    T(rows[i].value == texts[i]);
+  }
+};

http://git-wip-us.apache.org/repos/asf/couchdb/blob/e2d9c9b1/test/javascript/tests/uuids.js
----------------------------------------------------------------------
diff --git a/test/javascript/tests/uuids.js b/test/javascript/tests/uuids.js
new file mode 100644
index 0000000..d304c4e
--- /dev/null
+++ b/test/javascript/tests/uuids.js
@@ -0,0 +1,149 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+couchTests.uuids = function(debug) {
+  var etags = [];
+  var testHashBustingHeaders = function(xhr) {
+    T(xhr.getResponseHeader("Cache-Control").match(/no-cache/));
+    T(xhr.getResponseHeader("Pragma") == "no-cache");
+
+    var newetag = xhr.getResponseHeader("ETag");
+    T(etags.indexOf(newetag) < 0);
+    etags[etags.length] = newetag;
+    
+    // Removing the time based tests as they break easily when
+    // running CouchDB on a remote server in regards to the browser
+    // running the Futon test suite.
+    //
+    //var currentTime = new Date();
+    //var expiresHeader = Date.parse(xhr.getResponseHeader("Expires"));
+    //var dateHeader = Date.parse(xhr.getResponseHeader("Date"));
+
+    //T(expiresHeader < currentTime);
+    //T(currentTime - dateHeader < 3000);
+  };
+
+  var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
+  db.deleteDb();
+  db.createDb();
+  if (debug) debugger;
+
+  // a single UUID without an explicit count
+  var xhr = CouchDB.request("GET", "/_uuids");
+  T(xhr.status == 200);
+  var result = JSON.parse(xhr.responseText);
+  T(result.uuids.length == 1);
+  var first = result.uuids[0];
+  testHashBustingHeaders(xhr);
+
+  // a single UUID with an explicit count
+  xhr = CouchDB.request("GET", "/_uuids?count=1");
+  T(xhr.status == 200);
+  result = JSON.parse(xhr.responseText);
+  T(result.uuids.length == 1);
+  var second = result.uuids[0];
+  T(first != second);
+
+  // no collisions with 1,000 UUIDs
+  xhr = CouchDB.request("GET", "/_uuids?count=1000");
+  T(xhr.status == 200);
+  result = JSON.parse(xhr.responseText);
+  T( result.uuids.length == 1000 );
+  var seen = {};
+  for(var i in result.uuids) {
+    var id = result.uuids[i];
+    T(seen[id] === undefined);
+    seen[id] = 1;
+  }
+
+  // ensure we return a 405 on POST
+  xhr = CouchDB.request("POST", "/_uuids?count=1000");
+  T(xhr.status == 405);
+
+  // Test sequential uuids
+  var seq_testfun = function() {
+    xhr = CouchDB.request("GET", "/_uuids?count=1000");
+    T(xhr.status == 200);
+    result = JSON.parse(xhr.responseText);
+    for(var i = 1; i < result.uuids.length; i++) {
+      T(result.uuids[i].length == 32);
+      T(result.uuids[i-1] < result.uuids[i], "Sequential uuids are ordered.");
+    }
+  };
+
+  // test max_uuid_count
+  var xhr = CouchDB.request("GET", "/_uuids?count=1001");
+  TEquals(403, xhr.status, "should error when count > max_count");
+
+  run_on_modified_server([{
+      "section": "uuids",
+      "key": "algorithm",
+      "value": "sequential",
+    }],
+    seq_testfun
+  );
+
+  // Test utc_random uuids
+  var utc_testfun = function() {
+    xhr = CouchDB.request("GET", "/_uuids?count=1000");
+    T(xhr.status == 200);
+    result = JSON.parse(xhr.responseText);
+    T(result.uuids[1].length == 32);
+
+    // no collisions
+    var seen = {};
+    for(var i in result.uuids) {
+      var id = result.uuids[i];
+      T(seen[id] === undefined);
+      seen[id] = 1;
+    }
+
+    // roughly ordered
+    var u1 = result.uuids[1].substr(0, 13);
+    var u2 = result.uuids[result.uuids.length-1].substr(0, 13);
+    T(u1 < u2, "UTC uuids are only roughly ordered, so this assertion may fail occasionally. Don't sweat it.");
+  };
+
+  run_on_modified_server([{
+      "section": "uuids",
+      "key": "algorithm",
+      "value": "utc_random"
+    }],
+    utc_testfun
+  );
+
+  // Test utc_id uuids
+  var utc_id_suffix = "frog";
+  var suffix_testfun = function() {
+    xhr = CouchDB.request("GET", "/_uuids?count=10");
+    T(xhr.status == 200);
+    result = JSON.parse(xhr.responseText);
+    for(var i = 1; i < result.uuids.length; i++) {
+      T(result.uuids[i].length == 14 + utc_id_suffix.length);
+      T(result.uuids[i].substring(14) == utc_id_suffix);
+      T(result.uuids[i-1] < result.uuids[i], "utc_id_suffix uuids are ordered.");
+    }
+  };
+
+  run_on_modified_server([{
+      "section": "uuids",
+      "key": "algorithm",
+      "value": "utc_id"
+    }, {
+      "section": "uuids",
+      "key": "utc_id_suffix",
+      "value": utc_id_suffix
+    }],
+    suffix_testfun
+  );
+
+ };

http://git-wip-us.apache.org/repos/asf/couchdb/blob/e2d9c9b1/test/javascript/tests/view_collation.js
----------------------------------------------------------------------
diff --git a/test/javascript/tests/view_collation.js b/test/javascript/tests/view_collation.js
new file mode 100644
index 0000000..b01a5c5
--- /dev/null
+++ b/test/javascript/tests/view_collation.js
@@ -0,0 +1,116 @@
+// 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.view_collation = function(debug) {
+  var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
+  db.deleteDb();
+  db.createDb();
+  if (debug) debugger;
+
+  // NOTE, the values are already in their correct sort order. Consider this
+  // a specification of collation of json types.
+
+  var values = [];
+
+  // special values sort before all other types
+  values.push(null);
+  values.push(false);
+  values.push(true);
+
+  // then numbers
+  values.push(1);
+  values.push(2);
+  values.push(3.0);
+  values.push(4);
+
+  // then text, case sensitive
+  values.push("a");
+  values.push("A");
+  values.push("aa");
+  values.push("b");
+  values.push("B");
+  values.push("ba");
+  values.push("bb");
+
+  // then arrays. compared element by element until different.
+  // Longer arrays sort after their prefixes
+  values.push(["a"]);
+  values.push(["b"]);
+  values.push(["b","c"]);
+  values.push(["b","c", "a"]);
+  values.push(["b","d"]);
+  values.push(["b","d", "e"]);
+
+  // then object, compares each key value in the list until different.
+  // larger objects sort after their subset objects.
+  values.push({a:1});
+  values.push({a:2});
+  values.push({b:1});
+  values.push({b:2});
+  values.push({b:2, a:1}); // Member order does matter for collation.
+                           // CouchDB preserves member order
+                           // but doesn't require that clients will.
+                           // (this test might fail if used with a js engine
+                           // that doesn't preserve order)
+  values.push({b:2, c:2});
+
+  for (var i=0; i<values.length; i++) {
+    db.save({_id:(i).toString(), foo:values[i]});
+  }
+
+  var queryFun = function(doc) { emit(doc.foo, null); };
+  var rows = db.query(queryFun).rows;
+  for (i=0; i<values.length; i++) {
+    T(equals(rows[i].key, values[i]));
+  }
+
+  // everything has collated correctly. Now to check the descending output
+  rows = db.query(queryFun, null, {descending: true}).rows;
+  for (i=0; i<values.length; i++) {
+    T(equals(rows[i].key, values[values.length - 1 -i]));
+  }
+
+  // now check the key query args
+  for (i=1; i<values.length; i++) {
+    var queryOptions = {key:values[i]};
+    rows = db.query(queryFun, null, queryOptions).rows;
+    T(rows.length == 1 && equals(rows[0].key, values[i]));
+  }
+
+  // test inclusive_end=true (the default)
+  // the inclusive_end=true functionality is limited to endkey currently
+  // if you need inclusive_start=false for startkey, please do implement. ;)
+  var rows = db.query(queryFun, null, {endkey : "b", inclusive_end:true}).rows;
+  T(rows[rows.length-1].key == "b");
+  // descending=true
+  var rows = db.query(queryFun, null, {endkey : "b",
+    descending:true, inclusive_end:true}).rows;
+  T(rows[rows.length-1].key == "b");
+
+  // test inclusive_end=false
+  var rows = db.query(queryFun, null, {endkey : "b", inclusive_end:false}).rows;
+  T(rows[rows.length-1].key == "aa");
+  // descending=true
+  var rows = db.query(queryFun, null, {endkey : "b",
+    descending:true, inclusive_end:false}).rows;
+  T(rows[rows.length-1].key == "B");
+  
+  var rows = db.query(queryFun, null, {
+    endkey : "b", endkey_docid: "10",
+    inclusive_end:false}).rows;
+  T(rows[rows.length-1].key == "aa");
+  
+  var rows = db.query(queryFun, null, {
+    endkey : "b", endkey_docid: "11",
+    inclusive_end:false}).rows;
+  T(rows[rows.length-1].key == "b");
+};

http://git-wip-us.apache.org/repos/asf/couchdb/blob/e2d9c9b1/test/javascript/tests/view_collation_raw.js
----------------------------------------------------------------------
diff --git a/test/javascript/tests/view_collation_raw.js b/test/javascript/tests/view_collation_raw.js
new file mode 100644
index 0000000..779f7eb
--- /dev/null
+++ b/test/javascript/tests/view_collation_raw.js
@@ -0,0 +1,130 @@
+// 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.view_collation_raw = function(debug) {
+  var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
+  db.deleteDb();
+  db.createDb();
+  if (debug) debugger;
+
+  // NOTE, the values are already in their correct sort order. Consider this
+  // a specification of collation of json types.
+
+  var values = [];
+
+  //  numbers
+  values.push(1);
+  values.push(2);
+  values.push(3);
+  values.push(4);
+  
+  values.push(false);
+  values.push(null);
+  values.push(true);
+  
+  // then object, compares each key value in the list until different.
+  // larger objects sort after their subset objects.
+  values.push({a:1});
+  values.push({a:2});
+  values.push({b:1});
+  values.push({b:2});
+  values.push({b:2, a:1}); // Member order does matter for collation.
+                           // CouchDB preserves member order
+                           // but doesn't require that clients will.
+                           // (this test might fail if used with a js engine
+                           // that doesn't preserve order)
+  values.push({b:2, c:2});
+
+  // then arrays. compared element by element until different.
+  // Longer arrays sort after their prefixes
+  values.push(["a"]);
+  values.push(["b"]);
+  values.push(["b","c"]);
+  values.push(["b","c", "a"]);
+  values.push(["b","d"]);
+  values.push(["b","d", "e"]);
+
+
+  // then text, case sensitive
+  values.push("A");
+  values.push("B");
+  values.push("a");
+  values.push("aa");
+  values.push("b");
+  values.push("ba");
+  values.push("bb");
+
+  for (var i=0; i<values.length; i++) {
+    db.save({_id:(i).toString(), foo:values[i]});
+  }
+
+  var designDoc = {
+    _id:"_design/test", // turn off couch.js id escaping?
+    language: "javascript",
+    views: {
+      test: {map: "function(doc) { emit(doc.foo, null); }",
+            options: {collation:"raw"}}
+    }
+  }
+  T(db.save(designDoc).ok);
+
+  // Confirm that everything collates correctly.
+  var rows = db.view("test/test").rows;
+  for (i=0; i<values.length; i++) {
+    T(equals(rows[i].key, values[i]));
+  }
+
+  // Confirm that couch allows raw semantics in key ranges.
+  rows = db.view("test/test", {startkey:"Z", endkey:"a"}).rows;
+  TEquals(1, rows.length);
+  TEquals("a", rows[0].key);
+
+  // Check the descending output.
+  rows = db.view("test/test", {descending: true}).rows;
+  for (i=0; i<values.length; i++) {
+    T(equals(rows[i].key, values[values.length - 1 -i]));
+  }
+
+  // now check the key query args
+  for (i=1; i<values.length; i++) {
+    rows = db.view("test/test", {key:values[i]}).rows;
+    T(rows.length == 1 && equals(rows[0].key, values[i]));
+  }
+
+  // test inclusive_end=true (the default)
+  // the inclusive_end=true functionality is limited to endkey currently
+  // if you need inclusive_start=false for startkey, please do implement. ;)
+  var rows = db.view("test/test", {endkey : "b", inclusive_end:true}).rows;
+  T(rows[rows.length-1].key == "b");
+  // descending=true
+  var rows = db.view("test/test", {endkey : "b",
+    descending:true, inclusive_end:true}).rows;
+  T(rows[rows.length-1].key == "b");
+
+  // test inclusive_end=false
+  var rows = db.view("test/test", {endkey : "b", inclusive_end:false}).rows;
+  T(rows[rows.length-1].key == "aa");
+  // descending=true
+  var rows = db.view("test/test", {endkey : "b",
+    descending:true, inclusive_end:false}).rows;
+  T(rows[rows.length-1].key == "ba");
+  
+  var rows = db.view("test/test", {
+    endkey : "b", endkey_docid: "10",
+    inclusive_end:false}).rows;
+  T(rows[rows.length-1].key == "aa");
+  
+  var rows = db.view("test/test", {
+    endkey : "b", endkey_docid: "11",
+    inclusive_end:false}).rows;
+  T(rows[rows.length-1].key == "aa");
+};

http://git-wip-us.apache.org/repos/asf/couchdb/blob/e2d9c9b1/test/javascript/tests/view_compaction.js
----------------------------------------------------------------------
diff --git a/test/javascript/tests/view_compaction.js b/test/javascript/tests/view_compaction.js
new file mode 100644
index 0000000..35d6276
--- /dev/null
+++ b/test/javascript/tests/view_compaction.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.view_compaction = function(debug) {
+
+  if (debug) debugger;
+
+  var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit": "true"});
+
+  db.deleteDb();
+  db.createDb();
+
+  var ddoc = {
+    _id: "_design/foo",
+    language: "javascript",
+    views: {
+      view1: {
+        map: "function(doc) { emit(doc._id, doc.value) }"
+      },
+      view2: {
+        map: "function(doc) { if (typeof(doc.integer) === 'number') {emit(doc._id, doc.integer);} }",
+        reduce: "function(keys, values, rereduce) { return sum(values); }"
+      }
+    }
+  };
+  T(db.save(ddoc).ok);
+
+  var docs = makeDocs(0, 10000);
+  db.bulkSave(docs);
+
+  var resp = db.view('foo/view1', {});
+  TEquals(10000, resp.rows.length);
+
+  resp = db.view('foo/view2', {});
+  TEquals(1, resp.rows.length);
+
+  resp = db.designInfo("_design/foo");
+  TEquals(10001, resp.view_index.update_seq);
+
+
+  // update docs
+  for (var i = 0; i < docs.length; i++) {
+    docs[i].integer = docs[i].integer + 1;
+  }
+  db.bulkSave(docs);
+
+
+  resp = db.view('foo/view1', {});
+  TEquals(10000, resp.rows.length);
+
+  resp = db.view('foo/view2', {});
+  TEquals(1, resp.rows.length);
+
+  resp = db.designInfo("_design/foo");
+  TEquals(20001, resp.view_index.update_seq);
+
+
+  // update docs again...
+  for (var i = 0; i < docs.length; i++) {
+    docs[i].integer = docs[i].integer + 2;
+  }
+  db.bulkSave(docs);
+
+
+  resp = db.view('foo/view1', {});
+  TEquals(10000, resp.rows.length);
+
+  resp = db.view('foo/view2', {});
+  TEquals(1, resp.rows.length);
+
+  resp = db.designInfo("_design/foo");
+  TEquals(30001, resp.view_index.update_seq);
+
+  var disk_size_before_compact = resp.view_index.disk_size;
+  var data_size_before_compact = resp.view_index.data_size;
+
+  TEquals("number", typeof data_size_before_compact, "data size is a number");
+  T(data_size_before_compact < disk_size_before_compact, "data size < file size");
+
+  // compact view group
+  var xhr = CouchDB.request("POST", "/" + db.name + "/_design/foo/_compact");
+  T(JSON.parse(xhr.responseText).ok === true);
+
+  resp = db.designInfo("_design/foo");
+  while (resp.view_index.compact_running === true) {
+    resp = db.designInfo("_design/foo");
+  }
+
+
+  resp = db.view('foo/view1', {});
+  TEquals(10000, resp.rows.length);
+
+  resp = db.view('foo/view2', {});
+  TEquals(1, resp.rows.length);
+
+  resp = db.designInfo("_design/foo");
+  TEquals(30001, resp.view_index.update_seq);
+  T(resp.view_index.disk_size < disk_size_before_compact);
+  TEquals("number", typeof resp.view_index.data_size, "data size is a number");
+  T(resp.view_index.data_size < resp.view_index.disk_size, "data size < file size");
+};
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/couchdb/blob/e2d9c9b1/test/javascript/tests/view_conflicts.js
----------------------------------------------------------------------
diff --git a/test/javascript/tests/view_conflicts.js b/test/javascript/tests/view_conflicts.js
new file mode 100644
index 0000000..96f97d5
--- /dev/null
+++ b/test/javascript/tests/view_conflicts.js
@@ -0,0 +1,49 @@
+// 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.view_conflicts = function(debug) {
+  var dbA = new CouchDB("test_suite_db_a", {"X-Couch-Full-Commit":"false"});
+  dbA.deleteDb();
+  dbA.createDb();
+  var dbB = new CouchDB("test_suite_db_b", {"X-Couch-Full-Commit":"false"});
+  dbB.deleteDb();
+  dbB.createDb();
+  if (debug) debugger;
+
+  var docA = {_id: "foo", bar: 42};
+  T(dbA.save(docA).ok);
+  CouchDB.replicate(dbA.name, dbB.name);
+
+  var docB = dbB.open("foo");
+  docB.bar = 43;
+  dbB.save(docB);
+  docA.bar = 41;
+  dbA.save(docA);
+  CouchDB.replicate(dbA.name, dbB.name);
+
+  var doc = dbB.open("foo", {conflicts: true});
+  T(doc._conflicts.length == 1);
+  var conflictRev = doc._conflicts[0];
+  if (doc.bar == 41) { // A won
+    T(conflictRev == docB._rev);
+  } else { // B won
+    T(doc.bar == 43);
+    T(conflictRev == docA._rev);
+  }
+
+  var results = dbB.query(function(doc) {
+    if (doc._conflicts) {
+      emit(doc._id, doc._conflicts);
+    }
+  });
+  T(results.rows[0].value[0] == conflictRev);
+};

http://git-wip-us.apache.org/repos/asf/couchdb/blob/e2d9c9b1/test/javascript/tests/view_errors.js
----------------------------------------------------------------------
diff --git a/test/javascript/tests/view_errors.js b/test/javascript/tests/view_errors.js
new file mode 100644
index 0000000..e8bd08e
--- /dev/null
+++ b/test/javascript/tests/view_errors.js
@@ -0,0 +1,189 @@
+// 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.view_errors = function(debug) {
+  var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"true"});
+  db.deleteDb();
+  db.createDb();
+  if (debug) debugger;
+
+  run_on_modified_server(
+    [{section: "couchdb",
+      key: "os_process_timeout",
+      value: "500"}],
+    function() {
+      var doc = {integer: 1, string: "1", array: [1, 2, 3]};
+      T(db.save(doc).ok);
+
+      // emitting a key value that is undefined should result in that row
+      // being included in the view results as null
+      var results = db.query(function(doc) {
+        emit(doc.undef, null);
+      });
+      T(results.total_rows == 1);
+      T(results.rows[0].key == null);
+
+      // if a view function throws an exception, its results are not included in
+      // the view index, but the view does not itself raise an error
+      var results = db.query(function(doc) {
+        doc.undef(); // throws an error
+      });
+      T(results.total_rows == 0);
+
+      // if a view function includes an undefined value in the emitted key or
+      // value, it is treated as null
+      var results = db.query(function(doc) {
+        emit([doc._id, doc.undef], null);
+      });
+      T(results.total_rows == 1);
+      T(results.rows[0].key[1] == null);
+      
+      // querying a view with invalid params should give a resonable error message
+      var xhr = CouchDB.request("POST", "/test_suite_db/_temp_view?startkey=foo", {
+        headers: {"Content-Type": "application/json"},
+        body: JSON.stringify({language: "javascript",
+          map : "function(doc){emit(doc.integer)}"
+        })
+      });
+      T(JSON.parse(xhr.responseText).error == "bad_request");
+
+      // content type must be json
+      var xhr = CouchDB.request("POST", "/test_suite_db/_temp_view", {
+        headers: {"Content-Type": "application/x-www-form-urlencoded"},
+        body: JSON.stringify({language: "javascript",
+          map : "function(doc){}"
+        })
+      });
+      T(xhr.status == 415);
+
+      var map = function (doc) {emit(doc.integer, doc.integer);};
+
+      try {
+          db.query(map, null, {group: true});
+          T(0 == 1);
+      } catch(e) {
+          T(e.error == "query_parse_error");
+      }
+
+      var designDoc = {
+        _id:"_design/test",
+        language: "javascript",
+        views: {
+          "no_reduce": {map:"function(doc) {emit(doc._id, null);}"},
+          "with_reduce": {
+            map:"function (doc) {emit(doc.integer, doc.integer)};",
+            reduce:"function (keys, values) { return sum(values); };"}
+        }
+      };
+      T(db.save(designDoc).ok);
+
+      var designDoc2 = {
+        _id:"_design/testbig",
+        language: "javascript",
+        views: {
+          "reduce_too_big"  : {
+            map:"function (doc) {emit(doc.integer, doc.integer)};",
+            reduce:"function (keys, values) { var chars = []; for (var i=0; i < 1000; i++) {chars.push('wazzap');};return chars; };"}
+        }
+      };
+      T(db.save(designDoc2).ok);
+
+      try {
+          db.view("test/no_reduce", {group: true});
+          T(0 == 1);
+      } catch(e) {
+          T(db.last_req.status == 400);
+          T(e.error == "query_parse_error");
+      }
+
+      try {
+          db.view("test/no_reduce", {group_level: 1});
+          T(0 == 1);
+      } catch(e) {
+          T(db.last_req.status == 400);
+          T(e.error == "query_parse_error");
+      }
+
+      try {
+        db.view("test/no_reduce", {reduce: true});
+        T(0 == 1);
+      } catch(e) {
+        T(db.last_req.status == 400);
+        T(e.error == "query_parse_error");
+      }
+
+      db.view("test/no_reduce", {reduce: false});
+      TEquals(200, db.last_req.status, "reduce=false for map views (without"
+                                     + " group or group_level) is allowed");
+
+      try {
+          db.view("test/with_reduce", {group: true, reduce: false});
+          T(0 == 1);
+      } catch(e) {
+          T(db.last_req.status == 400);
+          T(e.error == "query_parse_error");
+      }
+
+      try {
+          db.view("test/with_reduce", {group_level: 1, reduce: false});
+          T(0 == 1);
+      } catch(e) {
+        T(db.last_req.status == 400);
+          T(e.error == "query_parse_error");
+      }
+
+      var designDoc3 = {
+        _id:"_design/infinite",
+        language: "javascript",
+        views: {
+          "infinite_loop" :{map:"function(doc) {while(true){emit(doc,doc);}};"}
+        }
+      };
+      T(db.save(designDoc3).ok);
+
+      try {
+          db.view("infinite/infinite_loop");
+          T(0 == 1);
+      } catch(e) {
+          T(e.error == "os_process_error");
+      }
+
+      // Check error responses for invalid multi-get bodies.
+      var path = "/test_suite_db/_design/test/_view/no_reduce";
+      var xhr = CouchDB.request("POST", path, {body: "[]"});
+      T(xhr.status == 400);
+      result = JSON.parse(xhr.responseText);
+      T(result.error == "bad_request");
+      T(result.reason == "Request body must be a JSON object");
+      var data = "{\"keys\": 1}";
+      xhr = CouchDB.request("POST", path, {body:data});
+      T(xhr.status == 400);
+      result = JSON.parse(xhr.responseText);
+      T(result.error == "bad_request");
+      T(result.reason == "`keys` member must be a array.");
+
+      // if the reduce grows to fast, throw an overflow error
+      var path = "/test_suite_db/_design/testbig/_view/reduce_too_big";
+      xhr = CouchDB.request("GET", path);
+      T(xhr.status == 500);
+      result = JSON.parse(xhr.responseText);
+      T(result.error == "reduce_overflow_error");
+
+      try {
+          db.query(function() {emit(null, null)}, null, {startkey: 2, endkey:1});
+          T(0 == 1);
+      } catch(e) {
+          T(e.error == "query_parse_error");
+          T(e.reason.match(/no rows can match/i));
+      }
+    });
+};

http://git-wip-us.apache.org/repos/asf/couchdb/blob/e2d9c9b1/test/javascript/tests/view_include_docs.js
----------------------------------------------------------------------
diff --git a/test/javascript/tests/view_include_docs.js b/test/javascript/tests/view_include_docs.js
new file mode 100644
index 0000000..dab79b8
--- /dev/null
+++ b/test/javascript/tests/view_include_docs.js
@@ -0,0 +1,192 @@
+// 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.view_include_docs = function(debug) {
+  var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
+  db.deleteDb();
+  db.createDb();
+  if (debug) debugger;
+
+  var docs = makeDocs(0, 100);
+  db.bulkSave(docs);
+
+  var designDoc = {
+    _id:"_design/test",
+    language: "javascript",
+    views: {
+      all_docs: {
+        map: "function(doc) { emit(doc.integer, doc.string) }"
+      },
+      with_prev: {
+        map: "function(doc){if(doc.prev) emit(doc._id,{'_rev':doc.prev}); else emit(doc._id,{'_rev':doc._rev});}"
+      },
+      with_id: {
+        map: "function(doc) {if(doc.link_id) { var value = {'_id':doc.link_id}; if (doc.link_rev) {value._rev = doc.link_rev}; emit(doc._id, value);}};"
+      },
+      summate: {
+        map:"function (doc) { if (typeof doc.integer === 'number') {emit(doc.integer, doc.integer)};}",
+        reduce:"function (keys, values) { return sum(values); };"
+      }
+    }
+  }
+  T(db.save(designDoc).ok);
+
+  var resp = db.view('test/all_docs', {include_docs: true, limit: 2});
+  T(resp.rows.length == 2);
+  T(resp.rows[0].id == "0");
+  T(resp.rows[0].doc._id == "0");
+  T(resp.rows[1].id == "1");
+  T(resp.rows[1].doc._id == "1");
+
+  resp = db.view('test/all_docs', {include_docs: true}, [29, 74]);
+  T(resp.rows.length == 2);
+  T(resp.rows[0].doc._id == "29");
+  T(resp.rows[1].doc.integer == 74);
+
+  resp = db.allDocs({limit: 2, skip: 1, include_docs: true});
+  T(resp.rows.length == 2);
+  T(resp.rows[0].doc.integer == 1);
+  T(resp.rows[1].doc.integer == 10);
+
+  resp = db.allDocs({include_docs: true}, ['not_a_doc']);
+  T(resp.rows.length == 1);
+  T(!resp.rows[0].doc);
+
+  resp = db.allDocs({include_docs: true}, ["1", "foo"]);
+  T(resp.rows.length == 2);
+  T(resp.rows[0].doc.integer == 1);
+  T(!resp.rows[1].doc);
+
+  resp = db.allDocs({include_docs: true, limit: 0});
+  T(resp.rows.length == 0);
+
+  // No reduce support
+  try {
+      resp = db.view('test/summate', {include_docs: true});
+      alert(JSON.stringify(resp));
+      T(0==1);
+  } catch (e) {
+      T(e.error == 'query_parse_error');
+  }
+
+  // Reduce support when reduce=false
+  resp = db.view('test/summate', {reduce: false, include_docs: true});
+  T(resp.rows.length == 100);
+
+  // Not an error with include_docs=false&reduce=true
+  resp = db.view('test/summate', {reduce: true, include_docs: false});
+  T(resp.rows.length == 1);
+  T(resp.rows[0].value == 4950);
+
+  T(db.save({
+    "_id": "link-to-10",
+    "link_id" : "10"
+  }).ok);
+  
+  // you can link to another doc from a value.
+  resp = db.view("test/with_id", {key:"link-to-10"});
+  T(resp.rows[0].key == "link-to-10");
+  T(resp.rows[0].value["_id"] == "10");
+  
+  resp = db.view("test/with_id", {key:"link-to-10",include_docs: true});
+  T(resp.rows[0].key == "link-to-10");
+  T(resp.rows[0].value["_id"] == "10");
+  T(resp.rows[0].doc._id == "10");
+
+  // Check emitted _rev controls things
+  resp = db.allDocs({include_docs: true}, ["0"]);
+  var before = resp.rows[0].doc;
+
+  var after = db.open("0");
+  after.integer = 100;
+  after.prev = after._rev;
+  resp = db.save(after)
+  T(resp.ok);
+  
+  var after = db.open("0");
+  TEquals(resp.rev, after._rev, "fails with firebug running");
+  T(after._rev != after.prev, "passes");
+  TEquals(100, after.integer, "fails with firebug running");
+
+  // should emit the previous revision
+  resp = db.view("test/with_prev", {include_docs: true}, ["0"]);
+  T(resp.rows[0].doc._id == "0");
+  T(resp.rows[0].doc._rev == before._rev);
+  T(!resp.rows[0].doc.prev);
+  T(resp.rows[0].doc.integer == 0);
+
+  var xhr = CouchDB.request("POST", "/test_suite_db/_compact");
+  T(xhr.status == 202)
+  while (db.info().compact_running) {}
+
+  resp = db.view("test/with_prev", {include_docs: true}, ["0", "23"]);
+  T(resp.rows.length == 2);
+  T(resp.rows[0].key == "0");
+  T(resp.rows[0].id == "0");
+  T(!resp.rows[0].doc);
+  T(resp.rows[0].doc == null);
+  T(resp.rows[1].doc.integer == 23);
+
+  // COUCHDB-549 - include_docs=true with conflicts=true
+
+  var dbA = new CouchDB("test_suite_db_a", {"X-Couch-Full-Commit":"false"});
+  var dbB = new CouchDB("test_suite_db_b", {"X-Couch-Full-Commit":"false"});
+
+  dbA.deleteDb();
+  dbA.createDb();
+  dbB.deleteDb();
+  dbB.createDb();
+
+  var ddoc = {
+    _id: "_design/mydesign",
+    language : "javascript",
+    views : {
+      myview : {
+        map: (function(doc) {
+          emit(doc.value, 1);
+        }).toString()
+      }
+    }
+  };
+  TEquals(true, dbA.save(ddoc).ok);
+
+  var doc1a = {_id: "foo", value: 1, str: "1"};
+  TEquals(true, dbA.save(doc1a).ok);
+
+  var doc1b = {_id: "foo", value: 1, str: "666"};
+  TEquals(true, dbB.save(doc1b).ok);
+
+  var doc2 = {_id: "bar", value: 2, str: "2"};
+  TEquals(true, dbA.save(doc2).ok);
+
+  TEquals(true, CouchDB.replicate(dbA.name, dbB.name).ok);
+
+  doc1b = dbB.open("foo", {conflicts: true});
+  TEquals(true, doc1b._conflicts instanceof Array);
+  TEquals(1, doc1b._conflicts.length);
+  var conflictRev = doc1b._conflicts[0];
+
+  doc2 = dbB.open("bar", {conflicts: true});
+  TEquals("undefined", typeof doc2._conflicts);
+
+  resp = dbB.view("mydesign/myview", {include_docs: true, conflicts: true});
+
+  TEquals(2, resp.rows.length);
+  TEquals(true, resp.rows[0].doc._conflicts instanceof Array);
+  TEquals(1, resp.rows[0].doc._conflicts.length);
+  TEquals(conflictRev, resp.rows[0].doc._conflicts[0]);
+  TEquals("undefined", typeof resp.rows[1].doc._conflicts);
+
+  // cleanup
+  dbA.deleteDb();
+  dbB.deleteDb();
+};

http://git-wip-us.apache.org/repos/asf/couchdb/blob/e2d9c9b1/test/javascript/tests/view_multi_key_all_docs.js
----------------------------------------------------------------------
diff --git a/test/javascript/tests/view_multi_key_all_docs.js b/test/javascript/tests/view_multi_key_all_docs.js
new file mode 100644
index 0000000..7c7f6f8
--- /dev/null
+++ b/test/javascript/tests/view_multi_key_all_docs.js
@@ -0,0 +1,95 @@
+// 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.view_multi_key_all_docs = function(debug) {
+  var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
+  db.deleteDb();
+  db.createDb();
+  if (debug) debugger;
+
+  var docs = makeDocs(0, 100);
+  db.bulkSave(docs);
+
+  var keys = ["10","15","30","37","50"];
+  var rows = db.allDocs({},keys).rows;
+  T(rows.length == keys.length);
+  for(var i=0; i<rows.length; i++)
+    T(rows[i].id == keys[i]);
+
+  // keys in GET parameters
+  rows = db.allDocs({keys:keys}, null).rows;
+  T(rows.length == keys.length);
+  for(var i=0; i<rows.length; i++)
+    T(rows[i].id == keys[i]);
+
+  rows = db.allDocs({limit: 1}, keys).rows;
+  T(rows.length == 1);
+  T(rows[0].id == keys[0]);
+
+  // keys in GET parameters
+  rows = db.allDocs({limit: 1, keys: keys}, null).rows;
+  T(rows.length == 1);
+  T(rows[0].id == keys[0]);
+
+  rows = db.allDocs({skip: 2}, keys).rows;
+  T(rows.length == 3);
+  for(var i=0; i<rows.length; i++)
+      T(rows[i].id == keys[i+2]);
+
+  // keys in GET parameters
+  rows = db.allDocs({skip: 2, keys: keys}, null).rows;
+  T(rows.length == 3);
+  for(var i=0; i<rows.length; i++)
+      T(rows[i].id == keys[i+2]);
+
+  rows = db.allDocs({descending: "true"}, keys).rows;
+  T(rows.length == keys.length);
+  for(var i=0; i<rows.length; i++)
+      T(rows[i].id == keys[keys.length-i-1]);
+
+  // keys in GET parameters
+  rows = db.allDocs({descending: "true", keys: keys}, null).rows;
+  T(rows.length == keys.length);
+  for(var i=0; i<rows.length; i++)
+      T(rows[i].id == keys[keys.length-i-1]);
+
+  rows = db.allDocs({descending: "true", skip: 3, limit:1}, keys).rows;
+  T(rows.length == 1);
+  T(rows[0].id == keys[1]);
+
+  // keys in GET parameters
+  rows = db.allDocs({descending: "true", skip: 3, limit:1, keys: keys}, null).rows;
+  T(rows.length == 1);
+  T(rows[0].id == keys[1]);
+
+  // Check we get invalid rows when the key doesn't exist
+  rows = db.allDocs({}, [1, "i_dont_exist", "0"]).rows;
+  T(rows.length == 3);
+  T(rows[0].error == "not_found");
+  T(!rows[0].id);
+  T(rows[1].error == "not_found");
+  T(!rows[1].id);
+  T(rows[2].id == rows[2].key && rows[2].key == "0");
+
+  // keys in GET parameters
+  rows = db.allDocs({keys: [1, "i_dont_exist", "0"]}, null).rows;
+  T(rows.length == 3);
+  T(rows[0].error == "not_found");
+  T(!rows[0].id);
+  T(rows[1].error == "not_found");
+  T(!rows[1].id);
+  T(rows[2].id == rows[2].key && rows[2].key == "0");
+
+  // empty keys
+  rows = db.allDocs({keys: []}, null).rows;
+  T(rows.length == 0);
+};

http://git-wip-us.apache.org/repos/asf/couchdb/blob/e2d9c9b1/test/javascript/tests/view_multi_key_design.js
----------------------------------------------------------------------
diff --git a/test/javascript/tests/view_multi_key_design.js b/test/javascript/tests/view_multi_key_design.js
new file mode 100644
index 0000000..a84d07a
--- /dev/null
+++ b/test/javascript/tests/view_multi_key_design.js
@@ -0,0 +1,220 @@
+// 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.view_multi_key_design = function(debug) {
+  var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
+  db.deleteDb();
+  db.createDb();
+  if (debug) debugger;
+
+  var docs = makeDocs(0, 100);
+  db.bulkSave(docs);
+
+  var designDoc = {
+    _id:"_design/test",
+    language: "javascript",
+    views: {
+      all_docs: {
+        map: "function(doc) { emit(doc.integer, doc.string) }"
+      },
+      multi_emit: {
+        map: "function(doc) {for(var i = 0 ; i < 3 ; i++) { emit(i, doc.integer) ; } }"
+      },
+      summate: {
+        map:"function (doc) {emit(doc.integer, doc.integer)};",
+        reduce:"function (keys, values) { return sum(values); };"
+      }
+    }
+  };
+  T(db.save(designDoc).ok);
+
+  // Test that missing keys work too
+  var keys = [101,30,15,37,50];
+  var reduce = db.view("test/summate",{group:true},keys).rows;
+  T(reduce.length == keys.length-1); // 101 is missing
+  for(var i=0; i<reduce.length; i++) {
+    T(keys.indexOf(reduce[i].key) != -1);
+    T(reduce[i].key == reduce[i].value);
+  }
+
+  // First, the goods:
+  var keys = [10,15,30,37,50];
+  var rows = db.view("test/all_docs",{},keys).rows;
+  for(var i=0; i<rows.length; i++) {
+    T(keys.indexOf(rows[i].key) != -1);
+    T(rows[i].key == rows[i].value);
+  }
+
+  // with GET keys
+  rows = db.view("test/all_docs",{keys:keys},null).rows;
+  for(var i=0;i<rows.length; i++) {
+    T(keys.indexOf(rows[i].key) != -1);
+    T(rows[i].key == rows[i].value);
+  }
+
+  // with empty keys
+  rows = db.view("test/all_docs",{keys:[]},null).rows;
+  T(rows.length == 0);
+
+  var reduce = db.view("test/summate",{group:true},keys).rows;
+  T(reduce.length == keys.length);
+  for(var i=0; i<reduce.length; i++) {
+    T(keys.indexOf(reduce[i].key) != -1);
+    T(reduce[i].key == reduce[i].value);
+  }
+
+  // with GET keys
+  reduce = db.view("test/summate",{group:true,keys:keys},null).rows;
+  T(reduce.length == keys.length);
+  for(var i=0; i<reduce.length; i++) {
+    T(keys.indexOf(reduce[i].key) != -1);
+    T(reduce[i].key == reduce[i].value);
+  }
+
+  // Test that invalid parameter combinations get rejected
+  var badargs = [{startkey:0}, {endkey:0}, {key: 0}, {group_level: 2}];
+  var getbadargs = [{startkey:0, keys:keys}, {endkey:0, keys:keys}, 
+      {key:0, keys:keys}, {group_level: 2, keys:keys}];
+  for(var i in badargs)
+  {
+      try {
+          db.view("test/all_docs",badargs[i],keys);
+          T(0==1);
+      } catch (e) {
+          T(e.error == "query_parse_error");
+      }
+
+      try {
+          db.view("test/all_docs",getbadargs[i],null);
+          T(0==1);
+      } catch (e) {
+          T(e.error = "query_parse_error");
+      }
+  }
+
+  try {
+      db.view("test/summate",{},keys);
+      T(0==1);
+  } catch (e) {
+      T(e.error == "query_parse_error");
+  }
+
+  try {
+      db.view("test/summate",{keys:keys},null);
+      T(0==1);
+  } catch (e) {
+      T(e.error == "query_parse_error");
+  }
+
+  // Test that a map & reduce containing func support keys when reduce=false
+  var resp = db.view("test/summate", {reduce: false}, keys);
+  T(resp.rows.length == 5);
+
+  resp = db.view("test/summate", {reduce: false, keys: keys}, null);
+  T(resp.rows.length == 5);
+
+  // Check that limiting by startkey_docid and endkey_docid get applied
+  // as expected.
+  var curr = db.view("test/multi_emit", {startkey_docid: 21, endkey_docid: 23}, [0, 2]).rows;
+  var exp_key = [ 0,  0,  0,  2,  2,  2] ;
+  var exp_val = [21, 22, 23, 21, 22, 23] ;
+  T(curr.length == 6);
+  for( var i = 0 ; i < 6 ; i++)
+  {
+      T(curr[i].key == exp_key[i]);
+      T(curr[i].value == exp_val[i]);
+  }
+
+  curr = db.view("test/multi_emit", {startkey_docid: 21, endkey_docid: 23, keys: [0, 2]}, null).rows;
+  T(curr.length == 6);
+  for( var i = 0 ; i < 6 ; i++)
+  {
+      T(curr[i].key == exp_key[i]);
+      T(curr[i].value == exp_val[i]);
+  }
+
+  // Check limit works
+  curr = db.view("test/all_docs", {limit: 1}, keys).rows;
+  T(curr.length == 1);
+  T(curr[0].key == 10);
+
+  curr = db.view("test/all_docs", {limit: 1, keys: keys}, null).rows;
+  T(curr.length == 1);
+  T(curr[0].key == 10);
+
+  // Check offset works
+  curr = db.view("test/multi_emit", {skip: 1}, [0]).rows;
+  T(curr.length == 99);
+  T(curr[0].value == 1);
+
+  curr = db.view("test/multi_emit", {skip: 1, keys: [0]}, null).rows;
+  T(curr.length == 99);
+  T(curr[0].value == 1);
+
+  // Check that dir works
+  curr = db.view("test/multi_emit", {descending: "true"}, [1]).rows;
+  T(curr.length == 100);
+  T(curr[0].value == 99);
+  T(curr[99].value == 0);
+
+  curr = db.view("test/multi_emit", {descending: "true", keys: [1]}, null).rows;
+  T(curr.length == 100);
+  T(curr[0].value == 99);
+  T(curr[99].value == 0);
+
+  // Check a couple combinations
+  curr = db.view("test/multi_emit", {descending: "true", skip: 3, limit: 2}, [2]).rows;
+  T(curr.length, 2);
+  T(curr[0].value == 96);
+  T(curr[1].value == 95);
+
+  curr = db.view("test/multi_emit", {descending: "true", skip: 3, limit: 2, keys: [2]}, null).rows;
+  T(curr.length, 2);
+  T(curr[0].value == 96);
+  T(curr[1].value == 95);
+
+  curr = db.view("test/multi_emit", {skip: 2, limit: 3, startkey_docid: "13"}, [0]).rows;
+  T(curr.length == 3);
+  T(curr[0].value == 15);
+  T(curr[1].value == 16);
+  T(curr[2].value == 17);
+
+  curr = db.view("test/multi_emit", {skip: 2, limit: 3, startkey_docid: "13", keys: [0]}, null).rows;
+  T(curr.length == 3);
+  T(curr[0].value == 15);
+  T(curr[1].value == 16);
+  T(curr[2].value == 17);
+
+  curr = db.view("test/multi_emit",
+          {skip: 1, limit: 5, startkey_docid: "25", endkey_docid: "27"}, [1]).rows;
+  T(curr.length == 2);
+  T(curr[0].value == 26);
+  T(curr[1].value == 27);
+
+  curr = db.view("test/multi_emit",
+          {skip: 1, limit: 5, startkey_docid: "25", endkey_docid: "27", keys: [1]}, null).rows;
+  T(curr.length == 2);
+  T(curr[0].value == 26);
+  T(curr[1].value == 27);
+
+  curr = db.view("test/multi_emit",
+          {skip: 1, limit: 5, startkey_docid: "28", endkey_docid: "26", descending: "true"}, [1]).rows;
+  T(curr.length == 2);
+  T(curr[0].value == 27);
+  T(curr[1].value == 26);
+
+  curr = db.view("test/multi_emit",
+          {skip: 1, limit: 5, startkey_docid: "28", endkey_docid: "26", descending: "true", keys: [1]}, null).rows;
+  T(curr.length == 2);
+  T(curr[0].value == 27);
+  T(curr[1].value == 26);
+};

http://git-wip-us.apache.org/repos/asf/couchdb/blob/e2d9c9b1/test/javascript/tests/view_multi_key_temp.js
----------------------------------------------------------------------
diff --git a/test/javascript/tests/view_multi_key_temp.js b/test/javascript/tests/view_multi_key_temp.js
new file mode 100644
index 0000000..3c05409
--- /dev/null
+++ b/test/javascript/tests/view_multi_key_temp.js
@@ -0,0 +1,40 @@
+// 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.view_multi_key_temp = function(debug) {
+  var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
+  db.deleteDb();
+  db.createDb();
+  if (debug) debugger;
+
+  var docs = makeDocs(0, 100);
+  db.bulkSave(docs);
+
+  var queryFun = function(doc) { emit(doc.integer, doc.integer) };
+  var reduceFun = function (keys, values) { return sum(values); };
+
+  var keys = [10,15,30,37,50];
+  var rows = db.query(queryFun, null, {}, keys).rows;
+  for(var i=0; i<rows.length; i++) {
+    T(keys.indexOf(rows[i].key) != -1);
+    T(rows[i].key == rows[i].value);
+  }
+
+  var reduce = db.query(queryFun, reduceFun, {group:true}, keys).rows;
+  for(var i=0; i<reduce.length; i++) {
+    T(keys.indexOf(reduce[i].key) != -1);
+    T(reduce[i].key == reduce[i].value);
+  }
+
+  rows = db.query(queryFun, null, {}, []).rows;
+  T(rows.length == 0);
+};

http://git-wip-us.apache.org/repos/asf/couchdb/blob/e2d9c9b1/test/javascript/tests/view_offsets.js
----------------------------------------------------------------------
diff --git a/test/javascript/tests/view_offsets.js b/test/javascript/tests/view_offsets.js
new file mode 100644
index 0000000..464a1ae
--- /dev/null
+++ b/test/javascript/tests/view_offsets.js
@@ -0,0 +1,108 @@
+// 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.view_offsets = function(debug) {
+  if (debug) debugger;
+
+  var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
+  db.deleteDb();
+  db.createDb();
+
+  var designDoc = {
+    _id : "_design/test",
+    views : {
+      offset : {
+        map : "function(doc) { emit([doc.letter, doc.number], doc); }",
+      }
+    }
+  };
+  T(db.save(designDoc).ok);
+
+  var docs = [
+    {_id : "a1", letter : "a", number : 1, foo: "bar"},
+    {_id : "a2", letter : "a", number : 2, foo: "bar"},
+    {_id : "a3", letter : "a", number : 3, foo: "bar"},
+    {_id : "b1", letter : "b", number : 1, foo: "bar"},
+    {_id : "b2", letter : "b", number : 2, foo: "bar"},
+    {_id : "b3", letter : "b", number : 3, foo: "bar"},
+    {_id : "b4", letter : "b", number : 4, foo: "bar"},
+    {_id : "b5", letter : "b", number : 5, foo: "bar"},
+    {_id : "c1", letter : "c", number : 1, foo: "bar"},
+    {_id : "c2", letter : "c", number : 2, foo: "bar"},
+  ];
+  db.bulkSave(docs);
+
+  var check = function(startkey, offset) {
+    var opts = {startkey: startkey, descending: true};
+    T(db.view("test/offset", opts).offset == offset);
+  };
+
+  [
+      [["c", 2], 0],
+      [["c", 1], 1],
+      [["b", 5], 2],
+      [["b", 4], 3],
+      [["b", 3], 4],
+      [["b", 2], 5],
+      [["b", 1], 6],
+      [["a", 3], 7],
+      [["a", 2], 8],
+      [["a", 1], 9]
+  ].forEach(function(row){ check(row[0], row[1]);});
+
+  var runTest = function () {
+    var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
+    db.deleteDb();
+    db.createDb();
+
+    var designDoc = {
+      _id : "_design/test",
+      views : {
+        offset : {
+          map : "function(doc) { emit([doc.letter, doc.number], doc);}",
+        }
+      }
+    };
+    T(db.save(designDoc).ok);
+
+    var docs = [
+      {_id : "a1", letter : "a", number : 1, foo : "bar"},
+      {_id : "a2", letter : "a", number : 2, foo : "bar"},
+      {_id : "a3", letter : "a", number : 3, foo : "bar"},
+      {_id : "b1", letter : "b", number : 1, foo : "bar"},
+      {_id : "b2", letter : "b", number : 2, foo : "bar"},
+      {_id : "b3", letter : "b", number : 3, foo : "bar"},
+      {_id : "b4", letter : "b", number : 4, foo : "bar"},
+      {_id : "b5", letter : "b", number : 5, foo : "bar"},
+      {_id : "c1", letter : "c", number : 1, foo : "bar"},
+      {_id : "c2", letter : "c", number : 2, foo : "bar"}
+    ];
+    db.bulkSave(docs);
+
+    var res1 = db.view("test/offset", {
+      startkey: ["b",4], startkey_docid: "b4", endkey: ["b"],
+      limit: 2, descending: true, skip: 1
+    })
+
+    var res2 = db.view("test/offset", {startkey: ["c", 3]});
+    var res3 = db.view("test/offset", {
+        startkey: ["b", 6],
+        endkey: ["b", 7]
+    });
+
+    return res1.offset == 4 && res2.offset == docs.length && res3.offset == 8;
+
+  };
+
+  for(var i = 0; i < 15; i++) T(runTest());
+}
+

http://git-wip-us.apache.org/repos/asf/couchdb/blob/e2d9c9b1/test/javascript/tests/view_pagination.js
----------------------------------------------------------------------
diff --git a/test/javascript/tests/view_pagination.js b/test/javascript/tests/view_pagination.js
new file mode 100644
index 0000000..ed3a7ee
--- /dev/null
+++ b/test/javascript/tests/view_pagination.js
@@ -0,0 +1,147 @@
+// 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.view_pagination = function(debug) {
+    var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
+    db.deleteDb();
+    db.createDb();
+    if (debug) debugger;
+
+    var docs = makeDocs(0, 100);
+    db.bulkSave(docs);
+
+    var queryFun = function(doc) { emit(doc.integer, null); };
+    var i;
+
+    // page through the view ascending
+    for (i = 0; i < docs.length; i += 10) {
+      var queryResults = db.query(queryFun, null, {
+        startkey: i,
+        startkey_docid: i,
+        limit: 10
+      });
+      T(queryResults.rows.length == 10);
+      T(queryResults.total_rows == docs.length);
+      T(queryResults.offset == i);
+      var j;
+      for (j = 0; j < 10;j++) {
+        T(queryResults.rows[j].key == i + j);
+      }
+
+      // test aliases start_key and start_key_doc_id
+      queryResults = db.query(queryFun, null, {
+        start_key: i,
+        start_key_doc_id: i,
+        limit: 10
+      });
+      T(queryResults.rows.length == 10);
+      T(queryResults.total_rows == docs.length);
+      T(queryResults.offset == i);
+      for (j = 0; j < 10;j++) {
+        T(queryResults.rows[j].key == i + j);
+      }
+    }
+
+    // page through the view descending
+    for (i = docs.length - 1; i >= 0; i -= 10) {
+      var queryResults = db.query(queryFun, null, {
+        startkey: i,
+        startkey_docid: i,
+        descending: true,
+        limit: 10
+      });
+      T(queryResults.rows.length == 10);
+      T(queryResults.total_rows == docs.length);
+      T(queryResults.offset == docs.length - i - 1);
+      var j;
+      for (j = 0; j < 10; j++) {
+        T(queryResults.rows[j].key == i - j);
+      }
+    }
+
+    // ignore decending=false. CouchDB should just ignore that.
+    for (i = 0; i < docs.length; i += 10) {
+      var queryResults = db.query(queryFun, null, {
+        startkey: i,
+        startkey_docid: i,
+        descending: false,
+        limit: 10
+      });
+      T(queryResults.rows.length == 10);
+      T(queryResults.total_rows == docs.length);
+      T(queryResults.offset == i);
+      var j;
+      for (j = 0; j < 10;j++) {
+        T(queryResults.rows[j].key == i + j);
+      }
+    }
+
+    function testEndkeyDocId(queryResults) {
+      T(queryResults.rows.length == 35);
+      T(queryResults.total_rows == docs.length);
+      T(queryResults.offset == 1);
+      T(queryResults.rows[0].id == "1");
+      T(queryResults.rows[1].id == "10");
+      T(queryResults.rows[2].id == "11");
+      T(queryResults.rows[3].id == "12");
+      T(queryResults.rows[4].id == "13");
+      T(queryResults.rows[5].id == "14");
+      T(queryResults.rows[6].id == "15");
+      T(queryResults.rows[7].id == "16");
+      T(queryResults.rows[8].id == "17");
+      T(queryResults.rows[9].id == "18");
+      T(queryResults.rows[10].id == "19");
+      T(queryResults.rows[11].id == "2");
+      T(queryResults.rows[12].id == "20");
+      T(queryResults.rows[13].id == "21");
+      T(queryResults.rows[14].id == "22");
+      T(queryResults.rows[15].id == "23");
+      T(queryResults.rows[16].id == "24");
+      T(queryResults.rows[17].id == "25");
+      T(queryResults.rows[18].id == "26");
+      T(queryResults.rows[19].id == "27");
+      T(queryResults.rows[20].id == "28");
+      T(queryResults.rows[21].id == "29");
+      T(queryResults.rows[22].id == "3");
+      T(queryResults.rows[23].id == "30");
+      T(queryResults.rows[24].id == "31");
+      T(queryResults.rows[25].id == "32");
+      T(queryResults.rows[26].id == "33");
+      T(queryResults.rows[27].id == "34");
+      T(queryResults.rows[28].id == "35");
+      T(queryResults.rows[29].id == "36");
+      T(queryResults.rows[30].id == "37");
+      T(queryResults.rows[31].id == "38");
+      T(queryResults.rows[32].id == "39");
+      T(queryResults.rows[33].id == "4");
+      T(queryResults.rows[34].id == "40");
+    }
+
+    // test endkey_docid
+    var queryResults = db.query(function(doc) { emit(null, null); }, null, {
+      startkey: null,
+      startkey_docid: 1,
+      endkey: null,
+      endkey_docid: 40
+    });
+    testEndkeyDocId(queryResults);
+
+    // test aliases end_key_doc_id and end_key
+    queryResults = db.query(function(doc) { emit(null, null); }, null, {
+      start_key: null,
+      start_key_doc_id: 1,
+      end_key: null,
+      end_key_doc_id: 40
+    });
+    testEndkeyDocId(queryResults);
+
+  };

http://git-wip-us.apache.org/repos/asf/couchdb/blob/e2d9c9b1/test/javascript/tests/view_sandboxing.js
----------------------------------------------------------------------
diff --git a/test/javascript/tests/view_sandboxing.js b/test/javascript/tests/view_sandboxing.js
new file mode 100644
index 0000000..5c73c5a
--- /dev/null
+++ b/test/javascript/tests/view_sandboxing.js
@@ -0,0 +1,140 @@
+// 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.view_sandboxing = function(debug) {
+  var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
+  db.deleteDb();
+  db.createDb();
+  if (debug) debugger;
+
+  var doc = {integer: 1, string: "1", array: [1, 2, 3]};
+  T(db.save(doc).ok);
+/*
+  // make sure that attempting to change the document throws an error
+  var results = db.query(function(doc) {
+    doc.integer = 2;
+    emit(null, doc);
+  });
+  T(results.total_rows == 0);
+
+  var results = db.query(function(doc) {
+    doc.array[0] = 0;
+    emit(null, doc);
+  });
+  T(results.total_rows == 0);
+*/
+  // make sure that a view cannot invoke interpreter internals such as the
+  // garbage collector
+  var results = db.query(function(doc) {
+    gc();
+    emit(null, doc);
+  });
+  T(results.total_rows == 0);
+
+  // make sure that a view cannot access the map_funs array defined used by
+  // the view server
+  var results = db.query(function(doc) { map_funs.push(1); emit(null, doc); });
+  T(results.total_rows == 0);
+
+  // make sure that a view cannot access the map_results array defined used by
+  // the view server
+  var results = db.query(function(doc) { map_results.push(1); emit(null, doc); });
+  T(results.total_rows == 0);
+
+  // test for COUCHDB-925
+  // altering 'doc' variable in map function affects other map functions
+  var ddoc = {
+    _id: "_design/foobar",
+    language: "javascript",
+    views: {
+      view1: {
+        map:
+          (function(doc) {
+            if (doc.values) {
+              doc.values = [666];
+            }
+            if (doc.tags) {
+              doc.tags.push("qwerty");
+            }
+            if (doc.tokens) {
+              doc.tokens["c"] = 3;
+            }
+          }).toString()
+      },
+      view2: {
+        map:
+          (function(doc) {
+            if (doc.values) {
+              emit(doc._id, doc.values);
+            }
+            if (doc.tags) {
+              emit(doc._id, doc.tags);
+            }
+            if (doc.tokens) {
+              emit(doc._id, doc.tokens);
+            }
+          }).toString()
+      }
+    }
+  };
+  var doc1 = {
+    _id: "doc1",
+    values: [1, 2, 3]
+  };
+  var doc2 = {
+    _id: "doc2",
+    tags: ["foo", "bar"],
+    tokens: {a: 1, b: 2}
+  };
+
+  db.deleteDb();
+  db.createDb();
+  T(db.save(ddoc).ok);
+  T(db.save(doc1).ok);
+  T(db.save(doc2).ok);
+
+  var view1Results = db.view(
+    "foobar/view1", {bypass_cache: Math.round(Math.random() * 1000)});
+  var view2Results = db.view(
+    "foobar/view2", {bypass_cache: Math.round(Math.random() * 1000)});
+
+  TEquals(0, view1Results.rows.length, "view1 has 0 rows");
+  TEquals(3, view2Results.rows.length, "view2 has 3 rows");
+
+  TEquals(doc1._id, view2Results.rows[0].key);
+  TEquals(doc2._id, view2Results.rows[1].key);
+  TEquals(doc2._id, view2Results.rows[2].key);
+
+  // https://bugzilla.mozilla.org/show_bug.cgi?id=449657
+  TEquals(3, view2Results.rows[0].value.length,
+    "Warning: installed SpiderMonkey version doesn't allow sealing of arrays");
+  if (view2Results.rows[0].value.length === 3) {
+    TEquals(1, view2Results.rows[0].value[0]);
+    TEquals(2, view2Results.rows[0].value[1]);
+    TEquals(3, view2Results.rows[0].value[2]);
+  }
+
+  TEquals(1, view2Results.rows[1].value["a"]);
+  TEquals(2, view2Results.rows[1].value["b"]);
+  TEquals('undefined', typeof view2Results.rows[1].value["c"],
+    "doc2.tokens object was not sealed");
+
+  TEquals(2, view2Results.rows[2].value.length,
+    "Warning: installed SpiderMonkey version doesn't allow sealing of arrays");
+  if (view2Results.rows[2].value.length === 2) {
+    TEquals("foo", view2Results.rows[2].value[0]);
+    TEquals("bar", view2Results.rows[2].value[1]);
+  }
+
+  // cleanup
+  db.deleteDb();
+};

http://git-wip-us.apache.org/repos/asf/couchdb/blob/e2d9c9b1/test/javascript/tests/view_update_seq.js
----------------------------------------------------------------------
diff --git a/test/javascript/tests/view_update_seq.js b/test/javascript/tests/view_update_seq.js
new file mode 100644
index 0000000..df92b11
--- /dev/null
+++ b/test/javascript/tests/view_update_seq.js
@@ -0,0 +1,106 @@
+// 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.view_update_seq = function(debug) {
+  var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"true"});
+  db.deleteDb();
+  db.createDb();
+  if (debug) debugger;
+
+  T(db.info().update_seq == 0);
+
+  var resp = db.allDocs({update_seq:true});
+
+  T(resp.rows.length == 0);
+  T(resp.update_seq == 0);
+
+  var designDoc = {
+    _id:"_design/test",
+    language: "javascript",
+    views: {
+      all_docs: {
+        map: "function(doc) { emit(doc.integer, doc.string) }"
+      },
+      summate: {
+        map:"function (doc) { if (typeof doc.integer === 'number') { emit(doc.integer, doc.integer)}; }",
+        reduce:"function (keys, values) { return sum(values); };"
+      }
+    }
+  };
+  T(db.save(designDoc).ok);
+
+  T(db.info().update_seq == 1);
+
+  resp = db.allDocs({update_seq:true});
+
+  T(resp.rows.length == 1);
+  T(resp.update_seq == 1);
+
+  var docs = makeDocs(0, 100);
+  db.bulkSave(docs);
+
+  resp = db.allDocs({limit: 1});
+  T(resp.rows.length == 1);
+  T(!resp.update_seq, "all docs");
+
+  resp = db.allDocs({limit: 1, update_seq:true});
+  T(resp.rows.length == 1);
+  T(resp.update_seq == 101);
+
+  resp = db.view('test/all_docs', {limit: 1, update_seq:true});
+  T(resp.rows.length == 1);
+  T(resp.update_seq == 101);
+
+  resp = db.view('test/all_docs', {limit: 1, update_seq:false});
+  T(resp.rows.length == 1);
+  T(!resp.update_seq, "view");
+
+  resp = db.view('test/summate', {update_seq:true});
+  T(resp.rows.length == 1);
+  T(resp.update_seq == 101);
+
+  db.save({"id":"0", "integer": 1});
+  resp = db.view('test/all_docs', {limit: 1,stale: "ok", update_seq:true});
+  T(resp.rows.length == 1);
+  T(resp.update_seq == 101);
+
+  db.save({"id":"00", "integer": 2});
+  resp = db.view('test/all_docs',
+    {limit: 1, stale: "update_after", update_seq: true});
+  T(resp.rows.length == 1);
+  T(resp.update_seq == 101);
+
+  // wait 5 seconds for the next assertions to pass in very slow machines
+  var t0 = new Date(), t1;
+  do {
+    CouchDB.request("GET", "/");
+    t1 = new Date();
+  } while ((t1 - t0) < 5000);
+
+  resp = db.view('test/all_docs', {limit: 1, stale: "ok", update_seq: true});
+  T(resp.rows.length == 1);
+  T(resp.update_seq == 103);
+
+  resp = db.view('test/all_docs', {limit: 1, update_seq:true});
+  T(resp.rows.length == 1);
+  T(resp.update_seq == 103);
+
+  resp = db.view('test/all_docs',{update_seq:true},["0","1"]);
+  T(resp.update_seq == 103);
+
+  resp = db.view('test/all_docs',{update_seq:true},["0","1"]);
+  T(resp.update_seq == 103);
+
+  resp = db.view('test/summate',{group:true, update_seq:true},[0,1]);
+  TEquals(103, resp.update_seq);
+
+};


Mime
View raw message