couchdb-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From j..@apache.org
Subject [17/37] move JS tests into safety
Date Fri, 10 Oct 2014 19:12:31 GMT
http://git-wip-us.apache.org/repos/asf/couchdb/blob/3ba4fc0b/share/test/changes.js
----------------------------------------------------------------------
diff --git a/share/test/changes.js b/share/test/changes.js
new file mode 100644
index 0000000..d5a4236
--- /dev/null
+++ b/share/test/changes.js
@@ -0,0 +1,738 @@
+// 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.
+
+function jsonp(obj) {
+  T(jsonp_flag == 0);
+  T(obj.results.length == 1 && obj.last_seq == 1, "jsonp");
+  jsonp_flag = 1;
+}
+
+couchTests.changes = function(debug) {
+  var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"true"});
+  db.deleteDb();
+  db.createDb();
+  if (debug) debugger;
+
+  var req = CouchDB.request("GET", "/test_suite_db/_changes");
+  var resp = JSON.parse(req.responseText);
+
+  T(resp.results.length == 0 && resp.last_seq == 0, "empty db");
+  var docFoo = {_id:"foo", bar:1};
+  T(db.save(docFoo).ok);
+  T(db.ensureFullCommit().ok);
+  T(db.open(docFoo._id)._id == docFoo._id);
+
+  req = CouchDB.request("GET", "/test_suite_db/_changes");
+  var resp = JSON.parse(req.responseText);
+
+  T(resp.last_seq == 1);
+  T(resp.results.length == 1, "one doc db");
+  T(resp.results[0].changes[0].rev == docFoo._rev);
+
+  // test with callback
+
+  run_on_modified_server(
+    [{section: "httpd",
+      key: "allow_jsonp",
+      value: "true"}],
+  function() {
+    var xhr = CouchDB.request("GET", "/test_suite_db/_changes?callback=jsonp");
+    T(xhr.status == 200);
+    jsonp_flag = 0;
+    eval(xhr.responseText);
+    T(jsonp_flag == 1);
+  });
+
+  req = CouchDB.request("GET", "/test_suite_db/_changes?feed=continuous&timeout=10");
+  var lines = req.responseText.split("\n");
+  T(JSON.parse(lines[0]).changes[0].rev == docFoo._rev);
+  T(JSON.parse(lines[1]).last_seq == 1);
+
+  var xhr;
+
+  try {
+    xhr = CouchDB.newXhr();
+  } catch (err) {
+  }
+
+  // poor man's browser detection
+  var is_safari = false;
+  if(typeof(navigator) == "undefined") {
+    is_safari = true; // For CouchHTTP based runners
+  } else if(navigator.userAgent.match(/AppleWebKit/)) {
+    is_safari = true;
+  };
+  if (!is_safari && xhr) {
+    // Only test the continuous stuff if we have a real XHR object
+    // with real async support.
+
+    // WebKit (last checked on nightly #47686) does fail on processing
+    // the async-request properly while javascript is executed.
+
+    xhr.open("GET", CouchDB.proxyUrl("/test_suite_db/_changes?feed=continuous&timeout=500"), true);
+    xhr.send("");
+
+    var docBar = {_id:"bar", bar:1};
+    db.save(docBar);
+
+    var lines, change1, change2;
+    waitForSuccess(function() {
+      lines = xhr.responseText.split("\n");
+      change1 = JSON.parse(lines[0]);
+      change2 = JSON.parse(lines[1]);
+      if (change2.seq != 2) {
+          throw "bad seq, try again";
+      }
+      return true;
+    }, "bar-only");
+
+    T(change1.seq == 1);
+    T(change1.id == "foo");
+
+    T(change2.seq == 2);
+    T(change2.id == "bar");
+    T(change2.changes[0].rev == docBar._rev);
+
+
+    var docBaz = {_id:"baz", baz:1};
+    db.save(docBaz);
+
+    var change3;
+    waitForSuccess(function() {
+      lines = xhr.responseText.split("\n");
+      change3 = JSON.parse(lines[2]);
+      if (change3.seq != 3) {
+        throw "bad seq, try again";
+      }
+      return true;
+    });
+
+    T(change3.seq == 3);
+    T(change3.id == "baz");
+    T(change3.changes[0].rev == docBaz._rev);
+
+
+    xhr = CouchDB.newXhr();
+
+    //verify the heartbeat newlines are sent
+    xhr.open("GET", CouchDB.proxyUrl("/test_suite_db/_changes?feed=continuous&heartbeat=10&timeout=500"), true);
+    xhr.send("");
+
+    var str;
+    waitForSuccess(function() {
+      str = xhr.responseText;
+      if (str.charAt(str.length - 1) != "\n" || str.charAt(str.length - 2) != "\n") {
+        throw("keep waiting");
+      }
+      return true;
+    }, "heartbeat");
+
+    T(str.charAt(str.length - 1) == "\n");
+    T(str.charAt(str.length - 2) == "\n");
+
+    // otherwise we'll continue to receive heartbeats forever
+    xhr.abort();
+
+    // test Server Sent Event (eventsource)
+    if (!!window.EventSource) {
+      var source = new EventSource(
+              "/test_suite_db/_changes?feed=eventsource");
+      var results = [];
+      var sourceListener = function(e) {
+        var data = JSON.parse(e.data);
+        results.push(data);
+      };
+
+      source.addEventListener('message', sourceListener , false);
+
+      waitForSuccess(function() {
+        if (results.length != 3) {
+          throw "bad seq, try again";
+        }
+        return true;
+      });
+
+      source.removeEventListener('message', sourceListener, false);
+
+      T(results[0].seq == 1);
+      T(results[0].id == "foo");
+
+      T(results[1].seq == 2);
+      T(results[1].id == "bar");
+      T(results[1].changes[0].rev == docBar._rev);
+    }
+
+    // test that we receive EventSource heartbeat events
+    if (!!window.EventSource) {
+      var source = new EventSource(
+              "/test_suite_db/_changes?feed=eventsource&heartbeat=10");
+
+      var count_heartbeats = 0;
+      source.addEventListener('heartbeat', function () { count_heartbeats = count_heartbeats + 1; } , false);
+
+      waitForSuccess(function() {
+        if (count_heartbeats < 3) {
+          throw "keep waiting";
+        }
+        return true;
+      }, "eventsource-heartbeat");
+
+      T(count_heartbeats >= 3);
+      source.close();
+    }
+
+    // test longpolling
+    xhr = CouchDB.newXhr();
+
+    xhr.open("GET", CouchDB.proxyUrl("/test_suite_db/_changes?feed=longpoll"), true);
+    xhr.send("");
+
+    waitForSuccess(function() {
+      lines = xhr.responseText.split("\n");
+      if (lines[5] != '"last_seq":3}') {
+        throw("still waiting");
+      }
+      return true;
+    }, "last_seq");
+
+    xhr = CouchDB.newXhr();
+
+    xhr.open("GET", CouchDB.proxyUrl("/test_suite_db/_changes?feed=longpoll&since=3"), true);
+    xhr.send("");
+
+    var docBarz = {_id:"barz", bar:1};
+    db.save(docBarz);
+
+    var parse_changes_line = function(line) {
+      if (line.charAt(line.length-1) == ",") {
+        var linetrimmed = line.substring(0, line.length-1);
+      } else {
+        var linetrimmed = line;
+      }
+      return JSON.parse(linetrimmed);
+    };
+
+    waitForSuccess(function() {
+      lines = xhr.responseText.split("\n");
+      if (lines[3] != '"last_seq":4}') {
+        throw("still waiting");
+      }
+      return true;
+    }, "change_lines");
+
+    var change = parse_changes_line(lines[1]);
+    T(change.seq == 4);
+    T(change.id == "barz");
+    T(change.changes[0].rev == docBarz._rev);
+    T(lines[3]=='"last_seq":4}');
+
+
+    // test since=now
+    xhr = CouchDB.newXhr();
+
+    xhr.open("GET", "/test_suite_db/_changes?feed=longpoll&since=now", true);
+    xhr.send("");
+
+    var docBarz = {_id:"barzzzz", bar:1};
+    db.save(docBarz);
+
+    var parse_changes_line = function(line) {
+      if (line.charAt(line.length-1) == ",") {
+        var linetrimmed = line.substring(0, line.length-1);
+      } else {
+        var linetrimmed = line;
+      }
+      return JSON.parse(linetrimmed);
+    };
+
+    waitForSuccess(function() {
+      lines = xhr.responseText.split("\n");
+      if (lines[3] != '"last_seq":5}') {
+        throw("still waiting");
+      }
+      return true;
+    }, "change_lines");
+
+    var change = parse_changes_line(lines[1]);
+    T(change.seq == 5);
+    T(change.id == "barzzzz");
+    T(change.changes[0].rev == docBarz._rev);
+    T(lines[3]=='"last_seq":5}');
+
+
+  }
+
+  // test the filtered changes
+  var ddoc = {
+    _id : "_design/changes_filter",
+    "filters" : {
+      "bop" : "function(doc, req) { return (doc.bop);}",
+      "dynamic" : stringFun(function(doc, req) {
+        var field = req.query.field;
+        return doc[field];
+      }),
+      "userCtx" : stringFun(function(doc, req) {
+        return doc.user && (doc.user == req.userCtx.name);
+      }),
+      "conflicted" : "function(doc, req) { return (doc._conflicts);}"
+    },
+    options : {
+      local_seq : true
+    },
+    views : {
+      local_seq : {
+        map : "function(doc) {emit(doc._local_seq, null)}"
+      },
+      blah: {
+        map : 'function(doc) {' +
+              '  if (doc._id == "blah") {' +
+              '    emit(null, null);' +
+              '  }' +
+              '}'
+      }
+    }
+  };
+
+  db.save(ddoc);
+
+  var req = CouchDB.request("GET", "/test_suite_db/_changes?filter=changes_filter/bop");
+  var resp = JSON.parse(req.responseText);
+  T(resp.results.length == 0);
+
+  db.save({"bop" : "foom"});
+  db.save({"bop" : false});
+
+  var req = CouchDB.request("GET", "/test_suite_db/_changes?filter=changes_filter/bop");
+  var resp = JSON.parse(req.responseText);
+  T(resp.results.length == 1, "filtered/bop");
+
+  req = CouchDB.request("GET", "/test_suite_db/_changes?filter=changes_filter/dynamic&field=woox");
+  resp = JSON.parse(req.responseText);
+  T(resp.results.length == 0);
+
+  req = CouchDB.request("GET", "/test_suite_db/_changes?filter=changes_filter/dynamic&field=bop");
+  resp = JSON.parse(req.responseText);
+  T(resp.results.length == 1, "changes_filter/dynamic&field=bop");
+
+  if (!is_safari && xhr) { // full test requires parallel connections
+    // filter with longpoll
+    // longpoll filters full history when run without a since seq
+    xhr = CouchDB.newXhr();
+    xhr.open("GET", CouchDB.proxyUrl("/test_suite_db/_changes?feed=longpoll&filter=changes_filter/bop"), false);
+    xhr.send("");
+    var resp = JSON.parse(xhr.responseText);
+    T(resp.last_seq == 8);
+    // longpoll waits until a matching change before returning
+    xhr = CouchDB.newXhr();
+    xhr.open("GET", CouchDB.proxyUrl("/test_suite_db/_changes?feed=longpoll&since=7&filter=changes_filter/bop"), true);
+    xhr.send("");
+    db.save({"_id":"falsy", "bop" : ""}); // empty string is falsy
+    db.save({"_id":"bingo","bop" : "bingo"});
+
+    waitForSuccess(function() {
+      resp = JSON.parse(xhr.responseText);
+      return true;
+    }, "longpoll-since");
+
+    T(resp.last_seq == 10);
+    T(resp.results && resp.results.length > 0 && resp.results[0]["id"] == "bingo", "filter the correct update");
+    xhr.abort();
+
+    var timeout = 500;
+    var last_seq = 11;
+    while (true) {
+
+      // filter with continuous
+      xhr = CouchDB.newXhr();
+      xhr.open("GET", CouchDB.proxyUrl("/test_suite_db/_changes?feed=continuous&filter=changes_filter/bop&timeout="+timeout), true);
+      xhr.send("");
+
+      db.save({"_id":"rusty", "bop" : "plankton"});
+      T(xhr.readyState != 4, "test client too slow");
+      var rusty = db.open("rusty", {cache_bust : new Date()});
+      T(rusty._id == "rusty");
+
+      waitForSuccess(function() { // throws an error after 5 seconds
+        if (xhr.readyState != 4) {
+          throw("still waiting");
+        }
+        return true;
+      }, "continuous-rusty");
+      lines = xhr.responseText.split("\n");
+      var good = false;
+      try {
+        JSON.parse(lines[3]);
+        good = true;
+      } catch(e) {
+      }
+      if (good) {
+        T(JSON.parse(lines[1]).id == "bingo", lines[1]);
+        T(JSON.parse(lines[2]).id == "rusty", lines[2]);
+        T(JSON.parse(lines[3]).last_seq == last_seq, lines[3]);
+        break;
+      } else {
+        xhr.abort();
+        db.deleteDoc(rusty);
+        timeout = timeout * 2;
+        last_seq = last_seq + 2;
+      }
+    }
+  }
+  // error conditions
+
+  // non-existing design doc
+  var req = CouchDB.request("GET",
+    "/test_suite_db/_changes?filter=nothingtosee/bop");
+  TEquals(404, req.status, "should return 404 for non existant design doc");
+
+  // non-existing filter
+  var req = CouchDB.request("GET",
+    "/test_suite_db/_changes?filter=changes_filter/movealong");
+  TEquals(404, req.status, "should return 404 for non existant filter fun");
+
+  // both
+  var req = CouchDB.request("GET",
+    "/test_suite_db/_changes?filter=nothingtosee/movealong");
+  TEquals(404, req.status,
+    "should return 404 for non existant design doc and filter fun");
+
+  // changes get all_docs style with deleted docs
+  var doc = {a:1};
+  db.save(doc);
+  db.deleteDoc(doc);
+  var req = CouchDB.request("GET",
+    "/test_suite_db/_changes?filter=changes_filter/bop&style=all_docs");
+  var resp = JSON.parse(req.responseText);
+  var expect = (!is_safari && xhr) ? 3: 1;
+  TEquals(expect, resp.results.length, "should return matching rows");
+
+  // test filter on view function (map)
+  //
+  T(db.save({"_id":"blah", "bop" : "plankton"}).ok);
+  var req = CouchDB.request("GET", "/test_suite_db/_changes?filter=_view&view=changes_filter/blah");
+  var resp = JSON.parse(req.responseText);
+  T(resp.results.length === 1);
+  T(resp.results[0].id === "blah");
+
+
+  // test for userCtx
+  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 authOpts = {"headers":{"WWW-Authenticate": "X-Couch-Test-Auth Chris Anderson:mp3"}};
+
+      var req = CouchDB.request("GET", "/_session", authOpts);
+      var resp = JSON.parse(req.responseText);
+
+      T(db.save({"user" : "Noah Slater"}).ok);
+      var req = CouchDB.request("GET", "/test_suite_db/_changes?filter=changes_filter/userCtx", authOpts);
+      var resp = JSON.parse(req.responseText);
+      T(resp.results.length == 0);
+
+      var docResp = db.save({"user" : "Chris Anderson"});
+      T(docResp.ok);
+      T(db.ensureFullCommit().ok);
+      req = CouchDB.request("GET", "/test_suite_db/_changes?filter=changes_filter/userCtx", authOpts);
+      resp = JSON.parse(req.responseText);
+      T(resp.results.length == 1, "userCtx");
+      T(resp.results[0].id == docResp.id);
+    }
+  );
+
+  req = CouchDB.request("GET", "/test_suite_db/_changes?limit=1");
+  resp = JSON.parse(req.responseText);
+  TEquals(1, resp.results.length);
+
+  //filter includes _conflicts
+  var id = db.save({'food' : 'pizza'}).id;
+  db.bulkSave([{_id: id, 'food' : 'pasta'}], {all_or_nothing:true});
+
+  req = CouchDB.request("GET", "/test_suite_db/_changes?filter=changes_filter/conflicted");
+  resp = JSON.parse(req.responseText);
+  T(resp.results.length == 1, "filter=changes_filter/conflicted");
+
+  // test with erlang filter function
+  run_on_modified_server([{
+    section: "native_query_servers",
+    key: "erlang",
+    value: "{couch_native_process, start_link, []}"
+  }], function() {
+    var erl_ddoc = {
+      _id: "_design/erlang",
+      language: "erlang",
+      filters: {
+        foo:
+          'fun({Doc}, Req) -> ' +
+          '  case couch_util:get_value(<<"value">>, Doc) of' +
+          '  undefined -> false;' +
+          '  Value -> (Value rem 2) =:= 0;' +
+          '  _ -> false' +
+          '  end ' +
+          'end.'
+      }
+    };
+
+    db.deleteDb();
+    db.createDb();
+    T(db.save(erl_ddoc).ok);
+
+    var req = CouchDB.request("GET", "/test_suite_db/_changes?filter=erlang/foo");
+    var resp = JSON.parse(req.responseText);
+    T(resp.results.length === 0);
+
+    T(db.save({_id: "doc1", value : 1}).ok);
+    T(db.save({_id: "doc2", value : 2}).ok);
+    T(db.save({_id: "doc3", value : 3}).ok);
+    T(db.save({_id: "doc4", value : 4}).ok);
+
+    var req = CouchDB.request("GET", "/test_suite_db/_changes?filter=erlang/foo");
+    var resp = JSON.parse(req.responseText);
+    T(resp.results.length === 2);
+    T(resp.results[0].id === "doc2");
+    T(resp.results[1].id === "doc4");
+
+    // test filtering on docids
+    //
+
+    var options = {
+        headers: {"Content-Type": "application/json"},
+        body: JSON.stringify({"doc_ids": ["something", "anotherthing", "andmore"]})
+    };
+
+    var req = CouchDB.request("POST", "/test_suite_db/_changes?filter=_doc_ids", options);
+    var resp = JSON.parse(req.responseText);
+    T(resp.results.length === 0);
+
+    T(db.save({"_id":"something", "bop" : "plankton"}).ok);
+    var req = CouchDB.request("POST", "/test_suite_db/_changes?filter=_doc_ids", options);
+    var resp = JSON.parse(req.responseText);
+    T(resp.results.length === 1);
+    T(resp.results[0].id === "something");
+
+    T(db.save({"_id":"anotherthing", "bop" : "plankton"}).ok);
+    var req = CouchDB.request("POST", "/test_suite_db/_changes?filter=_doc_ids", options);
+    var resp = JSON.parse(req.responseText);
+    T(resp.results.length === 2);
+    T(resp.results[0].id === "something");
+    T(resp.results[1].id === "anotherthing");
+
+    var docids = JSON.stringify(["something", "anotherthing", "andmore"]),
+        req = CouchDB.request("GET", "/test_suite_db/_changes?filter=_doc_ids&doc_ids="+docids, options);
+    var resp = JSON.parse(req.responseText);
+    T(resp.results.length === 2);
+    T(resp.results[0].id === "something");
+    T(resp.results[1].id === "anotherthing");
+
+    var req = CouchDB.request("GET", "/test_suite_db/_changes?filter=_design");
+    var resp = JSON.parse(req.responseText);
+    T(resp.results.length === 1);
+    T(resp.results[0].id === "_design/erlang");
+
+
+    if (!is_safari && xhr) {
+        // filter docids with continuous
+        xhr = CouchDB.newXhr();
+        xhr.open("POST", CouchDB.proxyUrl("/test_suite_db/_changes?feed=continuous&timeout=500&since=7&filter=_doc_ids"), true);
+        xhr.setRequestHeader("Content-Type", "application/json");
+
+        xhr.send(options.body);
+
+        T(db.save({"_id":"andmore", "bop" : "plankton"}).ok);
+
+        waitForSuccess(function() {
+            if (xhr.readyState != 4) {
+              throw("still waiting");
+            }
+            return true;
+        }, "andmore-only");
+
+        var line = JSON.parse(xhr.responseText.split("\n")[0]);
+        T(line.seq == 8);
+        T(line.id == "andmore");
+    }
+  });
+
+  // COUCHDB-1037 - empty result for ?limit=1&filter=foo/bar in some cases
+  T(db.deleteDb());
+  T(db.createDb());
+
+  ddoc = {
+    _id: "_design/testdocs",
+    filters: {
+      testdocsonly: (function(doc, req) {
+        return (typeof doc.integer === "number");
+      }).toString()
+    }
+  };
+  T(db.save(ddoc));
+
+  ddoc = {
+    _id: "_design/foobar",
+    foo: "bar"
+  };
+  T(db.save(ddoc));
+
+  db.bulkSave(makeDocs(0, 5));
+
+  req = CouchDB.request("GET", "/" + db.name + "/_changes");
+  resp = JSON.parse(req.responseText);
+  TEquals(7, resp.last_seq);
+  TEquals(7, resp.results.length);
+
+  req = CouchDB.request(
+    "GET", "/"+ db.name + "/_changes?limit=1&filter=testdocs/testdocsonly");
+  resp = JSON.parse(req.responseText);
+  TEquals(3, resp.last_seq);
+  TEquals(1, resp.results.length);
+  TEquals("0", resp.results[0].id);
+
+  req = CouchDB.request(
+    "GET", "/" + db.name + "/_changes?limit=2&filter=testdocs/testdocsonly");
+  resp = JSON.parse(req.responseText);
+  TEquals(4, resp.last_seq);
+  TEquals(2, resp.results.length);
+  TEquals("0", resp.results[0].id);
+  TEquals("1", resp.results[1].id);
+
+  TEquals(0, CouchDB.requestStats(['couchdb', 'httpd', 'clients_requesting_changes'], true).value);
+  CouchDB.request("GET", "/" + db.name + "/_changes");
+  TEquals(0, CouchDB.requestStats(['couchdb', 'httpd', 'clients_requesting_changes'], true).value);
+
+  // COUCHDB-1256
+  T(db.deleteDb());
+  T(db.createDb());
+
+  T(db.save({"_id":"foo", "a" : 123}).ok);
+  T(db.save({"_id":"bar", "a" : 456}).ok);
+
+  options = {
+      headers: {"Content-Type": "application/json"},
+      body: JSON.stringify({"_rev":"1-cc609831f0ca66e8cd3d4c1e0d98108a", "a":456})
+  };
+  req = CouchDB.request("PUT", "/" + db.name + "/foo?new_edits=false", options);
+
+  req = CouchDB.request("GET", "/" + db.name + "/_changes?style=all_docs");
+  resp = JSON.parse(req.responseText);
+
+  TEquals(3, resp.last_seq);
+  TEquals(2, resp.results.length);
+
+  req = CouchDB.request("GET", "/" + db.name + "/_changes?style=all_docs&since=2");
+  resp = JSON.parse(req.responseText);
+
+  TEquals(3, resp.last_seq);
+  TEquals(1, resp.results.length);
+  TEquals(2, resp.results[0].changes.length);
+
+  // COUCHDB-1852
+  T(db.deleteDb());
+  T(db.createDb());
+
+  // create 4 documents... this assumes the update sequnce will start from 0 and get to 4
+  db.save({"bop" : "foom"});
+  db.save({"bop" : "foom"});
+  db.save({"bop" : "foom"});
+  db.save({"bop" : "foom"});
+
+  // simulate an EventSource request with a Last-Event-ID header
+  req = CouchDB.request("GET", "/test_suite_db/_changes?feed=eventsource&timeout=0&since=0",
+        {"headers": {"Accept": "text/event-stream", "Last-Event-ID": "2"}});
+
+  // "parse" the eventsource response and collect only the "id: ..." lines
+  var changes = req.responseText.split('\n')
+     .map(function (el) {
+        return el.split(":").map(function (el) { return el.trim()});
+     })
+     .filter(function (el) { return (el[0] === "id"); })
+
+  // make sure we only got 2 changes, and they are update_seq=3 and update_seq=4
+  T(changes.length === 2);
+  T(changes[0][1] === "3");
+  T(changes[1][1] === "4");
+
+  // COUCHDB-1923
+  T(db.deleteDb());
+  T(db.createDb());
+
+  var attachmentData = "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ=";
+
+  db.bulkSave(makeDocs(20, 30, {
+    _attachments:{
+      "foo.txt": {
+        content_type:"text/plain",
+        data: attachmentData
+      },
+      "bar.txt": {
+        content_type:"text/plain",
+        data: attachmentData
+      }
+    }
+  }));
+
+  var mapFunction = function(doc) {
+    var count = 0;
+
+    for(var idx in doc._attachments) {
+      count = count + 1;
+    }
+
+    emit(parseInt(doc._id), count);
+  };
+
+  var req = CouchDB.request("GET", "/test_suite_db/_changes?include_docs=true");
+  var resp = JSON.parse(req.responseText);
+
+  T(resp.results.length == 10);
+  T(resp.results[0].doc._attachments['foo.txt'].stub === true);
+  T(resp.results[0].doc._attachments['foo.txt'].data === undefined);
+  T(resp.results[0].doc._attachments['foo.txt'].encoding === undefined);
+  T(resp.results[0].doc._attachments['foo.txt'].encoded_length === undefined);
+  T(resp.results[0].doc._attachments['bar.txt'].stub === true);
+  T(resp.results[0].doc._attachments['bar.txt'].data === undefined);
+  T(resp.results[0].doc._attachments['bar.txt'].encoding === undefined);
+  T(resp.results[0].doc._attachments['bar.txt'].encoded_length === undefined);
+
+  var req = CouchDB.request("GET", "/test_suite_db/_changes?include_docs=true&attachments=true");
+  var resp = JSON.parse(req.responseText);
+
+  T(resp.results.length == 10);
+  T(resp.results[0].doc._attachments['foo.txt'].stub === undefined);
+  T(resp.results[0].doc._attachments['foo.txt'].data === attachmentData);
+  T(resp.results[0].doc._attachments['foo.txt'].encoding === undefined);
+  T(resp.results[0].doc._attachments['foo.txt'].encoded_length === undefined);
+  T(resp.results[0].doc._attachments['bar.txt'].stub === undefined);
+  T(resp.results[0].doc._attachments['bar.txt'].data == attachmentData);
+  T(resp.results[0].doc._attachments['bar.txt'].encoding === undefined);
+  T(resp.results[0].doc._attachments['bar.txt'].encoded_length === undefined);
+
+  var req = CouchDB.request("GET", "/test_suite_db/_changes?include_docs=true&att_encoding_info=true");
+  var resp = JSON.parse(req.responseText);
+
+  T(resp.results.length == 10);
+  T(resp.results[0].doc._attachments['foo.txt'].stub === true);
+  T(resp.results[0].doc._attachments['foo.txt'].data === undefined);
+  T(resp.results[0].doc._attachments['foo.txt'].encoding === "gzip");
+  T(resp.results[0].doc._attachments['foo.txt'].encoded_length === 47);
+  T(resp.results[0].doc._attachments['bar.txt'].stub === true);
+  T(resp.results[0].doc._attachments['bar.txt'].data === undefined);
+  T(resp.results[0].doc._attachments['bar.txt'].encoding === "gzip");
+  T(resp.results[0].doc._attachments['bar.txt'].encoded_length === 47);
+
+  // cleanup
+  db.deleteDb();
+};

