couchdb-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From j..@apache.org
Subject [18/37] couchdb commit: updated refs/heads/goodbye-futon to 4fa015a
Date Fri, 10 Oct 2014 19:12:32 GMT
move JS tests into safety


Project: http://git-wip-us.apache.org/repos/asf/couchdb/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb/commit/3ba4fc0b
Tree: http://git-wip-us.apache.org/repos/asf/couchdb/tree/3ba4fc0b
Diff: http://git-wip-us.apache.org/repos/asf/couchdb/diff/3ba4fc0b

Branch: refs/heads/goodbye-futon
Commit: 3ba4fc0b487657d339fb7d3961dfc71e84a3a731
Parents: d309436
Author: Jan Lehnardt <jan@apache.org>
Authored: Fri Oct 10 20:46:08 2014 +0200
Committer: Jan Lehnardt <jan@apache.org>
Committed: Fri Oct 10 20:46:08 2014 +0200

----------------------------------------------------------------------
 share/test/all_docs.js                          |  142 ++
 share/test/attachment_names.js                  |   96 +
 share/test/attachment_paths.js                  |  153 ++
 share/test/attachment_ranges.js                 |  160 ++
 share/test/attachment_views.js                  |  140 ++
 share/test/attachments.js                       |  328 ++++
 share/test/attachments_multipart.js             |  416 ++++
 share/test/auth_cache.js                        |  269 +++
 share/test/basics.js                            |  290 +++
 share/test/batch_save.js                        |   48 +
 share/test/bulk_docs.js                         |  124 ++
 share/test/changes.js                           |  738 +++++++
 share/test/coffee.js                            |   67 +
 share/test/compact.js                           |   65 +
 share/test/config.js                            |  211 ++
 share/test/conflicts.js                         |  119 ++
 share/test/content_negotiation.js               |   39 +
 share/test/cookie_auth.js                       |  288 +++
 share/test/copy_doc.js                          |   65 +
 share/test/delayed_commits.js                   |  154 ++
 share/test/design_docs.js                       |  466 +++++
 share/test/design_options.js                    |   74 +
 share/test/design_paths.js                      |   72 +
 share/test/erlang_views.js                      |  136 ++
 share/test/etags_head.js                        |   78 +
 share/test/etags_views.js                       |  220 +++
 share/test/form_submit.js                       |   25 +
 share/test/http.js                              |   54 +
 share/test/invalid_docids.js                    |   77 +
 share/test/jsonp.js                             |   84 +
 share/test/large_docs.js                        |   33 +
 share/test/list_views.js                        |  492 +++++
 share/test/lorem.txt                            |  103 +
 share/test/lorem_b64.txt                        |    1 +
 share/test/lots_of_docs.js                      |   55 +
 share/test/method_override.js                   |   40 +
 share/test/multiple_rows.js                     |   80 +
 share/test/oauth.js                             |  294 +++
 share/test/oauth_users_db.js                    |  161 ++
 share/test/proxyauth.js                         |  130 ++
 share/test/purge.js                             |  145 ++
 share/test/reader_acl.js                        |  219 +++
 share/test/recreate_doc.js                      |  145 ++
 share/test/reduce.js                            |  414 ++++
 share/test/reduce_builtin.js                    |  179 ++
 share/test/reduce_false.js                      |   44 +
 share/test/reduce_false_temp.js                 |   37 +
 share/test/replication.js                       | 1795 ++++++++++++++++++
 share/test/replicator_db_bad_rep_id.js          |   77 +
 share/test/replicator_db_by_doc_id.js           |   99 +
 share/test/replicator_db_compact_rep_db.js      |  119 ++
 share/test/replicator_db_continuous.js          |  137 ++
 .../test/replicator_db_credential_delegation.js |  149 ++
 share/test/replicator_db_field_validation.js    |  178 ++
 share/test/replicator_db_filtered.js            |  105 +
 share/test/replicator_db_identical.js           |   87 +
 .../test/replicator_db_identical_continuous.js  |  139 ++
 share/test/replicator_db_invalid_filter.js      |  119 ++
 share/test/replicator_db_security.js            |  399 ++++
 share/test/replicator_db_simple.js              |  114 ++
 share/test/replicator_db_successive.js          |  127 ++
 share/test/replicator_db_survives.js            |  126 ++
 share/test/replicator_db_swap_rep_db.js         |  170 ++
 share/test/replicator_db_update_security.js     |   92 +
 share/test/replicator_db_user_ctx.js            |  272 +++
 share/test/replicator_db_write_auth.js          |  102 +
 share/test/rev_stemming.js                      |  110 ++
 share/test/rewrite.js                           |  505 +++++
 share/test/security_validation.js               |  338 ++++
 share/test/show_documents.js                    |  420 ++++
 share/test/stats.js                             |  348 ++++
 share/test/update_documents.js                  |  235 +++
 share/test/users_db.js                          |  173 ++
 share/test/users_db_security.js                 |  423 +++++
 share/test/utf8.js                              |   42 +
 share/test/uuids.js                             |  149 ++
 share/test/view_collation.js                    |  116 ++
 share/test/view_collation_raw.js                |  130 ++
 share/test/view_compaction.js                   |  110 ++
 share/test/view_conflicts.js                    |   49 +
 share/test/view_errors.js                       |  189 ++
 share/test/view_include_docs.js                 |  192 ++
 share/test/view_multi_key_all_docs.js           |   95 +
 share/test/view_multi_key_design.js             |  220 +++
 share/test/view_multi_key_temp.js               |   40 +
 share/test/view_offsets.js                      |  108 ++
 share/test/view_pagination.js                   |  147 ++
 share/test/view_sandboxing.js                   |  140 ++
 share/test/view_update_seq.js                   |  106 ++
 share/www/script/test/all_docs.js               |  142 --
 share/www/script/test/attachment_names.js       |   96 -
 share/www/script/test/attachment_paths.js       |  153 --
 share/www/script/test/attachment_ranges.js      |  160 --
 share/www/script/test/attachment_views.js       |  140 --
 share/www/script/test/attachments.js            |  328 ----
 share/www/script/test/attachments_multipart.js  |  416 ----
 share/www/script/test/auth_cache.js             |  269 ---
 share/www/script/test/basics.js                 |  290 ---
 share/www/script/test/batch_save.js             |   48 -
 share/www/script/test/bulk_docs.js              |  124 --
 share/www/script/test/changes.js                |  738 -------
 share/www/script/test/coffee.js                 |   67 -
 share/www/script/test/compact.js                |   65 -
 share/www/script/test/config.js                 |  211 --
 share/www/script/test/conflicts.js              |  119 --
 share/www/script/test/content_negotiation.js    |   39 -
 share/www/script/test/cookie_auth.js            |  288 ---
 share/www/script/test/copy_doc.js               |   65 -
 share/www/script/test/delayed_commits.js        |  154 --
 share/www/script/test/design_docs.js            |  466 -----
 share/www/script/test/design_options.js         |   74 -
 share/www/script/test/design_paths.js           |   72 -
 share/www/script/test/erlang_views.js           |  136 --
 share/www/script/test/etags_head.js             |   78 -
 share/www/script/test/etags_views.js            |  220 ---
 share/www/script/test/form_submit.js            |   25 -
 share/www/script/test/http.js                   |   54 -
 share/www/script/test/invalid_docids.js         |   77 -
 share/www/script/test/jsonp.js                  |   84 -
 share/www/script/test/large_docs.js             |   33 -
 share/www/script/test/list_views.js             |  492 -----
 share/www/script/test/lorem.txt                 |  103 -
 share/www/script/test/lorem_b64.txt             |    1 -
 share/www/script/test/lots_of_docs.js           |   55 -
 share/www/script/test/method_override.js        |   40 -
 share/www/script/test/multiple_rows.js          |   80 -
 share/www/script/test/oauth.js                  |  294 ---
 share/www/script/test/oauth_users_db.js         |  161 --
 share/www/script/test/proxyauth.js              |  130 --
 share/www/script/test/purge.js                  |  145 --
 share/www/script/test/reader_acl.js             |  219 ---
 share/www/script/test/recreate_doc.js           |  145 --
 share/www/script/test/reduce.js                 |  414 ----
 share/www/script/test/reduce_builtin.js         |  179 --
 share/www/script/test/reduce_false.js           |   44 -
 share/www/script/test/reduce_false_temp.js      |   37 -
 share/www/script/test/replication.js            | 1795 ------------------
 .../www/script/test/replicator_db_bad_rep_id.js |   77 -
 .../www/script/test/replicator_db_by_doc_id.js  |   99 -
 .../script/test/replicator_db_compact_rep_db.js |  119 --
 .../www/script/test/replicator_db_continuous.js |  137 --
 .../test/replicator_db_credential_delegation.js |  149 --
 .../test/replicator_db_field_validation.js      |  178 --
 share/www/script/test/replicator_db_filtered.js |  105 -
 .../www/script/test/replicator_db_identical.js  |   87 -
 .../test/replicator_db_identical_continuous.js  |  139 --
 .../script/test/replicator_db_invalid_filter.js |  119 --
 share/www/script/test/replicator_db_security.js |  399 ----
 share/www/script/test/replicator_db_simple.js   |  114 --
 .../www/script/test/replicator_db_successive.js |  127 --
 share/www/script/test/replicator_db_survives.js |  126 --
 .../script/test/replicator_db_swap_rep_db.js    |  170 --
 .../test/replicator_db_update_security.js       |   92 -
 share/www/script/test/replicator_db_user_ctx.js |  272 ---
 .../www/script/test/replicator_db_write_auth.js |  102 -
 share/www/script/test/rev_stemming.js           |  110 --
 share/www/script/test/rewrite.js                |  505 -----
 share/www/script/test/security_validation.js    |  338 ----
 share/www/script/test/show_documents.js         |  420 ----
 share/www/script/test/stats.js                  |  348 ----
 share/www/script/test/update_documents.js       |  235 ---
 share/www/script/test/users_db.js               |  173 --
 share/www/script/test/users_db_security.js      |  423 -----
 share/www/script/test/utf8.js                   |   42 -
 share/www/script/test/uuids.js                  |  149 --
 share/www/script/test/view_collation.js         |  116 --
 share/www/script/test/view_collation_raw.js     |  130 --
 share/www/script/test/view_compaction.js        |  110 --
 share/www/script/test/view_conflicts.js         |   49 -
 share/www/script/test/view_errors.js            |  189 --
 share/www/script/test/view_include_docs.js      |  192 --
 .../www/script/test/view_multi_key_all_docs.js  |   95 -
 share/www/script/test/view_multi_key_design.js  |  220 ---
 share/www/script/test/view_multi_key_temp.js    |   40 -
 share/www/script/test/view_offsets.js           |  108 --
 share/www/script/test/view_pagination.js        |  147 --
 share/www/script/test/view_sandboxing.js        |  140 --
 share/www/script/test/view_update_seq.js        |  106 --
 178 files changed, 16561 insertions(+), 16561 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb/blob/3ba4fc0b/share/test/all_docs.js
