You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by kx...@apache.org on 2015/02/09 21:00:27 UTC

[26/57] [abbrv] couchdb commit: updated refs/heads/developer-preview-2.0 to 849b334

http://git-wip-us.apache.org/repos/asf/couchdb/blob/6a4893aa/test/javascript/tests/attachments_multipart.js
----------------------------------------------------------------------
diff --git a/test/javascript/tests/attachments_multipart.js b/test/javascript/tests/attachments_multipart.js
new file mode 100644
index 0000000..6f924a7
--- /dev/null
+++ b/test/javascript/tests/attachments_multipart.js
@@ -0,0 +1,416 @@
+// 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.attachments_multipart= function(debug) {
+  var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
+  db.deleteDb();
+  db.createDb();
+  if (debug) debugger;
+  
+  // mime multipart
+            
+  var xhr = CouchDB.request("PUT", "/test_suite_db/multipart", {
+    headers: {"Content-Type": "multipart/related;boundary=\"abc123\""},
+    body:
+      "--abc123\r\n" +
+      "content-type: application/json\r\n" +
+      "\r\n" +
+      JSON.stringify({
+        "body":"This is a body.",
+        "_attachments":{
+          "foo.txt": {
+            "follows":true,
+            "content_type":"application/test",
+            "length":21
+            },
+          "bar.txt": {
+            "follows":true,
+            "content_type":"application/test",
+            "length":20
+            },
+          "baz.txt": {
+            "follows":true,
+            "content_type":"text/plain",
+            "length":19
+            }
+          }
+        }) +
+      "\r\n--abc123\r\n" +
+      "\r\n" +
+      "this is 21 chars long" +
+      "\r\n--abc123\r\n" +
+      "\r\n" +
+      "this is 20 chars lon" +
+      "\r\n--abc123\r\n" +
+      "\r\n" +
+      "this is 19 chars lo" +
+      "\r\n--abc123--epilogue"
+    });
+    
+  var result = JSON.parse(xhr.responseText);
+  
+  T(result.ok);
+  
+  
+    
+  TEquals(201, xhr.status, "should send 201 Accepted");
+  
+  xhr = CouchDB.request("GET", "/test_suite_db/multipart/foo.txt");
+  
+  T(xhr.responseText == "this is 21 chars long");
+  
+  xhr = CouchDB.request("GET", "/test_suite_db/multipart/bar.txt");
+  
+  T(xhr.responseText == "this is 20 chars lon");
+  
+  xhr = CouchDB.request("GET", "/test_suite_db/multipart/baz.txt");
+  
+  T(xhr.responseText == "this is 19 chars lo");
+  
+  // now edit an attachment
+  
+  var doc = db.open("multipart", {att_encoding_info: true});
+  var firstrev = doc._rev;
+  
+  T(doc._attachments["foo.txt"].stub == true);
+  T(doc._attachments["bar.txt"].stub == true);
+  T(doc._attachments["baz.txt"].stub == true);
+  TEquals("undefined", typeof doc._attachments["foo.txt"].encoding);
+  TEquals("undefined", typeof doc._attachments["bar.txt"].encoding);
+  TEquals("gzip", doc._attachments["baz.txt"].encoding);
+  
+  //lets change attachment bar
+  delete doc._attachments["bar.txt"].stub; // remove stub member (or could set to false)
+  delete doc._attachments["bar.txt"].digest; // remove the digest (it's for the gzip form)
+  doc._attachments["bar.txt"].length = 18;
+  doc._attachments["bar.txt"].follows = true;
+  //lets delete attachment baz:
+  delete doc._attachments["baz.txt"];
+  
+  var xhr = CouchDB.request("PUT", "/test_suite_db/multipart", {
+    headers: {"Content-Type": "multipart/related;boundary=\"abc123\""},
+    body:
+      "--abc123\r\n" +
+      "content-type: application/json\r\n" +
+      "\r\n" +
+      JSON.stringify(doc) +
+      "\r\n--abc123\r\n" +
+      "\r\n" +
+      "this is 18 chars l" +
+      "\r\n--abc123--"
+    });
+  TEquals(201, xhr.status);
+  
+  xhr = CouchDB.request("GET", "/test_suite_db/multipart/bar.txt");
+  
+  T(xhr.responseText == "this is 18 chars l");
+  
+  xhr = CouchDB.request("GET", "/test_suite_db/multipart/baz.txt");
+  T(xhr.status == 404);
+  
+  // now test receiving multipart docs
+  
+  function getBoundary(xhr) {
+    var ctype = CouchDB.xhrheader(xhr, "Content-Type");
+    var ctypeArgs = ctype.split("; ").slice(1);
+    var boundary = null;
+    for(var i=0; i<ctypeArgs.length; i++) {
+      if (ctypeArgs[i].indexOf("boundary=") == 0) {
+        boundary = ctypeArgs[i].split("=")[1];
+        if (boundary.charAt(0) == '"') {
+          // stringified boundary, parse as json 
+          // (will maybe not if there are escape quotes)
+          boundary = JSON.parse(boundary);
+        }
+      }
+    }
+    return boundary;
+  }
+  
+  function parseMultipart(xhr) {
+    var boundary = getBoundary(xhr);
+    var mimetext = CouchDB.xhrbody(xhr);
+    // strip off leading boundary
+    var leading = "--" + boundary + "\r\n";
+    var last = "\r\n--" + boundary + "--";
+    
+    // strip off leading and trailing boundary
+    var leadingIdx = mimetext.indexOf(leading) + leading.length;
+    var trailingIdx = mimetext.indexOf(last);
+    mimetext = mimetext.slice(leadingIdx, trailingIdx);
+    
+    // now split the sections
+    var sections = mimetext.split(new RegExp("\\r\\n--" + boundary));
+    
+    // spilt out the headers for each section
+    for(var i=0; i < sections.length; i++) {
+      var section = sections[i];
+      var headerEndIdx = section.indexOf("\r\n\r\n");
+      var headersraw = section.slice(0, headerEndIdx).split(/\r\n/);
+      var body = section.slice(headerEndIdx + 4);
+      var headers = {};
+      for(var j=0; j<headersraw.length; j++) {
+        var tmp = headersraw[j].split(": ");
+        headers[tmp[0]] = tmp[1]; 
+      }
+      sections[i] = {"headers":headers, "body":body};
+    }
+    
+    return sections;
+  }
+  
+  
+  xhr = CouchDB.request("GET", "/test_suite_db/multipart?attachments=true",
+    {headers:{"accept": "multipart/related,*/*;"}});
+  
+  T(xhr.status == 200);
+  
+  // parse out the multipart
+  var sections = parseMultipart(xhr);
+  TEquals("790", xhr.getResponseHeader("Content-Length"),
+    "Content-Length should be correct");
+  T(sections.length == 3);
+  // The first section is the json doc. Check it's content-type.
+  // Each part carries their own meta data.
+  TEquals("application/json", sections[0].headers['Content-Type'],
+    "Content-Type should be application/json for section[0]");
+  TEquals("application/test", sections[1].headers['Content-Type'],
+    "Content-Type should be application/test for section[1]");
+  TEquals("application/test", sections[2].headers['Content-Type'],
+    "Content-Type should be application/test for section[2]");
+
+  TEquals("21", sections[1].headers['Content-Length'],
+    "Content-Length should be 21 section[1]");
+  TEquals("18", sections[2].headers['Content-Length'],
+    "Content-Length should be 18 section[2]");
+
+  TEquals('attachment; filename="foo.txt"', sections[1].headers['Content-Disposition'],
+    "Content-Disposition should be foo.txt section[1]");
+  TEquals('attachment; filename="bar.txt"', sections[2].headers['Content-Disposition'],
+    "Content-Disposition should be bar.txt section[2]");
+
+  var doc = JSON.parse(sections[0].body);
+  
+  T(doc._attachments['foo.txt'].follows == true);
+  T(doc._attachments['bar.txt'].follows == true);
+  
+  T(sections[1].body == "this is 21 chars long");
+  TEquals("this is 18 chars l", sections[2].body, "should be 18 chars long");
+  
+  // now get attachments incrementally (only the attachments changes since
+  // a certain rev).
+  
+  xhr = CouchDB.request("GET", "/test_suite_db/multipart?atts_since=[\"" + firstrev + "\"]",
+    {headers:{"accept": "multipart/related, */*"}});
+  
+  T(xhr.status == 200);
+  
+  var sections = parseMultipart(xhr);
+  
+  T(sections.length == 2);
+  
+  var doc = JSON.parse(sections[0].body);
+  
+  T(doc._attachments['foo.txt'].stub == true);
+  T(doc._attachments['bar.txt'].follows == true);
+  
+  TEquals("this is 18 chars l", sections[1].body, "should be 18 chars long 2");
+
+  // try the atts_since parameter together with the open_revs parameter
+  xhr = CouchDB.request(
+    "GET",
+    '/test_suite_db/multipart?open_revs=["' +
+      doc._rev + '"]&atts_since=["' + firstrev + '"]',
+    {headers: {"accept": "multipart/mixed"}}
+  );
+
+  T(xhr.status === 200);
+
+  sections = parseMultipart(xhr);
+  // 1 section, with a multipart/related Content-Type
+  T(sections.length === 1);
+  T(sections[0].headers['Content-Type'].indexOf('multipart/related;') === 0);
+
+  var innerSections = parseMultipart(sections[0]);
+  // 2 inner sections: a document body section plus an attachment data section
+  T(innerSections.length === 2);
+  T(innerSections[0].headers['Content-Type'] === 'application/json');
+
+  doc = JSON.parse(innerSections[0].body);
+
+  T(doc._attachments['foo.txt'].stub === true);
+  T(doc._attachments['bar.txt'].follows === true);
+
+  T(innerSections[1].body === "this is 18 chars l");
+
+  // try it with a rev that doesn't exist (should get all attachments)
+  
+  xhr = CouchDB.request("GET", "/test_suite_db/multipart?atts_since=[\"1-2897589\"]",
+    {headers:{"accept": "multipart/related,*/*;"}});
+  
+  T(xhr.status == 200);
+  
+  var sections = parseMultipart(xhr);
+  
+  T(sections.length == 3);
+  
+  var doc = JSON.parse(sections[0].body);
+  
+  T(doc._attachments['foo.txt'].follows == true);
+  T(doc._attachments['bar.txt'].follows == true);
+  
+  T(sections[1].body == "this is 21 chars long");
+  TEquals("this is 18 chars l", sections[2].body, "should be 18 chars long 3");
+  // try it with a rev that doesn't exist, and one that does
+  
+  xhr = CouchDB.request("GET", "/test_suite_db/multipart?atts_since=[\"1-2897589\",\"" + firstrev + "\"]",
+    {headers:{"accept": "multipart/related,*/*;"}});
+  
+  T(xhr.status == 200);
+  
+  var sections = parseMultipart(xhr);
+  
+  T(sections.length == 2);
+  
+  var doc = JSON.parse(sections[0].body);
+  
+  T(doc._attachments['foo.txt'].stub == true);
+  T(doc._attachments['bar.txt'].follows == true);
+  
+  TEquals("this is 18 chars l", sections[1].body, "should be 18 chars long 4");
+
+  // check that with the document multipart/mixed API it's possible to receive
+  // attachments in compressed form (if they're stored in compressed form)
+
+  var server_config = [
+    {
+      section: "attachments",
+      key: "compression_level",
+      value: "8"
+    },
+    {
+      section: "attachments",
+      key: "compressible_types",
+      value: "text/plain"
+    }
+  ];
+
+  function testMultipartAttCompression() {
+    var doc = { _id: "foobar" };
+    var lorem =
+      CouchDB.request("GET", "/_utils/script/test/lorem.txt").responseText;
+    var helloData = "hello world";
+
+    TEquals(true, db.save(doc).ok);
+
+    var firstRev = doc._rev;
+    var xhr = CouchDB.request(
+      "PUT",
+      "/" + db.name + "/" + doc._id + "/data.bin?rev=" + firstRev,
+      {
+        body: helloData,
+        headers: {"Content-Type": "application/binary"}
+      }
+    );
+    TEquals(201, xhr.status);
+
+    var secondRev = db.open(doc._id)._rev;
+    xhr = CouchDB.request(
+      "PUT",
+      "/" + db.name + "/" + doc._id + "/lorem.txt?rev=" + secondRev,
+      {
+        body: lorem,
+        headers: {"Content-Type": "text/plain"}
+      }
+    );
+    TEquals(201, xhr.status);
+
+    var thirdRev = db.open(doc._id)._rev;
+
+    xhr = CouchDB.request(
+      "GET",
+      '/' + db.name + '/' + doc._id + '?open_revs=["' + thirdRev + '"]',
+      {
+        headers: {
+          "Accept": "multipart/mixed",
+          "X-CouchDB-Send-Encoded-Atts": "true"
+        }
+      }
+    );
+    TEquals(200, xhr.status);
+
+    var sections = parseMultipart(xhr);
+    // 1 section, with a multipart/related Content-Type
+    TEquals(1, sections.length);
+    TEquals(0,
+      sections[0].headers['Content-Type'].indexOf('multipart/related;'));
+
+    var innerSections = parseMultipart(sections[0]);
+    // 3 inner sections: a document body section plus 2 attachment data sections
+    TEquals(3, innerSections.length);
+    TEquals('application/json', innerSections[0].headers['Content-Type']);
+
+    doc = JSON.parse(innerSections[0].body);
+
+    TEquals(true, doc._attachments['lorem.txt'].follows);
+    TEquals("gzip", doc._attachments['lorem.txt'].encoding);
+    TEquals(true, doc._attachments['data.bin'].follows);
+    T(doc._attachments['data.bin'] !== "gzip");
+
+    if (innerSections[1].body === helloData) {
+      T(innerSections[2].body !== lorem);
+    } else if (innerSections[2].body === helloData) {
+      T(innerSections[1].body !== lorem);
+    } else {
+      T(false, "Could not found data.bin attachment data");
+    }
+
+    // now test that it works together with the atts_since parameter
+
+    xhr = CouchDB.request(
+      "GET",
+      '/' + db.name + '/' + doc._id + '?open_revs=["' + thirdRev + '"]' +
+        '&atts_since=["' + secondRev + '"]',
+      {
+        headers: {
+          "Accept": "multipart/mixed",
+          "X-CouchDB-Send-Encoded-Atts": "true"
+        }
+      }
+    );
+    TEquals(200, xhr.status);
+
+    sections = parseMultipart(xhr);
+    // 1 section, with a multipart/related Content-Type
+    TEquals(1, sections.length);
+    TEquals(0,
+      sections[0].headers['Content-Type'].indexOf('multipart/related;'));
+
+    innerSections = parseMultipart(sections[0]);
+    // 2 inner sections: a document body section plus 1 attachment data section
+    TEquals(2, innerSections.length);
+    TEquals('application/json', innerSections[0].headers['Content-Type']);
+
+    doc = JSON.parse(innerSections[0].body);
+
+    TEquals(true, doc._attachments['lorem.txt'].follows);
+    TEquals("gzip", doc._attachments['lorem.txt'].encoding);
+    TEquals("undefined", typeof doc._attachments['data.bin'].follows);
+    TEquals(true, doc._attachments['data.bin'].stub);
+    T(innerSections[1].body !== lorem);
+  }
+
+  run_on_modified_server(server_config, testMultipartAttCompression);
+
+  // cleanup
+  db.deleteDb();
+};