http://git-wip-us.apache.org/repos/asf/couchdb/blob/3ba4fc0b/share/test/coffee.js
----------------------------------------------------------------------
diff --git a/share/test/coffee.js b/share/test/coffee.js
new file mode 100644
index 0000000..9306124
--- /dev/null
+++ b/share/test/coffee.js
@@ -0,0 +1,67 @@
+// 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.
+
+// test basic coffeescript functionality
+couchTests.coffee = function(debug) {
+  var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
+  db.deleteDb();
+  db.createDb();
+  if (debug) debugger;
+
+  var ddoc = {
+    _id: "_design/coffee",
+    language: "coffeescript",
+    views: {
+      myview: {
+        map: '(doc) -> if doc.foo\n  emit(doc.foo, 1)',
+        reduce: '(keys, values, rereduce) ->\n  sum = 0\n  for x in values\n    sum = sum + x\n  sum'
+      }
+    },
+    shows: {
+      myshow: '(doc) ->\n  "Foo #{doc.foo}"'
+    },
+    lists: {
+      mylist: '(head, req) ->\n  while row = getRow()\n    send("Foo #{row.value}")\n  return "Foo"'
+    },
+    filters: {
+      filter: "(doc) ->\n  doc.foo"
+    }
+  };
+
+  db.save(ddoc);
+
+  var docs = [
+    {_id:"a", foo: 100},
+    {foo:1},
+    {foo:1},
+    {foo:2},
+    {foo:2},
+    {bar:1},
+    {bar:1},
+    {bar:2},
+    {bar:2}
+  ];
+
+  db.bulkSave(docs);
+
+  var res = db.view("coffee/myview");
+  TEquals(5, res.rows[0].value, "should sum up values");
+
+  var res = CouchDB.request("GET", "/" + db.name + "/_design/coffee/_show/myshow/a");
+  TEquals("Foo 100", res.responseText, "should show 100");
+
+  var res = CouchDB.request("GET", "/" + db.name + "/_design/coffee/_list/mylist/myview");
+  TEquals("Foo 5Foo", res.responseText, "should list");
+
+  var changes = db.changes({filter: "coffee/filter"});
+  TEquals(5, changes.results.length, "should have changes");
+};