----------------------------------------------------------------------
diff --git a/share/test/all_docs.js b/share/test/all_docs.js
new file mode 100644
index 0000000..66ad880
--- /dev/null
+++ b/share/test/all_docs.js
@@ -0,0 +1,142 @@
+// 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.all_docs = function(debug) {
+  var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
+  db.deleteDb();
+  db.createDb();
+  if (debug) debugger;
+
+  // Create some more documents.
+  // Notice the use of the ok member on the return result.
+  T(db.save({_id:"0",a:1,b:1}).ok);
+  T(db.save({_id:"3",a:4,b:16}).ok);
+  T(db.save({_id:"1",a:2,b:4}).ok);
+  T(db.save({_id:"2",a:3,b:9}).ok);
+
+  // Check the all docs
+  var results = db.allDocs();
+  var rows = results.rows;
+
+  T(results.total_rows == results.rows.length);
+
+  for(var i=0; i < rows.length; i++) {
+    T(rows[i].id >= "0" && rows[i].id <= "4");
+  }
+
+  // Check _all_docs with descending=true
+  var desc = db.allDocs({descending:true});
+  T(desc.total_rows == desc.rows.length);
+
+  // Check _all_docs offset
+  var all = db.allDocs({startkey:"2"});
+  T(all.offset == 2);
+
+  // Confirm that queries may assume raw collation.
+  var raw = db.allDocs({ startkey: "org.couchdb.user:",
+                         endkey  : "org.couchdb.user;"
+                       });
+  TEquals(0, raw.rows.length);
+
+  // check that the docs show up in the seq view in the order they were created
+  var changes = db.changes();
+  var ids = ["0","3","1","2"];
+  for (var i=0; i < changes.results.length; i++) {
+    var row = changes.results[i];
+    T(row.id == ids[i], "seq order");
+  };
+
+  // it should work in reverse as well
+  changes = db.changes({descending:true});
+  ids = ["2","1","3","0"];
+  for (var i=0; i < changes.results.length; i++) {
+    var row = changes.results[i];
+    T(row.id == ids[i], "descending=true");
+  };
+
+  // check that deletions also show up right
+  var doc1 = db.open("1");
+  var deleted = db.deleteDoc(doc1);
+  T(deleted.ok);
+  changes = db.changes();
+  // the deletion should make doc id 1 have the last seq num
+  T(changes.results.length == 4);
+  T(changes.results[3].id == "1");
+  T(changes.results[3].deleted);
+
+  // do an update
+  var doc2 = db.open("3");
+  doc2.updated = "totally";
+  db.save(doc2);
+  changes = db.changes();
+
+  // the update should make doc id 3 have the last seq num
+  T(changes.results.length == 4);
+  T(changes.results[3].id == "3");
+
+  // ok now lets see what happens with include docs
+  changes = db.changes({include_docs: true});
+  T(changes.results.length == 4);
+  T(changes.results[3].id == "3");
+  T(changes.results[3].doc.updated == "totally");
+
+  T(changes.results[2].doc);
+  T(changes.results[2].doc._deleted);
+
+  rows = db.allDocs({include_docs: true}, ["1"]).rows;
+  TEquals(1, rows.length);
+  TEquals("1", rows[0].key);
+  TEquals("1", rows[0].id);
+  TEquals(true, rows[0].value.deleted);
+  TEquals(null, rows[0].doc);
+
+  // add conflicts
+  var conflictDoc1 = {
+    _id: "3", _rev: "2-aa01552213fafa022e6167113ed01087", value: "X"
+  };
+  var conflictDoc2 = {
+    _id: "3", _rev: "2-ff01552213fafa022e6167113ed01087", value: "Z"
+  };
+  T(db.save(conflictDoc1, {new_edits: false}));
+  T(db.save(conflictDoc2, {new_edits: false}));
+
+  var winRev = db.open("3");
+
+  changes = db.changes({include_docs: true, conflicts: true, style: "all_docs"});
+  TEquals("3", changes.results[3].id);
+  TEquals(3, changes.results[3].changes.length);
+  TEquals(winRev._rev, changes.results[3].changes[0].rev);
+  TEquals("3", changes.results[3].doc._id);
+  TEquals(winRev._rev, changes.results[3].doc._rev);
+  TEquals(true, changes.results[3].doc._conflicts instanceof Array);
+  TEquals(2, changes.results[3].doc._conflicts.length);
+
+  rows = db.allDocs({include_docs: true, conflicts: true}).rows;
+  TEquals(3, rows.length);
+  TEquals("3", rows[2].key);
+  TEquals("3", rows[2].id);
+  TEquals(winRev._rev, rows[2].value.rev);
+  TEquals(winRev._rev, rows[2].doc._rev);
+  TEquals("3", rows[2].doc._id);
+  TEquals(true, rows[2].doc._conflicts instanceof Array);
+  TEquals(2, rows[2].doc._conflicts.length);
+
+  // test the all docs collates sanely
+  db.save({_id: "Z", foo: "Z"});
+  db.save({_id: "a", foo: "a"});
+
+  var rows = db.allDocs({startkey: "Z", endkey: "Z"}).rows;
+  T(rows.length == 1);
+
+  // cleanup
+  db.deleteDb();
+};