http://git-wip-us.apache.org/repos/asf/couchdb/blob/6a4893aa/test/javascript/tests/auth_cache.js
----------------------------------------------------------------------
diff --git a/test/javascript/tests/auth_cache.js b/test/javascript/tests/auth_cache.js
new file mode 100644
index 0000000..39b9887
--- /dev/null
+++ b/test/javascript/tests/auth_cache.js
@@ -0,0 +1,269 @@
+// 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.auth_cache = function(debug) {
+
+  if (debug) debugger;
+
+  // Simple secret key generator
+  function generateSecret(length) {
+    var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" +
+              "0123456789+/";
+    var secret = '';
+    for (var i = 0; i < length; i++) {
+      secret += tab.charAt(Math.floor(Math.random() * 64));
+    }
+    return secret;
+  }
+
+  var authDb = new CouchDB("test_suite_users", {"X-Couch-Full-Commit":"false"});
+  var server_config = [
+    {
+      section: "couch_httpd_auth",
+      key: "authentication_db",
+      value: authDb.name
+    },
+    {
+      section: "couch_httpd_auth",
+      key: "auth_cache_size",
+      value: "3"
+    },
+    {
+      section: "httpd",
+      key: "authentication_handlers",
+      value: "{couch_httpd_auth, default_authentication_handler}"
+    },
+    {
+      section: "couch_httpd_auth",
+      key: "secret",
+      value: generateSecret(64)
+    }
+  ];
+
+
+  function hits() {
+    var hits = CouchDB.requestStats(["couchdb", "auth_cache_hits"], true);
+    return hits.value || 0;
+  }
+
+
+  function misses() {
+    var misses = CouchDB.requestStats(["couchdb", "auth_cache_misses"], true);
+    return misses.value || 0;
+  }
+
+
+  function testFun() {
+    var hits_before,
+        misses_before,
+        hits_after,
+        misses_after;
+
+    var fdmanana = CouchDB.prepareUserDoc({
+      name: "fdmanana",
+      roles: ["dev"]
+    }, "qwerty");
+
+    T(authDb.save(fdmanana).ok);
+
+    var chris = CouchDB.prepareUserDoc({
+      name: "chris",
+      roles: ["dev", "mafia", "white_costume"]
+    }, "the_god_father");
+
+    T(authDb.save(chris).ok);
+
+    var joe = CouchDB.prepareUserDoc({
+      name: "joe",
+      roles: ["erlnager"]
+    }, "functional");
+
+    T(authDb.save(joe).ok);
+
+    var johndoe = CouchDB.prepareUserDoc({
+      name: "johndoe",
+      roles: ["user"]
+    }, "123456");
+
+    T(authDb.save(johndoe).ok);
+
+    hits_before = hits();
+    misses_before = misses();
+
+    T(CouchDB.login("fdmanana", "qwerty").ok);
+    T(CouchDB.logout().ok);
+
+    hits_after = hits();
+    misses_after = misses();
+
+    T(misses_after === (misses_before + 1));
+    T(hits_after === hits_before);
+
+    hits_before = hits_after;
+    misses_before = misses_after;
+
+    T(CouchDB.login("fdmanana", "qwerty").ok);
+    T(CouchDB.logout().ok);
+
+    hits_after = hits();
+    misses_after = misses();
+
+    T(misses_after === misses_before);
+    T(hits_after === (hits_before + 1));
+
+    hits_before = hits_after;
+    misses_before = misses_after;
+
+    T(CouchDB.login("chris", "the_god_father").ok);
+    T(CouchDB.logout().ok);
+
+    hits_after = hits();
+    misses_after = misses();
+
+    T(misses_after === (misses_before + 1));
+    T(hits_after === hits_before);
+
+    hits_before = hits_after;
+    misses_before = misses_after;
+
+    T(CouchDB.login("joe", "functional").ok);
+    T(CouchDB.logout().ok);
+
+    hits_after = hits();
+    misses_after = misses();
+
+    T(misses_after === (misses_before + 1));
+    T(hits_after === hits_before);
+
+    hits_before = hits_after;
+    misses_before = misses_after;
+
+    T(CouchDB.login("johndoe", "123456").ok);
+    T(CouchDB.logout().ok);
+
+    hits_after = hits();
+    misses_after = misses();
+
+    T(misses_after === (misses_before + 1));
+    T(hits_after === hits_before);
+
+    hits_before = hits_after;
+    misses_before = misses_after;
+
+    T(CouchDB.login("joe", "functional").ok);
+    T(CouchDB.logout().ok);
+
+    hits_after = hits();
+    misses_after = misses();
+
+    // it's an MRU cache, joe was removed from cache to add johndoe
+    T(misses_after === (misses_before + 1));
+    T(hits_after === hits_before);
+
+    hits_before = hits_after;
+    misses_before = misses_after;
+
+    T(CouchDB.login("fdmanana", "qwerty").ok);
+    T(CouchDB.logout().ok);
+
+    hits_after = hits();
+    misses_after = misses();
+
+    T(misses_after === misses_before);
+    T(hits_after === (hits_before + 1));
+
+    hits_before = hits_after;
+    misses_before = misses_after;
+
+    fdmanana.password = "foobar";
+    T(authDb.save(fdmanana).ok);
+
+    // cache was refreshed
+    T(CouchDB.login("fdmanana", "qwerty").error === "unauthorized");
+    T(CouchDB.login("fdmanana", "foobar").ok);
+    T(CouchDB.logout().ok);
+
+    hits_after = hits();
+    misses_after = misses();
+
+    T(misses_after === misses_before);
+    T(hits_after === (hits_before + 2));
+
+    hits_before = hits_after;
+    misses_before = misses_after;
+
+    // and yet another update
+    fdmanana.password = "javascript";
+    T(authDb.save(fdmanana).ok);
+
+    // cache was refreshed
+    T(CouchDB.login("fdmanana", "foobar").error === "unauthorized");
+    T(CouchDB.login("fdmanana", "javascript").ok);
+    T(CouchDB.logout().ok);
+
+    hits_after = hits();
+    misses_after = misses();
+
+    T(misses_after === misses_before);
+    T(hits_after === (hits_before + 2));
+
+    T(authDb.deleteDoc(fdmanana).ok);
+
+    hits_before = hits_after;
+    misses_before = misses_after;
+
+    T(CouchDB.login("fdmanana", "javascript").error === "unauthorized");
+
+    hits_after = hits();
+    misses_after = misses();
+
+    T(misses_after === misses_before);
+    T(hits_after === (hits_before + 1));
+
+    // login, compact authentication DB, login again and verify that
+    // there was a cache hit
+    hits_before = hits_after;
+    misses_before = misses_after;
+
+    T(CouchDB.login("johndoe", "123456").ok);
+    T(CouchDB.logout().ok);
+
+    hits_after = hits();
+    misses_after = misses();
+
+    T(misses_after === (misses_before + 1));
+    T(hits_after === hits_before);
+
+    T(authDb.compact().ok);
+
+    while (authDb.info().compact_running);
+
+    hits_before = hits_after;
+    misses_before = misses_after;
+
+    T(CouchDB.login("johndoe", "123456").ok);
+    T(CouchDB.logout().ok);
+
+    hits_after = hits();
+    misses_after = misses();
+
+    T(misses_after === misses_before);
+    T(hits_after === (hits_before + 1));
+  }
+
+
+  authDb.deleteDb();
+  run_on_modified_server(server_config, testFun);
+
+  // cleanup
+  authDb.deleteDb();
+}