http://git-wip-us.apache.org/repos/asf/couchdb/blob/3ba4fc0b/share/test/compact.js
----------------------------------------------------------------------
diff --git a/share/test/compact.js b/share/test/compact.js
new file mode 100644
index 0000000..68c83b3
--- /dev/null
+++ b/share/test/compact.js
@@ -0,0 +1,65 @@
+// 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.compact = 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, 20);
+  db.bulkSave(docs);
+
+  var binAttDoc = {
+    _id: "bin_doc",
+    _attachments:{
+      "foo.txt": {
+        content_type:"text/plain",
+        data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
+      }
+    }
+  };
+
+  T(db.save(binAttDoc).ok);
+
+  var originalsize = db.info().disk_size;
+  var originaldatasize = db.info().data_size;
+  var start_time = db.info().instance_start_time;
+
+  TEquals("number", typeof originaldatasize, "data_size is a number");
+  T(originaldatasize < originalsize, "data size is < then db file size");
+
+  for(var i in docs) {
+      db.deleteDoc(docs[i]);
+  }
+  T(db.ensureFullCommit().ok);
+  var deletesize = db.info().disk_size;
+  T(deletesize > originalsize);
+  T(db.setDbProperty("_revs_limit", 666).ok);
+
+  T(db.compact().ok);
+  T(db.last_req.status == 202);
+  // compaction isn't instantaneous, loop until done
+  while (db.info().compact_running) {};
+  T(db.info().instance_start_time == start_time);
+  T(db.getDbProperty("_revs_limit") === 666);
+
+  T(db.ensureFullCommit().ok);
+  restartServer();
+  var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc/foo.txt");
+  T(xhr.responseText == "This is a base64 encoded text");
+  T(xhr.getResponseHeader("Content-Type") == "text/plain");
+  T(db.info().doc_count == 1);
+  T(db.info().disk_size < deletesize);
+  TEquals("number", typeof db.info().data_size, "data_size is a number");
+  T(db.info().data_size < db.info().disk_size, "data size is < then db file size");
+
+};