http://git-wip-us.apache.org/repos/asf/couchdb/blob/3ba4fc0b/share/test/attachment_names.js
----------------------------------------------------------------------
diff --git a/share/test/attachment_names.js b/share/test/attachment_names.js
new file mode 100644
index 0000000..c9a5fcc
--- /dev/null
+++ b/share/test/attachment_names.js
@@ -0,0 +1,96 @@
+// 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.attachment_names = function(debug) {
+  var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
+  db.deleteDb();
+  db.createDb();
+  if (debug) debugger;
+
+  var goodDoc = {
+    _id: "good_doc",
+    _attachments: {
+      "Колян.txt": {
+       content_type:"application/octet-stream",
+       data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
+      }
+    }
+  };
+
+  var save_response = db.save(goodDoc);
+  T(save_response.ok);
+
+  var xhr = CouchDB.request("GET", "/test_suite_db/good_doc/Колян.txt");
+  T(xhr.responseText == "This is a base64 encoded text");
+  T(xhr.getResponseHeader("Content-Type") == "application/octet-stream");
+  TEquals("\"aEI7pOYCRBLTRQvvqYrrJQ==\"", xhr.getResponseHeader("Etag"));
+
+  var binAttDoc = {
+    _id: "bin_doc",
+    _attachments:{
+      "foo\x80txt": {
+        content_type:"text/plain",
+        data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
+      }
+    }
+  };
+
+  // inline attachments
+  resp = db.save(binAttDoc);
+  TEquals(true, resp.ok, "attachment_name: inline attachment");
+
+
+  // standalone docs
+  var bin_data = "JHAPDO*AU£PN ){(3u[d 93DQ9¡€])}    ææøo'∂ƒæ≤çæππ•¥∫¶®#†π¶®¥π€ª®˙π8np";
+
+
+  var xhr = (CouchDB.request("PUT", "/test_suite_db/bin_doc3/attachment\x80txt", {
+    headers:{"Content-Type":"text/plain;charset=utf-8"},
+    body:bin_data
+  }));
+
+  var resp = JSON.parse(xhr.responseText);
+  TEquals(201, xhr.status, "attachment_name: standalone API");
+  TEquals(true, resp.ok, "attachment_name: standalone API");
+
+  // bulk docs
+  var docs = { docs: [binAttDoc] };
+
+  var xhr = CouchDB.request("POST", "/test_suite_db/_bulk_docs", {
+    body: JSON.stringify(docs)
+  });
+
+  TEquals(201, xhr.status, "attachment_name: bulk docs");
+
+
+  // leading underscores
+  var binAttDoc = {
+    _id: "bin_doc2",
+    _attachments:{
+      "_foo.txt": {
+        content_type:"text/plain",
+        data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
+      }
+    }
+  };
+
+  try {
+    db.save(binAttDoc);
+    TEquals(1, 2, "Attachment name with leading underscore saved. Should never show!");
+  } catch (e) {
+    TEquals("bad_request", e.error, "attachment_name: leading underscore");
+    TEquals("Attachment name can't start with '_'", e.reason, "attachment_name: leading underscore");
+  }
+
+  // todo: form uploads, waiting for cmlenz' test case for form uploads
+
+};