http://git-wip-us.apache.org/repos/asf/couchdb/blob/6a4893aa/test/javascript/tests/basics.js
----------------------------------------------------------------------
diff --git a/test/javascript/tests/basics.js b/test/javascript/tests/basics.js
new file mode 100644
index 0000000..993456c
--- /dev/null
+++ b/test/javascript/tests/basics.js
@@ -0,0 +1,290 @@
+// 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 basic tests.
+couchTests.basics = function(debug) {
+  var result = JSON.parse(CouchDB.request("GET", "/").responseText);
+  T(result.couchdb == "Welcome");
+
+  var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
+  db.deleteDb();
+
+  // bug COUCHDB-100: DELETE on non-existent DB returns 500 instead of 404
+  db.deleteDb();
+
+  db.createDb();
+
+  // PUT on existing DB should return 412 instead of 500
+  xhr = CouchDB.request("PUT", "/test_suite_db/");
+  T(xhr.status == 412);
+  if (debug) debugger;
+
+  // creating a new DB should return Location header
+  // and it should work for dbs with slashes (COUCHDB-411)
+  var dbnames = ["test_suite_db", "test_suite_db%2Fwith_slashes"];
+  dbnames.forEach(function(dbname) {
+    xhr = CouchDB.request("DELETE", "/" + dbname);
+    xhr = CouchDB.request("PUT", "/" + dbname);
+    TEquals(dbname,
+      xhr.getResponseHeader("Location").substr(-dbname.length),
+      "should return Location header to newly created document");
+    TEquals(CouchDB.protocol,
+      xhr.getResponseHeader("Location").substr(0, CouchDB.protocol.length),
+      "should return absolute Location header to newly created document");
+  });
+
+  // Get the database info, check the db_name
+  T(db.info().db_name == "test_suite_db");
+  T(CouchDB.allDbs().indexOf("test_suite_db") != -1);
+
+  // Get the database info, check the doc_count
+  T(db.info().doc_count == 0);
+
+  // create a document and save it to the database
+  var doc = {_id:"0",a:1,b:1};
+  var result = db.save(doc);
+
+  T(result.ok==true); // return object has an ok member with a value true
+  T(result.id); // the _id of the document is set.
+  T(result.rev); // the revision id of the document is set.
+
+  // Verify the input doc is now set with the doc id and rev
+  // (for caller convenience).
+  T(doc._id == result.id && doc._rev == result.rev);
+
+  var id = result.id; // save off the id for later
+
+  // make sure the revs_info status is good
+  var doc = db.open(id, {revs_info:true});
+  T(doc._revs_info[0].status == "available");
+
+  // make sure you can do a seq=true option
+  var doc = db.open(id, {local_seq:true});
+  T(doc._local_seq == 1);
+
+
+  // Create some more documents.
+  // Notice the use of the ok member on the return result.
+  T(db.save({_id:"1",a:2,b:4}).ok);
+  T(db.save({_id:"2",a:3,b:9}).ok);
+  T(db.save({_id:"3",a:4,b:16}).ok);
+
+  // Check the database doc count
+  T(db.info().doc_count == 4);
+
+  // COUCHDB-954
+  var oldRev = db.save({_id:"COUCHDB-954", a:1}).rev;
+  var newRev = db.save({_id:"COUCHDB-954", _rev:oldRev}).rev;
+
+  // test behavior of open_revs with explicit revision list
+  var result = db.open("COUCHDB-954", {open_revs:[oldRev,newRev]});
+  T(result.length == 2, "should get two revisions back");
+  T(result[0].ok);
+  T(result[1].ok);
+
+  // latest=true suppresses non-leaf revisions
+  var result = db.open("COUCHDB-954", {open_revs:[oldRev,newRev], latest:true});
+  T(result.length == 1, "should only get the child revision with latest=true");
+  T(result[0].ok._rev == newRev, "should get the child and not the parent");
+
+  // latest=true returns a child when you ask for a parent
+  var result = db.open("COUCHDB-954", {open_revs:[oldRev], latest:true});
+  T(result[0].ok._rev == newRev, "should get child when we requested parent");
+
+  // clean up after ourselves
+  db.save({_id:"COUCHDB-954", _rev:newRev, _deleted:true});
+
+  // Test a simple map functions
+
+  // create a map function that selects all documents whose "a" member
+  // has a value of 4, and then returns the document's b value.
+  var mapFunction = function(doc){
+    if (doc.a==4)
+      emit(null, doc.b);
+  };
+
+  var results = db.query(mapFunction);
+
+  // verify only one document found and the result value (doc.b).
+  T(results.total_rows == 1 && results.rows[0].value == 16);
+
+  // reopen document we saved earlier
+  var existingDoc = db.open(id);
+
+  T(existingDoc.a==1);
+
+  //modify and save
+  existingDoc.a=4;
+  db.save(existingDoc);
+
+  // redo the map query
+  results = db.query(mapFunction);
+
+  // the modified document should now be in the results.
+  T(results.total_rows == 2);
+
+  // write 2 more documents
+  T(db.save({a:3,b:9}).ok);
+  T(db.save({a:4,b:16}).ok);
+
+  results = db.query(mapFunction);
+
+  // 1 more document should now be in the result.
+  T(results.total_rows == 3);
+  T(db.info().doc_count == 6);
+
+  var reduceFunction = function(keys, values){
+    return sum(values);
+  };
+
+  results = db.query(mapFunction, reduceFunction);
+
+  T(results.rows[0].value == 33);
+
+  // delete a document
+  T(db.deleteDoc(existingDoc).ok);
+
+  // make sure we can't open the doc
+  T(db.open(existingDoc._id) == null);
+
+  results = db.query(mapFunction);
+
+  // 1 less document should now be in the results.
+  T(results.total_rows == 2);
+  T(db.info().doc_count == 5);
+
+  // make sure we can still open the old rev of the deleted doc
+  T(db.open(existingDoc._id, {rev: existingDoc._rev}) != null);
+  // make sure restart works
+  T(db.ensureFullCommit().ok);
+  restartServer();
+
+  // make sure we can still open
+  T(db.open(existingDoc._id, {rev: existingDoc._rev}) != null);
+
+  // test that the POST response has a Location header
+  var xhr = CouchDB.request("POST", "/test_suite_db", {
+    body: JSON.stringify({"foo":"bar"}),
+    headers: {"Content-Type": "application/json"}
+  });
+  var resp = JSON.parse(xhr.responseText);
+  T(resp.ok);
+  var loc = xhr.getResponseHeader("Location");
+  T(loc, "should have a Location header");
+  var locs = loc.split('/');
+  T(locs[locs.length-1] == resp.id);
+  T(locs[locs.length-2] == "test_suite_db");
+
+  // test that that POST's with an _id aren't overriden with a UUID.
+  var xhr = CouchDB.request("POST", "/test_suite_db", {
+    headers: {"Content-Type": "application/json"},
+    body: JSON.stringify({"_id": "oppossum", "yar": "matey"})
+  });
+  var resp = JSON.parse(xhr.responseText);
+  T(resp.ok);
+  T(resp.id == "oppossum");
+  var doc = db.open("oppossum");
+  T(doc.yar == "matey");
+
+  // document put's should return a Location header
+  var xhr = CouchDB.request("PUT", "/test_suite_db/newdoc", {
+    body: JSON.stringify({"a":1})
+  });
+  TEquals("/test_suite_db/newdoc",
+    xhr.getResponseHeader("Location").substr(-21),
+    "should return Location header to newly created document");
+  TEquals(CouchDB.protocol,
+    xhr.getResponseHeader("Location").substr(0, CouchDB.protocol.length),
+    "should return absolute Location header to newly created document");
+
+  // deleting a non-existent doc should be 404
+  xhr = CouchDB.request("DELETE", "/test_suite_db/doc-does-not-exist");
+  T(xhr.status == 404);
+
+  // Check for invalid document members
+  var bad_docs = [
+    ["goldfish", {"_zing": 4}],
+    ["zebrafish", {"_zoom": "hello"}],
+    ["mudfish", {"zane": "goldfish", "_fan": "something smells delicious"}],
+    ["tastyfish", {"_bing": {"wha?": "soda can"}}]
+  ];
+  var test_doc = function(info) {
+  var data = JSON.stringify(info[1]);
+    xhr = CouchDB.request("PUT", "/test_suite_db/" + info[0], {body: data});
+    T(xhr.status == 500);
+    result = JSON.parse(xhr.responseText);
+    T(result.error == "doc_validation");
+
+    xhr = CouchDB.request("POST", "/test_suite_db/", {
+      headers: {"Content-Type": "application/json"},
+      body: data
+    });
+    T(xhr.status == 500);
+    result = JSON.parse(xhr.responseText);
+    T(result.error == "doc_validation");
+  };
+  bad_docs.forEach(test_doc);
+
+  // Check some common error responses.
+  // PUT body not an object
+  xhr = CouchDB.request("PUT", "/test_suite_db/bar", {body: "[]"});
+  T(xhr.status == 400);
+  result = JSON.parse(xhr.responseText);
+  T(result.error == "bad_request");
+  T(result.reason == "Document must be a JSON object");
+
+  // Body of a _bulk_docs is not an object
+  xhr = CouchDB.request("POST", "/test_suite_db/_bulk_docs", {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");
+
+  // Body of an _all_docs  multi-get is not a {"key": [...]} structure.
+  xhr = CouchDB.request("POST", "/test_suite_db/_all_docs", {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", "/test_suite_db/_all_docs", {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.");
+
+  // oops, the doc id got lost in code nirwana
+  xhr = CouchDB.request("DELETE", "/test_suite_db/?rev=foobarbaz");
+  TEquals(400, xhr.status, "should return a bad request");
+  result = JSON.parse(xhr.responseText);
+  TEquals("bad_request", result.error);
+  TEquals("You tried to DELETE a database with a ?rev= parameter. Did you mean to DELETE a document instead?", result.reason);
+
+  // On restart, a request for creating a database that already exists can
+  // not override the existing database file
+  db = new CouchDB("test_suite_foobar");
+  db.deleteDb();
+  xhr = CouchDB.request("PUT", "/" + db.name);
+  TEquals(201, xhr.status);
+
+  TEquals(true, db.save({"_id": "doc1"}).ok);
+  TEquals(true, db.ensureFullCommit().ok);
+
+  TEquals(1, db.info().doc_count);
+
+  restartServer();
+
+  xhr = CouchDB.request("PUT", "/" + db.name);
+  TEquals(412, xhr.status);
+
+  TEquals(1, db.info().doc_count);
+};

http://git-wip-us.apache.org/repos/asf/couchdb/blob/6a4893aa/test/javascript/tests/batch_save.js
----------------------------------------------------------------------
diff --git a/test/javascript/tests/batch_save.js b/test/javascript/tests/batch_save.js
new file mode 100644
index 0000000..a1b0019
--- /dev/null
+++ b/test/javascript/tests/batch_save.js
@@ -0,0 +1,48 @@
+// 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.batch_save = function(debug) {
+  var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
+  db.deleteDb();
+  db.createDb();
+  if (debug) debugger;
+
+  var i
+  for(i=0; i < 100; i++) {
+    T(db.save({_id:i.toString(),a:i,b:i},  {batch : "ok"}).ok);
+    
+    // test that response is 202 Accepted
+    T(db.last_req.status == 202);
+  }
+  
+  for(i=0; i < 100; i++) {
+    // attempt to save the same document a bunch of times
+    T(db.save({_id:"foo",a:i,b:i},  {batch : "ok"}).ok);
+    
+    // test that response is 202 Accepted
+    T(db.last_req.status == 202);
+  }
+  
+  while(db.allDocs().total_rows != 101){};
+
+  // repeat the tests for POST
+  for(i=0; i < 100; i++) {
+    var resp = db.request("POST", db.uri + "?batch=ok", {
+      headers: {"Content-Type": "application/json"},
+      body: JSON.stringify({a:1})
+    });
+    T(JSON.parse(resp.responseText).ok);
+  }
+  
+  while(db.allDocs().total_rows != 201){};
+
+};

http://git-wip-us.apache.org/repos/asf/couchdb/blob/6a4893aa/test/javascript/tests/bulk_docs.js
----------------------------------------------------------------------
diff --git a/test/javascript/tests/bulk_docs.js b/test/javascript/tests/bulk_docs.js
new file mode 100644
index 0000000..27a97c8
--- /dev/null
+++ b/test/javascript/tests/bulk_docs.js
@@ -0,0 +1,124 @@
+// 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.bulk_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(5);
+
+  // Create the docs
+  var results = db.bulkSave(docs);
+
+  T(results.length == 5);
+  for (var i = 0; i < 5; i++) {
+    T(results[i].id == docs[i]._id);
+    T(results[i].rev);
+    // Update the doc
+    docs[i].string = docs[i].string + ".00";
+  }
+
+  // Save the docs
+  results = db.bulkSave(docs);
+  T(results.length == 5);
+  for (i = 0; i < 5; i++) {
+    T(results[i].id == i.toString());
+
+    // set the delete flag to delete the docs in the next step
+    docs[i]._deleted = true;
+  }
+
+  // now test a bulk update with a conflict
+  // open and save
+  var doc = db.open("0");
+  db.save(doc);
+
+  // Now bulk delete the docs
+  results = db.bulkSave(docs);
+
+  // doc "0" should be a conflict
+  T(results.length == 5);
+  T(results[0].id == "0");
+  T(results[0].error == "conflict");
+  T(typeof results[0].rev === "undefined"); // no rev member when a conflict
+
+  // but the rest are not
+  for (i = 1; i < 5; i++) {
+    T(results[i].id == i.toString());
+    T(results[i].rev);
+    T(db.open(docs[i]._id) == null);
+  }
+
+  // now force a conflict to to save
+
+  // save doc 0, this will cause a conflict when we save docs[0]
+  var doc = db.open("0");
+  docs[0] = db.open("0");
+  db.save(doc);
+
+  docs[0].shooby = "dooby";
+
+  // Now save the bulk docs, When we use all_or_nothing, we don't get conflict
+  // checking, all docs are saved regardless of conflict status, or none are
+  // saved.
+  results = db.bulkSave(docs,{all_or_nothing:true});
+  T(results.error === undefined);
+
+  var doc = db.open("0", {conflicts:true});
+  var docConflict = db.open("0", {rev:doc._conflicts[0]});
+
+  T(doc.shooby == "dooby" || docConflict.shooby == "dooby");
+
+  // verify creating a document with no id returns a new id
+  var req = CouchDB.request("POST", "/test_suite_db/_bulk_docs", {
+    body: JSON.stringify({"docs": [{"foo":"bar"}]})
+  });
+  results = JSON.parse(req.responseText);
+
+  T(results[0].id != "");
+  T(results[0].rev != "");
+
+
+  // Regression test for failure on update/delete
+  var newdoc = {"_id": "foobar", "body": "baz"};
+  T(db.save(newdoc).ok);
+  var update = {"_id": newdoc._id, "_rev": newdoc._rev, "body": "blam"};
+  var torem = {"_id": newdoc._id, "_rev": newdoc._rev, "_deleted": true};
+  results = db.bulkSave([update, torem]);
+  T(results[0].error == "conflict" || results[1].error == "conflict");
+
+
+  // verify that sending a request with no docs causes error thrown
+  var req = CouchDB.request("POST", "/test_suite_db/_bulk_docs", {
+    body: JSON.stringify({"doc": [{"foo":"bar"}]})
+  });
+
+  T(req.status == 400 );
+  result = JSON.parse(req.responseText);
+  T(result.error == "bad_request");
+  T(result.reason == "Missing JSON list of 'docs'");
+
+  // jira-911
+  db.deleteDb();
+  db.createDb();
+  docs = [];
+  docs.push({"_id":"0", "a" : 0});
+  docs.push({"_id":"1", "a" : 1});
+  docs.push({"_id":"1", "a" : 2});
+  docs.push({"_id":"3", "a" : 3});
+  results = db.bulkSave(docs);
+  T(results[1].id == "1");
+  T(results[1].error == undefined);
+  T(results[2].error == "conflict");
+};

http://git-wip-us.apache.org/repos/asf/couchdb/blob/6a4893aa/test/javascript/tests/changes.js
----------------------------------------------------------------------
diff --git a/test/javascript/tests/changes.js b/test/javascript/tests/changes.js
new file mode 100644
index 0000000..d5a4236
--- /dev/null
+++ b/test/javascript/tests/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/6a4893aa/test/javascript/tests/coffee.js
----------------------------------------------------------------------
diff --git a/test/javascript/tests/coffee.js b/test/javascript/tests/coffee.js
new file mode 100644
index 0000000..9306124
--- /dev/null
+++ b/test/javascript/tests/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/6a4893aa/test/javascript/tests/compact.js
----------------------------------------------------------------------
diff --git a/test/javascript/tests/compact.js b/test/javascript/tests/compact.js
new file mode 100644
index 0000000..68c83b3
--- /dev/null
+++ b/test/javascript/tests/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/6a4893aa/test/javascript/tests/config.js
----------------------------------------------------------------------
diff --git a/test/javascript/tests/config.js b/test/javascript/tests/config.js
new file mode 100644
index 0000000..37b339b
--- /dev/null
+++ b/test/javascript/tests/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/6a4893aa/test/javascript/tests/conflicts.js
----------------------------------------------------------------------
diff --git a/test/javascript/tests/conflicts.js b/test/javascript/tests/conflicts.js
new file mode 100644
index 0000000..79266ab
--- /dev/null
+++ b/test/javascript/tests/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/6a4893aa/test/javascript/tests/content_negotiation.js
----------------------------------------------------------------------
diff --git a/test/javascript/tests/content_negotiation.js b/test/javascript/tests/content_negotiation.js
new file mode 100644
index 0000000..36e7dfb
--- /dev/null
+++ b/test/javascript/tests/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"));
+};