http://git-wip-us.apache.org/repos/asf/couchdb/blob/3ba4fc0b/share/test/config.js
----------------------------------------------------------------------
diff --git a/share/test/config.js b/share/test/config.js
new file mode 100644
index 0000000..37b339b
--- /dev/null
+++ b/share/test/config.js
@@ -0,0 +1,211 @@
+// 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.config = function(debug) {
+  var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
+  db.deleteDb();
+  db.createDb();
+  if (debug) debugger;
+
+  // test that /_config returns all the settings
+  var xhr = CouchDB.request("GET", "/_config");
+  var config = JSON.parse(xhr.responseText);
+
+  /*
+    if we run on standard ports, we can't extract
+    the number from the URL. Instead we try to guess
+    from the protocol what port we are running on.
+    If we can't guess, we don't test for the port.
+    Overengineering FTW.
+  */
+  var server_port = CouchDB.host.split(':');
+  if(server_port.length == 1 && CouchDB.inBrowser) {
+    if(CouchDB.protocol == "http://") {
+      port = "80";
+    }
+    if(CouchDB.protocol == "https://") {
+      port = "443";
+    }
+  } else {
+    port = server_port.pop();
+  }
+
+  if(CouchDB.protocol == "http://") {
+    config_port = config.httpd.port;
+  }
+  if(CouchDB.protocol == "https://") {
+    config_port = config.ssl.port;
+  }
+
+  if(port && config_port != "0") {
+    TEquals(config_port, port, "ports should match");
+  }
+
+  T(config.couchdb.database_dir);
+  T(config.daemons.httpd);
+  T(config.httpd_global_handlers._config);
+  // T(config.log.level);
+  T(config.query_servers.javascript);
+
+  // test that settings can be altered, and that an undefined whitelist allows any change
+  TEquals(undefined, config.httpd.config_whitelist, "Default whitelist is empty");
+  xhr = CouchDB.request("PUT", "/_config/test/foo",{
+    body : JSON.stringify("bar"),
+    headers: {"X-Couch-Persist": "false"}
+  });
+  T(xhr.status == 200);
+  xhr = CouchDB.request("GET", "/_config/test");
+  config = JSON.parse(xhr.responseText);
+  T(config.foo == "bar");
+
+  // you can get a single key
+  xhr = CouchDB.request("GET", "/_config/test/foo");
+  config = JSON.parse(xhr.responseText);
+  T(config == "bar");
+
+  // Server-side password hashing, and raw updates disabling that.
+  var password_plain = 's3cret';
+  var password_hashed = null;
+
+  xhr = CouchDB.request("PUT", "/_config/admins/administrator",{
+    body : JSON.stringify(password_plain),
+    headers: {"X-Couch-Persist": "false"}
+  });
+  TEquals(200, xhr.status, "Create an admin in the config");
+
+  T(CouchDB.login("administrator", password_plain).ok);
+
+  xhr = CouchDB.request("GET", "/_config/admins/administrator");
+  password_hashed = JSON.parse(xhr.responseText);
+  T(password_hashed.match(/^-pbkdf2-/) || password_hashed.match(/^-hashed-/),
+    "Admin password is hashed");
+
+  xhr = CouchDB.request("PUT", "/_config/admins/administrator?raw=nothanks",{
+    body : JSON.stringify(password_hashed),
+    headers: {"X-Couch-Persist": "false"}
+  });
+  TEquals(400, xhr.status, "CouchDB rejects an invalid 'raw' option");
+
+  xhr = CouchDB.request("PUT", "/_config/admins/administrator?raw=true",{
+    body : JSON.stringify(password_hashed),
+    headers: {"X-Couch-Persist": "false"}
+  });
+  TEquals(200, xhr.status, "Set an raw, pre-hashed admin password");
+
+  xhr = CouchDB.request("PUT", "/_config/admins/administrator?raw=false",{
+    body : JSON.stringify(password_hashed),
+    headers: {"X-Couch-Persist": "false"}
+  });
+  TEquals(200, xhr.status, "Set an admin password with raw=false");
+
+  // The password is literally the string "-pbkdf2-abcd...".
+  T(CouchDB.login("administrator", password_hashed).ok);
+
+  xhr = CouchDB.request("GET", "/_config/admins/administrator");
+  T(password_hashed != JSON.parse(xhr.responseText),
+    "Hashed password was not stored as a raw string");
+
+  xhr = CouchDB.request("DELETE", "/_config/admins/administrator",{
+    headers: {"X-Couch-Persist": "false"}
+  });
+  TEquals(200, xhr.status, "Delete an admin from the config");
+  T(CouchDB.logout().ok);
+
+  // Non-term whitelist values allow further modification of the whitelist.
+  xhr = CouchDB.request("PUT", "/_config/httpd/config_whitelist",{
+    body : JSON.stringify("!This is an invalid Erlang term!"),
+    headers: {"X-Couch-Persist": "false"}
+  });
+  TEquals(200, xhr.status, "Set config whitelist to an invalid Erlang term");
+  xhr = CouchDB.request("DELETE", "/_config/httpd/config_whitelist",{
+    headers: {"X-Couch-Persist": "false"}
+  });
+  TEquals(200, xhr.status, "Modify whitelist despite it being invalid syntax");
+
+  // Non-list whitelist values allow further modification of the whitelist.
+  xhr = CouchDB.request("PUT", "/_config/httpd/config_whitelist",{
+    body : JSON.stringify("{[yes, a_valid_erlang_term, but_unfortunately, not_a_list]}"),
+    headers: {"X-Couch-Persist": "false"}
+  });
+  TEquals(200, xhr.status, "Set config whitelist to an non-list term");
+  xhr = CouchDB.request("DELETE", "/_config/httpd/config_whitelist",{
+    headers: {"X-Couch-Persist": "false"}
+  });
+  TEquals(200, xhr.status, "Modify whitelist despite it not being a list");
+
+  // Keys not in the whitelist may not be modified.
+  xhr = CouchDB.request("PUT", "/_config/httpd/config_whitelist",{
+    body : JSON.stringify("[{httpd,config_whitelist}, {test,foo}]"),
+    headers: {"X-Couch-Persist": "false"}
+  });
+  TEquals(200, xhr.status, "Set config whitelist to something valid");
+
+  ["PUT", "DELETE"].forEach(function(method) {
+    ["test/not_foo", "not_test/foo", "neither_test/nor_foo"].forEach(function(pair) {
+      var path = "/_config/" + pair;
+      var test_name = method + " to " + path + " disallowed: not whitelisted";
+
+      xhr = CouchDB.request(method, path, {
+        body : JSON.stringify("Bummer! " + test_name),
+        headers: {"X-Couch-Persist": "false"}
+      });
+      TEquals(400, xhr.status, test_name);
+    });
+  });
+
+  // Keys in the whitelist may be modified.
+  ["PUT", "DELETE"].forEach(function(method) {
+    xhr = CouchDB.request(method, "/_config/test/foo",{
+      body : JSON.stringify(method + " to whitelisted config variable"),
+      headers: {"X-Couch-Persist": "false"}
+    });
+    TEquals(200, xhr.status, "Keys in the whitelist may be modified");
+  });
+
+  // Non-2-tuples in the whitelist are ignored
+  xhr = CouchDB.request("PUT", "/_config/httpd/config_whitelist",{
+    body : JSON.stringify("[{httpd,config_whitelist}, these, {are}, {nOt, 2, tuples}," +
+                          " [so], [they, will], [all, become, noops], {test,foo}]"),
+    headers: {"X-Couch-Persist": "false"}
+  });
+  TEquals(200, xhr.status, "Set config whitelist with some inert values");
+  ["PUT", "DELETE"].forEach(function(method) {
+    xhr = CouchDB.request(method, "/_config/test/foo",{
+      body : JSON.stringify(method + " to whitelisted config variable"),
+      headers: {"X-Couch-Persist": "false"}
+    });
+    TEquals(200, xhr.status, "Update whitelisted variable despite invalid entries");
+  });
+
+  // Atoms, binaries, and strings suffice as whitelist sections and keys.
+  ["{test,foo}", '{"test","foo"}', '{<<"test">>,<<"foo">>}'].forEach(function(pair) {
+    xhr = CouchDB.request("PUT", "/_config/httpd/config_whitelist",{
+      body : JSON.stringify("[{httpd,config_whitelist}, " + pair + "]"),
+      headers: {"X-Couch-Persist": "false"}
+    });
+    TEquals(200, xhr.status, "Set config whitelist to include " + pair);
+
+    var pair_format = {"t":"tuple", '"':"string", "<":"binary"}[pair[1]];
+    ["PUT", "DELETE"].forEach(function(method) {
+      xhr = CouchDB.request(method, "/_config/test/foo",{
+        body : JSON.stringify(method + " with " + pair_format),
+        headers: {"X-Couch-Persist": "false"}
+      });
+      TEquals(200, xhr.status, "Whitelist works with " + pair_format);
+    });
+  });
+
+  xhr = CouchDB.request("DELETE", "/_config/httpd/config_whitelist",{
+    headers: {"X-Couch-Persist": "false"}
+  });
+  TEquals(200, xhr.status, "Reset config whitelist to undefined");
+};

http://git-wip-us.apache.org/repos/asf/couchdb/blob/3ba4fc0b/share/test/conflicts.js
----------------------------------------------------------------------
diff --git a/share/test/conflicts.js b/share/test/conflicts.js
new file mode 100644
index 0000000..79266ab
--- /dev/null
+++ b/share/test/conflicts.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.
+
+// Do some edit conflict detection tests
+couchTests.conflicts = function(debug) {
+  var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
+  db.deleteDb();
+  db.createDb();
+  if (debug) debugger;
+
+  // create a doc and save
+  var doc = {_id:"foo",a:1,b:1};
+  T(db.save(doc).ok);
+
+  // reopen
+  var doc2 = db.open(doc._id);
+
+  // ensure the revisions are the same
+  T(doc._id == doc2._id && doc._rev == doc2._rev);
+
+  // edit the documents.
+  doc.a = 2;
+  doc2.a = 3;
+
+  // save one document
+  T(db.save(doc).ok);
+
+  // save the other document
+  try {
+    db.save(doc2);  // this should generate a conflict exception
+    T("no save conflict 1" && false); // we shouldn't hit here
+  } catch (e) {
+    T(e.error == "conflict");
+  }
+
+  var changes = db.changes();
+
+  T(changes.results.length == 1);
+
+  // Now clear out the _rev member and save. This indicates this document is
+  // new, not based on an existing revision.
+  doc2._rev = undefined;
+  try {
+    db.save(doc2); // this should generate a conflict exception
+    T("no save conflict 2" && false); // we shouldn't hit here
+  } catch (e) {
+    T(e.error == "conflict");
+  }
+
+  // Make a few bad requests, specifying conflicting revs
+  // ?rev doesn't match body
+  var xhr = CouchDB.request("PUT", "/test_suite_db/foo?rev=1-foobar", {
+    body : JSON.stringify(doc)
+  });
+  T(xhr.status == 400);
+
+  // If-Match doesn't match body
+  xhr = CouchDB.request("PUT", "/test_suite_db/foo", {
+    headers: {"If-Match": "1-foobar"},
+    body: JSON.stringify(doc)
+  });
+  T(xhr.status == 400);
+
+  // ?rev= doesn't match If-Match
+  xhr = CouchDB.request("PUT", "/test_suite_db/foo?rev=1-boobaz", {
+    headers: {"If-Match": "1-foobar"},
+    body: JSON.stringify(doc2)
+  });
+  T(xhr.status == 400);
+
+  // Now update the document using ?rev=
+  xhr = CouchDB.request("PUT", "/test_suite_db/foo?rev=" + doc._rev, {
+    body: JSON.stringify(doc)
+  });
+  T(xhr.status == 201);
+
+  // reopen
+  var doc = db.open(doc._id);
+
+  // Now delete the document from the database
+  T(db.deleteDoc(doc).ok);
+
+  T(db.save(doc2).ok);  // we can save a new document over a deletion without
+                        // knowing the deletion rev.
+
+  // Verify COUCHDB-1178
+  var r1 = {"_id":"doc","foo":"bar"};
+  var r2 = {"_id":"doc","foo":"baz","_rev":"1-4c6114c65e295552ab1019e2b046b10e"};
+  var r3 = {"_id":"doc","foo":"bam","_rev":"2-cfcd6781f13994bde69a1c3320bfdadb"};
+  var r4 = {"_id":"doc","foo":"bat","_rev":"3-cc2f3210d779aef595cd4738be0ef8ff"};
+
+  T(db.save({"_id":"_design/couchdb-1178","validate_doc_update":"function(){}"}).ok);
+  T(db.save(r1).ok);
+  T(db.save(r2).ok);
+  T(db.save(r3).ok);
+
+  T(db.compact().ok);
+  while (db.info().compact_running) {};
+
+  TEquals({"_id":"doc",
+        "_rev":"3-cc2f3210d779aef595cd4738be0ef8ff",
+        "foo":"bam",
+        "_revisions":{"start":3,
+          "ids":["cc2f3210d779aef595cd4738be0ef8ff",
+                 "cfcd6781f13994bde69a1c3320bfdadb",
+                                      "4c6114c65e295552ab1019e2b046b10e"]}},
+    db.open("doc", {"revs": true}));
+  TEquals([], db.bulkSave([r4, r3, r2], {"new_edits":false}), "no failures");
+
+};