http://git-wip-us.apache.org/repos/asf/couchdb/blob/3ba4fc0b/share/test/attachment_paths.js
----------------------------------------------------------------------
diff --git a/share/test/attachment_paths.js b/share/test/attachment_paths.js
new file mode 100644
index 0000000..3f6ffb7
--- /dev/null
+++ b/share/test/attachment_paths.js
@@ -0,0 +1,153 @@
+// 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.attachment_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();
+
+    // first just save a regular doc with an attachment that has a slash in the url.
+    // (also gonna run an encoding check case)
+    var binAttDoc = {
+      _id: "bin_doc",
+      _attachments:{
+        "foo/bar.txt": {
+          content_type:"text/plain",
+          data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
+        },
+        "foo%2Fbaz.txt": {
+          content_type:"text/plain",
+          data: "V2UgbGlrZSBwZXJjZW50IHR3byBGLg=="
+        }
+      }
+    };
+
+    T(db.save(binAttDoc).ok);
+
+    var xhr = CouchDB.request("GET", "/"+dbName+"/bin_doc/foo/bar.txt");
+    T(xhr.responseText == "This is a base64 encoded text");
+    T(xhr.getResponseHeader("Content-Type") == "text/plain");
+
+    // lets try it with an escaped attachment id...
+    // weird that it's at two urls
+    var xhr = CouchDB.request("GET", "/"+dbName+"/bin_doc/foo%2Fbar.txt");
+    T(xhr.status == 200);
+    // xhr.responseText == "This is a base64 encoded text"
+
+    var xhr = CouchDB.request("GET", "/"+dbName+"/bin_doc/foo/baz.txt");
+    T(xhr.status == 404);
+
+    var xhr = CouchDB.request("GET", "/"+dbName+"/bin_doc/foo%252Fbaz.txt");
+    T(xhr.status == 200);
+    T(xhr.responseText == "We like percent two F.");
+
+    // require a _rev to PUT
+    var xhr = CouchDB.request("PUT", "/"+dbName+"/bin_doc/foo/attachment.txt", {
+      headers:{"Content-Type":"text/plain;charset=utf-8"},
+      body:"Just some text"
+    });
+    T(xhr.status == 409);
+
+    var xhr = CouchDB.request("PUT", "/"+dbName+"/bin_doc/foo/bar2.txt?rev=" + binAttDoc._rev, {
+      body:"This is no base64 encoded text",
+      headers:{"Content-Type": "text/plain;charset=utf-8"}
+    });
+    T(xhr.status == 201);
+    var rev = JSON.parse(xhr.responseText).rev;
+
+    binAttDoc = db.open("bin_doc");
+
+    T(binAttDoc._attachments["foo/bar.txt"] !== undefined);
+    T(binAttDoc._attachments["foo%2Fbaz.txt"] !== undefined);
+    T(binAttDoc._attachments["foo/bar2.txt"] !== undefined);
+    TEquals("text/plain;charset=utf-8",                   // thank you Safari
+      binAttDoc._attachments["foo/bar2.txt"].content_type.toLowerCase(),
+      "correct content-type"
+    );
+    T(binAttDoc._attachments["foo/bar2.txt"].length == 30);
+
+    //// now repeat the while thing with a design doc
+
+    // first just save a regular doc with an attachment that has a slash in the url.
+    // (also gonna run an encoding check case)
+    var binAttDoc = {
+      _id: "_design/bin_doc",
+      _attachments:{
+        "foo/bar.txt": {
+          content_type:"text/plain",
+          data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
+        },
+        "foo%2Fbaz.txt": {
+          content_type:"text/plain",
+          data: "V2UgbGlrZSBwZXJjZW50IHR3byBGLg=="
+        }
+      }
+    };
+
+    T(db.save(binAttDoc).ok);
+
+    var xhr = CouchDB.request("GET", "/"+dbName+"/_design%2Fbin_doc/foo/bar.txt");
+    T(xhr.responseText == "This is a base64 encoded text");
+    T(xhr.getResponseHeader("Content-Type") == "text/plain");
+
+    // lets try it with an escaped attachment id...
+    // weird that it's at two urls
+    var xhr = CouchDB.request("GET", "/"+dbName+"/_design%2Fbin_doc/foo%2Fbar.txt");
+    T(xhr.responseText == "This is a base64 encoded text");
+    T(xhr.status == 200);
+
+    // err, 3 urls
+    var xhr = CouchDB.request("GET", "/"+dbName+"/_design/bin_doc/foo%2Fbar.txt");
+    T(xhr.responseText == "This is a base64 encoded text");
+    T(xhr.status == 200);
+
+    // I mean um, 4 urls
+    var xhr = CouchDB.request("GET", "/"+dbName+"/_design/bin_doc/foo/bar.txt");
+    T(xhr.responseText == "This is a base64 encoded text");
+    T(xhr.status == 200);
+
+    var xhr = CouchDB.request("GET", "/"+dbName+"/_design%2Fbin_doc/foo/baz.txt");
+    T(xhr.status == 404);
+
+    var xhr = CouchDB.request("GET", "/"+dbName+"/_design%2Fbin_doc/foo%252Fbaz.txt");
+    T(xhr.status == 200);
+    T(xhr.responseText == "We like percent two F.");
+
+    // require a _rev to PUT
+    var xhr = CouchDB.request("PUT", "/"+dbName+"/_design%2Fbin_doc/foo/attachment.txt", {
+      headers:{"Content-Type":"text/plain;charset=utf-8"},
+      body:"Just some text"
+    });
+    T(xhr.status == 409);
+
+    var xhr = CouchDB.request("PUT", "/"+dbName+"/_design%2Fbin_doc/foo/bar2.txt?rev=" + binAttDoc._rev, {
+      body:"This is no base64 encoded text",
+      headers:{"Content-Type": "text/plain;charset=utf-8"}
+    });
+    T(xhr.status == 201);
+    var rev = JSON.parse(xhr.responseText).rev;
+
+    binAttDoc = db.open("_design/bin_doc");
+
+    T(binAttDoc._attachments["foo/bar.txt"] !== undefined);
+    T(binAttDoc._attachments["foo/bar2.txt"] !== undefined);
+    TEquals("text/plain;charset=utf-8",                   // thank you Safari
+      binAttDoc._attachments["foo/bar2.txt"].content_type.toLowerCase(),
+      "correct content-type"
+    );
+    T(binAttDoc._attachments["foo/bar2.txt"].length == 30);
+  }
+};