http://git-wip-us.apache.org/repos/asf/couchdb/blob/3ba4fc0b/share/test/content_negotiation.js
----------------------------------------------------------------------
diff --git a/share/test/content_negotiation.js b/share/test/content_negotiation.js
new file mode 100644
index 0000000..36e7dfb
--- /dev/null
+++ b/share/test/content_negotiation.js
@@ -0,0 +1,39 @@
+// 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.content_negotiation = function(debug) {
+  var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
+  db.deleteDb();
+  db.createDb();
+  if (debug) debugger;
+  var xhr;
+
+  // with no accept header
+  var req = CouchDB.newXhr();
+  req.open("GET", CouchDB.proxyUrl("/test_suite_db/"), false);
+  req.send("");
+  TEquals("text/plain; charset=utf-8", req.getResponseHeader("Content-Type"));
+
+  // make sure JSON responses end in a newline
+  var text = req.responseText;
+  TEquals("\n", text[text.length-1]);
+
+  xhr = CouchDB.request("GET", "/test_suite_db/", {
+    headers: {"Accept": "text/html; text/plain;*/*"}
+  });
+  TEquals("text/plain; charset=utf-8", xhr.getResponseHeader("Content-Type"));
+
+  xhr = CouchDB.request("GET", "/test_suite_db/", {
+    headers: {"Accept": "application/json"}
+  });
+  TEquals("application/json", xhr.getResponseHeader("Content-Type"));
+};

http://git-wip-us.apache.org/repos/asf/couchdb/blob/3ba4fc0b/share/test/cookie_auth.js
----------------------------------------------------------------------
diff --git a/share/test/cookie_auth.js b/share/test/cookie_auth.js
new file mode 100644
index 0000000..9b4bd64
--- /dev/null
+++ b/share/test/cookie_auth.js
@@ -0,0 +1,288 @@
+// 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.cookie_auth = function(debug) {
+  // This tests cookie-based authentication.
+
+  var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
+  db.deleteDb();
+  db.createDb();
+  if (debug) debugger;
+
+  var password = "3.141592653589";
+
+  var loginUser = function(username) {
+    var pws = {
+      jan: "apple",
+      "Jason Davies": password,
+      jchris: "funnybone"
+    };
+    var username1 = username.replace(/[0-9]$/, "");
+    var password = pws[username];
+    //console.log("Logging in '" + username1 + "' with password '" + password + "'");
+    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 save_as = function(db, doc, username)
+  {
+    loginUser(username);
+    try {
+      return db.save(doc);
+    } catch (ex) {
+      return ex;
+    } finally {
+      CouchDB.logout();
+    }
+  };
+
+  // Simple secret key generator
+  function generateSecret(length) {
+    var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+    var secret = '';
+    for (var i=0; i<length; i++) {
+      secret += tab.charAt(Math.floor(Math.random() * 64));
+    }
+    return secret;
+  }
+
+  // this function will be called on the modified server
+  var testFun = function () {
+    try {
+
+      // test that the users db is born with the auth ddoc
+      var ddoc = open_as(usersDb, "_design/_auth", "jan");
+      T(ddoc.validate_doc_update);
+
+      // TODO test that changing the config so an existing db becomes the users db installs the ddoc also
+
+      // Create a user
+      var jasonUserDoc = CouchDB.prepareUserDoc({
+        name: "Jason Davies"
+      }, password);
+      T(usersDb.save(jasonUserDoc).ok);
+
+      var checkDoc = open_as(usersDb, jasonUserDoc._id, "jan");
+      TEquals("Jason Davies", checkDoc.name);
+
+      var jchrisUserDoc = CouchDB.prepareUserDoc({
+        name: "jchris@apache.org"
+      }, "funnybone");
+      T(usersDb.save(jchrisUserDoc).ok);
+
+      // make sure we cant create duplicate users
+      var duplicateJchrisDoc = CouchDB.prepareUserDoc({
+        name: "jchris@apache.org"
+      }, "eh, Boo-Boo?");
+
+      try {
+        usersDb.save(duplicateJchrisDoc);
+        T(false && "Can't create duplicate user names. Should have thrown an error.");
+      } catch (e) {
+        TEquals("conflict", e.error);
+        TEquals(409, usersDb.last_req.status);
+      }
+
+      // we can't create _names
+      var underscoreUserDoc = CouchDB.prepareUserDoc({
+        name: "_why"
+      }, "copperfield");
+
+      try {
+        usersDb.save(underscoreUserDoc);
+        T(false && "Can't create underscore user names. Should have thrown an error.");
+      } catch (e) {
+        TEquals("forbidden", e.error);
+        TEquals(403, usersDb.last_req.status);
+      }
+
+      // we can't create docs with malformed ids
+      var badIdDoc = CouchDB.prepareUserDoc({
+        name: "w00x"
+      }, "bar");
+
+      badIdDoc._id = "org.apache.couchdb:w00x";
+
+      try {
+        usersDb.save(badIdDoc);
+        T(false && "Can't create malformed docids. Should have thrown an error.");
+      } catch (e) {
+        TEquals("forbidden", e.error);
+        TEquals(403, usersDb.last_req.status);
+      }
+
+      // login works
+      T(CouchDB.login('Jason Davies', password).ok);
+      TEquals('Jason Davies', CouchDB.session().userCtx.name);
+
+      // JSON login works
+      var xhr = CouchDB.request("POST", "/_session", {
+        headers: {"Content-Type": "application/json"},
+        body: JSON.stringify({
+          name: 'Jason Davies',
+          password: password
+        })
+      });
+
+      T(JSON.parse(xhr.responseText).ok);
+      TEquals('Jason Davies', CouchDB.session().userCtx.name);
+
+      // update one's own credentials document
+      jasonUserDoc.foo=2;
+      T(usersDb.save(jasonUserDoc).ok);
+      T(CouchDB.session().userCtx.roles.indexOf("_admin") == -1);
+      // can't delete another users doc unless you are admin
+      try {
+        usersDb.deleteDoc(jchrisUserDoc);
+        T(false && "Can't delete other users docs. Should have thrown an error.");
+      } catch (e) {
+        TEquals("not_found", e.error);
+        TEquals(404, usersDb.last_req.status);
+      }
+
+      // TODO should login() throw an exception here?
+       T(!CouchDB.login('Jason Davies', "2.71828").ok);
+       T(!CouchDB.login('Robert Allen Zimmerman', 'd00d').ok);
+
+       // a failed login attempt should log you out
+       T(CouchDB.session().userCtx.name != 'Jason Davies');
+
+       // test redirect on success
+       xhr = CouchDB.request("POST", "/_session?next=/", {
+         headers: {"Content-Type": "application/x-www-form-urlencoded"},
+         body: "name=Jason%20Davies&password="+encodeURIComponent(password)
+       });
+       // the browser should transparently follow the redirect and GET the server root (/)
+       // see http://dev.w3.org/2006/webapi/XMLHttpRequest/#infrastructure-for-the-send-method
+       if (xhr.status == 200) {
+         T(/Welcome/.test(xhr.responseText))
+       }
+
+       // test redirect on fail
+       xhr = CouchDB.request("POST", "/_session?fail=/", {
+         headers: {"Content-Type": "application/x-www-form-urlencoded"},
+         body: "name=Jason%20Davies&password=foobar"
+       });
+       if (xhr.status == 200) {
+         T(/Welcome/.test(xhr.responseText));
+       }
+
+      // test users db validations
+      //
+      // test that you can't update docs unless you are logged in as the user (or are admin)
+      T(CouchDB.login("jchris@apache.org", "funnybone").ok);
+      T(CouchDB.session().userCtx.name == "jchris@apache.org");
+      T(CouchDB.session().userCtx.roles.length == 0);
+
+      jasonUserDoc.foo=3;
+
+      try {
+        usersDb.save(jasonUserDoc);
+        T(false && "Can't update someone else's user doc. Should have thrown an error.");
+      } catch (e) {
+        T(e.error == "not_found");
+        T(usersDb.last_req.status == 404);
+      }
+
+      // test that you can't edit roles unless you are admin
+      jchrisUserDoc.roles = ["foo"];
+
+      try {
+        usersDb.save(jchrisUserDoc);
+        T(false && "Can't set roles unless you are admin. Should have thrown an error.");
+      } catch (e) {
+        T(e.error == "forbidden");
+        T(usersDb.last_req.status == 403);
+      }
+
+      T(CouchDB.logout().ok);
+
+      jchrisUserDoc.foo = ["foo"];
+      T(save_as(usersDb, jchrisUserDoc, "jan"));
+
+      // test that you can't save system (underscore) roles even if you are admin
+      jchrisUserDoc.roles = ["_bar"];
+
+      var res = save_as(usersDb, jchrisUserDoc, "jan");
+      T(res.error == "forbidden");
+      T(usersDb.last_req.status == 403);
+
+      // make sure the foo role has been applied
+      T(CouchDB.login("jchris@apache.org", "funnybone").ok);
+      T(CouchDB.session().userCtx.name == "jchris@apache.org");
+      T(CouchDB.session().userCtx.roles.indexOf("_admin") == -1);
+      T(CouchDB.session().userCtx.roles.indexOf("foo") != -1);
+
+      // now let's make jchris a server admin
+      T(CouchDB.logout().ok);
+
+      // set the -hashed- password so the salt matches
+      // todo ask on the ML about this
+
+      TEquals(true, CouchDB.login("jan", "apple").ok);
+      run_on_modified_server([{section: "admins",
+        key: "jchris@apache.org", value: "funnybone"}], function() {
+          T(CouchDB.login("jchris@apache.org", "funnybone").ok);
+          T(CouchDB.session().userCtx.name == "jchris@apache.org");
+          T(CouchDB.session().userCtx.roles.indexOf("_admin") != -1);
+          // test that jchris still has the foo role
+          T(CouchDB.session().userCtx.roles.indexOf("foo") != -1);
+
+          // should work even when user doc has no password
+          jchrisUserDoc = usersDb.open(jchrisUserDoc._id);
+          delete jchrisUserDoc.salt;
+          delete jchrisUserDoc.password_sha;
+          T(usersDb.save(jchrisUserDoc).ok);
+          T(CouchDB.logout().ok);
+          T(CouchDB.login("jchris@apache.org", "funnybone").ok);
+          var s = CouchDB.session();
+          T(s.userCtx.name == "jchris@apache.org");
+          T(s.userCtx.roles.indexOf("_admin") != -1);
+          // test session info
+          T(s.info.authenticated == "cookie");
+          T(s.info.authentication_db == "test_suite_users");
+          // test that jchris still has the foo role
+          T(CouchDB.session().userCtx.roles.indexOf("foo") != -1);
+        });
+
+    } finally {
+      // Make sure we erase any auth cookies so we don't affect other tests
+      T(CouchDB.logout().ok);
+    }
+    // log in one last time so run_on_modified_server can clean up the admin account
+    TEquals(true, CouchDB.login("jan", "apple").ok);
+  };
+
+  var usersDb = new CouchDB("test_suite_users", {"X-Couch-Full-Commit":"false"});
+  usersDb.deleteDb();
+
+  run_on_modified_server(
+    [
+     {section: "couch_httpd_auth",
+      key: "authentication_db", value: "test_suite_users"},
+     {section: "couch_httpd_auth",
+      key: "iterations", value: "1"},
+     {section: "admins",
+       key: "jan", value: "apple"}
+    ],
+    testFun
+  );
+
+};

http://git-wip-us.apache.org/repos/asf/couchdb/blob/3ba4fc0b/share/test/copy_doc.js
----------------------------------------------------------------------
diff --git a/share/test/copy_doc.js b/share/test/copy_doc.js
new file mode 100644
index 0000000..d595761
--- /dev/null
+++ b/share/test/copy_doc.js
@@ -0,0 +1,65 @@
+// 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.copy_doc = function(debug) {
+  var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
+  db.deleteDb();
+  db.createDb();
+  if (debug) debugger;
+
+  // copy a doc
+  var ok = db.save({_id:"doc_to_be_copied",v:1}).ok;
+  TEquals(true, ok, "Should return ok:true");
+  var xhr = CouchDB.request("COPY", "/test_suite_db/doc_to_be_copied", {
+    headers: {"Destination":"doc_that_was_copied"}
+  });
+
+  TEquals(true, JSON.parse(xhr.responseText).ok, "Should return ok:true");
+
+  TEquals(201, xhr.status, "Should return 201 status");
+  TEquals(1, db.open("doc_that_was_copied").v, "Should have value 1");
+
+  // COPY with existing target
+  var ok = db.save({_id:"doc_to_be_copied2",v:1}).ok;
+  TEquals(true, ok, "Should return ok:true");
+  var doc = db.save({_id:"doc_to_be_overwritten",v:2});
+  TEquals(true, doc.ok, "Should return ok:true");
+
+  // error condition
+  var xhr = CouchDB.request("COPY", "/test_suite_db/doc_to_be_copied2", {
+      headers: {"Destination":"doc_to_be_overwritten"}
+  });
+  TEquals(409, xhr.status, "Should return 409 status"); // conflict
+
+  var xhr = CouchDB.request("COPY", "/test_suite_db/doc_to_be_copied2");
+  TEquals(400, xhr.status, "Should return 400 status");
+  TEquals("Destination header is mandatory for COPY.", JSON.parse(xhr.responseText).reason,
+    "Should report missing destination header");
+
+  var xhr = CouchDB.request("COPY", "/test_suite_db/doc_to_be_copied2", {
+    headers: {
+      "Destination": "http://localhost:5984/test_suite_db/doc_to_be_written"
+  }});
+  TEquals(400, xhr.status, "Should return 400 status");
+  TEquals("Destination URL must be relative.", JSON.parse(xhr.responseText).reason,
+    "Should report invalid destination header");
+
+  var rev = db.open("doc_to_be_overwritten")._rev;
+  var xhr = CouchDB.request("COPY", "/test_suite_db/doc_to_be_copied2", {
+    headers: {"Destination":"doc_to_be_overwritten?rev=" + rev}
+  });
+  TEquals(201, xhr.status, "Should return 201 status");
+
+  var over = db.open("doc_to_be_overwritten");
+  T(rev != over._rev);
+  TEquals(1, over.v, "Should be value 1");
+};

http://git-wip-us.apache.org/repos/asf/couchdb/blob/3ba4fc0b/share/test/delayed_commits.js
----------------------------------------------------------------------
diff --git a/share/test/delayed_commits.js b/share/test/delayed_commits.js
new file mode 100644
index 0000000..dbb072f
--- /dev/null
+++ b/share/test/delayed_commits.js
@@ -0,0 +1,154 @@
+// 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.delayed_commits = function(debug) {
+  var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
+  db.deleteDb();
+  db.createDb();
+  if (debug) debugger;
+
+  run_on_modified_server(
+    [{section: "couchdb",
+      key: "delayed_commits",
+      value: "true"}],
+
+    function () {
+      // By default, couchdb doesn't fully commit documents to disk right away,
+      // it waits about a second to batch the full commit flush along with any
+      // other updates. If it crashes or is restarted you may lose the most
+      // recent commits.
+
+      T(db.save({_id:"1",a:2,b:4}).ok);
+      T(db.open("1") != null);
+
+      restartServer();
+
+      T(db.open("1") == null); // lost the update.
+      // note if we waited > 1 sec before the restart, the doc would likely
+      // commit.
+
+
+      // Retry the same thing but with full commits on.
+
+      var db2 = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"true"});
+
+      T(db2.save({_id:"1",a:2,b:4}).ok);
+      T(db2.open("1") != null);
+
+      restartServer();
+
+      T(db2.open("1") != null);
+
+      // You can update but without committing immediately, and then ensure
+      // everything is commited in the last step.
+
+      T(db.save({_id:"2",a:2,b:4}).ok);
+      T(db.open("2") != null);
+      T(db.ensureFullCommit().ok);
+      restartServer();
+
+      T(db.open("2") != null);
+
+      // However, it's possible even when flushed, that the server crashed between
+      // the update and the commit, and you don't want to check to make sure
+      // every doc you updated actually made it to disk. So record the instance
+      // start time of the database before the updates and then check it again
+      // after the flush (the instance start time is returned by the flush
+      // operation). if they are the same, we know everything was updated
+      // safely.
+
+      // First try it with a crash.
+
+      var instanceStartTime = db.info().instance_start_time;
+
+      T(db.save({_id:"3",a:2,b:4}).ok);
+      T(db.open("3") != null);
+
+      restartServer();
+
+      var commitResult = db.ensureFullCommit();
+      T(commitResult.ok && commitResult.instance_start_time != instanceStartTime);
+      // start times don't match, meaning the server lost our change
+
+      T(db.open("3") == null); // yup lost it
+
+      // retry with no server restart
+
+      var instanceStartTime = db.info().instance_start_time;
+
+      T(db.save({_id:"4",a:2,b:4}).ok);
+      T(db.open("4") != null);
+
+      var commitResult = db.ensureFullCommit();
+      T(commitResult.ok && commitResult.instance_start_time == instanceStartTime);
+      // Successful commit, start times match!
+
+      restartServer();
+
+      T(db.open("4") != null);
+    });
+
+  // Now test that when we exceed the max_dbs_open, pending commits are safely
+  // written.
+  T(db.save({_id:"5",foo:"bar"}).ok);
+  var max = 2;
+  run_on_modified_server(
+    [{section: "couchdb",
+      key: "delayed_commits",
+      value: "true"},
+     {section: "couchdb",
+      key: "max_dbs_open",
+      value: max.toString()}],
+
+    function () {
+      for(var i=0; i<max; i++) {
+        var dbi = new CouchDB("test_suite_db" + i);
+        dbi.deleteDb();
+        dbi.createDb();
+      }
+      T(db.open("5").foo=="bar");
+      for(var i=0; i<max+1; i++) {
+        var dbi = new CouchDB("test_suite_db" + i);
+        dbi.deleteDb();
+      }
+    });
+
+
+  // Test that a conflict can't cause delayed commits to fail
+  run_on_modified_server(
+    [{section: "couchdb",
+      key: "delayed_commits",
+      value: "true"}],
+
+    function() {
+      //First save a document and commit it
+      T(db.save({_id:"6",a:2,b:4}).ok);
+      T(db.ensureFullCommit().ok);
+      //Generate a conflict
+      try {
+        db.save({_id:"6",a:2,b:4});
+      } catch( e) {
+        T(e.error == "conflict");
+      }
+      //Wait for the delayed commit interval to pass
+      var time = new Date();
+      while(new Date() - time < 2000);
+      //Save a new doc
+      T(db.save({_id:"7",a:2,b:4}).ok);
+      //Wait for the delayed commit interval to pass
+      var time = new Date();
+      while(new Date() - time < 2000);
+      //Crash the server and make sure the last doc was written
+      restartServer();
+      T(db.open("7") != null);
+    });
+};