http://git-wip-us.apache.org/repos/asf/couchdb/blob/3ba4fc0b/share/test/attachment_ranges.js
----------------------------------------------------------------------
diff --git a/share/test/attachment_ranges.js b/share/test/attachment_ranges.js
new file mode 100644
index 0000000..7d9afb5
--- /dev/null
+++ b/share/test/attachment_ranges.js
@@ -0,0 +1,160 @@
+// 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 cacheBust() {
+    return "?anti-cache=" + String(Math.round(Math.random() * 1000000));
+};
+
+couchTests.attachment_ranges = function(debug) {
+    var db = new CouchDB("test_suite_db", {
+        "X-Couch-Full-Commit": "false"
+    });
+    db.deleteDb();
+    db.createDb();
+
+    if (debug) debugger;
+
+    if((typeof window != "undefined") && window.navigator.userAgent.match(/Chrome/)) {
+        // Chrome is broken.
+        return;
+    }
+
+    var binAttDoc = {
+        _id: "bin_doc",
+        _attachments: {
+            "foo.txt": {
+                content_type: "application/octet-stream",
+                data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
+            }
+        }
+    };
+
+    var save_response = db.save(binAttDoc);
+    T(save_response.ok);
+
+    // Fetching the whole entity is a 206.
+    var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc/foo.txt" + cacheBust(), {
+        headers: {
+            "Range": "bytes=0-28"
+        }
+    });
+    TEquals(206, xhr.status, "fetch 0-28");
+    TEquals("This is a base64 encoded text", xhr.responseText);
+    TEquals("bytes 0-28/29", xhr.getResponseHeader("Content-Range"));
+    TEquals("29", xhr.getResponseHeader("Content-Length"));
+
+    // Fetch the whole entity without an end offset is a 200.
+    var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc/foo.txt" + cacheBust(), {
+        headers: {
+            "Range": "bytes=0-"
+        }
+    });
+    TEquals(200, xhr.status, "fetch 0-");
+    TEquals("This is a base64 encoded text", xhr.responseText);
+    TEquals(null, xhr.getResponseHeader("Content-Range"));
+    TEquals("29", xhr.getResponseHeader("Content-Length"));
+
+    // Even if you ask multiple times.
+    var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc/foo.txt" + cacheBust(), {
+        headers: {
+            "Range": "bytes=0-,0-,0-"
+        }
+    });
+    TEquals(200, xhr.status, "multiple 0-'s");
+
+    // Badly formed range header is a 200.
+    var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc/foo.txt" + cacheBust(), {
+        headers: {
+            "Range": "bytes:0-"
+        }
+    });
+    TEquals(200, xhr.status, "fetch with bad range header");
+
+    // Fetch the end of an entity without an end offset is a 206.
+    var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc/foo.txt"  + cacheBust(), {
+        headers: {
+            "Range": "bytes=2-"
+        }
+    });
+    TEquals(206, xhr.status, "fetch 2-");
+    TEquals("is is a base64 encoded text", xhr.responseText);
+    TEquals("bytes 2-28/29", xhr.getResponseHeader("Content-Range"));
+    TEquals("27", xhr.getResponseHeader("Content-Length"));
+
+    // Fetch past the end of the entity is a 206
+    var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc/foo.txt"  + cacheBust(), {
+        headers: {
+            "Range": "bytes=0-29"
+        }
+    });
+    TEquals(206, xhr.status, "fetch 0-29");
+    TEquals("bytes 0-28/29", xhr.getResponseHeader("Content-Range"));
+    TEquals("29", xhr.getResponseHeader("Content-Length"));
+
+    // Fetch first part of entity is a 206
+    var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc/foo.txt" + cacheBust(), {
+        headers: {
+            "Range": "bytes=0-3"
+        }
+    });
+    TEquals(206, xhr.status, "fetch 0-3");
+    TEquals("This", xhr.responseText);
+    TEquals("4", xhr.getResponseHeader("Content-Length"));
+    TEquals("bytes 0-3/29", xhr.getResponseHeader("Content-Range"));
+
+    // Fetch middle of entity is also a 206
+    var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc/foo.txt" + cacheBust(), {
+        headers: {
+            "Range": "bytes=10-15"
+        }
+    });
+    TEquals(206, xhr.status, "fetch 10-15");
+    TEquals("base64", xhr.responseText);
+    TEquals("6", xhr.getResponseHeader("Content-Length"));
+    TEquals("bytes 10-15/29", xhr.getResponseHeader("Content-Range"));
+
+    // Fetch end of entity is also a 206
+    var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc/foo.txt" + cacheBust(), {
+        headers: {
+            "Range": "bytes=-3"
+        }
+    });
+    TEquals(206, xhr.status, "fetch -3");
+    TEquals("ext", xhr.responseText);
+    TEquals("3", xhr.getResponseHeader("Content-Length"));
+    TEquals("bytes 26-28/29", xhr.getResponseHeader("Content-Range"));
+    
+    // backward range is 416
+    var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc/foo.txt" + cacheBust(), {
+       headers: {
+           "Range": "bytes=5-3"
+       }
+    });
+    TEquals(416, xhr.status, "fetch 5-3");
+
+    // range completely outside of entity is 416
+    var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc/foo.txt" + cacheBust(), {
+        headers: {
+            "Range": "bytes=300-310"
+        }
+    });
+    TEquals(416, xhr.status, "fetch 300-310");
+
+    // We ignore a Range header with too many ranges
+    var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc/foo.txt" + cacheBust(), {
+        headers: {
+            "Range": "bytes=0-1,0-1,0-1,0-1,0-1,0-1,0-1,0-1,0-1,0-1"
+        }
+    });
+    TEquals(200, xhr.status, "too many ranges");
+
+};