http://git-wip-us.apache.org/repos/asf/couchdb/blob/3ba4fc0b/share/test/design_docs.js
----------------------------------------------------------------------
diff --git a/share/test/design_docs.js b/share/test/design_docs.js
new file mode 100644
index 0000000..dd38858
--- /dev/null
+++ b/share/test/design_docs.js
@@ -0,0 +1,466 @@
+// 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.design_docs = function(debug) {
+  var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
+  var db2 = new CouchDB("test_suite_db_a", {"X-Couch-Full-Commit":"false"});
+
+  if (debug) debugger;
+
+  db.deleteDb();
+  db.createDb();
+  db2.deleteDb();
+  db2.createDb();
+
+  var server_config = [
+    {
+      section: "query_server_config",
+      key: "reduce_limit",
+      value: "false"
+    }
+  ];
+
+  var testFun = function() {
+    var numDocs = 500;
+
+    function makebigstring(power) {
+      var str = "a";
+      while(power-- > 0) {
+        str = str + str;
+      }
+      return str;
+    }
+
+    var designDoc = {
+      _id: "_design/test",
+      language: "javascript",
+      whatever : {
+        stringzone : "exports.string = 'plankton';",
+        commonjs : {
+          whynot : "exports.test = require('../stringzone'); " +
+            "exports.foo = require('whatever/stringzone');",
+          upper : "exports.testing = require('./whynot').test.string.toUpperCase()+" +
+            "module.id+require('./whynot').foo.string",
+          circular_one: "require('./circular_two'); exports.name = 'One';",
+          circular_two: "require('./circular_one'); exports.name = 'Two';"
+        },
+        // paths relative to parent
+        idtest1: {
+          a: {
+            b: {d: "module.exports = require('../c/e').id;"},
+            c: {e: "exports.id = module.id;"}
+          }
+        },
+        // multiple paths relative to parent
+        idtest2: {
+          a: {
+            b: {d: "module.exports = require('../../a/c/e').id;"},
+            c: {e: "exports.id = module.id;"}
+          }
+        },
+        // paths relative to module
+        idtest3: {
+          a: {
+            b: "module.exports = require('./c/d').id;",
+            c: {
+              d: "module.exports = require('./e');",
+              e: "exports.id = module.id;"
+            }
+          }
+        },
+        // paths relative to module and parent
+        idtest4: {
+          a: {
+            b: "module.exports = require('../a/./c/d').id;",
+            c: {
+              d: "module.exports = require('./e');",
+              e: "exports.id = module.id;"
+            }
+          }
+        },
+        // paths relative to root
+        idtest5: {
+          a: "module.exports = require('whatever/idtest5/b').id;",
+          b: "exports.id = module.id;"
+        }
+      },
+      views: {
+        all_docs_twice: {
+          map:
+            (function(doc) {
+              emit(doc.integer, null);
+              emit(doc.integer, null);
+            }).toString()
+        },
+        no_docs: {
+          map:
+            (function(doc) {
+            }).toString()
+        },
+        single_doc: {
+          map:
+            (function(doc) {
+              if (doc._id === "1") {
+                emit(1, null);
+              }
+            }).toString()
+        },
+        summate: {
+          map:
+            (function(doc) {
+              emit(doc.integer, doc.integer);
+            }).toString(),
+          reduce:
+            (function(keys, values) {
+              return sum(values);
+            }).toString()
+        },
+        summate2: {
+          map:
+            (function(doc) {
+              emit(doc.integer, doc.integer);
+            }).toString(),
+          reduce:
+            (function(keys, values) {
+              return sum(values);
+            }).toString()
+        },
+        huge_src_and_results: {
+          map:
+            (function(doc) {
+              if (doc._id === "1") {
+                emit(makebigstring(16), null);
+              }
+            }).toString(),
+          reduce:
+            (function(keys, values) {
+              return makebigstring(16);
+            }).toString()
+        },
+        lib : {
+          baz : "exports.baz = 'bam';",
+          foo : {
+            foo : "exports.foo = 'bar';",
+            boom : "exports.boom = 'ok';",
+            zoom : "exports.zoom = 'yeah';"
+          }
+        },
+        commonjs : {
+          map :
+            (function(doc) {
+              emit(null, require('views/lib/foo/boom').boom);
+            }).toString()
+        }
+      },
+      shows: {
+        simple:
+          (function() {
+            return 'ok';
+          }).toString(),
+        requirey:
+          (function() {
+            var lib = require('whatever/commonjs/upper');
+            return lib.testing;
+          }).toString(),
+        circular:
+          (function() {
+            var lib = require('whatever/commonjs/upper');
+            return JSON.stringify(this);
+          }).toString(),
+        circular_require:
+          (function() {
+            return require('whatever/commonjs/circular_one').name;
+          }).toString(),
+        idtest1: (function() {
+            return require('whatever/idtest1/a/b/d');
+          }).toString(),
+        idtest2: (function() {
+            return require('whatever/idtest2/a/b/d');
+          }).toString(),
+        idtest3: (function() {
+            return require('whatever/idtest3/a/b');
+          }).toString(),
+        idtest4: (function() {
+            return require('whatever/idtest4/a/b');
+          }).toString(),
+        idtest5: (function() {
+            return require('whatever/idtest5/a');
+          }).toString()
+      }
+    }; // designDoc
+
+    var xhr = CouchDB.request(
+      "PUT", "/test_suite_db_a/_design/test", {body: JSON.stringify(designDoc)}
+    );
+    var resp = JSON.parse(xhr.responseText);
+
+    TEquals(resp.rev, db.save(designDoc).rev);
+
+    // test that editing a show fun on the ddoc results in a change in output
+    xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_show/simple");
+    T(xhr.status == 200);
+    TEquals(xhr.responseText, "ok");
+
+    designDoc.shows.simple = (function() {
+      return 'ko';
+    }).toString();
+    T(db.save(designDoc).ok);
+
+    xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_show/simple");
+    T(xhr.status == 200);
+    TEquals(xhr.responseText, "ko");
+
+    xhr = CouchDB.request(
+      "GET", "/test_suite_db_a/_design/test/_show/simple?cache=buster"
+    );
+    T(xhr.status == 200);
+    TEquals("ok", xhr.responseText, 'query server used wrong ddoc');
+
+    // test commonjs require
+    xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_show/requirey");
+    T(xhr.status == 200);
+    TEquals("PLANKTONwhatever/commonjs/upperplankton", xhr.responseText);
+
+    xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_show/circular");
+    T(xhr.status == 200);
+    TEquals("javascript", JSON.parse(xhr.responseText).language);
+
+    // test circular commonjs dependencies
+    xhr = CouchDB.request(
+      "GET",
+      "/test_suite_db/_design/test/_show/circular_require"
+    );
+    TEquals(200, xhr.status);
+    TEquals("One", xhr.responseText);
+
+    // Test that changes to the design doc properly invalidate cached modules:
+
+    // update the designDoc and replace
+    designDoc.whatever.commonjs.circular_one = "exports.name = 'Updated';"
+    T(db.save(designDoc).ok);
+
+    // request circular_require show function again and check the response has
+    // changed
+    xhr = CouchDB.request(
+      "GET",
+      "/test_suite_db/_design/test/_show/circular_require"
+    );
+    TEquals(200, xhr.status);
+    TEquals("Updated", xhr.responseText);
+
+
+    // test module id values are as expected:
+    xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_show/idtest1");
+    TEquals(200, xhr.status);
+    TEquals("whatever/idtest1/a/c/e", xhr.responseText);
+
+    xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_show/idtest2");
+    TEquals(200, xhr.status);
+    TEquals("whatever/idtest2/a/c/e", xhr.responseText);
+
+    xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_show/idtest3");
+    TEquals(200, xhr.status);
+    TEquals("whatever/idtest3/a/c/e", xhr.responseText);
+
+    xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_show/idtest4");
+    TEquals(200, xhr.status);
+    TEquals("whatever/idtest4/a/c/e", xhr.responseText);
+
+    xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_show/idtest5");
+    TEquals(200, xhr.status);
+    TEquals("whatever/idtest5/b", xhr.responseText);
+
+
+    var prev_view_sig = db.designInfo("_design/test").view_index.signature;
+    var prev_view_size = db.designInfo("_design/test").view_index.disk_size;
+
+    db.bulkSave(makeDocs(1, numDocs + 1));
+    T(db.ensureFullCommit().ok);
+
+    // test that we get correct design doc info back,
+    // and also that GET /db/_design/test/_info
+    // hasn't triggered an update of the views
+    db.view("test/summate", {stale: "ok"}); // make sure view group's open
+    for (var i = 0; i < 2; i++) {
+      var dinfo = db.designInfo("_design/test");
+      TEquals("test", dinfo.name);
+      var vinfo = dinfo.view_index;
+      TEquals(prev_view_size, vinfo.disk_size, "view group disk size didn't change");
+      TEquals(false, vinfo.compact_running);
+      TEquals(prev_view_sig, vinfo.signature, 'ddoc sig');
+      // wait some time (there were issues where an update
+      // of the views had been triggered in the background)
+      var start = new Date().getTime();
+      while (new Date().getTime() < start + 2000);
+      TEquals(0, db.view("test/all_docs_twice", {stale: "ok"}).total_rows, 'view info');
+      TEquals(0, db.view("test/single_doc", {stale: "ok"}).total_rows, 'view info');
+      TEquals(0, db.view("test/summate", {stale: "ok"}).rows.length, 'view info');
+      T(db.ensureFullCommit().ok);
+      restartServer();
+    };
+
+    db.bulkSave(makeDocs(numDocs + 1, numDocs * 2 + 1));
+    T(db.ensureFullCommit().ok);
+
+    // open view group
+    db.view("test/summate", {stale: "ok"});
+    // wait so the views can get initialized
+    var start = new Date().getTime();
+    while (new Date().getTime() < start + 2000);
+
+    // test that POST /db/_view_cleanup
+    // doesn't trigger an update of the views
+    var len1 = db.view("test/all_docs_twice", {stale: "ok"}).total_rows;
+    var len2 = db.view("test/single_doc", {stale: "ok"}).total_rows;
+    var len3 = db.view("test/summate", {stale: "ok"}).rows.length;
+    for (i = 0; i < 2; i++) {
+      T(db.viewCleanup().ok);
+      // wait some time (there were issues where an update
+      // of the views had been triggered in the background)
+      start = new Date().getTime();
+      while (new Date().getTime() < start + 2000);
+      TEquals(len1, db.view("test/all_docs_twice", {stale: "ok"}).total_rows, 'view cleanup');
+      TEquals(len2, db.view("test/single_doc", {stale: "ok"}).total_rows, 'view cleanup');
+      TEquals(len3, db.view("test/summate", {stale: "ok"}).rows.length, 'view cleanup');
+      T(db.ensureFullCommit().ok);
+      restartServer();
+      // we'll test whether the view group stays closed
+      // and the views stay uninitialized (they should!)
+      len1 = len2 = len3 = 0;
+    };
+
+    // test commonjs in map functions
+    resp = db.view("test/commonjs", {limit:1});
+    T(resp.rows[0].value == 'ok');
+
+    // test that the _all_docs view returns correctly with keys
+    var results = db.allDocs({startkey:"_design", endkey:"_design0"});
+    T(results.rows.length == 1);
+
+    for (i = 0; i < 2; i++) {
+      var rows = db.view("test/all_docs_twice").rows;
+      for (var j = 0; j < numDocs; j++) {
+        T(rows[2 * j].key == (j + 1));
+        T(rows[(2 * j) + 1].key == (j + 1));
+      };
+      T(db.view("test/no_docs").total_rows == 0);
+      T(db.view("test/single_doc").total_rows == 1);
+      T(db.ensureFullCommit().ok);
+      restartServer();
+    };
+
+    // test when language not specified, Javascript is implied
+    var designDoc2 = {
+      _id: "_design/test2",
+      // language: "javascript",
+      views: {
+        single_doc: {
+          map:
+            (function(doc) {
+              if (doc._id === "1") {
+                emit(1, null);
+              }
+            }).toString()
+        }
+      }
+    };
+
+    T(db.save(designDoc2).ok);
+    T(db.view("test2/single_doc").total_rows == 1);
+
+    var summate = function(N) {
+      return (N + 1) * (N / 2);
+    };
+    var result = db.view("test/summate");
+    T(result.rows[0].value == summate(numDocs * 2));
+
+    result = db.view("test/summate", {startkey: 4, endkey: 4});
+    T(result.rows[0].value == 4);
+
+    result = db.view("test/summate", {startkey: 4, endkey: 5});
+    T(result.rows[0].value == 9);
+
+    result = db.view("test/summate", {startkey: 4, endkey: 6});
+    T(result.rows[0].value == 15);
+
+    // test start_key and end_key aliases
+    result = db.view("test/summate", {start_key: 4, end_key: 6});
+    T(result.rows[0].value == 15);
+
+    // Verify that a shared index (view def is an exact copy of "summate")
+    // does not confuse the reduce stage
+    result = db.view("test/summate2", {startkey: 4, endkey: 6});
+    T(result.rows[0].value == 15);
+
+    for(i = 1; i < (numDocs / 2); i += 30) {
+      result = db.view("test/summate", {startkey: i, endkey: (numDocs - i)});
+      T(result.rows[0].value == summate(numDocs - i) - summate(i - 1));
+    }
+
+    T(db.deleteDoc(designDoc).ok);
+    T(db.open(designDoc._id) == null);
+    T(db.view("test/no_docs") == null);
+
+    T(db.ensureFullCommit().ok);
+    restartServer();
+    T(db.open(designDoc._id) == null);
+    T(db.view("test/no_docs") == null);
+
+    // trigger ddoc cleanup
+    T(db.viewCleanup().ok);
+  }; // enf of testFun
+
+  run_on_modified_server(server_config, testFun);
+
+  // COUCHDB-1227 - if a design document is deleted, by adding a "_deleted"
+  // field with the boolean value true, its validate_doc_update functions
+  // should no longer have effect.
+  db.deleteDb();
+  db.createDb();
+  var ddoc = {
+    _id: "_design/test",
+    language: "javascript",
+    validate_doc_update: (function(newDoc, oldDoc, userCtx, secObj) {
+       if (newDoc.value % 2 == 0) {
+          throw({forbidden: "dont like even numbers"});
+       }
+       return true;
+    }).toString()
+  };
+
+  TEquals(true, db.save(ddoc).ok);
+  try {
+    db.save({_id: "doc1", value: 4});
+    T(false, "doc insertion should have failed");
+  } catch (x) {
+    TEquals("forbidden", x.error);
+  }
+
+  var doc = db.open("doc1");
+  TEquals(null, doc);
+  ddoc._deleted = true;
+  TEquals(true, db.save(ddoc).ok);
+
+  try {
+    TEquals(true, db.save({_id: "doc1", value: 4}).ok);
+  } catch (x) {
+    T(false, "doc insertion should have succeeded");
+  }
+
+  doc = db.open("doc1");
+  TEquals(true, doc !== null, "doc was not persisted");
+  TEquals(4, doc.value);
+
+  // cleanup
+  db.deleteDb();
+  db2.deleteDb();
+};

http://git-wip-us.apache.org/repos/asf/couchdb/blob/3ba4fc0b/share/test/design_options.js
----------------------------------------------------------------------
diff --git a/share/test/design_options.js b/share/test/design_options.js
new file mode 100644
index 0000000..05764e2
--- /dev/null
+++ b/share/test/design_options.js
@@ -0,0 +1,74 @@
+// 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.design_options = function(debug) {
+  var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
+  db.deleteDb();
+  db.createDb();
+  if (debug) debugger;
+
+  //// test the includes_design option
+  var map = "function (doc) {emit(null, doc._id);}";
+  var withseq = "function(doc) {emit(doc._local_seq, null)}"
+
+  // we need a design doc even to test temp views with it
+  var designDoc = {
+    _id:"_design/fu",
+    language: "javascript",
+    options: {
+      include_design: true,
+      local_seq: true
+    },
+    views: {
+      data: {"map": map},
+      with_seq : {"map" : withseq}
+    }
+  };
+  T(db.save(designDoc).ok);
+
+  // should work for temp views
+  var rows = db.query(map, null, {options:{include_design: true}}).rows;
+  T(rows.length == 1);
+  T(rows[0].value == "_design/fu");
+
+  rows = db.query(map).rows;
+  T(rows.length == 0);
+
+  // when true, should include design docs in views
+  rows = db.view("fu/data").rows;
+  T(rows.length == 1);
+  T(rows[0].value == "_design/fu");
+
+  // when false, should not
+  designDoc.options.include_design = false;
+  delete designDoc._rev;
+  designDoc._id = "_design/bingo";
+  T(db.save(designDoc).ok);
+  rows = db.view("bingo/data").rows;
+  T(rows.length == 0);
+
+  // should default to false
+  delete designDoc.options;
+  delete designDoc._rev;
+  designDoc._id = "_design/bango";
+  T(db.save(designDoc).ok);
+  rows = db.view("bango/data").rows;
+  T(rows.length == 0);
+
+  // should also have local_seq in the view
+  var resp = db.save({});
+  rows = db.view("fu/with_seq").rows;
+  T(rows[0].key == 1)
+  T(rows[1].key == 2)
+  var doc = db.open(resp.id);
+  db.deleteDoc(doc);
+};

http://git-wip-us.apache.org/repos/asf/couchdb/blob/3ba4fc0b/share/test/design_paths.js
----------------------------------------------------------------------
diff --git a/share/test/design_paths.js b/share/test/design_paths.js
new file mode 100644
index 0000000..426a252
--- /dev/null
+++ b/share/test/design_paths.js
@@ -0,0 +1,72 @@
+// 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.design_paths = 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();
+
+    // create a ddoc w bulk_docs
+    db.bulkSave([{
+      _id : "_design/test",
+      views : {
+        "testing" : {
+          "map" : "function(){emit(1,1)}"
+        }
+      }
+    }]);
+
+    // ddoc is getable
+    var xhr = CouchDB.request("GET", "/"+dbName+"/_design/test");
+    var resp = JSON.parse(xhr.responseText);
+    T(resp._id == "_design/test");
+
+    // it's at 2 urls...
+    var xhr = CouchDB.request("GET", "/"+dbName+"/_design%2Ftest");
+    var resp = JSON.parse(xhr.responseText);
+    T(resp._id == "_design/test");
+
+    // ensure that views are addressable
+    resp = db.view("test/testing")
+    T(resp.total_rows == 0)
+
+    // create a ddoc by putting to url with raw slash
+    var xhr = CouchDB.request("PUT", "/"+dbName+"/_design/test2",{
+      body : JSON.stringify({
+        _id : "_design/test2",
+        views : {
+          "testing" : {
+            "map" : "function(){emit(1,1)}"
+          }
+        }
+      })
+    });
+
+    // ddoc is getable
+    var xhr = CouchDB.request("GET", "/"+dbName+"/_design/test2");
+    var resp = JSON.parse(xhr.responseText);
+    T(resp._id == "_design/test2");
+
+    // it's at 2 urls...
+    var xhr = CouchDB.request("GET", "/"+dbName+"/_design%2Ftest2");
+    var resp = JSON.parse(xhr.responseText);
+    T(resp._id == "_design/test2");
+
+    // ensure that views are addressable
+    resp = db.view("test2/testing");
+    T(resp.total_rows == 0);
+  };
+};

http://git-wip-us.apache.org/repos/asf/couchdb/blob/3ba4fc0b/share/test/erlang_views.js
----------------------------------------------------------------------
diff --git a/share/test/erlang_views.js b/share/test/erlang_views.js
new file mode 100644
index 0000000..c6bc5d7
--- /dev/null
+++ b/share/test/erlang_views.js
@@ -0,0 +1,136 @@
+// 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.erlang_views = function(debug) {
+  var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
+  db.deleteDb();
+  db.createDb();
+  if (debug) debugger;
+
+
+
+  run_on_modified_server(
+    [{section: "native_query_servers",
+      key: "erlang",
+      value: "{couch_native_process, start_link, []}"}],
+    function() {
+      // Note we just do some basic 'smoke tests' here - the
+      // test/query_server_spec.rb tests have more comprehensive tests
+      var doc = {_id: "1", integer: 1, string: "str1", array: [1, 2, 3]};
+      T(db.save(doc).ok);
+
+      var mfun = 'fun({Doc}) -> ' +
+                 ' K = couch_util:get_value(<<"integer">>, Doc, null), ' +
+                 ' V = couch_util:get_value(<<"string">>, Doc, null), ' +
+                 ' Emit(K, V) ' +
+                 'end.';
+
+      // emitting a key value that is undefined should result in that row not
+      // being included in the view results
+      var results = db.query(mfun, null, null, null, "erlang");
+      T(results.total_rows == 1);
+      T(results.rows[0].key == 1);
+      T(results.rows[0].value == "str1");
+      
+      // check simple reduction - another doc with same key.
+      var doc = {_id: "2", integer: 1, string: "str2"};
+      T(db.save(doc).ok);
+      rfun = 'fun' +
+              ' (_, Values, false) -> length(Values); ' +
+              ' (_, Values, true) -> lists:sum(Values) ' +
+              ' end.';
+      results = db.query(mfun, rfun, null, null, "erlang");
+      T(results.rows[0].value == 2);
+
+      // simple 'list' tests
+      var designDoc = {
+        _id:"_design/erlview",
+        language: "erlang",
+        shows: {
+          simple:
+            'fun(Doc, {Req}) -> ' +
+            '  {Info} = couch_util:get_value(<<"info">>, Req, {[]}), ' +
+            '  Purged = couch_util:get_value(<<"purge_seq">>, Info, -1), ' +
+            '  Verb = couch_util:get_value(<<"method">>, Req, <<"not_get">>), ' +
+            '  R = list_to_binary(io_lib:format("~b - ~s", [Purged, Verb])), ' +
+            '  {[{<<"code">>, 200}, {<<"headers">>, {[]}}, {<<"body">>, R}]} ' +
+            'end.'
+        },
+        lists: {
+          simple_list :
+            'fun(Head, {Req}) -> ' +
+            '  Send(<<"head">>), ' +
+            '  Fun = fun({Row}, _) -> ' +
+            '    Val = couch_util:get_value(<<"value">>, Row, -1), ' +
+            '    Send(list_to_binary(integer_to_list(Val))), ' +
+            '    {ok, nil} ' +
+            '  end, ' +
+            '  {ok, _} = FoldRows(Fun, nil), ' +
+            '  <<"tail">> ' +
+            'end. '
+        },
+        views: {
+          simple_view : {
+            map: mfun,
+            reduce: rfun
+          }
+        }
+      };
+      T(db.save(designDoc).ok);
+
+      var url = "/test_suite_db/_design/erlview/_show/simple/1";
+      var xhr = CouchDB.request("GET", url);
+      T(xhr.status == 200, "standard get should be 200");
+      T(xhr.responseText == "0 - GET");
+
+      var url = "/test_suite_db/_design/erlview/_list/simple_list/simple_view";
+      var xhr = CouchDB.request("GET", url);
+      T(xhr.status == 200, "standard get should be 200");
+      T(xhr.responseText == "head2tail");
+
+      // Larger dataset
+
+      db.deleteDb();
+      db.createDb();
+      var words = "foo bar abc def baz xxyz".split(/\s+/);
+      
+      var docs = [];
+      for(var i = 0; i < 250; i++) {
+        var body = [];
+        for(var j = 0; j < 100; j++) {
+          body.push({
+            word: words[j%words.length],
+            count: j
+          });
+        }
+        docs.push({
+          "_id": "test-" + i,
+          "words": body
+        });
+      }
+      T(db.bulkSave(docs).length, 250, "Saved big doc set.");
+      
+      var mfun = 'fun({Doc}) -> ' +
+        'Words = couch_util:get_value(<<"words">>, Doc), ' +
+        'lists:foreach(fun({Word}) -> ' +
+            'WordString = couch_util:get_value(<<"word">>, Word), ' + 
+            'Count = couch_util:get_value(<<"count">>, Word), ' + 
+            'Emit(WordString , Count) ' +
+          'end, Words) ' +
+        'end.';
+      
+      var rfun = 'fun(Keys, Values, RR) -> length(Values) end.';
+      var results = db.query(mfun, rfun, null, null, "erlang");
+      T(results.rows[0].key === null, "Returned a reduced value.");
+      T(results.rows[0].value > 0, "Reduce value exists.");
+    });
+};


Mime
View raw message