http://git-wip-us.apache.org/repos/asf/couchdb/blob/3ba4fc0b/share/test/attachment_views.js
----------------------------------------------------------------------
diff --git a/share/test/attachment_views.js b/share/test/attachment_views.js
new file mode 100644
index 0000000..b55aabe
--- /dev/null
+++ b/share/test/attachment_views.js
@@ -0,0 +1,140 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+couchTests.attachment_views= function(debug) {
+
+  var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
+  db.deleteDb();
+  db.createDb();
+  if (debug) debugger;
+
+  // count attachments in a view
+
+  var attachmentData = "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ=";
+
+  db.bulkSave(makeDocs(0, 10));
+
+  db.bulkSave(makeDocs(10, 20, {
+    _attachments:{
+      "foo.txt": {
+        content_type:"text/plain",
+        data: attachmentData
+      }
+    }
+  }));
+
+  db.bulkSave(makeDocs(20, 30, {
+    _attachments:{
+      "foo.txt": {
+        content_type:"text/plain",
+        data: attachmentData
+      },
+      "bar.txt": {
+        content_type:"text/plain",
+        data: attachmentData
+      }
+    }
+  }));
+
+  db.bulkSave(makeDocs(30, 40, {
+    _attachments:{
+      "foo.txt": {
+        content_type:"text/plain",
+        data: attachmentData
+      },
+      "bar.txt": {
+        content_type:"text/plain",
+        data: attachmentData
+      },
+      "baz.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 reduceFunction = function(key, values) {
+    return sum(values);
+  };
+
+  var result = db.query(mapFunction, reduceFunction);
+
+  T(result.rows.length == 1);
+  T(result.rows[0].value == 60);
+
+  var result = db.query(mapFunction, reduceFunction, {
+    startkey:10,
+    endkey:19
+  });
+
+  T(result.rows.length == 1);
+  T(result.rows[0].value == 10);
+
+  var result = db.query(mapFunction, reduceFunction, {
+    startkey:20,
+    endkey:29
+  });
+
+  T(result.rows.length == 1);
+  T(result.rows[0].value == 20);
+
+  var result = db.query(mapFunction, null, {
+    startkey: 30,
+    endkey: 39,
+    include_docs: true
+  });
+
+  T(result.rows.length == 10);
+  T(result.rows[0].value == 3);
+  T(result.rows[0].doc._attachments['baz.txt'].stub === true);
+  T(result.rows[0].doc._attachments['baz.txt'].data === undefined);
+  T(result.rows[0].doc._attachments['baz.txt'].encoding === undefined);
+  T(result.rows[0].doc._attachments['baz.txt'].encoded_length === undefined);
+
+  var result = db.query(mapFunction, null, {
+    startkey: 30,
+    endkey: 39,
+    include_docs: true,
+    attachments: true
+  });
+
+  T(result.rows.length == 10);
+  T(result.rows[0].value == 3);
+  T(result.rows[0].doc._attachments['baz.txt'].data === attachmentData);
+  T(result.rows[0].doc._attachments['baz.txt'].stub === undefined);
+  T(result.rows[0].doc._attachments['baz.txt'].encoding === undefined);
+  T(result.rows[0].doc._attachments['baz.txt'].encoded_length === undefined);
+
+  var result = db.query(mapFunction, null, {
+    startkey: 30,
+    endkey: 39,
+    include_docs: true,
+    att_encoding_info: true
+  });
+
+  T(result.rows.length == 10);
+  T(result.rows[0].value == 3);
+  T(result.rows[0].doc._attachments['baz.txt'].data === undefined);
+  T(result.rows[0].doc._attachments['baz.txt'].stub === true);
+  T(result.rows[0].doc._attachments['baz.txt'].encoding === "gzip");
+  T(result.rows[0].doc._attachments['baz.txt'].encoded_length === 47);
+};

http://git-wip-us.apache.org/repos/asf/couchdb/blob/3ba4fc0b/share/test/attachments.js
----------------------------------------------------------------------
diff --git a/share/test/attachments.js b/share/test/attachments.js
new file mode 100644
index 0000000..2fa08ee
--- /dev/null
+++ b/share/test/attachments.js
@@ -0,0 +1,328 @@
+// 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= function(debug) {
+  var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
+  db.deleteDb();
+  db.createDb();
+  if (debug) debugger;
+
+
+  // MD5 Digests of compressible attachments and therefore Etags
+  // will vary depending on platform gzip implementation.
+  // These MIME types are defined in [attachments] compressible_types
+  var binAttDoc = {
+    _id: "bin_doc",
+    _attachments:{
+      "foo.txt": {
+        content_type:"application/octet-stream",
+        data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
+      }
+    }
+  };
+
+  var save_response = db.save(binAttDoc);
+  T(save_response.ok);
+
+  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") == "application/octet-stream");
+  TEquals("\"aEI7pOYCRBLTRQvvqYrrJQ==\"", xhr.getResponseHeader("Etag"));
+
+  // empty attachment
+  var binAttDoc2 = {
+    _id: "bin_doc2",
+    _attachments:{
+      "foo.txt": {
+        content_type:"text/plain",
+        data: ""
+      }
+    }
+  }
+
+  T(db.save(binAttDoc2).ok);
+
+  var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc2/foo.txt");
+  T(xhr.responseText.length == 0);
+  T(xhr.getResponseHeader("Content-Type") == "text/plain");
+
+  // test RESTful doc API
+
+  var xhr = CouchDB.request("PUT", "/test_suite_db/bin_doc2/foo2.txt?rev=" + binAttDoc2._rev, {
+    body:"This is no base64 encoded text",
+    headers:{"Content-Type": "text/plain;charset=utf-8"}
+  });
+  T(xhr.status == 201);
+  TEquals("/bin_doc2/foo2.txt",
+    xhr.getResponseHeader("Location").substr(-18),
+    "should return Location header to newly created or updated attachment");
+
+  var rev = JSON.parse(xhr.responseText).rev;
+
+  binAttDoc2 = db.open("bin_doc2");
+
+  T(binAttDoc2._attachments["foo.txt"] !== undefined);
+  T(binAttDoc2._attachments["foo2.txt"] !== undefined);
+  TEqualsIgnoreCase("text/plain;charset=utf-8", binAttDoc2._attachments["foo2.txt"].content_type);
+  T(binAttDoc2._attachments["foo2.txt"].length == 30);
+
+  var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc2/foo2.txt");
+  T(xhr.responseText == "This is no base64 encoded text");
+  TEqualsIgnoreCase("text/plain;charset=utf-8", xhr.getResponseHeader("Content-Type"));
+
+  // test without rev, should fail
+  var xhr = CouchDB.request("DELETE", "/test_suite_db/bin_doc2/foo2.txt");
+  T(xhr.status == 409);
+
+  // test with rev, should not fail
+  var xhr = CouchDB.request("DELETE", "/test_suite_db/bin_doc2/foo2.txt?rev=" + rev);
+  T(xhr.status == 200);
+  TEquals(null, xhr.getResponseHeader("Location"),
+    "should not return Location header on DELETE request");
+
+  // test binary data
+  var bin_data = "JHAPDO*AU£PN ){(3u[d 93DQ9¡€])}    ææøo'∂ƒæ≤çæππ•¥∫¶®#†π¶®¥π€ª®˙π8np";
+  var xhr = CouchDB.request("PUT", "/test_suite_db/bin_doc3/attachment.txt", {
+    headers:{"Content-Type":"text/plain;charset=utf-8"},
+    body:bin_data
+  });
+  T(xhr.status == 201);
+  var rev = JSON.parse(xhr.responseText).rev;
+  TEquals('"' + rev + '"', xhr.getResponseHeader("Etag"));
+
+  var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc3/attachment.txt");
+  T(xhr.responseText == bin_data);
+  TEqualsIgnoreCase("text/plain;charset=utf-8", xhr.getResponseHeader("Content-Type"));
+
+  // without rev
+  var xhr = CouchDB.request("PUT", "/test_suite_db/bin_doc3/attachment.txt", {
+    headers:{"Content-Type":"text/plain;charset=utf-8"},
+    body:bin_data
+  });
+  T(xhr.status == 409);
+
+  // with nonexistent rev
+  var xhr = CouchDB.request("PUT", "/test_suite_db/bin_doc3/attachment.txt"  + "?rev=1-adae8575ecea588919bd08eb020c708e", {
+    headers:{"Content-Type":"text/plain;charset=utf-8"},
+    body:bin_data
+  });
+  T(xhr.status == 409);
+
+  // with current rev
+  var xhr = CouchDB.request("PUT", "/test_suite_db/bin_doc3/attachment.txt?rev=" + rev, {
+    headers:{"Content-Type":"text/plain;charset=utf-8"},
+    body:bin_data
+  });
+  T(xhr.status == 201);
+  var rev = JSON.parse(xhr.responseText).rev;
+  TEquals('"' + rev + '"', xhr.getResponseHeader("Etag"));
+
+  var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc3/attachment.txt");
+  T(xhr.responseText == bin_data);
+  TEqualsIgnoreCase("text/plain;charset=utf-8", xhr.getResponseHeader("Content-Type"));
+
+  var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc3/attachment.txt?rev=" + rev);
+  T(xhr.responseText == bin_data);
+  TEqualsIgnoreCase("text/plain;charset=utf-8", xhr.getResponseHeader("Content-Type"));
+
+  var xhr = CouchDB.request("DELETE", "/test_suite_db/bin_doc3/attachment.txt?rev=" + rev);
+  T(xhr.status == 200);
+
+  var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc3/attachment.txt");
+  T(xhr.status == 404);
+
+  // deleted attachment is still accessible with revision
+  var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc3/attachment.txt?rev=" + rev);
+  T(xhr.status == 200);
+  T(xhr.responseText == bin_data);
+  TEqualsIgnoreCase("text/plain;charset=utf-8", xhr.getResponseHeader("Content-Type"));
+
+  // empty attachments
+  var xhr = CouchDB.request("PUT", "/test_suite_db/bin_doc4/attachment.txt", {
+    headers:{"Content-Type":"text/plain;charset=utf-8"},
+    body:""
+  });
+  T(xhr.status == 201);
+  var rev = JSON.parse(xhr.responseText).rev;
+
+  var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc4/attachment.txt");
+  T(xhr.status == 200);
+  T(xhr.responseText.length == 0);
+
+  // overwrite previsously empty attachment
+  var xhr = CouchDB.request("PUT", "/test_suite_db/bin_doc4/attachment.txt?rev=" + rev, {
+    headers:{"Content-Type":"text/plain;charset=utf-8"},
+    body:"This is a string"
+  });
+  T(xhr.status == 201);
+
+  var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc4/attachment.txt");
+  T(xhr.status == 200);
+  T(xhr.responseText == "This is a string");
+
+  // Attachment sparseness COUCHDB-220
+
+  var docs = [];
+  for (var i = 0; i < 5; i++) {
+    var doc = {
+      _id: (i).toString(),
+      _attachments:{
+        "foo.txt": {
+          content_type:"text/plain",
+          data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
+        }
+      }
+    };
+    docs.push(doc);
+  }
+
+  var saved = db.bulkSave(docs);
+  // now delete the docs, and while we are looping over them, remove the
+  // '_rev' field so we can re-create after deletion.
+  var to_up = [];
+  for (i=0;i<saved.length;i++) {
+    to_up.push({'_id': saved[i]['id'], '_rev': saved[i]['rev'], '_deleted': true});
+    delete docs[i]._rev;
+  }
+  // delete them.
+  var saved2 = db.bulkSave(to_up);
+  // re-create them
+  var saved3 = db.bulkSave(docs);
+
+  var before = db.info().disk_size;
+
+  // Compact it.
+  T(db.compact().ok);
+  T(db.last_req.status == 202);
+  // compaction isn't instantaneous, loop until done
+  while (db.info().compact_running) {};
+
+  var after = db.info().disk_size;
+
+  // Compaction should reduce the database slightly, but not
+  // orders of magnitude (unless attachments introduce sparseness)
+  T(after > before * 0.1, "before: " + before + " after: " + after);
+
+
+  // test large attachments - COUCHDB-366
+  var lorem = CouchDB.request("GET", "/_utils/script/test/lorem.txt").responseText;
+
+  var xhr = CouchDB.request("PUT", "/test_suite_db/bin_doc5/lorem.txt", {
+    headers:{"Content-Type":"text/plain;charset=utf-8"},
+    body:lorem
+  });
+  T(xhr.status == 201);
+  var rev = JSON.parse(xhr.responseText).rev;
+
+  var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc5/lorem.txt");
+  T(xhr.responseText == lorem);
+  TEqualsIgnoreCase("text/plain;charset=utf-8", xhr.getResponseHeader("Content-Type"));
+
+  // test large inline attachment too
+  var lorem_b64 = CouchDB.request("GET", "/_utils/script/test/lorem_b64.txt").responseText;
+  var doc = db.open("bin_doc5", {attachments:true});
+  T(doc._attachments["lorem.txt"].data == lorem_b64);
+
+  // test etags for attachments.
+  var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc5/lorem.txt");
+  T(xhr.status == 200);
+  var etag = xhr.getResponseHeader("etag");
+  xhr = CouchDB.request("GET", "/test_suite_db/bin_doc5/lorem.txt", {
+    headers: {"if-none-match": etag}
+  });
+  T(xhr.status == 304);
+
+  // test COUCHDB-497 - empty attachments
+  var xhr = CouchDB.request("PUT", "/test_suite_db/bin_doc5/empty.txt?rev="+rev, {
+    headers:{"Content-Type":"text/plain;charset=utf-8", "Content-Length": "0"},
+    body:""
+  });
+  TEquals(201, xhr.status, "should send 201 Accepted");
+  var rev = JSON.parse(xhr.responseText).rev;
+  var xhr = CouchDB.request("PUT", "/test_suite_db/bin_doc5/empty.txt?rev="+rev, {
+    headers:{"Content-Type":"text/plain;charset=utf-8"}
+  });
+  TEquals(201, xhr.status, "should send 201 Accepted");
+
+  // implicit doc creation allows creating docs with a reserved id. COUCHDB-565
+  var xhr = CouchDB.request("PUT", "/test_suite_db/_nonexistant/attachment.txt", {
+    headers: {"Content-Type":"text/plain;charset=utf-8"},
+    body: "THIS IS AN ATTACHMENT. BOOYA!"
+  });
+  TEquals(400, xhr.status, "should return error code 400 Bad Request");
+
+  // test COUCHDB-809 - stubs should only require the 'stub' field
+  var bin_doc6 = {
+    _id: "bin_doc6",
+    _attachments:{
+      "foo.txt": {
+        content_type:"text/plain",
+        data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
+      }
+    }
+  };
+  T(db.save(bin_doc6).ok);
+  // stub out the attachment
+  bin_doc6._attachments["foo.txt"] = { stub: true };
+  T(db.save(bin_doc6).ok == true);
+
+  // wrong rev pos specified
+  
+  // stub out the attachment with the wrong revpos
+  bin_doc6._attachments["foo.txt"] = { stub: true, revpos: 10};
+  try {
+      T(db.save(bin_doc6).ok == true);
+      T(false && "Shouldn't get here!");
+  } catch (e) {
+      T(e.error == "missing_stub");
+  }
+
+  // test MD5 header
+  var bin_data = "foo bar"
+  var xhr = CouchDB.request("PUT", "/test_suite_db/bin_doc7/attachment.txt", {
+    headers:{"Content-Type":"application/octet-stream",
+             "Content-MD5":"MntvB0NYESObxH4VRDUycw=="},
+    body:bin_data
+  });
+  TEquals(201, xhr.status);
+
+  var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc7/attachment.txt");
+  TEquals('MntvB0NYESObxH4VRDUycw==', xhr.getResponseHeader("Content-MD5"));
+
+  // test attachment via multipart/form-data
+  var bin_doc8 = {
+    _id: "bin_doc8"
+  };
+  T(db.save(bin_doc8).ok);
+  var doc = db.open("bin_doc8");
+  var body = "------TF\r\n" +
+    "Content-Disposition: form-data; name=\"_rev\"\r\n\r\n" +
+    doc._rev + "\r\n" +
+    "------TF\r\n" +
+    "Content-Disposition: form-data; name=\"_attachments\"; filename=\"file.txt\"\r\n" +
+    "Content-Type: text/plain\r\n\r\n" +
+    "contents of file.txt\r\n\r\n" +
+    "------TF--"
+  xhr = CouchDB.request("POST", "/test_suite_db/bin_doc8", {
+    headers: {
+      "Content-Type": "multipart/form-data; boundary=----TF",
+      "Content-Length": body.length
+    },
+    body: body
+  });
+  TEquals(201, xhr.status);
+  TEquals(true, JSON.parse(xhr.responseText).ok);
+  var doc = db.open("bin_doc8");
+  T(doc._attachments);
+  T(doc._attachments['file.txt']);
+
+};

http://git-wip-us.apache.org/repos/asf/couchdb/blob/3ba4fc0b/share/test/attachments_multipart.js
----------------------------------------------------------------------
diff --git a/share/test/attachments_multipart.js b/share/test/attachments_multipart.js
new file mode 100644
index 0000000..6f924a7
--- /dev/null
+++ b/share/test/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/3ba4fc0b/share/test/auth_cache.js
----------------------------------------------------------------------
diff --git a/share/test/auth_cache.js b/share/test/auth_cache.js
new file mode 100644
index 0000000..39b9887
--- /dev/null
+++ b/share/test/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/3ba4fc0b/share/test/basics.js
----------------------------------------------------------------------
diff --git a/share/test/basics.js b/share/test/basics.js
new file mode 100644
index 0000000..993456c
--- /dev/null
+++ b/share/test/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/3ba4fc0b/share/test/batch_save.js
----------------------------------------------------------------------
diff --git a/share/test/batch_save.js b/share/test/batch_save.js
new file mode 100644
index 0000000..a1b0019
--- /dev/null
+++ b/share/test/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/3ba4fc0b/share/test/bulk_docs.js
----------------------------------------------------------------------
diff --git a/share/test/bulk_docs.js b/share/test/bulk_docs.js
new file mode 100644
index 0000000..27a97c8
--- /dev/null
+++ b/share/test/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");
+};


Mime
View raw message