summaryrefslogtreecommitdiff
path: root/share/www
diff options
context:
space:
mode:
authorJohn Christopher Anderson <jchris@apache.org>2009-02-15 23:59:38 +0000
committerJohn Christopher Anderson <jchris@apache.org>2009-02-15 23:59:38 +0000
commit59a60a319bead55aa188e8cb1516d5e5146c4492 (patch)
tree223063b38170d593def1e31ed12b6505dcd70c79 /share/www
parent146bc594aef47b675670e7a7fd7f89b7c6a10843 (diff)
Reorganize the tests into one file per test. No other changes.
git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@744782 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'share/www')
-rw-r--r--share/www/couch_tests.html2
-rw-r--r--share/www/script/couch_test_runner.js12
-rw-r--r--share/www/script/couch_tests.js3366
-rw-r--r--share/www/script/test/all_docs.js100
-rw-r--r--share/www/script/test/attachment_paths.js147
-rw-r--r--share/www/script/test/attachment_views.js98
-rw-r--r--share/www/script/test/attachments.js146
-rw-r--r--share/www/script/test/basics.js144
-rw-r--r--share/www/script/test/bulk_docs.js56
-rw-r--r--share/www/script/test/compact.js56
-rw-r--r--share/www/script/test/config.js66
-rw-r--r--share/www/script/test/conflicts.js60
-rw-r--r--share/www/script/test/content_negotiation.js32
-rw-r--r--share/www/script/test/copy_move_doc.js65
-rw-r--r--share/www/script/test/delayed_commits.js93
-rw-r--r--share/www/script/test/design_docs.js107
-rw-r--r--share/www/script/test/design_options.js63
-rw-r--r--share/www/script/test/design_paths.js72
-rw-r--r--share/www/script/test/etags_head.js78
-rw-r--r--share/www/script/test/etags_views.js87
-rw-r--r--share/www/script/test/invalid_docids.js54
-rw-r--r--share/www/script/test/large_docs.js33
-rw-r--r--share/www/script/test/list_views.js216
-rw-r--r--share/www/script/test/lots_of_docs.js56
-rw-r--r--share/www/script/test/max_dbs_open.js41
-rw-r--r--share/www/script/test/multiple_rows.js80
-rw-r--r--share/www/script/test/purge.js101
-rw-r--r--share/www/script/test/recreate_doc.js39
-rw-r--r--share/www/script/test/reduce.js169
-rw-r--r--share/www/script/test/reduce_false.js45
-rw-r--r--share/www/script/test/replication.js207
-rw-r--r--share/www/script/test/security_validation.js154
-rw-r--r--share/www/script/test/show_documents.js282
-rw-r--r--share/www/script/test/utf8.js41
-rw-r--r--share/www/script/test/uuids.js62
-rw-r--r--share/www/script/test/view_collation.js88
-rw-r--r--share/www/script/test/view_conflicts.js49
-rw-r--r--share/www/script/test/view_errors.js43
-rw-r--r--share/www/script/test/view_include_docs.js111
-rw-r--r--share/www/script/test/view_multi_key_all_docs.js54
-rw-r--r--share/www/script/test/view_multi_key_design.js138
-rw-r--r--share/www/script/test/view_multi_key_temp.js37
-rw-r--r--share/www/script/test/view_pagination.js122
-rw-r--r--share/www/script/test/view_sandboxing.js52
-rw-r--r--share/www/script/test/view_xml.js39
45 files changed, 3836 insertions, 3327 deletions
diff --git a/share/www/couch_tests.html b/share/www/couch_tests.html
index 1bfc7813..5d1b4178 100644
--- a/share/www/couch_tests.html
+++ b/share/www/couch_tests.html
@@ -34,7 +34,7 @@ specific language governing permissions and limitations under the License.
});
});
var testsPath = document.location.toString().split('?')[1];
- loadTests(testsPath||"script/couch_tests.js")
+ loadScript(testsPath||"script/couch_tests.js")
</script>
</head>
<body><div id="wrap">
diff --git a/share/www/script/couch_test_runner.js b/share/www/script/couch_test_runner.js
index 31bd4a4a..ae357aeb 100644
--- a/share/www/script/couch_test_runner.js
+++ b/share/www/script/couch_test_runner.js
@@ -12,8 +12,8 @@
// *********************** Test Framework of Sorts ************************* //
-function loadTests(url) {
- document.write('<script src="'+url+'"></script>');
+function loadScript(url) {
+ if (typeof document != "undefined") document.write('<script src="'+url+'"></script>');
};
function patchTest(fun) {
@@ -67,7 +67,7 @@ function runTest(button, callback, debug) {
var row = currentRow = $(button).parents("tr").get(0);
$("td.status", row).removeClass("error").removeClass("failure").removeClass("success");
$("td", row).text("");
- var testFun = tests[row.id];
+ var testFun = couchTests[row.id];
function run() {
numFailures = 0;
var start = new Date().getTime();
@@ -110,12 +110,12 @@ function showSource(cell) {
var name = $(cell).text();
var win = window.open("", name, "width=700,height=500,resizable=yes,scrollbars=yes");
win.document.title = name;
- $("<pre></pre>").text(tests[name].toString()).appendTo(win.document.body).fadeIn();
+ $("<pre></pre>").text(couchTests[name].toString()).appendTo(win.document.body).fadeIn();
}
function updateTestsListing() {
- for (var name in tests) {
- var testFunction = tests[name];
+ for (var name in couchTests) {
+ var testFunction = couchTests[name];
var row = $("<tr><th></th><td></td><td></td><td></td></tr>")
.find("th").text(name).attr("title", "Show source").click(function() {
showSource(this);
diff --git a/share/www/script/couch_tests.js b/share/www/script/couch_tests.js
index 7cf2c6a4..7b25cb4f 100644
--- a/share/www/script/couch_tests.js
+++ b/share/www/script/couch_tests.js
@@ -19,3329 +19,55 @@ if (typeof window == 'undefined' || !window) {
CouchDB.inBrowser = true;
}
-var tests = {
+var couchTests = {};
- // Do some basic tests.
- basics: function(debug) {
- var result = JSON.parse(CouchDB.request("GET", "/").responseText);
- T(result.couchdb == "Welcome");
-
- var db = new CouchDB("test_suite_db");
- 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;
-
- // Get the database info, check the db_name
- T(db.info().db_name == "test_suite_db");
-
- // 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");
-
- // 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);
-
- // 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);
- };
-
- 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
- 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"})
- });
- 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[4] == resp.id);
- T(locs[3] == "test_suite_db");
- },
-
- delayed_commits: function(debug) {
- var db = new CouchDB("test_suite_db");
- db.deleteDb();
- db.createDb();
- if (debug) debugger;
-
- // By default, couchdb doesn't fully commit documents to disk right away,
- // it waits about a second to batch the full commit flush along with any
- // other updates. If it crashes or is restarted you may lose the most
- // recent commits.
-
- T(db.save({_id:"1",a:2,b:4}).ok);
- T(db.open("1") != null);
-
- restartServer();
-
- T(db.open("1") == null); // lost the update.
- // note if we waited > 1 sec before the restart, the doc would likely
- // commit.
-
-
- // Retry the same thing but with full commits on.
-
- var db2 = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"true"});
-
- T(db2.save({_id:"1",a:2,b:4}).ok);
- T(db2.open("1") != null);
-
- restartServer();
-
- T(db2.open("1") != null);
-
- // You can update but without committing immediately, and then ensure
- // everything is commited in the last step.
-
- T(db.save({_id:"2",a:2,b:4}).ok);
- T(db.open("2") != null);
- T(db.ensureFullCommit().ok);
- restartServer();
-
- T(db.open("2") != null);
-
- // However, it's possible even when flushed, that the server crashed between
- // the update and the commit, and you don't want to check to make sure
- // every doc you updated actually made it to disk. So record the instance
- // start time of the database before the updates and then check it again
- // after the flush (the instance start time is returned by the flush
- // operation). if they are the same, we know everything was updated
- // safely.
-
- // First try it with a crash.
-
- var instanceStartTime = db.info().instance_start_time;
-
- T(db.save({_id:"3",a:2,b:4}).ok);
- T(db.open("3") != null);
-
- restartServer();
-
- var commitResult = db.ensureFullCommit();
- T(commitResult.ok && commitResult.instance_start_time != instanceStartTime);
- // start times don't match, meaning the server lost our change
-
- T(db.open("3") == null); // yup lost it
-
- // retry with no server restart
-
- var instanceStartTime = db.info().instance_start_time;
-
- T(db.save({_id:"4",a:2,b:4}).ok);
- T(db.open("4") != null);
-
- var commitResult = db.ensureFullCommit();
- T(commitResult.ok && commitResult.instance_start_time == instanceStartTime);
- // Successful commit, start times match!
-
- restartServer();
-
- T(db.open("4") != null);
-
- },
-
- all_docs: function(debug) {
- var db = new CouchDB("test_suite_db");
- 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);
-
- // check that the docs show up in the seq view in the order they were created
- var all_seq = db.allDocsBySeq();
- var ids = ["0","3","1","2"];
- for (var i=0; i < all_seq.rows.length; i++) {
- var row = all_seq.rows[i];
- T(row.id == ids[i]);
- };
-
- // it should work in reverse as well
- all_seq = db.allDocsBySeq({descending:true});
- ids = ["2","1","3","0"];
- for (var i=0; i < all_seq.rows.length; i++) {
- var row = all_seq.rows[i];
- T(row.id == ids[i]);
- };
-
- // check that deletions also show up right
- var doc1 = db.open("1");
- var deleted = db.deleteDoc(doc1);
- T(deleted.ok);
- all_seq = db.allDocsBySeq();
-
- // the deletion should make doc id 1 have the last seq num
- T(all_seq.rows.length == 4);
- T(all_seq.rows[3].id == "1");
- T(all_seq.rows[3].value.deleted);
-
- // is this a bug?
- // T(all_seq.rows.length == all_seq.total_rows);
-
- // do an update
- var doc2 = db.open("3");
- doc2.updated = "totally";
- db.save(doc2);
- all_seq = db.allDocsBySeq();
-
- // the update should make doc id 3 have the last seq num
- T(all_seq.rows.length == 4);
- T(all_seq.rows[3].id == "3");
-
- // ok now lets see what happens with include docs
- all_seq = db.allDocsBySeq({include_docs: true});
- T(all_seq.rows.length == 4);
- T(all_seq.rows[3].id == "3");
- T(all_seq.rows[3].doc.updated == "totally");
-
- // and on the deleted one, no doc
- T(all_seq.rows[2].value.deleted);
- T(!all_seq.rows[2].doc);
-
- // 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);
- },
-
- // Do some edit conflict detection tests
- conflicts: function(debug) {
- var db = new CouchDB("test_suite_db");
- 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");
- }
-
- // 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");
- }
-
- // 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.
- },
-
- recreate_doc: function(debug) {
- var db = new CouchDB("test_suite_db");
- db.deleteDb();
- db.createDb();
- if (debug) debugger;
-
- // First create a new document with the ID "foo", and delete it again
- var doc = {_id: "foo", a: "bar", b: 42};
- T(db.save(doc).ok);
- T(db.deleteDoc(doc).ok);
-
- // Now create a new document with the same ID, save it, and then modify it
- // This should work fine, but currently results in a conflict error, at
- // least "sometimes"
- for (var i = 0; i < 10; i++) {
- doc = {_id: "foo"};
- T(db.save(doc).ok);
- doc = db.open("foo");
- doc.a = "baz";
- try {
- T(db.save(doc).ok);
- } finally {
- // And now, we can't even delete the document anymore :/
- T(db.deleteDoc(doc).rev != undefined);
- }
- }
- },
-
- copy_move_doc: function(debug) {
- var db = new CouchDB("test_suite_db");
- db.deleteDb();
- db.createDb();
- if (debug) debugger;
-
- // copy a doc
- T(db.save({_id:"doc_to_be_copied",v:1}).ok);
- var xhr = CouchDB.request("COPY", "/test_suite_db/doc_to_be_copied", {
- headers: {"Destination":"doc_that_was_copied"}
- });
-
- T(xhr.status == 201);
- T(db.open("doc_that_was_copied").v == 1);
-
- // move a doc
-
- // test error condition
- var xhr = CouchDB.request("MOVE", "/test_suite_db/doc_to_be_copied", {
- headers: {"Destination":"doc_that_was_moved"}
- });
- T(xhr.status == 400); // bad request, MOVE requires source rev.
-
- var rev = db.open("doc_to_be_copied")._rev;
- var xhr = CouchDB.request("MOVE", "/test_suite_db/doc_to_be_copied?rev=" + rev, {
- headers: {"Destination":"doc_that_was_moved"}
- });
-
- T(xhr.status == 201);
- T(db.open("doc_that_was_moved").v == 1);
- T(db.open("doc_to_be_copied") == null);
-
- // COPY with existing target
- T(db.save({_id:"doc_to_be_copied",v:1}).ok);
- var doc = db.save({_id:"doc_to_be_overwritten",v:2});
- T(doc.ok);
-
- // error condition
- var xhr = CouchDB.request("COPY", "/test_suite_db/doc_to_be_copied", {
- headers: {"Destination":"doc_to_be_overwritten"}
- });
- T(xhr.status == 409); // conflict
-
- var rev = db.open("doc_to_be_overwritten")._rev;
- var xhr = CouchDB.request("COPY", "/test_suite_db/doc_to_be_copied", {
- headers: {"Destination":"doc_to_be_overwritten?rev=" + rev}
- });
- T(xhr.status == 201);
-
- var over = db.open("doc_to_be_overwritten");
- T(rev != over._rev);
- T(over.v == 1);
- },
-
- uuids: function(debug) {
- var testHashBustingHeaders = function(xhr) {
- T(xhr.getResponseHeader("Cache-Control").match(/no-cache/));
- T(xhr.getResponseHeader("Pragma") == "no-cache");
-
- var currentTime = new Date();
- var expiresHeader = Date.parse(xhr.getResponseHeader("Expires"));
- var dateHeader = Date.parse(xhr.getResponseHeader("Date"));
-
- T(expiresHeader < currentTime);
- T(currentTime - dateHeader < 3000);
- };
-
- var db = new CouchDB("test_suite_db");
- db.deleteDb();
- db.createDb();
- if (debug) debugger;
-
- // a single UUID without an explicit count
- var xhr = CouchDB.request("GET", "/_uuids");
- T(xhr.status == 200);
- var result = JSON.parse(xhr.responseText);
- T(result.uuids.length == 1);
- var first = result.uuids[0];
- testHashBustingHeaders(xhr);
-
- // a single UUID with an explicit count
- xhr = CouchDB.request("GET", "/_uuids?count=1");
- T(xhr.status == 200);
- result = JSON.parse(xhr.responseText);
- T(result.uuids.length == 1);
- var second = result.uuids[0];
- T(first != second);
-
- // no collisions with 1,000 UUIDs
- xhr = CouchDB.request("GET", "/_uuids?count=1000");
- T(xhr.status == 200);
- result = JSON.parse(xhr.responseText);
- T( result.uuids.length == 1000 );
- var seen = {};
- for(var i in result.uuids) {
- var id = result.uuids[i];
- T(seen[id] === undefined);
- seen[id] = 1;
- }
-
- // ensure we return a 405 on POST
- xhr = CouchDB.request("POST", "/_uuids?count=1000");
- T(xhr.status == 405);
- },
-
- bulk_docs: function(debug) {
- var db = new CouchDB("test_suite_db");
- db.deleteDb();
- db.createDb();
- if (debug) debugger;
-
- var docs = makeDocs(5);
-
- // Create the docs
- var result = db.bulkSave(docs);
- T(result.ok);
- T(result.new_revs.length == 5);
- for (var i = 0; i < 5; i++) {
- T(result.new_revs[i].id == docs[i]._id);
- T(result.new_revs[i].rev);
- docs[i].string = docs[i].string + ".00";
- }
-
- // Update the docs
- result = db.bulkSave(docs);
- T(result.ok);
- T(result.new_revs.length == 5);
- for (i = 0; i < 5; i++) {
- T(result.new_revs[i].id == i.toString());
- docs[i]._deleted = true;
- }
-
- // Delete the docs
- result = db.bulkSave(docs);
- T(result.ok);
- T(result.new_revs.length == 5);
- for (i = 0; i < 5; i++) {
- T(db.open(docs[i]._id) == null);
- }
-
- // 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"}]})
- });
- result = JSON.parse(req.responseText);
-
- T(result.new_revs[0].id != "");
- T(result.new_revs[0].rev != "");
- },
-
- // test saving a semi-large quanitity of documents and do some view queries.
- lots_of_docs: function(debug) {
- var db = new CouchDB("test_suite_db");
- db.deleteDb();
- db.createDb();
- if (debug) debugger;
-
- // keep number lowish for now to keep tests fasts. Crank up manually to
- // to really test.
- var numDocsToCreate = 500;
-
- for(var i=0; i < numDocsToCreate; i += 100) {
- var createNow = Math.min(numDocsToCreate - i, 100);
- var docs = makeDocs(i, i + createNow);
- T(db.bulkSave(docs).ok);
- }
-
- // query all documents, and return the doc.integer member as a key.
- results = db.query(function(doc){ emit(doc.integer, null) });
-
- T(results.total_rows == numDocsToCreate);
-
- // validate the keys are ordered ascending
- for(var i=0; i<numDocsToCreate; i++) {
- T(results.rows[i].key==i);
- }
-
- // do the query again, but with descending output
- results = db.query(function(doc){ emit(doc.integer, null) }, null, {
- descending: true
- });
-
- T(results.total_rows == numDocsToCreate);
-
- // validate the keys are ordered descending
- for(var i=0; i<numDocsToCreate; i++) {
- T(results.rows[numDocsToCreate-1-i].key==i);
- }
-
- // Check _all_docs with descending=true again (now that there are many docs)
- var desc = db.allDocs({descending:true});
- T(desc.total_rows == desc.rows.length);
- },
-
- reduce: function(debug) {
- var db = new CouchDB("test_suite_db");
- db.deleteDb();
- db.createDb();
- if (debug) debugger;
- var numDocs = 500
- var docs = makeDocs(1,numDocs + 1);
- T(db.bulkSave(docs).ok);
- var summate = function(N) {return (N+1)*N/2;};
-
- var map = function (doc) {
- emit(doc.integer, doc.integer);
- emit(doc.integer, doc.integer)};
- var reduce = function (keys, values) { return sum(values); };
- var result = db.query(map, reduce);
- T(result.rows[0].value == 2*summate(numDocs));
-
- result = db.query(map, reduce, {startkey: 4, endkey: 4});
- T(result.rows[0].value == 8);
-
- result = db.query(map, reduce, {startkey: 4, endkey: 5});
- T(result.rows[0].value == 18);
-
- result = db.query(map, reduce, {startkey: 4, endkey: 6});
- T(result.rows[0].value == 30);
-
- result = db.query(map, reduce, {group:true, limit:3});
- T(result.rows[0].value == 2);
- T(result.rows[1].value == 4);
- T(result.rows[2].value == 6);
-
- for(var i=1; i<numDocs/2; i+=30) {
- result = db.query(map, reduce, {startkey: i, endkey: numDocs - i});
- T(result.rows[0].value == 2*(summate(numDocs-i) - summate(i-1)));
- }
-
- db.deleteDb();
- db.createDb();
-
- for(var i=1; i <= 5; i++) {
-
- for(var j=0; j < 10; j++) {
- // these docs are in the order of the keys collation, for clarity
- var docs = [];
- docs.push({keys:["a"]});
- docs.push({keys:["a"]});
- docs.push({keys:["a", "b"]});
- docs.push({keys:["a", "b"]});
- docs.push({keys:["a", "b", "c"]});
- docs.push({keys:["a", "b", "d"]});
- docs.push({keys:["a", "c", "d"]});
- docs.push({keys:["d"]});
- docs.push({keys:["d", "a"]});
- docs.push({keys:["d", "b"]});
- docs.push({keys:["d", "c"]});
- T(db.bulkSave(docs).ok);
- T(db.info().doc_count == ((i - 1) * 10 * 11) + ((j + 1) * 11));
- }
-
- map = function (doc) {emit(doc.keys, 1)};
- reduce = function (keys, values) { return sum(values); };
-
- var results = db.query(map, reduce, {group:true});
-
- //group by exact key match
- T(equals(results.rows[0], {key:["a"],value:20*i}));
- T(equals(results.rows[1], {key:["a","b"],value:20*i}));
- T(equals(results.rows[2], {key:["a", "b", "c"],value:10*i}));
- T(equals(results.rows[3], {key:["a", "b", "d"],value:10*i}));
-
- // test to make sure group reduce and limit params provide valid json
- var results = db.query(map, reduce, {group: true, limit: 2});
- T(equals(results.rows[0], {key: ["a"], value: 20*i}));
- T(equals(results.rows.length, 2));
-
- //group by the first element in the key array
- var results = db.query(map, reduce, {group_level:1});
- T(equals(results.rows[0], {key:["a"],value:70*i}));
- T(equals(results.rows[1], {key:["d"],value:40*i}));
-
- //group by the first 2 elements in the key array
- var results = db.query(map, reduce, {group_level:2});
- T(equals(results.rows[0], {key:["a"],value:20*i}));
- T(equals(results.rows[1], {key:["a","b"],value:40*i}));
- T(equals(results.rows[2], {key:["a","c"],value:10*i}));
- T(equals(results.rows[3], {key:["d"],value:10*i}));
- T(equals(results.rows[4], {key:["d","a"],value:10*i}));
- T(equals(results.rows[5], {key:["d","b"],value:10*i}));
- T(equals(results.rows[6], {key:["d","c"],value:10*i}));
- }
-
- // now test out more complex reductions that need to use the combine option.
-
- db.deleteDb();
- db.createDb();
-
-
- var map = function (doc) {emit(doc.val, doc.val)};
- var reduceCombine = function (keys, values, rereduce) {
- // This computes the standard deviation of the mapped results
- var stdDeviation=0.0;
- var count=0;
- var total=0.0;
- var sqrTotal=0.0;
-
- if (!rereduce) {
- // This is the reduce phase, we are reducing over emitted values from
- // the map functions.
- for(var i in values) {
- total = total + values[i];
- sqrTotal = sqrTotal + (values[i] * values[i]);
- }
- count = values.length;
- }
- else {
- // This is the rereduce phase, we are re-reducing previosuly
- // reduced values.
- for(var i in values) {
- count = count + values[i].count;
- total = total + values[i].total;
- sqrTotal = sqrTotal + values[i].sqrTotal;
- }
- }
-
- var variance = (sqrTotal - ((total * total)/count)) / count;
- stdDeviation = Math.sqrt(variance);
-
- // the reduce result. It contains enough information to be rereduced
- // with other reduce results.
- return {"stdDeviation":stdDeviation,"count":count,
- "total":total,"sqrTotal":sqrTotal};
- };
-
- // Save a bunch a docs.
-
- for(var i=0; i < 10; i++) {
- var docs = [];
- docs.push({val:10});
- docs.push({val:20});
- docs.push({val:30});
- docs.push({val:40});
- docs.push({val:50});
- docs.push({val:60});
- docs.push({val:70});
- docs.push({val:80});
- docs.push({val:90});
- docs.push({val:100});
- T(db.bulkSave(docs).ok);
- }
-
- var results = db.query(map, reduceCombine);
-
- var difference = results.rows[0].value.stdDeviation - 28.722813232690143;
- // account for floating point rounding error
- T(Math.abs(difference) < 0.0000000001);
-
- },
-
- reduce_false: function(debug) {
- var db = new CouchDB("test_suite_db");
- db.deleteDb();
- db.createDb();
- if (debug) debugger;
-
- var numDocs = 5;
- var docs = makeDocs(1,numDocs + 1);
- T(db.bulkSave(docs).ok);
- var summate = function(N) {return (N+1)*N/2;};
-
- var designDoc = {
- _id:"_design/test",
- language: "javascript",
- views: {
- summate: {map:"function (doc) {emit(doc.integer, doc.integer)};",
- reduce:"function (keys, values) { return sum(values); };"},
- }
- };
- T(db.save(designDoc).ok);
-
- // Test that the reduce works
- var res = db.view('test/summate');
- T(res.rows.length == 1 && res.rows[0].value == summate(5));
-
- //Test that we get our docs back
- res = db.view('test/summate', {reduce: false});
- T(res.rows.length == 5);
- for(var i=0; i<5; i++)
- {
- T(res.rows[i].value == i+1);
- }
- },
-
- design_options: function(debug) {
- var db = new CouchDB("test_suite_db");
- db.deleteDb();
- db.createDb();
- if (debug) debugger;
-
- //// test the includes_design option
- var map = "function (doc) {emit(null, doc._id);}";
-
- // we need a design doc even to test temp views with it
- var designDoc = {
- _id:"_design/fu",
- language: "javascript",
- options: {
- include_design: true
- },
- views: {
- data: {"map": map}
- }
- };
- T(db.save(designDoc).ok);
-
- // should work for temp views
- var rows = db.query(map, null, {options:{include_design: true}}).rows;
- T(rows.length == 1);
- T(rows[0].value == "_design/fu");
-
- rows = db.query(map).rows;
- T(rows.length == 0);
-
- // when true, should include design docs in views
- rows = db.view("fu/data").rows;
- T(rows.length == 1);
- T(rows[0].value == "_design/fu");
-
- // when false, should not
- designDoc.options.include_design = false;
- delete designDoc._rev;
- designDoc._id = "_design/bingo";
- T(db.save(designDoc).ok);
- rows = db.view("bingo/data").rows;
- T(rows.length == 0);
-
- // should default to false
- delete designDoc.options;
- delete designDoc._rev;
- designDoc._id = "_design/bango";
- T(db.save(designDoc).ok);
- rows = db.view("bango/data").rows;
- T(rows.length == 0);
- },
-
- multiple_rows: function(debug) {
- var db = new CouchDB("test_suite_db");
- db.deleteDb();
- db.createDb();
- if (debug) debugger;
-
- var nc = {_id:"NC", cities:["Charlotte", "Raleigh"]};
- var ma = {_id:"MA", cities:["Boston", "Lowell", "Worcester", "Cambridge", "Springfield"]};
- var fl = {_id:"FL", cities:["Miami", "Tampa", "Orlando", "Springfield"]};
-
- T(db.save(nc).ok);
- T(db.save(ma).ok);
- T(db.save(fl).ok);
-
- var generateListOfCitiesAndState = "function(doc) {" +
- " for (var i = 0; i < doc.cities.length; i++)" +
- " emit(doc.cities[i] + \", \" + doc._id, null);" +
- "}";
-
- var results = db.query(generateListOfCitiesAndState);
- var rows = results.rows;
-
- T(rows[0].key == "Boston, MA");
- T(rows[1].key == "Cambridge, MA");
- T(rows[2].key == "Charlotte, NC");
- T(rows[3].key == "Lowell, MA");
- T(rows[4].key == "Miami, FL");
- T(rows[5].key == "Orlando, FL");
- T(rows[6].key == "Raleigh, NC");
- T(rows[7].key == "Springfield, FL");
- T(rows[8].key == "Springfield, MA");
- T(rows[9].key == "Tampa, FL");
- T(rows[10].key == "Worcester, MA");
-
- // add another city to NC
- nc.cities.push("Wilmington");
- T(db.save(nc).ok);
-
- var results = db.query(generateListOfCitiesAndState);
- var rows = results.rows;
-
- T(rows[0].key == "Boston, MA");
- T(rows[1].key == "Cambridge, MA");
- T(rows[2].key == "Charlotte, NC");
- T(rows[3].key == "Lowell, MA");
- T(rows[4].key == "Miami, FL");
- T(rows[5].key == "Orlando, FL");
- T(rows[6].key == "Raleigh, NC");
- T(rows[7].key == "Springfield, FL");
- T(rows[8].key == "Springfield, MA");
- T(rows[9].key == "Tampa, FL");
- T(rows[10].key == "Wilmington, NC");
- T(rows[11].key == "Worcester, MA");
-
- // now delete MA
- T(db.deleteDoc(ma).ok);
-
- var results = db.query(generateListOfCitiesAndState);
- var rows = results.rows;
-
- T(rows[0].key == "Charlotte, NC");
- T(rows[1].key == "Miami, FL");
- T(rows[2].key == "Orlando, FL");
- T(rows[3].key == "Raleigh, NC");
- T(rows[4].key == "Springfield, FL");
- T(rows[5].key == "Tampa, FL");
- T(rows[6].key == "Wilmington, NC");
- },
-
- large_docs: function(debug) {
- var db = new CouchDB("test_suite_db");
- db.deleteDb();
- db.createDb();
- if (debug) debugger;
-
- var longtext = "0123456789\n";
-
- for (var i=0; i<10; i++) {
- longtext = longtext + longtext
- }
- T(db.save({"longtest":longtext}).ok);
- T(db.save({"longtest":longtext}).ok);
- T(db.save({"longtest":longtext}).ok);
- T(db.save({"longtest":longtext}).ok);
-
- // query all documents, and return the doc.foo member as a key.
- results = db.query(function(doc){
- emit(null, doc.longtest);
- });
- },
-
- utf8: function(debug) {
- var db = new CouchDB("test_suite_db");
- db.deleteDb();
- db.createDb();
- if (debug) debugger;
-
- var texts = [];
-
- texts[0] = "1. Ascii: hello"
- texts[1] = "2. Russian: На берегу пустынных волн"
- texts[2] = "3. Math: ∮ E⋅da = Q, n → ∞, ∑ f(i) = ∏ g(i),"
- texts[3] = "4. Geek: STARGΛ̊TE SG-1"
- texts[4] = "5. Braille: ⡌⠁⠧⠑ ⠼⠁⠒ ⡍⠜⠇⠑⠹⠰⠎ ⡣⠕⠌"
-
- // check that we can save a reload with full fidelity
- for (var i=0; i<texts.length; i++) {
- T(db.save({_id:i.toString(), text:texts[i]}).ok);
- }
-
- for (var i=0; i<texts.length; i++) {
- T(db.open(i.toString()).text == texts[i]);
- }
-
- // check that views and key collation don't blow up
- var rows = db.query(function(doc) { emit(null, doc.text) }).rows;
- for (var i=0; i<texts.length; i++) {
- T(rows[i].value == texts[i]);
- }
- },
-
- attachments: function(debug) {
- var db = new CouchDB("test_suite_db");
- db.deleteDb();
- db.createDb();
- if (debug) debugger;
-
- var binAttDoc = {
- _id: "bin_doc",
- _attachments:{
- "foo.txt": {
- content_type:"text/plain",
- 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") == "text/plain");
- T(xhr.getResponseHeader("Etag") == save_response.rev);
-
- // 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);
- var rev = JSON.parse(xhr.responseText).rev;
-
- binAttDoc2 = db.open("bin_doc2");
-
- T(binAttDoc2._attachments["foo.txt"] !== undefined);
- T(binAttDoc2._attachments["foo2.txt"] !== undefined);
- T(binAttDoc2._attachments["foo2.txt"].content_type == "text/plain;charset=utf-8");
- 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");
- T(xhr.getResponseHeader("Content-Type") == "text/plain;charset=utf-8");
-
- // 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);
-
-
- // 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;
-
- var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc3/attachment.txt");
- T(xhr.responseText == bin_data);
- T(xhr.getResponseHeader("Content-Type") == "text/plain;charset=utf-8");
-
- 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);
-
- 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;
-
- var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc3/attachment.txt");
- T(xhr.responseText == bin_data);
- T(xhr.getResponseHeader("Content-Type") == "text/plain;charset=utf-8");
-
- var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc3/attachment.txt?rev=" + rev);
- T(xhr.responseText == bin_data);
- T(xhr.getResponseHeader("Content-Type") == "text/plain;charset=utf-8");
-
- 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?rev=" + rev);
- T(xhr.status == 404);
-
- // 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_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);
- T(binAttDoc._attachments["foo/bar2.txt"].content_type == "text/plain;charset=utf-8");
- 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);
- T(binAttDoc._attachments["foo/bar2.txt"].content_type == "text/plain;charset=utf-8");
- T(binAttDoc._attachments["foo/bar2.txt"].length == 30);
- }
- },
-
- attachment_views: function(debug) {
-
- var db = new CouchDB("test_suite_db");
- db.deleteDb();
- db.createDb();
- if (debug) debugger;
-
- // count attachments in a view
-
- db.bulkSave(makeDocs(0, 10));
-
- db.bulkSave(makeDocs(10, 20, {
- _attachments:{
- "foo.txt": {
- content_type:"text/plain",
- data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
- }
- }
- }));
-
- db.bulkSave(makeDocs(20, 30, {
- _attachments:{
- "foo.txt": {
- content_type:"text/plain",
- data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
- },
- "bar.txt": {
- content_type:"text/plain",
- data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
- }
- }
- }));
-
- db.bulkSave(makeDocs(30, 40, {
- _attachments:{
- "foo.txt": {
- content_type:"text/plain",
- data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
- },
- "bar.txt": {
- content_type:"text/plain",
- data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
- },
- "baz.txt": {
- content_type:"text/plain",
- data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
- }
- }
- }));
-
- 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);
-
- },
-
- design_paths : function(debug) {
- if (debug) debugger;
- var dbNames = ["test_suite_db", "test_suite_db/with_slashes"];
- for (var i=0; i < dbNames.length; i++) {
- var db = new CouchDB(dbNames[i]);
- var dbName = encodeURIComponent(dbNames[i]);
- db.deleteDb();
- db.createDb();
-
- // create a ddoc w bulk_docs
- db.bulkSave([{
- _id : "_design/test",
- views : {
- "testing" : {
- "map" : "function(){emit(1,1)}"
- }
- }
- }]);
-
- // ddoc is getable
- var xhr = CouchDB.request("GET", "/"+dbName+"/_design/test");
- var resp = JSON.parse(xhr.responseText);
- T(resp._id == "_design/test");
-
- // it's at 2 urls...
- var xhr = CouchDB.request("GET", "/"+dbName+"/_design%2Ftest");
- var resp = JSON.parse(xhr.responseText);
- T(resp._id == "_design/test");
-
- // ensure that views are addressable
- resp = db.view("test/testing")
- T(resp.total_rows == 0)
-
- // create a ddoc by putting to url with raw slash
- var xhr = CouchDB.request("PUT", "/"+dbName+"/_design/test2",{
- body : JSON.stringify({
- _id : "_design/test2",
- views : {
- "testing" : {
- "map" : "function(){emit(1,1)}"
- }
- }
- })
- });
-
- // ddoc is getable
- var xhr = CouchDB.request("GET", "/"+dbName+"/_design/test2");
- var resp = JSON.parse(xhr.responseText);
- T(resp._id == "_design/test2");
-
- // it's at 2 urls...
- var xhr = CouchDB.request("GET", "/"+dbName+"/_design%2Ftest2");
- var resp = JSON.parse(xhr.responseText);
- T(resp._id == "_design/test2");
-
- // ensure that views are addressable
- resp = db.view("test2/testing");
- T(resp.total_rows == 0);
- };
- },
-
- content_negotiation: function(debug) {
- var db = new CouchDB("test_suite_db");
- db.deleteDb();
- db.createDb();
- if (debug) debugger;
- var xhr;
-
- xhr = CouchDB.request("GET", "/test_suite_db/");
- T(xhr.getResponseHeader("Content-Type") == "text/plain;charset=utf-8");
-
- xhr = CouchDB.request("GET", "/test_suite_db/", {
- headers: {"Accept": "text/html;text/plain;*/*"}
- });
- T(xhr.getResponseHeader("Content-Type") == "text/plain;charset=utf-8");
-
- xhr = CouchDB.request("GET", "/test_suite_db/", {
- headers: {"Accept": "application/json"}
- });
- T(xhr.getResponseHeader("Content-Type") == "application/json");
- },
-
- design_docs: function(debug) {
- var db = new CouchDB("test_suite_db");
- db.deleteDb();
- db.createDb();
- if (debug) debugger;
-
- var numDocs = 500;
-
- function makebigstring(power) {
- var str = "a";
- while(power-- > 0) {
- str = str + str;
- }
- return str;
- }
-
- var designDoc = {
- _id:"_design/test", // turn off couch.js id escaping?
- language: "javascript",
- views: {
- all_docs_twice: {map: "function(doc) { emit(doc.integer, null); emit(doc.integer, null) }"},
- no_docs: {map: "function(doc) {}"},
- single_doc: {map: "function(doc) { if (doc._id == \"1\") { emit(1, null) }}"},
- summate: {map:"function (doc) {emit(doc.integer, doc.integer)};",
- reduce:"function (keys, values) { return sum(values); };"},
- summate2: {map:"function (doc) {emit(doc.integer, doc.integer)};",
- reduce:"function (keys, values) { return sum(values); };"},
- huge_src_and_results: {map: "function(doc) { if (doc._id == \"1\") { emit(\"" + makebigstring(16) + "\", null) }}",
- reduce:"function (keys, values) { return \"" + makebigstring(16) + "\"; };"}
- }
- }
- T(db.save(designDoc).ok);
-
- T(db.bulkSave(makeDocs(1, numDocs + 1)).ok);
-
- // test that the _all_docs view returns correctly with keys
- var results = db.allDocs({startkey:"_design", endkey:"_design0"});
- T(results.rows.length == 1);
-
- for (var loop = 0; loop < 2; loop++) {
- var rows = db.view("test/all_docs_twice").rows;
- for (var i = 0; i < numDocs; i++) {
- T(rows[2*i].key == i+1);
- T(rows[(2*i)+1].key == i+1);
- }
- T(db.view("test/no_docs").total_rows == 0)
- T(db.view("test/single_doc").total_rows == 1)
- T(db.ensureFullCommit().ok);
- restartServer();
- };
-
- // test when language not specified, Javascript is implied
- var designDoc2 = {
- _id:"_design/test2",
- // language: "javascript",
- views: {
- single_doc: {map: "function(doc) { if (doc._id == \"1\") { emit(1, null) }}"}
- }
- };
-
- T(db.save(designDoc2).ok);
- T(db.view("test2/single_doc").total_rows == 1);
-
- var summate = function(N) {return (N+1)*N/2;};
- var result = db.view("test/summate");
- T(result.rows[0].value == summate(numDocs));
-
- result = db.view("test/summate", {startkey:4,endkey:4});
- T(result.rows[0].value == 4);
-
- result = db.view("test/summate", {startkey:4,endkey:5});
- T(result.rows[0].value == 9);
-
- result = db.view("test/summate", {startkey:4,endkey:6});
- T(result.rows[0].value == 15);
-
- // Verify that a shared index (view def is an exact copy of "summate")
- // does not confuse the reduce stage
- result = db.view("test/summate2", {startkey:4,endkey:6});
- T(result.rows[0].value == 15);
-
- for(var i=1; i<numDocs/2; i+=30) {
- result = db.view("test/summate", {startkey:i,endkey:numDocs-i});
- T(result.rows[0].value == summate(numDocs-i) - summate(i-1));
- }
-
- T(db.deleteDoc(designDoc).ok);
- T(db.open(designDoc._id) == null);
- T(db.view("test/no_docs") == null);
-
- T(db.ensureFullCommit().ok);
- restartServer();
- T(db.open(designDoc._id) == null);
- T(db.view("test/no_docs") == null);
- },
-
- invalid_docids: function(debug) {
- var db = new CouchDB("test_suite_db");
- db.deleteDb();
- db.createDb();
- if (debug) debugger;
-
- // Test _local explicitly first.
- T(db.save({"_id": "_local/foo"}).ok);
- T(db.open("_local/foo")._id == "_local/foo");
-
- //Test non-string
- try {
- db.save({"_id": 1});
- T(1 == 0);
- } catch(e) {
- T(db.last_req.status == 400);
- T(e.error == "invalid_doc");
- }
-
- // Test invalid _prefix
- try {
- db.save({"_id": "_invalid"});
- T(1 == 0);
- } catch(e) {
- T(db.last_req.status == 400);
- T(e.error == "invalid_doc");
- }
-
- // Test _bulk_docs explicitly.
- var docs = [{"_id": "_design/foo"}, {"_id": "_local/bar"}];
- T(db.bulkSave(docs).ok);
- docs.forEach(function(d) {T(db.open(d._id)._id == d._id);});
-
- docs = [{"_id": "_invalid"}];
- try {
- db.bulkSave(docs);
- T(1 == 0);
- } catch(e) {
- T(db.last_req.status == 400);
- T(e.error == "invalid_doc");
- }
- },
-
- view_collation: function(debug) {
- var db = new CouchDB("test_suite_db");
- db.deleteDb();
- db.createDb();
- if (debug) debugger;
-
- // NOTE, the values are already in their correct sort order. Consider this
- // a specification of collation of json types.
-
- var values = [];
-
- // special values sort before all other types
- values.push(null);
- values.push(false);
- values.push(true);
-
- // then numbers
- values.push(1);
- values.push(2);
- values.push(3.0);
- values.push(4);
-
- // then text, case sensitive
- values.push("a");
- values.push("A");
- values.push("aa");
- values.push("b");
- values.push("B");
- values.push("ba");
- values.push("bb");
-
- // then arrays. compared element by element until different.
- // Longer arrays sort after their prefixes
- values.push(["a"]);
- values.push(["b"]);
- values.push(["b","c"]);
- values.push(["b","c", "a"]);
- values.push(["b","d"]);
- values.push(["b","d", "e"]);
-
- // then object, compares each key value in the list until different.
- // larger objects sort after their subset objects.
- values.push({a:1});
- values.push({a:2});
- values.push({b:1});
- values.push({b:2});
- values.push({b:2, a:1}); // Member order does matter for collation.
- // CouchDB preserves member order
- // but doesn't require that clients will.
- // (this test might fail if used with a js engine
- // that doesn't preserve order)
- values.push({b:2, c:2});
-
- for (var i=0; i<values.length; i++) {
- db.save({_id:(i).toString(), foo:values[i]});
- }
-
- var queryFun = function(doc) { emit(doc.foo, null); };
- var rows = db.query(queryFun).rows;
- for (i=0; i<values.length; i++) {
- T(equals(rows[i].key, values[i]));
- }
-
- // everything has collated correctly. Now to check the descending output
- rows = db.query(queryFun, null, {descending: true}).rows;
- for (i=0; i<values.length; i++) {
- T(equals(rows[i].key, values[values.length - 1 -i]));
- }
-
- // now check the key query args
- for (i=1; i<values.length; i++) {
- var queryOptions = {key:values[i]};
- rows = db.query(queryFun, null, queryOptions).rows;
- T(rows.length == 1 && equals(rows[0].key, values[i]));
- }
- },
-
- view_conflicts: function(debug) {
- var dbA = new CouchDB("test_suite_db_a");
- dbA.deleteDb();
- dbA.createDb();
- var dbB = new CouchDB("test_suite_db_b");
- dbB.deleteDb();
- dbB.createDb();
- if (debug) debugger;
-
- var docA = {_id: "foo", bar: 42};
- T(dbA.save(docA).ok);
- CouchDB.replicate(dbA.name, dbB.name);
-
- var docB = dbB.open("foo");
- docB.bar = 43;
- dbB.save(docB);
- docA.bar = 41;
- dbA.save(docA);
- CouchDB.replicate(dbA.name, dbB.name);
-
- var doc = dbB.open("foo", {conflicts: true});
- T(doc._conflicts.length == 1);
- var conflictRev = doc._conflicts[0];
- if (doc.bar == 41) { // A won
- T(conflictRev == docB._rev);
- } else { // B won
- T(doc.bar == 43);
- T(conflictRev == docA._rev);
- }
-
- var results = dbB.query(function(doc) {
- if (doc._conflicts) {
- emit(doc._id, doc._conflicts);
- }
- });
- T(results.rows[0].value[0] == conflictRev);
- },
-
- view_errors: function(debug) {
- var db = new CouchDB("test_suite_db");
- db.deleteDb();
- db.createDb();
- if (debug) debugger;
-
- var doc = {integer: 1, string: "1", array: [1, 2, 3]};
- T(db.save(doc).ok);
-
- // emitting a key value that is undefined should result in that row not
- // being included in the view results
- var results = db.query(function(doc) {
- emit(doc.undef, null);
- });
- T(results.total_rows == 0);
-
- // if a view function throws an exception, its results are not included in
- // the view index, but the view does not itself raise an error
- var results = db.query(function(doc) {
- doc.undef(); // throws an error
- });
- T(results.total_rows == 0);
-
- // if a view function includes an undefined value in the emitted key or
- // value, an error is logged and the result is not included in the view
- // index, and the view itself does not raise an error
- var results = db.query(function(doc) {
- emit([doc._id, doc.undef], null);
- });
- T(results.total_rows == 0);
- },
-
- view_include_docs: function(debug) {
- var db = new CouchDB("test_suite_db");
- db.deleteDb();
- db.createDb();
- if (debug) debugger;
-
- var docs = makeDocs(0, 100);
- T(db.bulkSave(docs).ok);
-
- var designDoc = {
- _id:"_design/test",
- language: "javascript",
- views: {
- all_docs: {
- map: "function(doc) { emit(doc.integer, doc.string) }"
- },
- with_prev: {
- map: "function(doc){if(doc.prev) emit(doc._id,{'_rev':doc.prev}); else emit(doc._id,{'_rev':doc._rev});}"
- },
- summate: {
- map:"function (doc) {emit(doc.integer, doc.integer)};",
- reduce:"function (keys, values) { return sum(values); };"
- }
- }
- }
- T(db.save(designDoc).ok);
-
- var resp = db.view('test/all_docs', {include_docs: true, limit: 2});
- T(resp.rows.length == 2);
- T(resp.rows[0].id == "0");
- T(resp.rows[0].doc._id == "0");
- T(resp.rows[1].id == "1");
- T(resp.rows[1].doc._id == "1");
-
- resp = db.view('test/all_docs', {include_docs: true}, [29, 74]);
- T(resp.rows.length == 2);
- T(resp.rows[0].doc._id == "29");
- T(resp.rows[1].doc.integer == 74);
-
- resp = db.allDocs({limit: 2, skip: 1, include_docs: true});
- T(resp.rows.length == 2);
- T(resp.rows[0].doc.integer == 1);
- T(resp.rows[1].doc.integer == 10);
-
- resp = db.allDocs({include_docs: true}, ['not_a_doc']);
- T(resp.rows.length == 1);
- T(!resp.rows[0].doc);
-
- resp = db.allDocs({include_docs: true}, ["1", "foo"]);
- T(resp.rows.length == 2);
- T(resp.rows[0].doc.integer == 1);
- T(!resp.rows[1].doc);
-
- resp = db.allDocs({include_docs: true, limit: 0});
- T(resp.rows.length == 0);
-
- // No reduce support
- try {
- resp = db.view('test/summate', {include_docs: true});
- alert(JSON.stringify(resp));
- T(0==1);
- } catch (e) {
- T(e.error == 'query_parse_error');
- }
-
- // Reduce support when reduce=false
- resp = db.view('test/summate', {reduce: false, include_docs: true});
- T(resp.rows.length == 100);
-
- // Check emitted _rev controls things
- resp = db.allDocs({include_docs: true}, ["0"]);
- var before = resp.rows[0].doc;
- var after = db.open("0");
- after.integer = 100
- after.prev = after._rev;
- db.save(after);
- after = db.open("0");
- T(after._rev != after.prev);
- T(after.integer == 100);
-
- // should emit the previous revision
- resp = db.view("test/with_prev", {include_docs: true}, ["0"]);
- T(resp.rows[0].doc._id == "0");
- T(resp.rows[0].doc._rev == before._rev);
- T(!resp.rows[0].doc.prev);
- T(resp.rows[0].doc.integer == 0);
-
- var xhr = CouchDB.request("POST", "/test_suite_db/_compact");
- T(xhr.status == 202)
- while (db.info().compact_running) {}
-
- resp = db.view("test/with_prev", {include_docs: true}, ["0", "23"]);
- T(resp.rows.length == 2);
- T(resp.rows[0].key == "0");
- T(resp.rows[0].id == "0");
- T(!resp.rows[0].doc);
- T(resp.rows[0].error == "missing");
- T(resp.rows[1].doc.integer == 23);
- },
-
- view_multi_key_all_docs: function(debug) {
- var db = new CouchDB("test_suite_db");
- db.deleteDb();
- db.createDb();
- if (debug) debugger;
-
- var docs = makeDocs(0, 100);
- T(db.bulkSave(docs).ok);
-
- var keys = ["10","15","30","37","50"];
- var rows = db.allDocs({},keys).rows;
- T(rows.length == keys.length);
- for(var i=0; i<rows.length; i++)
- T(rows[i].id == keys[i]);
-
- rows = db.allDocs({limit: 1}, keys).rows;
- T(rows.length == 1);
- T(rows[0].id == keys[0]);
-
- rows = db.allDocs({skip: 2}, keys).rows;
- T(rows.length == 3);
- for(var i=0; i<rows.length; i++)
- T(rows[i].id == keys[i+2]);
-
- rows = db.allDocs({descending: "true"}, keys).rows;
- T(rows.length == keys.length);
- for(var i=0; i<rows.length; i++)
- T(rows[i].id == keys[keys.length-i-1]);
-
- rows = db.allDocs({descending: "true", skip: 3, limit:1}, keys).rows;
- T(rows.length == 1);
- T(rows[0].id == keys[1]);
-
- // Check we get invalid rows when the key doesn't exist
- rows = db.allDocs({}, [1, "i_dont_exist", "0"]).rows;
- T(rows.length == 3);
- T(rows[0].error == "not_found");
- T(!rows[0].id);
- T(rows[1].error == "not_found");
- T(!rows[1].id);
- T(rows[2].id == rows[2].key && rows[2].key == "0");
- },
-
- view_multi_key_design: function(debug) {
- var db = new CouchDB("test_suite_db");
- db.deleteDb();
- db.createDb();
- if (debug) debugger;
-
- var docs = makeDocs(0, 100);
- T(db.bulkSave(docs).ok);
-
- var designDoc = {
- _id:"_design/test",
- language: "javascript",
- views: {
- all_docs: {
- map: "function(doc) { emit(doc.integer, doc.string) }"
- },
- multi_emit: {
- map: "function(doc) {for(var i = 0 ; i < 3 ; i++) { emit(i, doc.integer) ; } }"
- },
- summate: {
- map:"function (doc) {emit(doc.integer, doc.integer)};",
- reduce:"function (keys, values) { return sum(values); };"
- }
- }
- }
- T(db.save(designDoc).ok);
-
- // Test that missing keys work too
- var keys = [101,30,15,37,50]
- var reduce = db.view("test/summate",{group:true},keys).rows;
- T(reduce.length == keys.length-1); // 101 is missing
- for(var i=0; i<reduce.length; i++) {
- T(keys.indexOf(reduce[i].key) != -1);
- T(reduce[i].key == reduce[i].value);
- }
-
- // First, the goods:
- var keys = [10,15,30,37,50];
- var rows = db.view("test/all_docs",{},keys).rows;
- for(var i=0; i<rows.length; i++) {
- T(keys.indexOf(rows[i].key) != -1);
- T(rows[i].key == rows[i].value);
- }
-
- var reduce = db.view("test/summate",{group:true},keys).rows;
- T(reduce.length == keys.length);
- for(var i=0; i<reduce.length; i++) {
- T(keys.indexOf(reduce[i].key) != -1);
- T(reduce[i].key == reduce[i].value);
- }
-
- // Test that invalid parameter combinations get rejected
- var badargs = [{startkey:0}, {endkey:0}, {key: 0}, {group_level: 2}];
- for(var i in badargs)
- {
- try {
- db.view("test/all_docs",badargs[i],keys);
- T(0==1);
- } catch (e) {
- T(e.error == "query_parse_error");
- }
- }
-
- try {
- db.view("test/summate",{},keys);
- T(0==1);
- } catch (e) {
- T(e.error == "query_parse_error");
- }
-
- // Test that a map & reduce containing func support keys when reduce=false
- resp = db.view("test/summate", {reduce: false}, keys);
- T(resp.rows.length == 5);
-
- // Check that limiting by startkey_docid and endkey_docid get applied
- // as expected.
- var curr = db.view("test/multi_emit", {startkey_docid: 21, endkey_docid: 23}, [0, 2]).rows;
- var exp_key = [ 0, 0, 0, 2, 2, 2] ;
- var exp_val = [21, 22, 23, 21, 22, 23] ;
- T(curr.length == 6);
- for( var i = 0 ; i < 6 ; i++)
- {
- T(curr[i].key == exp_key[i]);
- T(curr[i].value == exp_val[i]);
- }
-
- // Check limit works
- curr = db.view("test/all_docs", {limit: 1}, keys).rows;
- T(curr.length == 1);
- T(curr[0].key == 10);
-
- // Check offset works
- curr = db.view("test/multi_emit", {skip: 1}, [0]).rows;
- T(curr.length == 99);
- T(curr[0].value == 1);
-
- // Check that dir works
- curr = db.view("test/multi_emit", {descending: "true"}, [1]).rows;
- T(curr.length == 100);
- T(curr[0].value == 99);
- T(curr[99].value == 0);
-
- // Check a couple combinations
- curr = db.view("test/multi_emit", {descending: "true", skip: 3, limit: 2}, [2]).rows;
- T(curr.length, 2);
- T(curr[0].value == 96);
- T(curr[1].value == 95);
-
- curr = db.view("test/multi_emit", {skip: 2, limit: 3, startkey_docid: "13"}, [0]).rows;
- T(curr.length == 3);
- T(curr[0].value == 15);
- T(curr[1].value == 16);
- T(curr[2].value == 17);
-
- curr = db.view("test/multi_emit",
- {skip: 1, limit: 5, startkey_docid: "25", endkey_docid: "27"}, [1]).rows;
- T(curr.length == 2);
- T(curr[0].value == 26);
- T(curr[1].value == 27);
-
- curr = db.view("test/multi_emit",
- {skip: 1, limit: 5, startkey_docid: "28", endkey_docid: "26", descending: "true"}, [1]).rows;
- T(curr.length == 2);
- T(curr[0].value == 27);
- T(curr[1].value == 26);
- },
-
- view_multi_key_temp: function(debug) {
- var db = new CouchDB("test_suite_db");
- db.deleteDb();
- db.createDb();
- if (debug) debugger;
-
- var docs = makeDocs(0, 100);
- T(db.bulkSave(docs).ok);
-
- var queryFun = function(doc) { emit(doc.integer, doc.integer) };
- var reduceFun = function (keys, values) { return sum(values); };
-
- var keys = [10,15,30,37,50];
- var rows = db.query(queryFun, null, {}, keys).rows;
- for(var i=0; i<rows.length; i++) {
- T(keys.indexOf(rows[i].key) != -1);
- T(rows[i].key == rows[i].value);
- }
-
- var reduce = db.query(queryFun, reduceFun, {group:true}, keys).rows;
- for(var i=0; i<reduce.length; i++) {
- T(keys.indexOf(reduce[i].key) != -1);
- T(reduce[i].key == reduce[i].value);
- }
- },
-
- view_pagination: function(debug) {
- var db = new CouchDB("test_suite_db");
- db.deleteDb();
- db.createDb();
- if (debug) debugger;
-
- var docs = makeDocs(0, 100);
- T(db.bulkSave(docs).ok);
-
- var queryFun = function(doc) { emit(doc.integer, null) };
- var i;
-
- // page through the view ascending
- for (i = 0; i < docs.length; i += 10) {
- var queryResults = db.query(queryFun, null, {
- startkey: i,
- startkey_docid: i,
- limit: 10
- });
- T(queryResults.rows.length == 10)
- T(queryResults.total_rows == docs.length)
- T(queryResults.offset == i)
- var j;
- for (j = 0; j < 10;j++) {
- T(queryResults.rows[j].key == i + j);
- }
- }
-
- // page through the view descending
- for (i = docs.length - 1; i >= 0; i -= 10) {
- var queryResults = db.query(queryFun, null, {
- startkey: i,
- startkey_docid: i,
- descending: true,
- limit: 10
- });
- T(queryResults.rows.length == 10)
- T(queryResults.total_rows == docs.length)
- T(queryResults.offset == docs.length - i - 1)
- var j;
- for (j = 0; j < 10; j++) {
- T(queryResults.rows[j].key == i - j);
- }
- }
-
- // ignore decending=false. CouchDB should just ignore that.
- for (i = 0; i < docs.length; i += 10) {
- var queryResults = db.query(queryFun, null, {
- startkey: i,
- startkey_docid: i,
- descending: false,
- limit: 10
- });
- T(queryResults.rows.length == 10)
- T(queryResults.total_rows == docs.length)
- T(queryResults.offset == i)
- var j;
- for (j = 0; j < 10;j++) {
- T(queryResults.rows[j].key == i + j);
- }
- }
-
- // test endkey_docid
- var queryResults = db.query(function(doc) { emit(null, null);}, null, {
- startkey: null,
- startkey_docid: 1,
- endkey: null,
- endkey_docid: 40
- });
-
- T(queryResults.rows.length == 35)
- T(queryResults.total_rows == docs.length)
- T(queryResults.offset == 1)
- T(queryResults.rows[0].id == "1");
- T(queryResults.rows[1].id == "10");
- T(queryResults.rows[2].id == "11");
- T(queryResults.rows[3].id == "12");
- T(queryResults.rows[4].id == "13");
- T(queryResults.rows[5].id == "14");
- T(queryResults.rows[6].id == "15");
- T(queryResults.rows[7].id == "16");
- T(queryResults.rows[8].id == "17");
- T(queryResults.rows[9].id == "18");
- T(queryResults.rows[10].id == "19");
- T(queryResults.rows[11].id == "2");
- T(queryResults.rows[12].id == "20");
- T(queryResults.rows[13].id == "21");
- T(queryResults.rows[14].id == "22");
- T(queryResults.rows[15].id == "23");
- T(queryResults.rows[16].id == "24");
- T(queryResults.rows[17].id == "25");
- T(queryResults.rows[18].id == "26");
- T(queryResults.rows[19].id == "27");
- T(queryResults.rows[20].id == "28");
- T(queryResults.rows[21].id == "29");
- T(queryResults.rows[22].id == "3");
- T(queryResults.rows[23].id == "30");
- T(queryResults.rows[24].id == "31");
- T(queryResults.rows[25].id == "32");
- T(queryResults.rows[26].id == "33");
- T(queryResults.rows[27].id == "34");
- T(queryResults.rows[28].id == "35");
- T(queryResults.rows[29].id == "36");
- T(queryResults.rows[30].id == "37");
- T(queryResults.rows[31].id == "38");
- T(queryResults.rows[32].id == "39");
- T(queryResults.rows[33].id == "4");
- T(queryResults.rows[34].id == "40");
-
- },
-
- view_sandboxing: function(debug) {
- var db = new CouchDB("test_suite_db");
- db.deleteDb();
- db.createDb();
- if (debug) debugger;
-
- var doc = {integer: 1, string: "1", array: [1, 2, 3]};
- T(db.save(doc).ok);
-/*
- // make sure that attempting to change the document throws an error
- var results = db.query(function(doc) {
- doc.integer = 2;
- emit(null, doc);
- });
- T(results.total_rows == 0);
-
- var results = db.query(function(doc) {
- doc.array[0] = 0;
- emit(null, doc);
- });
- T(results.total_rows == 0);
-*/
- // make sure that a view cannot invoke interpreter internals such as the
- // garbage collector
- var results = db.query(function(doc) {
- gc();
- emit(null, doc);
- });
- T(results.total_rows == 0);
-
- // make sure that a view cannot access the map_funs array defined used by
- // the view server
- var results = db.query(function(doc) { map_funs.push(1); emit(null, doc) });
- T(results.total_rows == 0);
-
- // make sure that a view cannot access the map_results array defined used by
- // the view server
- var results = db.query(function(doc) { map_results.push(1); emit(null, doc) });
- T(results.total_rows == 0);
- },
-
- view_xml: function(debug) {
- var db = new CouchDB("test_suite_db");
- db.deleteDb();
- db.createDb();
- if (debug) debugger;
-
- db.save({content: "<doc><title id='xml'>Testing XML</title></doc>"});
- db.save({content: "<doc><title id='e4x'>Testing E4X</title></doc>"});
-
- var results = db.query(
- "function(doc) {\n" +
- " var xml = new XML(doc.content);\n" +
- " emit(xml.title.text(), null);\n" +
- "}");
- T(results.total_rows == 2);
- T(results.rows[0].key == "Testing E4X");
- T(results.rows[1].key == "Testing XML");
-
- var results = db.query(
- "function(doc) {\n" +
- " var xml = new XML(doc.content);\n" +
- " emit(xml.title.@id, null);\n" +
- "}");
- T(results.total_rows == 2);
- T(results.rows[0].key == "e4x");
- T(results.rows[1].key == "xml");
- },
-
- replication: function(debug) {
- if (debug) debugger;
- var host = CouchDB.host;
- var dbPairs = [
- {source:"test_suite_db_a",
- target:"test_suite_db_b"},
- {source:"test_suite_db_a",
- target:"http://" + host + "/test_suite_db_b"},
- {source:"http://" + host + "/test_suite_db_a",
- target:"test_suite_db_b"},
- {source:"http://" + host + "/test_suite_db_a",
- target:"http://" + host + "/test_suite_db_b"}
- ]
- var dbA = new CouchDB("test_suite_db_a");
- var dbB = new CouchDB("test_suite_db_b");
- var numDocs = 10;
- var xhr;
- for (var testPair = 0; testPair < dbPairs.length; testPair++) {
- var A = dbPairs[testPair].source
- var B = dbPairs[testPair].target
-
- dbA.deleteDb();
- dbA.createDb();
- dbB.deleteDb();
- dbB.createDb();
-
- var repTests = {
- // copy and paste and put your code in. delete unused steps.
- test_template: new function () {
- this.init = function(dbA, dbB) {
- // before anything has happened
- }
- this.afterAB1 = function(dbA, dbB) {
- // called after replicating src=A tgt=B first time.
- };
- this.afterBA1 = function(dbA, dbB) {
- // called after replicating src=B tgt=A first time.
- };
- this.afterAB2 = function(dbA, dbB) {
- // called after replicating src=A tgt=B second time.
- };
- this.afterBA2 = function(dbA, dbB) {
- // etc...
- };
- },
-
- simple_test: new function () {
- this.init = function(dbA, dbB) {
- var docs = makeDocs(0, numDocs);
- T(dbA.bulkSave(docs).ok);
- };
-
- this.afterAB1 = function(dbA, dbB) {
- for (var j = 0; j < numDocs; j++) {
- var docA = dbA.open("" + j);
- var docB = dbB.open("" + j);
- T(docA._rev == docB._rev);
- }
- };
- },
-
- deletes_test: new function () {
- this.init = function(dbA, dbB) {
- T(dbA.save({_id:"foo1",value:"a"}).ok);
- };
-
- this.afterAB1 = function(dbA, dbB) {
- var docA = dbA.open("foo1");
- var docB = dbB.open("foo1");
- T(docA._rev == docB._rev);
-
- dbA.deleteDoc(docA);
- };
-
- this.afterAB2 = function(dbA, dbB) {
- T(dbA.open("foo1") == null);
- T(dbB.open("foo1") == null);
- };
- },
-
- slashes_in_ids_test: new function () {
- // make sure docs with slashes in id replicate properly
- this.init = function(dbA, dbB) {
- dbA.save({ _id:"abc/def", val:"one" });
- };
-
- this.afterAB1 = function(dbA, dbB) {
- var docA = dbA.open("abc/def");
- var docB = dbB.open("abc/def");
- T(docA._rev == docB._rev);
- };
- },
-
- design_docs_test: new function() {
- // make sure design docs replicate properly
- this.init = function(dbA, dbB) {
- dbA.save({ _id:"_design/test" });
- };
-
- this.afterAB1 = function() {
- var docA = dbA.open("_design/test");
- var docB = dbB.open("_design/test");
- T(docA._rev == docB._rev);
- };
- },
-
- attachments_test: new function () {
- // Test attachments
- this.init = function(dbA, dbB) {
- dbA.save({
- _id:"bin_doc",
- _attachments:{
- "foo.txt": {
- "type":"base64",
- "data": "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
- }
- }
- });
- };
-
- this.afterAB1 = function(dbA, dbB) {
- var xhr = CouchDB.request("GET", "/test_suite_db_a/bin_doc/foo.txt");
- T(xhr.responseText == "This is a base64 encoded text")
-
- xhr = CouchDB.request("GET", "/test_suite_db_b/bin_doc/foo.txt");
- T(xhr.responseText == "This is a base64 encoded text")
- };
- },
-
- conflicts_test: new function () {
- // test conflicts
- this.init = function(dbA, dbB) {
- dbA.save({_id:"foo",value:"a"});
- dbB.save({_id:"foo",value:"b"});
- };
-
- this.afterBA1 = function(dbA, dbB) {
- var docA = dbA.open("foo", {conflicts: true});
- var docB = dbB.open("foo", {conflicts: true});
-
- // make sure the same rev is in each db
- T(docA._rev === docB._rev);
-
- // make sure the conflicts are the same in each db
- T(docA._conflicts[0] === docB._conflicts[0]);
-
- // delete a conflict.
- dbA.deleteDoc({_id:"foo", _rev:docA._conflicts[0]});
- };
-
- this.afterBA2 = function(dbA, dbB) {
- // open documents and include the conflict meta data
- var docA = dbA.open("foo", {conflicts: true});
- var docB = dbB.open("foo", {conflicts: true});
-
- // We should have no conflicts this time
- T(docA._conflicts === undefined)
- T(docB._conflicts === undefined);
- };
- }
- };
-
- var test;
- for(test in repTests) {
- if(repTests[test].init) {
- repTests[test].init(dbA, dbB);
- }
- }
-
- T(CouchDB.replicate(A, B).ok);
-
- for(test in repTests) {
- if(repTests[test].afterAB1) repTests[test].afterAB1(dbA, dbB);
- }
-
- T(CouchDB.replicate(B, A).ok);
-
- for(test in repTests) {
- if(repTests[test].afterBA1) repTests[test].afterBA1(dbA, dbB);
- }
-
- T(CouchDB.replicate(A, B).ok);
-
- for(test in repTests) {
- if(repTests[test].afterAB2) repTests[test].afterAB2(dbA, dbB);
- }
-
- T(CouchDB.replicate(B, A).ok);
-
- for(test in repTests) {
- if(repTests[test].afterBA2) repTests[test].afterBA2(dbA, dbB);
- }
-
- }
- },
-
- etags_head: function(debug) {
- var db = new CouchDB("test_suite_db");
- db.deleteDb();
- db.createDb();
- if (debug) debugger;
-
- var xhr;
-
- // create a new doc
- xhr = CouchDB.request("PUT", "/test_suite_db/1", {
- body: "{}"
- });
- T(xhr.status == 201);
-
- // extract the ETag header values
- var etag = xhr.getResponseHeader("etag");
-
- // get the doc and verify the headers match
- xhr = CouchDB.request("GET", "/test_suite_db/1");
- T(etag == xhr.getResponseHeader("etag"));
-
- // 'head' the doc and verify the headers match
- xhr = CouchDB.request("HEAD", "/test_suite_db/1", {
- headers: {"if-none-match": "s"}
- });
- T(etag == xhr.getResponseHeader("etag"));
-
- // replace a doc
- xhr = CouchDB.request("PUT", "/test_suite_db/1", {
- body: "{}",
- headers: {"if-match": etag}
- });
- T(xhr.status == 201);
-
- // extract the new ETag value
- var etagOld= etag;
- etag = xhr.getResponseHeader("etag");
-
- // fail to replace a doc
- xhr = CouchDB.request("PUT", "/test_suite_db/1", {
- body: "{}"
- });
- T(xhr.status == 409);
-
- // verify get w/Etag
- xhr = CouchDB.request("GET", "/test_suite_db/1", {
- headers: {"if-none-match": etagOld}
- });
- T(xhr.status == 200);
- xhr = CouchDB.request("GET", "/test_suite_db/1", {
- headers: {"if-none-match": etag}
- });
- T(xhr.status == 304);
-
- // fail to delete a doc
- xhr = CouchDB.request("DELETE", "/test_suite_db/1", {
- headers: {"if-match": etagOld}
- });
- T(xhr.status == 409);
-
- //now do it for real
- xhr = CouchDB.request("DELETE", "/test_suite_db/1", {
- headers: {"if-match": etag}
- });
- T(xhr.status == 200);
- },
- etags_views: function(debug) {
- var db = new CouchDB("test_suite_db");
- db.deleteDb();
- db.createDb();
- if (debug) debugger;
-
- var designDoc = {
- _id:"_design/etags",
- language: "javascript",
- views : {
- basicView : {
- map : stringFun(function(doc) {
- emit(doc.integer, doc.string);
- })
- },
- withReduce : {
- map : stringFun(function(doc) {
- emit(doc.integer, doc.string);
- }),
- reduce : stringFun(function(keys, values, rereduce) {
- if (rereduce) {
- return sum(values);
- } else {
- return values.length;
- }
- })
- }
- }
- }
- T(db.save(designDoc).ok);
- var xhr;
- var docs = makeDocs(0, 10);
- var saveResult = db.bulkSave(docs);
- T(saveResult.ok);
-
- // verify get w/Etag on map view
- xhr = CouchDB.request("GET", "/test_suite_db/_view/etags/basicView");
- T(xhr.status == 200);
- var etag = xhr.getResponseHeader("etag");
- xhr = CouchDB.request("GET", "/test_suite_db/_view/etags/basicView", {
- headers: {"if-none-match": etag}
- });
- T(xhr.status == 304);
- // TODO GET with keys (when that is available)
-
- // reduce view
- xhr = CouchDB.request("GET", "/test_suite_db/_view/etags/withReduce");
- T(xhr.status == 200);
- var etag = xhr.getResponseHeader("etag");
- xhr = CouchDB.request("GET", "/test_suite_db/_view/etags/withReduce", {
- headers: {"if-none-match": etag}
- });
- T(xhr.status == 304);
-
- // all docs
- xhr = CouchDB.request("GET", "/test_suite_db/_all_docs");
- T(xhr.status == 200);
- var etag = xhr.getResponseHeader("etag");
- xhr = CouchDB.request("GET", "/test_suite_db/_all_docs", {
- headers: {"if-none-match": etag}
- });
- T(xhr.status == 304);
-
- // by seq
- xhr = CouchDB.request("GET", "/test_suite_db/_all_docs_by_seq");
- T(xhr.status == 200);
- var etag = xhr.getResponseHeader("etag");
- xhr = CouchDB.request("GET", "/test_suite_db/_all_docs_by_seq", {
- headers: {"if-none-match": etag}
- });
- T(xhr.status == 304);
-
- // list etag
- // in the list test for now
- },
-
- show_documents: function(debug) {
- var db = new CouchDB("test_suite_db");
- db.deleteDb();
- db.createDb();
- if (debug) debugger;
-
- var designDoc = {
- _id:"_design/template",
- language: "javascript",
- shows: {
- "hello" : stringFun(function(doc) {
- if (doc) {
- return "Hello World";
- } else {
- return "Empty World";
- }
- }),
- "just-name" : stringFun(function(doc, req) {
- return {
- body : "Just " + doc.name
- };
- }),
- "req-info" : stringFun(function(doc, req) {
- return {
- json : req
- }
- }),
- "xml-type" : stringFun(function(doc, req) {
- return {
- "headers" : {
- "Content-Type" : "application/xml"
- },
- "body" : new XML('<xml><node foo="bar"/></xml>')
- }
- }),
- "no-set-etag" : stringFun(function(doc, req) {
- return {
- headers : {
- "Etag" : "skipped"
- },
- "body" : "something"
- }
- }),
- "accept-switch" : stringFun(function(doc, req) {
- if (req.headers["Accept"].match(/image/)) {
- return {
- // a 16x16 px version of the CouchDB logo
- "base64" :
-["iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAsV",
-"BMVEUAAAD////////////////////////5ur3rEBn////////////////wDBL/",
-"AADuBAe9EB3IEBz/7+//X1/qBQn2AgP/f3/ilpzsDxfpChDtDhXeCA76AQH/v7",
-"/84eLyWV/uc3bJPEf/Dw/uw8bRWmP1h4zxSlD6YGHuQ0f6g4XyQkXvCA36MDH6",
-"wMH/z8/yAwX64ODeh47BHiv/Ly/20dLQLTj98PDXWmP/Pz//39/wGyJ7Iy9JAA",
-"AADHRSTlMAbw8vf08/bz+Pv19jK/W3AAAAg0lEQVR4Xp3LRQ4DQRBD0QqTm4Y5",
-"zMxw/4OleiJlHeUtv2X6RbNO1Uqj9g0RMCuQO0vBIg4vMFeOpCWIWmDOw82fZx",
-"vaND1c8OG4vrdOqD8YwgpDYDxRgkSm5rwu0nQVBJuMg++pLXZyr5jnc1BaH4GT",
-"LvEliY253nA3pVhQqdPt0f/erJkMGMB8xucAAAAASUVORK5CYII="].join(''),
- headers : {
- "Content-Type" : "image/png",
- "Vary" : "Accept" // we set this for proxy caches
- }
- };
- } else {
- return {
- "body" : "accepting text requests",
- headers : {
- "Content-Type" : "text/html",
- "Vary" : "Accept"
- }
- };
- }
- }),
- "respondWith" : stringFun(function(doc, req) {
- registerType("foo", "application/foo","application/x-foo");
- return respondWith(req, {
- html : function() {
- return {
- body:"Ha ha, you said \"" + doc.word + "\"."
- };
- },
- xml : function() {
- var xml = new XML('<xml><node/></xml>');
- // Becase Safari can't stand to see that dastardly
- // E4X outside of a string. Outside of tests you
- // can just use E4X literals.
- this.eval('xml.node.@foo = doc.word');
- return {
- body: xml
- };
- },
- foo : function() {
- return {
- body: "foofoo"
- };
- },
- fallback : "html"
- });
- })
- }
- };
- T(db.save(designDoc).ok);
-
- var doc = {"word":"plankton", "name":"Rusty"}
- var resp = db.save(doc);
- T(resp.ok);
- var docid = resp.id;
-
- // show error
- var xhr = CouchDB.request("GET", "/test_suite_db/_show/");
- T(xhr.status == 404);
- T(JSON.parse(xhr.responseText).reason == "Invalid path.");
-
- // hello template world
- xhr = CouchDB.request("GET", "/test_suite_db/_show/template/hello/"+docid);
- T(xhr.responseText == "Hello World");
-
- // hello template world (no docid)
- xhr = CouchDB.request("GET", "/test_suite_db/_show/template/hello");
- T(xhr.responseText == "Empty World");
-
-
- // show with doc
- xhr = CouchDB.request("GET", "/test_suite_db/_show/template/just-name/"+docid);
- T(xhr.responseText == "Just Rusty");
-
- // show with missing doc
- xhr = CouchDB.request("GET", "/test_suite_db/_show/template/just-name/missingdoc");
- T(xhr.status == 404);
- var resp = JSON.parse(xhr.responseText);
- T(resp.error == "not_found");
- T(resp.reason == "missing");
-
- // show with missing func
- xhr = CouchDB.request("GET", "/test_suite_db/_show/template/missing/"+docid);
- T(xhr.status == 404);
-
- // missing design doc
- xhr = CouchDB.request("GET", "/test_suite_db/_show/missingdoc/just-name/"+docid);
- T(xhr.status == 404);
- var resp = JSON.parse(xhr.responseText);
- T(resp.error == "not_found");
-
- // query parameters
- xhr = CouchDB.request("GET", "/test_suite_db/_show/template/req-info/"+docid+"?foo=bar", {
- headers: {
- "Accept": "text/html;text/plain;*/*",
- "X-Foo" : "bar"
- }
- });
- var resp = JSON.parse(xhr.responseText);
- T(equals(resp.headers["X-Foo"], "bar"));
- T(equals(resp.query, {foo:"bar"}));
- T(equals(resp.verb, "GET"));
- T(equals(resp.path[4], docid));
- T(equals(resp.info.db_name, "test_suite_db"));
-
- // returning a content-type
- xhr = CouchDB.request("GET", "/test_suite_db/_show/template/xml-type/"+docid);
- T("application/xml" == xhr.getResponseHeader("Content-Type"));
- T("Accept" == xhr.getResponseHeader("Vary"));
-
- // accept header switching
- // different mime has different etag
-
- xhr = CouchDB.request("GET", "/test_suite_db/_show/template/accept-switch/"+docid, {
- headers: {"Accept": "text/html;text/plain;*/*"}
- });
- T("text/html" == xhr.getResponseHeader("Content-Type"));
- T("Accept" == xhr.getResponseHeader("Vary"));
- var etag = xhr.getResponseHeader("etag");
-
- xhr = CouchDB.request("GET", "/test_suite_db/_show/template/accept-switch/"+docid, {
- headers: {"Accept": "image/png;*/*"}
- });
- T(xhr.responseText.match(/PNG/))
- T("image/png" == xhr.getResponseHeader("Content-Type"));
- var etag2 = xhr.getResponseHeader("etag");
- T(etag2 != etag);
-
- // proper etags
- // show with doc
- xhr = CouchDB.request("GET", "/test_suite_db/_show/template/just-name/"+docid);
- // extract the ETag header values
- etag = xhr.getResponseHeader("etag");
- // get again with etag in request
- xhr = CouchDB.request("GET", "/test_suite_db/_show/template/just-name/"+docid, {
- headers: {"if-none-match": etag}
- });
- // should be 304
- T(xhr.status == 304);
-
- // update the doc
- doc.name = "Crusty";
- resp = db.save(doc);
- T(resp.ok);
- // req with same etag
- xhr = CouchDB.request("GET", "/test_suite_db/_show/template/just-name/"+docid, {
- headers: {"if-none-match": etag}
- });
- // status is 200
- T(xhr.status == 200);
-
- // get new etag and request again
- etag = xhr.getResponseHeader("etag");
- xhr = CouchDB.request("GET", "/test_suite_db/_show/template/just-name/"+docid, {
- headers: {"if-none-match": etag}
- });
- // should be 304
- T(xhr.status == 304);
-
- // update design doc (but not function)
- designDoc.isChanged = true;
- T(db.save(designDoc).ok);
-
- xhr = CouchDB.request("GET", "/test_suite_db/_show/template/just-name/"+docid, {
- headers: {"if-none-match": etag}
- });
- // should be 304
- T(xhr.status == 304);
-
- // update design doc function
- designDoc.shows["just-name"] = (function(doc, req) {
- return {
- body : "Just old " + doc.name
- };
- }).toString();
- T(db.save(designDoc).ok);
-
- xhr = CouchDB.request("GET", "/test_suite_db/_show/template/just-name/"+docid, {
- headers: {"if-none-match": etag}
- });
- // status is 200
- T(xhr.status == 200);
-
-
- // JS can't set etag
- xhr = CouchDB.request("GET", "/test_suite_db/_show/template/no-set-etag/"+docid);
- // extract the ETag header values
- etag = xhr.getResponseHeader("etag");
- T(etag != "skipped")
-
- // test the respondWith mime matcher
- xhr = CouchDB.request("GET", "/test_suite_db/_show/template/respondWith/"+docid, {
- headers: {
- "Accept": 'text/html,application/atom+xml; q=0.9'
- }
- });
- T(xhr.getResponseHeader("Content-Type") == "text/html");
- T(xhr.responseText == "Ha ha, you said \"plankton\".");
-
- // now with xml
- xhr = CouchDB.request("GET", "/test_suite_db/_show/template/respondWith/"+docid, {
- headers: {
- "Accept": 'application/xml'
- }
- });
- T(xhr.getResponseHeader("Content-Type") == "application/xml");
- T(xhr.responseText.match(/node/));
- T(xhr.responseText.match(/plankton/));
-
- // registering types works
- xhr = CouchDB.request("GET", "/test_suite_db/_show/template/respondWith/"+docid, {
- headers: {
- "Accept": "application/x-foo"
- }
- });
- T(xhr.getResponseHeader("Content-Type") == "application/x-foo");
- T(xhr.responseText.match(/foofoo/));
- },
-
- list_views : function(debug) {
- var db = new CouchDB("test_suite_db");
- db.deleteDb();
- db.createDb();
- if (debug) debugger;
-
- var designDoc = {
- _id:"_design/lists",
- language: "javascript",
- views : {
- basicView : {
- map : stringFun(function(doc) {
- emit(doc.integer, doc.string);
- })
- },
- withReduce : {
- map : stringFun(function(doc) {
- emit(doc.integer, doc.string);
- }),
- reduce : stringFun(function(keys, values, rereduce) {
- if (rereduce) {
- return sum(values);
- } else {
- return values.length;
- }
- })
- }
- },
- lists: {
- simpleForm: stringFun(function(head, row, req, row_info) {
- if (row) {
- // we ignore headers on rows and tail
- return {
- body : '\n<li>Key: '+row.key
- +' Value: '+row.value
- +' LineNo: '+row_info.row_number+'</li>'
- };
- } else if (head) {
- // we return an object (like those used by external and show)
- // so that we can specify headers
- return {
- body : '<h1>Total Rows: '
- + head.total_rows
- + ' Offset: ' + head.offset
- + '</h1><ul>'
- };
- } else {
- // tail
- return {body : '</ul>'+
- '<p>FirstKey: '+row_info.first_key+
- ' LastKey: '+row_info.prev_key+'</p>'};
- }
- }),
- acceptSwitch: stringFun(function(head, row, req, row_info) {
- return respondWith(req, {
- html : function() {
- // If you're outputting text and you're not setting
- // any headers, you can just return a string.
- if (head) {
- return "HTML <ul>";
- } else if (row) {
- return '\n<li>Key: '
- +row.key+' Value: '+row.value
- +' LineNo: '+row_info.row_number+'</li>';
- } else { // tail
- return '</ul>';
-
- }
- },
- xml : function() {
- if (head) {
- return '<feed xmlns="http://www.w3.org/2005/Atom">'
- +'<title>Test XML Feed</title>';
- } else if (row) {
- // Becase Safari can't stand to see that dastardly
- // E4X outside of a string. Outside of tests you
- // can just use E4X literals.
- var entry = new XML('<entry/>');
- entry.id = row.id;
- entry.title = row.key;
- entry.content = row.value;
- // We'll also let you return just an E4X object
- // if you aren't setting headers.
- return entry;
- } else {
- return "</feed>";
- }
- }
- })
- }),
- qsParams: stringFun(function(head, row, req, row_info) {
- if(head) return {body: req.query.foo};
- else return {body: "\n"};
- }),
- stopIter: stringFun(function(head, row, req, row_info) {
- if(head) {
- return {body: "head"};
- } else if(row) {
- if(row_info.row_number > 2) return {stop: true};
- return {body: " " + row_info.row_number};
- } else {
- return {body: " tail"};
- }
- }),
- stopIter2: stringFun(function(head, row, req, row_info) {
- return respondWith(req, {
- html: function() {
- if(head) {
- return "head";
- } else if(row) {
- if(row_info.row_number > 2) return {stop: true};
- return " " + row_info.row_number;
- } else {
- return " tail";
- }
- }
- });
- })
- }
- };
-
- T(db.save(designDoc).ok);
-
- var docs = makeDocs(0, 10);
- var saveResult = db.bulkSave(docs);
- T(saveResult.ok);
-
- var view = db.view('lists/basicView');
- T(view.total_rows == 10);
-
- // standard get
- var xhr = CouchDB.request("GET", "/test_suite_db/_list/lists/simpleForm/basicView");
- T(xhr.status == 200);
- T(/Total Rows/.test(xhr.responseText));
- T(/Key: 1/.test(xhr.responseText));
- T(/LineNo: 0/.test(xhr.responseText));
- T(/LineNo: 5/.test(xhr.responseText));
- T(/FirstKey: 0/.test(xhr.responseText));
- T(/LastKey: 9/.test(xhr.responseText));
-
-
- var lines = xhr.responseText.split('\n');
- T(/LineNo: 5/.test(lines[6]));
-
- // test that etags are available
- var etag = xhr.getResponseHeader("etag");
- xhr = CouchDB.request("GET", "/test_suite_db/_list/lists/simpleForm/basicView", {
- headers: {"if-none-match": etag}
- });
- T(xhr.status == 304);
-
- // get with query params
- var xhr = CouchDB.request("GET", "/test_suite_db/_list/lists/simpleForm/basicView?startkey=3");
- T(xhr.status == 200);
- T(/Total Rows/.test(xhr.responseText));
- T(!(/Key: 1/.test(xhr.responseText)));
- T(/FirstKey: 3/.test(xhr.responseText));
- T(/LastKey: 9/.test(xhr.responseText));
-
-
- // with 0 rows
- var xhr = CouchDB.request("GET", "/test_suite_db/_list/lists/simpleForm/basicView?startkey=30");
- T(xhr.status == 200);
- T(/Total Rows/.test(xhr.responseText));
- T(/Offset: null/.test(xhr.responseText));
-
- // when there is a reduce present, but not used
- var xhr = CouchDB.request("GET", "/test_suite_db/_list/lists/simpleForm/withReduce?reduce=false");
- T(xhr.status == 200);
- T(/Total Rows/.test(xhr.responseText));
- T(/Key: 1/.test(xhr.responseText));
-
- // with accept headers for HTML
- xhr = CouchDB.request("GET", "/test_suite_db/_list/lists/acceptSwitch/basicView", {
- headers: {
- "Accept": 'text/html'
- }
- });
- T(xhr.getResponseHeader("Content-Type") == "text/html");
- T(xhr.responseText.match(/HTML/));
- T(xhr.responseText.match(/Value/));
-
- // now with xml
- xhr = CouchDB.request("GET", "/test_suite_db/_list/lists/acceptSwitch/basicView", {
- headers: {
- "Accept": 'application/xml'
- }
- });
- T(xhr.getResponseHeader("Content-Type") == "application/xml");
- T(xhr.responseText.match(/XML/));
- T(xhr.responseText.match(/entry/));
-
- // now with extra qs params
- xhr = CouchDB.request("GET", "/test_suite_db/_list/lists/qsParams/basicView?foo=blam");
- T(xhr.responseText.match(/blam/));
-
-
- // aborting iteration
- xhr = CouchDB.request("GET", "/test_suite_db/_list/lists/stopIter/basicView");
- T(xhr.responseText.match(/^head 0 1 2 tail$/));
- xhr = CouchDB.request("GET", "/test_suite_db/_list/lists/stopIter2/basicView");
- T(xhr.responseText.match(/^head 0 1 2 tail$/));
-
-
- },
-
- compact: function(debug) {
- var db = new CouchDB("test_suite_db");
- db.deleteDb();
- db.createDb();
- if (debug) debugger;
- var docs = makeDocs(0, 10);
- var saveResult = db.bulkSave(docs);
- T(saveResult.ok);
-
- 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;
-
- for(var i in docs) {
- db.deleteDoc(docs[i]);
- }
- db.setAdmins(["Foo bar"]);
- var deletesize = db.info().disk_size;
- T(deletesize > originalsize);
-
- var xhr = CouchDB.request("POST", "/test_suite_db/_compact");
- T(xhr.status == 202);
- // compaction isn't instantaneous, loop until done
- while (db.info().compact_running) {};
-
- 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);
-
- },
-
- purge: function(debug) {
- var db = new CouchDB("test_suite_db");
- db.deleteDb();
- db.createDb();
- if (debug) debugger;
-
- /*
- purge is not to be confused with a document deletion. It removes the
- document and all edit history from the local instance of the database.
- */
-
- var numDocs = 10;
-
- var designDoc = {
- _id:"_design/test",
- language: "javascript",
- views: {
- all_docs_twice: {map: "function(doc) { emit(doc.integer, null); emit(doc.integer, null) }"},
- single_doc: {map: "function(doc) { if (doc._id == \"1\") { emit(1, null) }}"}
- }
- }
-
- T(db.save(designDoc).ok);
-
- T(db.bulkSave(makeDocs(1, numDocs + 1)).ok);
-
- // go ahead and validate the views before purging
- var rows = db.view("test/all_docs_twice").rows;
- for (var i = 0; i < numDocs; i++) {
- T(rows[2*i].key == i+1);
- T(rows[(2*i)+1].key == i+1);
- }
- T(db.view("test/single_doc").total_rows == 1);
-
- var info = db.info();
- var doc1 = db.open("1");
- var doc2 = db.open("2");
-
- // purge the documents
- var xhr = CouchDB.request("POST", "/test_suite_db/_purge", {
- body: JSON.stringify({"1":[doc1._rev], "2":[doc2._rev]}),
- });
- T(xhr.status == 200);
-
- var newInfo = db.info();
- // purging increments the update sequence
- T(info.update_seq+1 == newInfo.update_seq);
- // and it increments the purge_seq
- T(info.purge_seq+1 == newInfo.purge_seq);
-
- var result = JSON.parse(xhr.responseText);
- T(result.purged["1"][0] == doc1._rev);
- T(result.purged["2"][0] == doc2._rev);
-
- T(db.open("1") == null);
- T(db.open("2") == null);
-
- var rows = db.view("test/all_docs_twice").rows;
- for (var i = 2; i < numDocs; i++) {
- T(rows[2*(i-2)].key == i+1);
- T(rows[(2*(i-2))+1].key == i+1);
- }
- T(db.view("test/single_doc").total_rows == 0);
-
- // purge documents twice in a row without loading views
- // (causes full view rebuilds)
-
- var doc3 = db.open("3");
- var doc4 = db.open("4");
-
- xhr = CouchDB.request("POST", "/test_suite_db/_purge", {
- body: JSON.stringify({"3":[doc3._rev]}),
- });
-
- T(xhr.status == 200);
-
- xhr = CouchDB.request("POST", "/test_suite_db/_purge", {
- body: JSON.stringify({"4":[doc4._rev]}),
- });
-
- T(xhr.status == 200);
-
- var rows = db.view("test/all_docs_twice").rows;
- for (var i = 4; i < numDocs; i++) {
- T(rows[2*(i-4)].key == i+1);
- T(rows[(2*(i-4))+1].key == i+1);
- }
- T(db.view("test/single_doc").total_rows == 0);
- },
-
- config : function(debug) {
- var db = new CouchDB("test_suite_db");
- 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) {
- var proto = window.location.protocol;
- if(proto == "http:") {
- port = 80;
- }
- if(proto == "https:") {
- port = 443;
- }
- } else {
- port = server_port.pop();
- }
-
- if(port) {
- T(config.httpd.port == port);
- }
-
- 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
- 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");
- T(xhr.responseText == '"bar"');
- },
-
- security_validation : function(debug) {
- // This tests couchdb's security and validation features. This does
- // not test authentication, except to use test authentication code made
- // specifically for this testing. It is a WWWW-Authenticate scheme named
- // X-Couch-Test-Auth, and the user names and passwords are hard coded
- // on the server-side.
- //
- // We could have used Basic authentication, however the XMLHttpRequest
- // implementation for Firefox and Safari, and probably other browsers are
- // broken (Firefox always prompts the user on 401 failures, Safari gives
- // odd security errors when using different name/passwords, perhaps due
- // to cross site scripting prevention). These problems essentially make Basic
- // authentication testing in the browser impossible. But while hard to
- // test automated in the browser, Basic auth may still useful for real
- // world use where these bugs/behaviors don't matter.
- //
- // So for testing purposes we are using this custom X-Couch-Test-Auth.
- // It's identical to Basic auth, except it doesn't even base64 encode
- // the "username:password" string, it's sent completely plain text.
- // Firefox and Safari both deal with this correctly (which is to say
- // they correctly do nothing special).
-
-
- var db = new CouchDB("test_suite_db");
- db.deleteDb();
- db.createDb();
- if (debug) debugger;
-
- run_on_modified_server(
- [{section: "httpd",
- key: "authentication_handler",
- value: "{couch_httpd, special_test_authentication_handler}"},
- {section:"httpd",
- key: "WWW-Authenticate",
- value: "X-Couch-Test-Auth"}],
-
- function () {
-
- // try saving document usin the wrong credentials
- var wrongPasswordDb = new CouchDB("test_suite_db",
- {"WWW-Authenticate": "X-Couch-Test-Auth Damien Katz:foo"}
- );
-
- try {
- wrongPasswordDb.save({foo:1,author:"Damien Katz"});
- T(false && "Can't get here. Should have thrown an error 1");
- } catch (e) {
- T(e.error == "unauthorized");
- T(wrongPasswordDb.last_req.status == 401);
- }
-
-
- // Create the design doc that will run custom validation code
- var designDoc = {
- _id:"_design/test",
- language: "javascript",
- validate_doc_update: "(" + (function (newDoc, oldDoc, userCtx) {
- // docs should have an author field.
- if (!newDoc._deleted && !newDoc.author) {
- throw {forbidden:
- "Documents must have an author field"};
- }
- if (oldDoc && oldDoc.author != userCtx.name) {
- throw {unauthorized:
- "You are not the author of this document. You jerk."};
- }
- }).toString() + ")"
- }
-
- // Save a document normally
- var userDb = new CouchDB("test_suite_db",
- {"WWW-Authenticate": "X-Couch-Test-Auth Damien Katz:pecan pie"}
- );
-
- T(userDb.save({_id:"testdoc", foo:1, author:"Damien Katz"}).ok);
-
- // Attempt to save the design as a non-admin
- try {
- userDb.save(designDoc);
- T(false && "Can't get here. Should have thrown an error on design doc");
- } catch (e) {
- T(e.error == "unauthorized");
- T(userDb.last_req.status == 401);
- }
-
- // add user as admin
- db.setAdmins(["Damien Katz"]);
-
- T(userDb.save(designDoc).ok);
-
- // update the document
- var doc = userDb.open("testdoc");
- doc.foo=2;
- T(userDb.save(doc).ok);
-
- // Save a document that's missing an author field.
- try {
- userDb.save({foo:1});
- T(false && "Can't get here. Should have thrown an error 2");
- } catch (e) {
- T(e.error == "forbidden");
- T(userDb.last_req.status == 403);
- }
-
- // Now attempt to update the document as a different user, Jan
- var user2Db = new CouchDB("test_suite_db",
- {"WWW-Authenticate": "X-Couch-Test-Auth Jan Lehnardt:apple"}
- );
-
- var doc = user2Db.open("testdoc");
- doc.foo=3;
- try {
- user2Db.save(doc);
- T(false && "Can't get here. Should have thrown an error 3");
- } catch (e) {
- T(e.error == "unauthorized");
- T(user2Db.last_req.status == 401);
- }
-
- // Now have Damien change the author to Jan
- doc = userDb.open("testdoc");
- doc.author="Jan Lehnardt";
- T(userDb.save(doc).ok);
-
- // Now update the document as Jan
- doc = user2Db.open("testdoc");
- doc.foo = 3;
- T(user2Db.save(doc).ok);
-
- // Damien can't delete it
- try {
- userDb.deleteDoc(doc);
- T(false && "Can't get here. Should have thrown an error 4");
- } catch (e) {
- T(e.error == "unauthorized");
- T(userDb.last_req.status == 401);
- }
-
- // Now delete document
- T(user2Db.deleteDoc(doc).ok);
- });
- },
-
-
- max_dbs_open : function(debug) {
- if (debug) debugger;
- restartServer();
- var max = 5;
- run_on_modified_server(
- [{section: "couchdb",
- key: "max_dbs_open",
- value: max.toString()}],
-
- function () {
- for(var i=0; i<max*2; i++) {
- var db = new CouchDB("test_suite_db"+ i);
- db.deleteDb();
- db.createDb();
- }
-
- var stats = JSON.parse(CouchDB.request("GET", "/_stats").responseText);
- T(stats.dbs_open == max);
-
-
- for(var i=0; i<max*2; i++) {
- var db = new CouchDB("test_suite_db"+ i);
- db.deleteDb();
- }
-
- var stats = JSON.parse(CouchDB.request("GET", "/_stats").responseText);
- T(stats.dbs_open == 0);
- })
- },
+function loadTest(file) {
+ loadScript("script/test/"+file);
};
+loadTest("basics.js");
+loadTest("delayed_commits.js");
+loadTest("all_docs.js");
+loadTest("conflicts.js");
+loadTest("recreate_doc.js");
+loadTest("copy_move_doc.js");
+loadTest("uuids.js");
+loadTest("bulk_docs.js");
+loadTest("lots_of_docs.js");
+loadTest("reduce.js");
+loadTest("reduce_false.js");
+loadTest("design_options.js");
+loadTest("multiple_rows.js");
+loadTest("large_docs.js");
+loadTest("utf8.js");
+loadTest("attachments.js");
+loadTest("attachment_paths.js");
+loadTest("attachment_views.js");
+loadTest("design_paths.js");
+loadTest("content_negotiation.js");
+loadTest("design_docs.js");
+loadTest("invalid_docids.js");
+loadTest("view_collation.js");
+loadTest("view_conflicts.js");
+loadTest("view_errors.js");
+loadTest("view_include_docs.js");
+loadTest("view_multi_key_all_docs.js");
+loadTest("view_multi_key_design.js");
+loadTest("view_multi_key_temp.js");
+loadTest("view_pagination.js");
+loadTest("view_sandboxing.js");
+loadTest("view_xml.js");
+loadTest("replication.js");
+loadTest("etags_head.js");
+loadTest("etags_views.js");
+loadTest("show_documents.js");
+loadTest("list_views.js");
+loadTest("compact.js");
+loadTest("purge.js");
+loadTest("config.js");
+loadTest("security_validation.js");
+loadTest("max_dbs_open.js");
+
function makeDocs(start, end, templateDoc) {
var templateDocSrc = templateDoc ? JSON.stringify(templateDoc) : "{}"
if (end === undefined) {
diff --git a/share/www/script/test/all_docs.js b/share/www/script/test/all_docs.js
new file mode 100644
index 00000000..3dd3aa53
--- /dev/null
+++ b/share/www/script/test/all_docs.js
@@ -0,0 +1,100 @@
+// 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");
+ 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);
+
+ // check that the docs show up in the seq view in the order they were created
+ var all_seq = db.allDocsBySeq();
+ var ids = ["0","3","1","2"];
+ for (var i=0; i < all_seq.rows.length; i++) {
+ var row = all_seq.rows[i];
+ T(row.id == ids[i]);
+ };
+
+ // it should work in reverse as well
+ all_seq = db.allDocsBySeq({descending:true});
+ ids = ["2","1","3","0"];
+ for (var i=0; i < all_seq.rows.length; i++) {
+ var row = all_seq.rows[i];
+ T(row.id == ids[i]);
+ };
+
+ // check that deletions also show up right
+ var doc1 = db.open("1");
+ var deleted = db.deleteDoc(doc1);
+ T(deleted.ok);
+ all_seq = db.allDocsBySeq();
+
+ // the deletion should make doc id 1 have the last seq num
+ T(all_seq.rows.length == 4);
+ T(all_seq.rows[3].id == "1");
+ T(all_seq.rows[3].value.deleted);
+
+ // is this a bug?
+ // T(all_seq.rows.length == all_seq.total_rows);
+
+ // do an update
+ var doc2 = db.open("3");
+ doc2.updated = "totally";
+ db.save(doc2);
+ all_seq = db.allDocsBySeq();
+
+ // the update should make doc id 3 have the last seq num
+ T(all_seq.rows.length == 4);
+ T(all_seq.rows[3].id == "3");
+
+ // ok now lets see what happens with include docs
+ all_seq = db.allDocsBySeq({include_docs: true});
+ T(all_seq.rows.length == 4);
+ T(all_seq.rows[3].id == "3");
+ T(all_seq.rows[3].doc.updated == "totally");
+
+ // and on the deleted one, no doc
+ T(all_seq.rows[2].value.deleted);
+ T(!all_seq.rows[2].doc);
+
+ // 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);
+};
diff --git a/share/www/script/test/attachment_paths.js b/share/www/script/test/attachment_paths.js
new file mode 100644
index 00000000..ef9fa869
--- /dev/null
+++ b/share/www/script/test/attachment_paths.js
@@ -0,0 +1,147 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+couchTests.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);
+ T(binAttDoc._attachments["foo/bar2.txt"].content_type == "text/plain;charset=utf-8");
+ 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);
+ T(binAttDoc._attachments["foo/bar2.txt"].content_type == "text/plain;charset=utf-8");
+ T(binAttDoc._attachments["foo/bar2.txt"].length == 30);
+ }
+};
diff --git a/share/www/script/test/attachment_views.js b/share/www/script/test/attachment_views.js
new file mode 100644
index 00000000..b97a4130
--- /dev/null
+++ b/share/www/script/test/attachment_views.js
@@ -0,0 +1,98 @@
+// 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");
+ db.deleteDb();
+ db.createDb();
+ if (debug) debugger;
+
+ // count attachments in a view
+
+ db.bulkSave(makeDocs(0, 10));
+
+ db.bulkSave(makeDocs(10, 20, {
+ _attachments:{
+ "foo.txt": {
+ content_type:"text/plain",
+ data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
+ }
+ }
+ }));
+
+ db.bulkSave(makeDocs(20, 30, {
+ _attachments:{
+ "foo.txt": {
+ content_type:"text/plain",
+ data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
+ },
+ "bar.txt": {
+ content_type:"text/plain",
+ data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
+ }
+ }
+ }));
+
+ db.bulkSave(makeDocs(30, 40, {
+ _attachments:{
+ "foo.txt": {
+ content_type:"text/plain",
+ data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
+ },
+ "bar.txt": {
+ content_type:"text/plain",
+ data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
+ },
+ "baz.txt": {
+ content_type:"text/plain",
+ data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
+ }
+ }
+ }));
+
+ 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);
+
+};
diff --git a/share/www/script/test/attachments.js b/share/www/script/test/attachments.js
new file mode 100644
index 00000000..409426c7
--- /dev/null
+++ b/share/www/script/test/attachments.js
@@ -0,0 +1,146 @@
+// 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");
+ db.deleteDb();
+ db.createDb();
+ if (debug) debugger;
+
+ var binAttDoc = {
+ _id: "bin_doc",
+ _attachments:{
+ "foo.txt": {
+ content_type:"text/plain",
+ 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") == "text/plain");
+ T(xhr.getResponseHeader("Etag") == save_response.rev);
+
+ // 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);
+ var rev = JSON.parse(xhr.responseText).rev;
+
+ binAttDoc2 = db.open("bin_doc2");
+
+ T(binAttDoc2._attachments["foo.txt"] !== undefined);
+ T(binAttDoc2._attachments["foo2.txt"] !== undefined);
+ T(binAttDoc2._attachments["foo2.txt"].content_type == "text/plain;charset=utf-8");
+ 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");
+ T(xhr.getResponseHeader("Content-Type") == "text/plain;charset=utf-8");
+
+ // 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);
+
+
+ // 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;
+
+ var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc3/attachment.txt");
+ T(xhr.responseText == bin_data);
+ T(xhr.getResponseHeader("Content-Type") == "text/plain;charset=utf-8");
+
+ 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);
+
+ 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;
+
+ var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc3/attachment.txt");
+ T(xhr.responseText == bin_data);
+ T(xhr.getResponseHeader("Content-Type") == "text/plain;charset=utf-8");
+
+ var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc3/attachment.txt?rev=" + rev);
+ T(xhr.responseText == bin_data);
+ T(xhr.getResponseHeader("Content-Type") == "text/plain;charset=utf-8");
+
+ 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?rev=" + rev);
+ T(xhr.status == 404);
+
+ // 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");
+
+};
diff --git a/share/www/script/test/basics.js b/share/www/script/test/basics.js
new file mode 100644
index 00000000..89e99a64
--- /dev/null
+++ b/share/www/script/test/basics.js
@@ -0,0 +1,144 @@
+// 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");
+ 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;
+
+ // Get the database info, check the db_name
+ T(db.info().db_name == "test_suite_db");
+
+ // 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");
+
+ // 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);
+
+ // 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);
+ };
+
+ 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
+ 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"})
+ });
+ 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[4] == resp.id);
+ T(locs[3] == "test_suite_db");
+ };
diff --git a/share/www/script/test/bulk_docs.js b/share/www/script/test/bulk_docs.js
new file mode 100644
index 00000000..d781b2c6
--- /dev/null
+++ b/share/www/script/test/bulk_docs.js
@@ -0,0 +1,56 @@
+// 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");
+ db.deleteDb();
+ db.createDb();
+ if (debug) debugger;
+
+ var docs = makeDocs(5);
+
+ // Create the docs
+ var result = db.bulkSave(docs);
+ T(result.ok);
+ T(result.new_revs.length == 5);
+ for (var i = 0; i < 5; i++) {
+ T(result.new_revs[i].id == docs[i]._id);
+ T(result.new_revs[i].rev);
+ docs[i].string = docs[i].string + ".00";
+ }
+
+ // Update the docs
+ result = db.bulkSave(docs);
+ T(result.ok);
+ T(result.new_revs.length == 5);
+ for (i = 0; i < 5; i++) {
+ T(result.new_revs[i].id == i.toString());
+ docs[i]._deleted = true;
+ }
+
+ // Delete the docs
+ result = db.bulkSave(docs);
+ T(result.ok);
+ T(result.new_revs.length == 5);
+ for (i = 0; i < 5; i++) {
+ T(db.open(docs[i]._id) == null);
+ }
+
+ // 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"}]})
+ });
+ result = JSON.parse(req.responseText);
+
+ T(result.new_revs[0].id != "");
+ T(result.new_revs[0].rev != "");
+};
diff --git a/share/www/script/test/compact.js b/share/www/script/test/compact.js
new file mode 100644
index 00000000..4612621e
--- /dev/null
+++ b/share/www/script/test/compact.js
@@ -0,0 +1,56 @@
+// 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");
+ db.deleteDb();
+ db.createDb();
+ if (debug) debugger;
+ var docs = makeDocs(0, 10);
+ var saveResult = db.bulkSave(docs);
+ T(saveResult.ok);
+
+ 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;
+
+ for(var i in docs) {
+ db.deleteDoc(docs[i]);
+ }
+ db.setAdmins(["Foo bar"]);
+ var deletesize = db.info().disk_size;
+ T(deletesize > originalsize);
+
+ var xhr = CouchDB.request("POST", "/test_suite_db/_compact");
+ T(xhr.status == 202);
+ // compaction isn't instantaneous, loop until done
+ while (db.info().compact_running) {};
+
+ 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);
+
+};
diff --git a/share/www/script/test/config.js b/share/www/script/test/config.js
new file mode 100644
index 00000000..a156be37
--- /dev/null
+++ b/share/www/script/test/config.js
@@ -0,0 +1,66 @@
+// 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");
+ 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) {
+ var proto = window.location.protocol;
+ if(proto == "http:") {
+ port = 80;
+ }
+ if(proto == "https:") {
+ port = 443;
+ }
+ } else {
+ port = server_port.pop();
+ }
+
+ if(port) {
+ T(config.httpd.port == port);
+ }
+
+ 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
+ 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");
+ T(xhr.responseText == '"bar"');
+};
diff --git a/share/www/script/test/conflicts.js b/share/www/script/test/conflicts.js
new file mode 100644
index 00000000..02e50035
--- /dev/null
+++ b/share/www/script/test/conflicts.js
@@ -0,0 +1,60 @@
+// 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");
+ 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");
+ }
+
+ // 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");
+ }
+
+ // 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.
+};
diff --git a/share/www/script/test/content_negotiation.js b/share/www/script/test/content_negotiation.js
new file mode 100644
index 00000000..757105ff
--- /dev/null
+++ b/share/www/script/test/content_negotiation.js
@@ -0,0 +1,32 @@
+// 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");
+ db.deleteDb();
+ db.createDb();
+ if (debug) debugger;
+ var xhr;
+
+ xhr = CouchDB.request("GET", "/test_suite_db/");
+ T(xhr.getResponseHeader("Content-Type") == "text/plain;charset=utf-8");
+
+ xhr = CouchDB.request("GET", "/test_suite_db/", {
+ headers: {"Accept": "text/html;text/plain;*/*"}
+ });
+ T(xhr.getResponseHeader("Content-Type") == "text/plain;charset=utf-8");
+
+ xhr = CouchDB.request("GET", "/test_suite_db/", {
+ headers: {"Accept": "application/json"}
+ });
+ T(xhr.getResponseHeader("Content-Type") == "application/json");
+};
diff --git a/share/www/script/test/copy_move_doc.js b/share/www/script/test/copy_move_doc.js
new file mode 100644
index 00000000..050f8113
--- /dev/null
+++ b/share/www/script/test/copy_move_doc.js
@@ -0,0 +1,65 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+couchTests.copy_move_doc = function(debug) {
+ var db = new CouchDB("test_suite_db");
+ db.deleteDb();
+ db.createDb();
+ if (debug) debugger;
+
+ // copy a doc
+ T(db.save({_id:"doc_to_be_copied",v:1}).ok);
+ var xhr = CouchDB.request("COPY", "/test_suite_db/doc_to_be_copied", {
+ headers: {"Destination":"doc_that_was_copied"}
+ });
+
+ T(xhr.status == 201);
+ T(db.open("doc_that_was_copied").v == 1);
+
+ // move a doc
+
+ // test error condition
+ var xhr = CouchDB.request("MOVE", "/test_suite_db/doc_to_be_copied", {
+ headers: {"Destination":"doc_that_was_moved"}
+ });
+ T(xhr.status == 400); // bad request, MOVE requires source rev.
+
+ var rev = db.open("doc_to_be_copied")._rev;
+ var xhr = CouchDB.request("MOVE", "/test_suite_db/doc_to_be_copied?rev=" + rev, {
+ headers: {"Destination":"doc_that_was_moved"}
+ });
+
+ T(xhr.status == 201);
+ T(db.open("doc_that_was_moved").v == 1);
+ T(db.open("doc_to_be_copied") == null);
+
+ // COPY with existing target
+ T(db.save({_id:"doc_to_be_copied",v:1}).ok);
+ var doc = db.save({_id:"doc_to_be_overwritten",v:2});
+ T(doc.ok);
+
+ // error condition
+ var xhr = CouchDB.request("COPY", "/test_suite_db/doc_to_be_copied", {
+ headers: {"Destination":"doc_to_be_overwritten"}
+ });
+ T(xhr.status == 409); // conflict
+
+ var rev = db.open("doc_to_be_overwritten")._rev;
+ var xhr = CouchDB.request("COPY", "/test_suite_db/doc_to_be_copied", {
+ headers: {"Destination":"doc_to_be_overwritten?rev=" + rev}
+ });
+ T(xhr.status == 201);
+
+ var over = db.open("doc_to_be_overwritten");
+ T(rev != over._rev);
+ T(over.v == 1);
+};
diff --git a/share/www/script/test/delayed_commits.js b/share/www/script/test/delayed_commits.js
new file mode 100644
index 00000000..daebdb8d
--- /dev/null
+++ b/share/www/script/test/delayed_commits.js
@@ -0,0 +1,93 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+couchTests.delayed_commits = function(debug) {
+ var db = new CouchDB("test_suite_db");
+ db.deleteDb();
+ db.createDb();
+ if (debug) debugger;
+
+ // By default, couchdb doesn't fully commit documents to disk right away,
+ // it waits about a second to batch the full commit flush along with any
+ // other updates. If it crashes or is restarted you may lose the most
+ // recent commits.
+
+ T(db.save({_id:"1",a:2,b:4}).ok);
+ T(db.open("1") != null);
+
+ restartServer();
+
+ T(db.open("1") == null); // lost the update.
+ // note if we waited > 1 sec before the restart, the doc would likely
+ // commit.
+
+
+ // Retry the same thing but with full commits on.
+
+ var db2 = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"true"});
+
+ T(db2.save({_id:"1",a:2,b:4}).ok);
+ T(db2.open("1") != null);
+
+ restartServer();
+
+ T(db2.open("1") != null);
+
+ // You can update but without committing immediately, and then ensure
+ // everything is commited in the last step.
+
+ T(db.save({_id:"2",a:2,b:4}).ok);
+ T(db.open("2") != null);
+ T(db.ensureFullCommit().ok);
+ restartServer();
+
+ T(db.open("2") != null);
+
+ // However, it's possible even when flushed, that the server crashed between
+ // the update and the commit, and you don't want to check to make sure
+ // every doc you updated actually made it to disk. So record the instance
+ // start time of the database before the updates and then check it again
+ // after the flush (the instance start time is returned by the flush
+ // operation). if they are the same, we know everything was updated
+ // safely.
+
+ // First try it with a crash.
+
+ var instanceStartTime = db.info().instance_start_time;
+
+ T(db.save({_id:"3",a:2,b:4}).ok);
+ T(db.open("3") != null);
+
+ restartServer();
+
+ var commitResult = db.ensureFullCommit();
+ T(commitResult.ok && commitResult.instance_start_time != instanceStartTime);
+ // start times don't match, meaning the server lost our change
+
+ T(db.open("3") == null); // yup lost it
+
+ // retry with no server restart
+
+ var instanceStartTime = db.info().instance_start_time;
+
+ T(db.save({_id:"4",a:2,b:4}).ok);
+ T(db.open("4") != null);
+
+ var commitResult = db.ensureFullCommit();
+ T(commitResult.ok && commitResult.instance_start_time == instanceStartTime);
+ // Successful commit, start times match!
+
+ restartServer();
+
+ T(db.open("4") != null);
+
+};
diff --git a/share/www/script/test/design_docs.js b/share/www/script/test/design_docs.js
new file mode 100644
index 00000000..aedf741a
--- /dev/null
+++ b/share/www/script/test/design_docs.js
@@ -0,0 +1,107 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+couchTests.design_docs = function(debug) {
+ var db = new CouchDB("test_suite_db");
+ db.deleteDb();
+ db.createDb();
+ if (debug) debugger;
+
+ var numDocs = 500;
+
+ function makebigstring(power) {
+ var str = "a";
+ while(power-- > 0) {
+ str = str + str;
+ }
+ return str;
+ }
+
+ var designDoc = {
+ _id:"_design/test", // turn off couch.js id escaping?
+ language: "javascript",
+ views: {
+ all_docs_twice: {map: "function(doc) { emit(doc.integer, null); emit(doc.integer, null) }"},
+ no_docs: {map: "function(doc) {}"},
+ single_doc: {map: "function(doc) { if (doc._id == \"1\") { emit(1, null) }}"},
+ summate: {map:"function (doc) {emit(doc.integer, doc.integer)};",
+ reduce:"function (keys, values) { return sum(values); };"},
+ summate2: {map:"function (doc) {emit(doc.integer, doc.integer)};",
+ reduce:"function (keys, values) { return sum(values); };"},
+ huge_src_and_results: {map: "function(doc) { if (doc._id == \"1\") { emit(\"" + makebigstring(16) + "\", null) }}",
+ reduce:"function (keys, values) { return \"" + makebigstring(16) + "\"; };"}
+ }
+ }
+ T(db.save(designDoc).ok);
+
+ T(db.bulkSave(makeDocs(1, numDocs + 1)).ok);
+
+ // test that the _all_docs view returns correctly with keys
+ var results = db.allDocs({startkey:"_design", endkey:"_design0"});
+ T(results.rows.length == 1);
+
+ for (var loop = 0; loop < 2; loop++) {
+ var rows = db.view("test/all_docs_twice").rows;
+ for (var i = 0; i < numDocs; i++) {
+ T(rows[2*i].key == i+1);
+ T(rows[(2*i)+1].key == i+1);
+ }
+ T(db.view("test/no_docs").total_rows == 0)
+ T(db.view("test/single_doc").total_rows == 1)
+ T(db.ensureFullCommit().ok);
+ restartServer();
+ };
+
+ // test when language not specified, Javascript is implied
+ var designDoc2 = {
+ _id:"_design/test2",
+ // language: "javascript",
+ views: {
+ single_doc: {map: "function(doc) { if (doc._id == \"1\") { emit(1, null) }}"}
+ }
+ };
+
+ T(db.save(designDoc2).ok);
+ T(db.view("test2/single_doc").total_rows == 1);
+
+ var summate = function(N) {return (N+1)*N/2;};
+ var result = db.view("test/summate");
+ T(result.rows[0].value == summate(numDocs));
+
+ result = db.view("test/summate", {startkey:4,endkey:4});
+ T(result.rows[0].value == 4);
+
+ result = db.view("test/summate", {startkey:4,endkey:5});
+ T(result.rows[0].value == 9);
+
+ result = db.view("test/summate", {startkey:4,endkey:6});
+ T(result.rows[0].value == 15);
+
+ // Verify that a shared index (view def is an exact copy of "summate")
+ // does not confuse the reduce stage
+ result = db.view("test/summate2", {startkey:4,endkey:6});
+ T(result.rows[0].value == 15);
+
+ for(var i=1; i<numDocs/2; i+=30) {
+ result = db.view("test/summate", {startkey:i,endkey:numDocs-i});
+ T(result.rows[0].value == summate(numDocs-i) - summate(i-1));
+ }
+
+ T(db.deleteDoc(designDoc).ok);
+ T(db.open(designDoc._id) == null);
+ T(db.view("test/no_docs") == null);
+
+ T(db.ensureFullCommit().ok);
+ restartServer();
+ T(db.open(designDoc._id) == null);
+ T(db.view("test/no_docs") == null);
+};
diff --git a/share/www/script/test/design_options.js b/share/www/script/test/design_options.js
new file mode 100644
index 00000000..c2764b91
--- /dev/null
+++ b/share/www/script/test/design_options.js
@@ -0,0 +1,63 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+couchTests.design_options = function(debug) {
+ var db = new CouchDB("test_suite_db");
+ db.deleteDb();
+ db.createDb();
+ if (debug) debugger;
+
+ //// test the includes_design option
+ var map = "function (doc) {emit(null, doc._id);}";
+
+ // we need a design doc even to test temp views with it
+ var designDoc = {
+ _id:"_design/fu",
+ language: "javascript",
+ options: {
+ include_design: true
+ },
+ views: {
+ data: {"map": map}
+ }
+ };
+ T(db.save(designDoc).ok);
+
+ // should work for temp views
+ var rows = db.query(map, null, {options:{include_design: true}}).rows;
+ T(rows.length == 1);
+ T(rows[0].value == "_design/fu");
+
+ rows = db.query(map).rows;
+ T(rows.length == 0);
+
+ // when true, should include design docs in views
+ rows = db.view("fu/data").rows;
+ T(rows.length == 1);
+ T(rows[0].value == "_design/fu");
+
+ // when false, should not
+ designDoc.options.include_design = false;
+ delete designDoc._rev;
+ designDoc._id = "_design/bingo";
+ T(db.save(designDoc).ok);
+ rows = db.view("bingo/data").rows;
+ T(rows.length == 0);
+
+ // should default to false
+ delete designDoc.options;
+ delete designDoc._rev;
+ designDoc._id = "_design/bango";
+ T(db.save(designDoc).ok);
+ rows = db.view("bango/data").rows;
+ T(rows.length == 0);
+};
diff --git a/share/www/script/test/design_paths.js b/share/www/script/test/design_paths.js
new file mode 100644
index 00000000..7722a188
--- /dev/null
+++ b/share/www/script/test/design_paths.js
@@ -0,0 +1,72 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+couchTests.design_paths = function(debug) {
+ if (debug) debugger;
+ var dbNames = ["test_suite_db", "test_suite_db/with_slashes"];
+ for (var i=0; i < dbNames.length; i++) {
+ var db = new CouchDB(dbNames[i]);
+ var dbName = encodeURIComponent(dbNames[i]);
+ db.deleteDb();
+ db.createDb();
+
+ // create a ddoc w bulk_docs
+ db.bulkSave([{
+ _id : "_design/test",
+ views : {
+ "testing" : {
+ "map" : "function(){emit(1,1)}"
+ }
+ }
+ }]);
+
+ // ddoc is getable
+ var xhr = CouchDB.request("GET", "/"+dbName+"/_design/test");
+ var resp = JSON.parse(xhr.responseText);
+ T(resp._id == "_design/test");
+
+ // it's at 2 urls...
+ var xhr = CouchDB.request("GET", "/"+dbName+"/_design%2Ftest");
+ var resp = JSON.parse(xhr.responseText);
+ T(resp._id == "_design/test");
+
+ // ensure that views are addressable
+ resp = db.view("test/testing")
+ T(resp.total_rows == 0)
+
+ // create a ddoc by putting to url with raw slash
+ var xhr = CouchDB.request("PUT", "/"+dbName+"/_design/test2",{
+ body : JSON.stringify({
+ _id : "_design/test2",
+ views : {
+ "testing" : {
+ "map" : "function(){emit(1,1)}"
+ }
+ }
+ })
+ });
+
+ // ddoc is getable
+ var xhr = CouchDB.request("GET", "/"+dbName+"/_design/test2");
+ var resp = JSON.parse(xhr.responseText);
+ T(resp._id == "_design/test2");
+
+ // it's at 2 urls...
+ var xhr = CouchDB.request("GET", "/"+dbName+"/_design%2Ftest2");
+ var resp = JSON.parse(xhr.responseText);
+ T(resp._id == "_design/test2");
+
+ // ensure that views are addressable
+ resp = db.view("test2/testing");
+ T(resp.total_rows == 0);
+ };
+};
diff --git a/share/www/script/test/etags_head.js b/share/www/script/test/etags_head.js
new file mode 100644
index 00000000..097e6f9a
--- /dev/null
+++ b/share/www/script/test/etags_head.js
@@ -0,0 +1,78 @@
+// 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.etags_head = function(debug) {
+ var db = new CouchDB("test_suite_db");
+ db.deleteDb();
+ db.createDb();
+ if (debug) debugger;
+
+ var xhr;
+
+ // create a new doc
+ xhr = CouchDB.request("PUT", "/test_suite_db/1", {
+ body: "{}"
+ });
+ T(xhr.status == 201);
+
+ // extract the ETag header values
+ var etag = xhr.getResponseHeader("etag");
+
+ // get the doc and verify the headers match
+ xhr = CouchDB.request("GET", "/test_suite_db/1");
+ T(etag == xhr.getResponseHeader("etag"));
+
+ // 'head' the doc and verify the headers match
+ xhr = CouchDB.request("HEAD", "/test_suite_db/1", {
+ headers: {"if-none-match": "s"}
+ });
+ T(etag == xhr.getResponseHeader("etag"));
+
+ // replace a doc
+ xhr = CouchDB.request("PUT", "/test_suite_db/1", {
+ body: "{}",
+ headers: {"if-match": etag}
+ });
+ T(xhr.status == 201);
+
+ // extract the new ETag value
+ var etagOld= etag;
+ etag = xhr.getResponseHeader("etag");
+
+ // fail to replace a doc
+ xhr = CouchDB.request("PUT", "/test_suite_db/1", {
+ body: "{}"
+ });
+ T(xhr.status == 409);
+
+ // verify get w/Etag
+ xhr = CouchDB.request("GET", "/test_suite_db/1", {
+ headers: {"if-none-match": etagOld}
+ });
+ T(xhr.status == 200);
+ xhr = CouchDB.request("GET", "/test_suite_db/1", {
+ headers: {"if-none-match": etag}
+ });
+ T(xhr.status == 304);
+
+ // fail to delete a doc
+ xhr = CouchDB.request("DELETE", "/test_suite_db/1", {
+ headers: {"if-match": etagOld}
+ });
+ T(xhr.status == 409);
+
+ //now do it for real
+ xhr = CouchDB.request("DELETE", "/test_suite_db/1", {
+ headers: {"if-match": etag}
+ });
+ T(xhr.status == 200);
+};
diff --git a/share/www/script/test/etags_views.js b/share/www/script/test/etags_views.js
new file mode 100644
index 00000000..808a6829
--- /dev/null
+++ b/share/www/script/test/etags_views.js
@@ -0,0 +1,87 @@
+// 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.etags_views = function(debug) {
+ var db = new CouchDB("test_suite_db");
+ db.deleteDb();
+ db.createDb();
+ if (debug) debugger;
+
+ var designDoc = {
+ _id:"_design/etags",
+ language: "javascript",
+ views : {
+ basicView : {
+ map : stringFun(function(doc) {
+ emit(doc.integer, doc.string);
+ })
+ },
+ withReduce : {
+ map : stringFun(function(doc) {
+ emit(doc.integer, doc.string);
+ }),
+ reduce : stringFun(function(keys, values, rereduce) {
+ if (rereduce) {
+ return sum(values);
+ } else {
+ return values.length;
+ }
+ })
+ }
+ }
+ }
+ T(db.save(designDoc).ok);
+ var xhr;
+ var docs = makeDocs(0, 10);
+ var saveResult = db.bulkSave(docs);
+ T(saveResult.ok);
+
+ // verify get w/Etag on map view
+ xhr = CouchDB.request("GET", "/test_suite_db/_view/etags/basicView");
+ T(xhr.status == 200);
+ var etag = xhr.getResponseHeader("etag");
+ xhr = CouchDB.request("GET", "/test_suite_db/_view/etags/basicView", {
+ headers: {"if-none-match": etag}
+ });
+ T(xhr.status == 304);
+ // TODO GET with keys (when that is available)
+
+ // reduce view
+ xhr = CouchDB.request("GET", "/test_suite_db/_view/etags/withReduce");
+ T(xhr.status == 200);
+ var etag = xhr.getResponseHeader("etag");
+ xhr = CouchDB.request("GET", "/test_suite_db/_view/etags/withReduce", {
+ headers: {"if-none-match": etag}
+ });
+ T(xhr.status == 304);
+
+ // all docs
+ xhr = CouchDB.request("GET", "/test_suite_db/_all_docs");
+ T(xhr.status == 200);
+ var etag = xhr.getResponseHeader("etag");
+ xhr = CouchDB.request("GET", "/test_suite_db/_all_docs", {
+ headers: {"if-none-match": etag}
+ });
+ T(xhr.status == 304);
+
+ // by seq
+ xhr = CouchDB.request("GET", "/test_suite_db/_all_docs_by_seq");
+ T(xhr.status == 200);
+ var etag = xhr.getResponseHeader("etag");
+ xhr = CouchDB.request("GET", "/test_suite_db/_all_docs_by_seq", {
+ headers: {"if-none-match": etag}
+ });
+ T(xhr.status == 304);
+
+ // list etag
+ // in the list test for now
+};
diff --git a/share/www/script/test/invalid_docids.js b/share/www/script/test/invalid_docids.js
new file mode 100644
index 00000000..6ed01f0b
--- /dev/null
+++ b/share/www/script/test/invalid_docids.js
@@ -0,0 +1,54 @@
+// 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.invalid_docids = function(debug) {
+ var db = new CouchDB("test_suite_db");
+ db.deleteDb();
+ db.createDb();
+ if (debug) debugger;
+
+ // Test _local explicitly first.
+ T(db.save({"_id": "_local/foo"}).ok);
+ T(db.open("_local/foo")._id == "_local/foo");
+
+ //Test non-string
+ try {
+ db.save({"_id": 1});
+ T(1 == 0);
+ } catch(e) {
+ T(db.last_req.status == 400);
+ T(e.error == "invalid_doc");
+ }
+
+ // Test invalid _prefix
+ try {
+ db.save({"_id": "_invalid"});
+ T(1 == 0);
+ } catch(e) {
+ T(db.last_req.status == 400);
+ T(e.error == "invalid_doc");
+ }
+
+ // Test _bulk_docs explicitly.
+ var docs = [{"_id": "_design/foo"}, {"_id": "_local/bar"}];
+ T(db.bulkSave(docs).ok);
+ docs.forEach(function(d) {T(db.open(d._id)._id == d._id);});
+
+ docs = [{"_id": "_invalid"}];
+ try {
+ db.bulkSave(docs);
+ T(1 == 0);
+ } catch(e) {
+ T(db.last_req.status == 400);
+ T(e.error == "invalid_doc");
+ }
+};
diff --git a/share/www/script/test/large_docs.js b/share/www/script/test/large_docs.js
new file mode 100644
index 00000000..4f0b343d
--- /dev/null
+++ b/share/www/script/test/large_docs.js
@@ -0,0 +1,33 @@
+// 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.large_docs = function(debug) {
+ var db = new CouchDB("test_suite_db");
+ db.deleteDb();
+ db.createDb();
+ if (debug) debugger;
+
+ var longtext = "0123456789\n";
+
+ for (var i=0; i<10; i++) {
+ longtext = longtext + longtext
+ }
+ T(db.save({"longtest":longtext}).ok);
+ T(db.save({"longtest":longtext}).ok);
+ T(db.save({"longtest":longtext}).ok);
+ T(db.save({"longtest":longtext}).ok);
+
+ // query all documents, and return the doc.foo member as a key.
+ results = db.query(function(doc){
+ emit(null, doc.longtest);
+ });
+};
diff --git a/share/www/script/test/list_views.js b/share/www/script/test/list_views.js
new file mode 100644
index 00000000..de593035
--- /dev/null
+++ b/share/www/script/test/list_views.js
@@ -0,0 +1,216 @@
+// 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.list_views = function(debug) {
+ var db = new CouchDB("test_suite_db");
+ db.deleteDb();
+ db.createDb();
+ if (debug) debugger;
+
+ var designDoc = {
+ _id:"_design/lists",
+ language: "javascript",
+ views : {
+ basicView : {
+ map : stringFun(function(doc) {
+ emit(doc.integer, doc.string);
+ })
+ },
+ withReduce : {
+ map : stringFun(function(doc) {
+ emit(doc.integer, doc.string);
+ }),
+ reduce : stringFun(function(keys, values, rereduce) {
+ if (rereduce) {
+ return sum(values);
+ } else {
+ return values.length;
+ }
+ })
+ }
+ },
+ lists: {
+ simpleForm: stringFun(function(head, row, req, row_info) {
+ if (row) {
+ // we ignore headers on rows and tail
+ return {
+ body : '\n<li>Key: '+row.key
+ +' Value: '+row.value
+ +' LineNo: '+row_info.row_number+'</li>'
+ };
+ } else if (head) {
+ // we return an object (like those used by external and show)
+ // so that we can specify headers
+ return {
+ body : '<h1>Total Rows: '
+ + head.total_rows
+ + ' Offset: ' + head.offset
+ + '</h1><ul>'
+ };
+ } else {
+ // tail
+ return {body : '</ul>'+
+ '<p>FirstKey: '+row_info.first_key+
+ ' LastKey: '+row_info.prev_key+'</p>'};
+ }
+ }),
+ acceptSwitch: stringFun(function(head, row, req, row_info) {
+ return respondWith(req, {
+ html : function() {
+ // If you're outputting text and you're not setting
+ // any headers, you can just return a string.
+ if (head) {
+ return "HTML <ul>";
+ } else if (row) {
+ return '\n<li>Key: '
+ +row.key+' Value: '+row.value
+ +' LineNo: '+row_info.row_number+'</li>';
+ } else { // tail
+ return '</ul>';
+
+ }
+ },
+ xml : function() {
+ if (head) {
+ return '<feed xmlns="http://www.w3.org/2005/Atom">'
+ +'<title>Test XML Feed</title>';
+ } else if (row) {
+ // Becase Safari can't stand to see that dastardly
+ // E4X outside of a string. Outside of tests you
+ // can just use E4X literals.
+ var entry = new XML('<entry/>');
+ entry.id = row.id;
+ entry.title = row.key;
+ entry.content = row.value;
+ // We'll also let you return just an E4X object
+ // if you aren't setting headers.
+ return entry;
+ } else {
+ return "</feed>";
+ }
+ }
+ })
+ }),
+ qsParams: stringFun(function(head, row, req, row_info) {
+ if(head) return {body: req.query.foo};
+ else return {body: "\n"};
+ }),
+ stopIter: stringFun(function(head, row, req, row_info) {
+ if(head) {
+ return {body: "head"};
+ } else if(row) {
+ if(row_info.row_number > 2) return {stop: true};
+ return {body: " " + row_info.row_number};
+ } else {
+ return {body: " tail"};
+ }
+ }),
+ stopIter2: stringFun(function(head, row, req, row_info) {
+ return respondWith(req, {
+ html: function() {
+ if(head) {
+ return "head";
+ } else if(row) {
+ if(row_info.row_number > 2) return {stop: true};
+ return " " + row_info.row_number;
+ } else {
+ return " tail";
+ }
+ }
+ });
+ })
+ }
+ };
+
+ T(db.save(designDoc).ok);
+
+ var docs = makeDocs(0, 10);
+ var saveResult = db.bulkSave(docs);
+ T(saveResult.ok);
+
+ var view = db.view('lists/basicView');
+ T(view.total_rows == 10);
+
+ // standard get
+ var xhr = CouchDB.request("GET", "/test_suite_db/_list/lists/simpleForm/basicView");
+ T(xhr.status == 200);
+ T(/Total Rows/.test(xhr.responseText));
+ T(/Key: 1/.test(xhr.responseText));
+ T(/LineNo: 0/.test(xhr.responseText));
+ T(/LineNo: 5/.test(xhr.responseText));
+ T(/FirstKey: 0/.test(xhr.responseText));
+ T(/LastKey: 9/.test(xhr.responseText));
+
+
+ var lines = xhr.responseText.split('\n');
+ T(/LineNo: 5/.test(lines[6]));
+
+ // test that etags are available
+ var etag = xhr.getResponseHeader("etag");
+ xhr = CouchDB.request("GET", "/test_suite_db/_list/lists/simpleForm/basicView", {
+ headers: {"if-none-match": etag}
+ });
+ T(xhr.status == 304);
+
+ // get with query params
+ var xhr = CouchDB.request("GET", "/test_suite_db/_list/lists/simpleForm/basicView?startkey=3");
+ T(xhr.status == 200);
+ T(/Total Rows/.test(xhr.responseText));
+ T(!(/Key: 1/.test(xhr.responseText)));
+ T(/FirstKey: 3/.test(xhr.responseText));
+ T(/LastKey: 9/.test(xhr.responseText));
+
+
+ // with 0 rows
+ var xhr = CouchDB.request("GET", "/test_suite_db/_list/lists/simpleForm/basicView?startkey=30");
+ T(xhr.status == 200);
+ T(/Total Rows/.test(xhr.responseText));
+ T(/Offset: null/.test(xhr.responseText));
+
+ // when there is a reduce present, but not used
+ var xhr = CouchDB.request("GET", "/test_suite_db/_list/lists/simpleForm/withReduce?reduce=false");
+ T(xhr.status == 200);
+ T(/Total Rows/.test(xhr.responseText));
+ T(/Key: 1/.test(xhr.responseText));
+
+ // with accept headers for HTML
+ xhr = CouchDB.request("GET", "/test_suite_db/_list/lists/acceptSwitch/basicView", {
+ headers: {
+ "Accept": 'text/html'
+ }
+ });
+ T(xhr.getResponseHeader("Content-Type") == "text/html");
+ T(xhr.responseText.match(/HTML/));
+ T(xhr.responseText.match(/Value/));
+
+ // now with xml
+ xhr = CouchDB.request("GET", "/test_suite_db/_list/lists/acceptSwitch/basicView", {
+ headers: {
+ "Accept": 'application/xml'
+ }
+ });
+ T(xhr.getResponseHeader("Content-Type") == "application/xml");
+ T(xhr.responseText.match(/XML/));
+ T(xhr.responseText.match(/entry/));
+
+ // now with extra qs params
+ xhr = CouchDB.request("GET", "/test_suite_db/_list/lists/qsParams/basicView?foo=blam");
+ T(xhr.responseText.match(/blam/));
+
+
+ // aborting iteration
+ xhr = CouchDB.request("GET", "/test_suite_db/_list/lists/stopIter/basicView");
+ T(xhr.responseText.match(/^head 0 1 2 tail$/));
+ xhr = CouchDB.request("GET", "/test_suite_db/_list/lists/stopIter2/basicView");
+ T(xhr.responseText.match(/^head 0 1 2 tail$/));
+
+};
diff --git a/share/www/script/test/lots_of_docs.js b/share/www/script/test/lots_of_docs.js
new file mode 100644
index 00000000..702317e5
--- /dev/null
+++ b/share/www/script/test/lots_of_docs.js
@@ -0,0 +1,56 @@
+// 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 saving a semi-large quanitity of documents and do some view queries.
+couchTests.lots_of_docs = function(debug) {
+ var db = new CouchDB("test_suite_db");
+ db.deleteDb();
+ db.createDb();
+ if (debug) debugger;
+
+ // keep number lowish for now to keep tests fasts. Crank up manually to
+ // to really test.
+ var numDocsToCreate = 500;
+
+ for(var i=0; i < numDocsToCreate; i += 100) {
+ var createNow = Math.min(numDocsToCreate - i, 100);
+ var docs = makeDocs(i, i + createNow);
+ T(db.bulkSave(docs).ok);
+ }
+
+ // query all documents, and return the doc.integer member as a key.
+ results = db.query(function(doc){ emit(doc.integer, null) });
+
+ T(results.total_rows == numDocsToCreate);
+
+ // validate the keys are ordered ascending
+ for(var i=0; i<numDocsToCreate; i++) {
+ T(results.rows[i].key==i);
+ }
+
+ // do the query again, but with descending output
+ results = db.query(function(doc){ emit(doc.integer, null) }, null, {
+ descending: true
+ });
+
+ T(results.total_rows == numDocsToCreate);
+
+ // validate the keys are ordered descending
+ for(var i=0; i<numDocsToCreate; i++) {
+ T(results.rows[numDocsToCreate-1-i].key==i);
+ }
+
+ // Check _all_docs with descending=true again (now that there are many docs)
+ var desc = db.allDocs({descending:true});
+ T(desc.total_rows == desc.rows.length);
+};
diff --git a/share/www/script/test/max_dbs_open.js b/share/www/script/test/max_dbs_open.js
new file mode 100644
index 00000000..44c4fa38
--- /dev/null
+++ b/share/www/script/test/max_dbs_open.js
@@ -0,0 +1,41 @@
+// 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.max_dbs_open = function(debug) {
+ if (debug) debugger;
+ restartServer();
+ var max = 5;
+ run_on_modified_server(
+ [{section: "couchdb",
+ key: "max_dbs_open",
+ value: max.toString()}],
+
+ function () {
+ for(var i=0; i<max*2; i++) {
+ var db = new CouchDB("test_suite_db"+ i);
+ db.deleteDb();
+ db.createDb();
+ }
+
+ var stats = JSON.parse(CouchDB.request("GET", "/_stats").responseText);
+ T(stats.dbs_open == max);
+
+
+ for(var i=0; i<max*2; i++) {
+ var db = new CouchDB("test_suite_db"+ i);
+ db.deleteDb();
+ }
+
+ var stats = JSON.parse(CouchDB.request("GET", "/_stats").responseText);
+ T(stats.dbs_open == 0);
+ })
+};
diff --git a/share/www/script/test/multiple_rows.js b/share/www/script/test/multiple_rows.js
new file mode 100644
index 00000000..8db2702c
--- /dev/null
+++ b/share/www/script/test/multiple_rows.js
@@ -0,0 +1,80 @@
+// 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.multiple_rows = function(debug) {
+ var db = new CouchDB("test_suite_db");
+ db.deleteDb();
+ db.createDb();
+ if (debug) debugger;
+
+ var nc = {_id:"NC", cities:["Charlotte", "Raleigh"]};
+ var ma = {_id:"MA", cities:["Boston", "Lowell", "Worcester", "Cambridge", "Springfield"]};
+ var fl = {_id:"FL", cities:["Miami", "Tampa", "Orlando", "Springfield"]};
+
+ T(db.save(nc).ok);
+ T(db.save(ma).ok);
+ T(db.save(fl).ok);
+
+ var generateListOfCitiesAndState = "function(doc) {" +
+ " for (var i = 0; i < doc.cities.length; i++)" +
+ " emit(doc.cities[i] + \", \" + doc._id, null);" +
+ "}";
+
+ var results = db.query(generateListOfCitiesAndState);
+ var rows = results.rows;
+
+ T(rows[0].key == "Boston, MA");
+ T(rows[1].key == "Cambridge, MA");
+ T(rows[2].key == "Charlotte, NC");
+ T(rows[3].key == "Lowell, MA");
+ T(rows[4].key == "Miami, FL");
+ T(rows[5].key == "Orlando, FL");
+ T(rows[6].key == "Raleigh, NC");
+ T(rows[7].key == "Springfield, FL");
+ T(rows[8].key == "Springfield, MA");
+ T(rows[9].key == "Tampa, FL");
+ T(rows[10].key == "Worcester, MA");
+
+ // add another city to NC
+ nc.cities.push("Wilmington");
+ T(db.save(nc).ok);
+
+ var results = db.query(generateListOfCitiesAndState);
+ var rows = results.rows;
+
+ T(rows[0].key == "Boston, MA");
+ T(rows[1].key == "Cambridge, MA");
+ T(rows[2].key == "Charlotte, NC");
+ T(rows[3].key == "Lowell, MA");
+ T(rows[4].key == "Miami, FL");
+ T(rows[5].key == "Orlando, FL");
+ T(rows[6].key == "Raleigh, NC");
+ T(rows[7].key == "Springfield, FL");
+ T(rows[8].key == "Springfield, MA");
+ T(rows[9].key == "Tampa, FL");
+ T(rows[10].key == "Wilmington, NC");
+ T(rows[11].key == "Worcester, MA");
+
+ // now delete MA
+ T(db.deleteDoc(ma).ok);
+
+ var results = db.query(generateListOfCitiesAndState);
+ var rows = results.rows;
+
+ T(rows[0].key == "Charlotte, NC");
+ T(rows[1].key == "Miami, FL");
+ T(rows[2].key == "Orlando, FL");
+ T(rows[3].key == "Raleigh, NC");
+ T(rows[4].key == "Springfield, FL");
+ T(rows[5].key == "Tampa, FL");
+ T(rows[6].key == "Wilmington, NC");
+};
diff --git a/share/www/script/test/purge.js b/share/www/script/test/purge.js
new file mode 100644
index 00000000..e05ac024
--- /dev/null
+++ b/share/www/script/test/purge.js
@@ -0,0 +1,101 @@
+// 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.purge = function(debug) {
+ var db = new CouchDB("test_suite_db");
+ db.deleteDb();
+ db.createDb();
+ if (debug) debugger;
+
+ /*
+ purge is not to be confused with a document deletion. It removes the
+ document and all edit history from the local instance of the database.
+ */
+
+ var numDocs = 10;
+
+ var designDoc = {
+ _id:"_design/test",
+ language: "javascript",
+ views: {
+ all_docs_twice: {map: "function(doc) { emit(doc.integer, null); emit(doc.integer, null) }"},
+ single_doc: {map: "function(doc) { if (doc._id == \"1\") { emit(1, null) }}"}
+ }
+ }
+
+ T(db.save(designDoc).ok);
+
+ T(db.bulkSave(makeDocs(1, numDocs + 1)).ok);
+
+ // go ahead and validate the views before purging
+ var rows = db.view("test/all_docs_twice").rows;
+ for (var i = 0; i < numDocs; i++) {
+ T(rows[2*i].key == i+1);
+ T(rows[(2*i)+1].key == i+1);
+ }
+ T(db.view("test/single_doc").total_rows == 1);
+
+ var info = db.info();
+ var doc1 = db.open("1");
+ var doc2 = db.open("2");
+
+ // purge the documents
+ var xhr = CouchDB.request("POST", "/test_suite_db/_purge", {
+ body: JSON.stringify({"1":[doc1._rev], "2":[doc2._rev]}),
+ });
+ T(xhr.status == 200);
+
+ var newInfo = db.info();
+ // purging increments the update sequence
+ T(info.update_seq+1 == newInfo.update_seq);
+ // and it increments the purge_seq
+ T(info.purge_seq+1 == newInfo.purge_seq);
+
+ var result = JSON.parse(xhr.responseText);
+ T(result.purged["1"][0] == doc1._rev);
+ T(result.purged["2"][0] == doc2._rev);
+
+ T(db.open("1") == null);
+ T(db.open("2") == null);
+
+ var rows = db.view("test/all_docs_twice").rows;
+ for (var i = 2; i < numDocs; i++) {
+ T(rows[2*(i-2)].key == i+1);
+ T(rows[(2*(i-2))+1].key == i+1);
+ }
+ T(db.view("test/single_doc").total_rows == 0);
+
+ // purge documents twice in a row without loading views
+ // (causes full view rebuilds)
+
+ var doc3 = db.open("3");
+ var doc4 = db.open("4");
+
+ xhr = CouchDB.request("POST", "/test_suite_db/_purge", {
+ body: JSON.stringify({"3":[doc3._rev]}),
+ });
+
+ T(xhr.status == 200);
+
+ xhr = CouchDB.request("POST", "/test_suite_db/_purge", {
+ body: JSON.stringify({"4":[doc4._rev]}),
+ });
+
+ T(xhr.status == 200);
+
+ var rows = db.view("test/all_docs_twice").rows;
+ for (var i = 4; i < numDocs; i++) {
+ T(rows[2*(i-4)].key == i+1);
+ T(rows[(2*(i-4))+1].key == i+1);
+ }
+ T(db.view("test/single_doc").total_rows == 0);
+};
diff --git a/share/www/script/test/recreate_doc.js b/share/www/script/test/recreate_doc.js
new file mode 100644
index 00000000..d730c2bd
--- /dev/null
+++ b/share/www/script/test/recreate_doc.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.recreate_doc = function(debug) {
+ var db = new CouchDB("test_suite_db");
+ db.deleteDb();
+ db.createDb();
+ if (debug) debugger;
+
+ // First create a new document with the ID "foo", and delete it again
+ var doc = {_id: "foo", a: "bar", b: 42};
+ T(db.save(doc).ok);
+ T(db.deleteDoc(doc).ok);
+
+ // Now create a new document with the same ID, save it, and then modify it
+ // This should work fine, but currently results in a conflict error, at
+ // least "sometimes"
+ for (var i = 0; i < 10; i++) {
+ doc = {_id: "foo"};
+ T(db.save(doc).ok);
+ doc = db.open("foo");
+ doc.a = "baz";
+ try {
+ T(db.save(doc).ok);
+ } finally {
+ // And now, we can't even delete the document anymore :/
+ T(db.deleteDoc(doc).rev != undefined);
+ }
+ }
+};
diff --git a/share/www/script/test/reduce.js b/share/www/script/test/reduce.js
new file mode 100644
index 00000000..d3245f98
--- /dev/null
+++ b/share/www/script/test/reduce.js
@@ -0,0 +1,169 @@
+// 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.reduce = function(debug) {
+ var db = new CouchDB("test_suite_db");
+ db.deleteDb();
+ db.createDb();
+ if (debug) debugger;
+ var numDocs = 500
+ var docs = makeDocs(1,numDocs + 1);
+ T(db.bulkSave(docs).ok);
+ var summate = function(N) {return (N+1)*N/2;};
+
+ var map = function (doc) {
+ emit(doc.integer, doc.integer);
+ emit(doc.integer, doc.integer)};
+ var reduce = function (keys, values) { return sum(values); };
+ var result = db.query(map, reduce);
+ T(result.rows[0].value == 2*summate(numDocs));
+
+ result = db.query(map, reduce, {startkey: 4, endkey: 4});
+ T(result.rows[0].value == 8);
+
+ result = db.query(map, reduce, {startkey: 4, endkey: 5});
+ T(result.rows[0].value == 18);
+
+ result = db.query(map, reduce, {startkey: 4, endkey: 6});
+ T(result.rows[0].value == 30);
+
+ result = db.query(map, reduce, {group:true, limit:3});
+ T(result.rows[0].value == 2);
+ T(result.rows[1].value == 4);
+ T(result.rows[2].value == 6);
+
+ for(var i=1; i<numDocs/2; i+=30) {
+ result = db.query(map, reduce, {startkey: i, endkey: numDocs - i});
+ T(result.rows[0].value == 2*(summate(numDocs-i) - summate(i-1)));
+ }
+
+ db.deleteDb();
+ db.createDb();
+
+ for(var i=1; i <= 5; i++) {
+
+ for(var j=0; j < 10; j++) {
+ // these docs are in the order of the keys collation, for clarity
+ var docs = [];
+ docs.push({keys:["a"]});
+ docs.push({keys:["a"]});
+ docs.push({keys:["a", "b"]});
+ docs.push({keys:["a", "b"]});
+ docs.push({keys:["a", "b", "c"]});
+ docs.push({keys:["a", "b", "d"]});
+ docs.push({keys:["a", "c", "d"]});
+ docs.push({keys:["d"]});
+ docs.push({keys:["d", "a"]});
+ docs.push({keys:["d", "b"]});
+ docs.push({keys:["d", "c"]});
+ T(db.bulkSave(docs).ok);
+ T(db.info().doc_count == ((i - 1) * 10 * 11) + ((j + 1) * 11));
+ }
+
+ map = function (doc) {emit(doc.keys, 1)};
+ reduce = function (keys, values) { return sum(values); };
+
+ var results = db.query(map, reduce, {group:true});
+
+ //group by exact key match
+ T(equals(results.rows[0], {key:["a"],value:20*i}));
+ T(equals(results.rows[1], {key:["a","b"],value:20*i}));
+ T(equals(results.rows[2], {key:["a", "b", "c"],value:10*i}));
+ T(equals(results.rows[3], {key:["a", "b", "d"],value:10*i}));
+
+ // test to make sure group reduce and limit params provide valid json
+ var results = db.query(map, reduce, {group: true, limit: 2});
+ T(equals(results.rows[0], {key: ["a"], value: 20*i}));
+ T(equals(results.rows.length, 2));
+
+ //group by the first element in the key array
+ var results = db.query(map, reduce, {group_level:1});
+ T(equals(results.rows[0], {key:["a"],value:70*i}));
+ T(equals(results.rows[1], {key:["d"],value:40*i}));
+
+ //group by the first 2 elements in the key array
+ var results = db.query(map, reduce, {group_level:2});
+ T(equals(results.rows[0], {key:["a"],value:20*i}));
+ T(equals(results.rows[1], {key:["a","b"],value:40*i}));
+ T(equals(results.rows[2], {key:["a","c"],value:10*i}));
+ T(equals(results.rows[3], {key:["d"],value:10*i}));
+ T(equals(results.rows[4], {key:["d","a"],value:10*i}));
+ T(equals(results.rows[5], {key:["d","b"],value:10*i}));
+ T(equals(results.rows[6], {key:["d","c"],value:10*i}));
+ }
+
+ // now test out more complex reductions that need to use the combine option.
+
+ db.deleteDb();
+ db.createDb();
+
+
+ var map = function (doc) {emit(doc.val, doc.val)};
+ var reduceCombine = function (keys, values, rereduce) {
+ // This computes the standard deviation of the mapped results
+ var stdDeviation=0.0;
+ var count=0;
+ var total=0.0;
+ var sqrTotal=0.0;
+
+ if (!rereduce) {
+ // This is the reduce phase, we are reducing over emitted values from
+ // the map functions.
+ for(var i in values) {
+ total = total + values[i];
+ sqrTotal = sqrTotal + (values[i] * values[i]);
+ }
+ count = values.length;
+ }
+ else {
+ // This is the rereduce phase, we are re-reducing previosuly
+ // reduced values.
+ for(var i in values) {
+ count = count + values[i].count;
+ total = total + values[i].total;
+ sqrTotal = sqrTotal + values[i].sqrTotal;
+ }
+ }
+
+ var variance = (sqrTotal - ((total * total)/count)) / count;
+ stdDeviation = Math.sqrt(variance);
+
+ // the reduce result. It contains enough information to be rereduced
+ // with other reduce results.
+ return {"stdDeviation":stdDeviation,"count":count,
+ "total":total,"sqrTotal":sqrTotal};
+ };
+
+ // Save a bunch a docs.
+
+ for(var i=0; i < 10; i++) {
+ var docs = [];
+ docs.push({val:10});
+ docs.push({val:20});
+ docs.push({val:30});
+ docs.push({val:40});
+ docs.push({val:50});
+ docs.push({val:60});
+ docs.push({val:70});
+ docs.push({val:80});
+ docs.push({val:90});
+ docs.push({val:100});
+ T(db.bulkSave(docs).ok);
+ }
+
+ var results = db.query(map, reduceCombine);
+
+ var difference = results.rows[0].value.stdDeviation - 28.722813232690143;
+ // account for floating point rounding error
+ T(Math.abs(difference) < 0.0000000001);
+
+};
diff --git a/share/www/script/test/reduce_false.js b/share/www/script/test/reduce_false.js
new file mode 100644
index 00000000..41f0c669
--- /dev/null
+++ b/share/www/script/test/reduce_false.js
@@ -0,0 +1,45 @@
+// 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.reduce_false = function(debug) {
+ var db = new CouchDB("test_suite_db");
+ db.deleteDb();
+ db.createDb();
+ if (debug) debugger;
+
+ var numDocs = 5;
+ var docs = makeDocs(1,numDocs + 1);
+ T(db.bulkSave(docs).ok);
+ var summate = function(N) {return (N+1)*N/2;};
+
+ var designDoc = {
+ _id:"_design/test",
+ language: "javascript",
+ views: {
+ summate: {map:"function (doc) {emit(doc.integer, doc.integer)};",
+ reduce:"function (keys, values) { return sum(values); };"},
+ }
+ };
+ T(db.save(designDoc).ok);
+
+ // Test that the reduce works
+ var res = db.view('test/summate');
+ T(res.rows.length == 1 && res.rows[0].value == summate(5));
+
+ //Test that we get our docs back
+ res = db.view('test/summate', {reduce: false});
+ T(res.rows.length == 5);
+ for(var i=0; i<5; i++)
+ {
+ T(res.rows[i].value == i+1);
+ }
+};
diff --git a/share/www/script/test/replication.js b/share/www/script/test/replication.js
new file mode 100644
index 00000000..59dc4700
--- /dev/null
+++ b/share/www/script/test/replication.js
@@ -0,0 +1,207 @@
+// 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.replication = function(debug) {
+ if (debug) debugger;
+ var host = CouchDB.host;
+ var dbPairs = [
+ {source:"test_suite_db_a",
+ target:"test_suite_db_b"},
+ {source:"test_suite_db_a",
+ target:"http://" + host + "/test_suite_db_b"},
+ {source:"http://" + host + "/test_suite_db_a",
+ target:"test_suite_db_b"},
+ {source:"http://" + host + "/test_suite_db_a",
+ target:"http://" + host + "/test_suite_db_b"}
+ ]
+ var dbA = new CouchDB("test_suite_db_a");
+ var dbB = new CouchDB("test_suite_db_b");
+ var numDocs = 10;
+ var xhr;
+ for (var testPair = 0; testPair < dbPairs.length; testPair++) {
+ var A = dbPairs[testPair].source
+ var B = dbPairs[testPair].target
+
+ dbA.deleteDb();
+ dbA.createDb();
+ dbB.deleteDb();
+ dbB.createDb();
+
+ var repTests = {
+ // copy and paste and put your code in. delete unused steps.
+ test_template: new function () {
+ this.init = function(dbA, dbB) {
+ // before anything has happened
+ }
+ this.afterAB1 = function(dbA, dbB) {
+ // called after replicating src=A tgt=B first time.
+ };
+ this.afterBA1 = function(dbA, dbB) {
+ // called after replicating src=B tgt=A first time.
+ };
+ this.afterAB2 = function(dbA, dbB) {
+ // called after replicating src=A tgt=B second time.
+ };
+ this.afterBA2 = function(dbA, dbB) {
+ // etc...
+ };
+ },
+
+ simple_test: new function () {
+ this.init = function(dbA, dbB) {
+ var docs = makeDocs(0, numDocs);
+ T(dbA.bulkSave(docs).ok);
+ };
+
+ this.afterAB1 = function(dbA, dbB) {
+ for (var j = 0; j < numDocs; j++) {
+ var docA = dbA.open("" + j);
+ var docB = dbB.open("" + j);
+ T(docA._rev == docB._rev);
+ }
+ };
+ },
+
+ deletes_test: new function () {
+ this.init = function(dbA, dbB) {
+ T(dbA.save({_id:"foo1",value:"a"}).ok);
+ };
+
+ this.afterAB1 = function(dbA, dbB) {
+ var docA = dbA.open("foo1");
+ var docB = dbB.open("foo1");
+ T(docA._rev == docB._rev);
+
+ dbA.deleteDoc(docA);
+ };
+
+ this.afterAB2 = function(dbA, dbB) {
+ T(dbA.open("foo1") == null);
+ T(dbB.open("foo1") == null);
+ };
+ },
+
+ slashes_in_ids_test: new function () {
+ // make sure docs with slashes in id replicate properly
+ this.init = function(dbA, dbB) {
+ dbA.save({ _id:"abc/def", val:"one" });
+ };
+
+ this.afterAB1 = function(dbA, dbB) {
+ var docA = dbA.open("abc/def");
+ var docB = dbB.open("abc/def");
+ T(docA._rev == docB._rev);
+ };
+ },
+
+ design_docs_test: new function() {
+ // make sure design docs replicate properly
+ this.init = function(dbA, dbB) {
+ dbA.save({ _id:"_design/test" });
+ };
+
+ this.afterAB1 = function() {
+ var docA = dbA.open("_design/test");
+ var docB = dbB.open("_design/test");
+ T(docA._rev == docB._rev);
+ };
+ },
+
+ attachments_test: new function () {
+ // Test attachments
+ this.init = function(dbA, dbB) {
+ dbA.save({
+ _id:"bin_doc",
+ _attachments:{
+ "foo.txt": {
+ "type":"base64",
+ "data": "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
+ }
+ }
+ });
+ };
+
+ this.afterAB1 = function(dbA, dbB) {
+ var xhr = CouchDB.request("GET", "/test_suite_db_a/bin_doc/foo.txt");
+ T(xhr.responseText == "This is a base64 encoded text")
+
+ xhr = CouchDB.request("GET", "/test_suite_db_b/bin_doc/foo.txt");
+ T(xhr.responseText == "This is a base64 encoded text")
+ };
+ },
+
+ conflicts_test: new function () {
+ // test conflicts
+ this.init = function(dbA, dbB) {
+ dbA.save({_id:"foo",value:"a"});
+ dbB.save({_id:"foo",value:"b"});
+ };
+
+ this.afterBA1 = function(dbA, dbB) {
+ var docA = dbA.open("foo", {conflicts: true});
+ var docB = dbB.open("foo", {conflicts: true});
+
+ // make sure the same rev is in each db
+ T(docA._rev === docB._rev);
+
+ // make sure the conflicts are the same in each db
+ T(docA._conflicts[0] === docB._conflicts[0]);
+
+ // delete a conflict.
+ dbA.deleteDoc({_id:"foo", _rev:docA._conflicts[0]});
+ };
+
+ this.afterBA2 = function(dbA, dbB) {
+ // open documents and include the conflict meta data
+ var docA = dbA.open("foo", {conflicts: true});
+ var docB = dbB.open("foo", {conflicts: true});
+
+ // We should have no conflicts this time
+ T(docA._conflicts === undefined)
+ T(docB._conflicts === undefined);
+ };
+ }
+ };
+
+ var test;
+ for(test in repTests) {
+ if(repTests[test].init) {
+ repTests[test].init(dbA, dbB);
+ }
+ }
+
+ T(CouchDB.replicate(A, B).ok);
+
+ for(test in repTests) {
+ if(repTests[test].afterAB1) repTests[test].afterAB1(dbA, dbB);
+ }
+
+ T(CouchDB.replicate(B, A).ok);
+
+ for(test in repTests) {
+ if(repTests[test].afterBA1) repTests[test].afterBA1(dbA, dbB);
+ }
+
+ T(CouchDB.replicate(A, B).ok);
+
+ for(test in repTests) {
+ if(repTests[test].afterAB2) repTests[test].afterAB2(dbA, dbB);
+ }
+
+ T(CouchDB.replicate(B, A).ok);
+
+ for(test in repTests) {
+ if(repTests[test].afterBA2) repTests[test].afterBA2(dbA, dbB);
+ }
+
+ }
+};
diff --git a/share/www/script/test/security_validation.js b/share/www/script/test/security_validation.js
new file mode 100644
index 00000000..61f56b39
--- /dev/null
+++ b/share/www/script/test/security_validation.js
@@ -0,0 +1,154 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+couchTests.security_validation = function(debug) {
+ // This tests couchdb's security and validation features. This does
+ // not test authentication, except to use test authentication code made
+ // specifically for this testing. It is a WWWW-Authenticate scheme named
+ // X-Couch-Test-Auth, and the user names and passwords are hard coded
+ // on the server-side.
+ //
+ // We could have used Basic authentication, however the XMLHttpRequest
+ // implementation for Firefox and Safari, and probably other browsers are
+ // broken (Firefox always prompts the user on 401 failures, Safari gives
+ // odd security errors when using different name/passwords, perhaps due
+ // to cross site scripting prevention). These problems essentially make Basic
+ // authentication testing in the browser impossible. But while hard to
+ // test automated in the browser, Basic auth may still useful for real
+ // world use where these bugs/behaviors don't matter.
+ //
+ // So for testing purposes we are using this custom X-Couch-Test-Auth.
+ // It's identical to Basic auth, except it doesn't even base64 encode
+ // the "username:password" string, it's sent completely plain text.
+ // Firefox and Safari both deal with this correctly (which is to say
+ // they correctly do nothing special).
+
+
+ var db = new CouchDB("test_suite_db");
+ db.deleteDb();
+ db.createDb();
+ if (debug) debugger;
+
+ run_on_modified_server(
+ [{section: "httpd",
+ key: "authentication_handler",
+ value: "{couch_httpd, special_test_authentication_handler}"},
+ {section:"httpd",
+ key: "WWW-Authenticate",
+ value: "X-Couch-Test-Auth"}],
+
+ function () {
+
+ // try saving document usin the wrong credentials
+ var wrongPasswordDb = new CouchDB("test_suite_db",
+ {"WWW-Authenticate": "X-Couch-Test-Auth Damien Katz:foo"}
+ );
+
+ try {
+ wrongPasswordDb.save({foo:1,author:"Damien Katz"});
+ T(false && "Can't get here. Should have thrown an error 1");
+ } catch (e) {
+ T(e.error == "unauthorized");
+ T(wrongPasswordDb.last_req.status == 401);
+ }
+
+
+ // Create the design doc that will run custom validation code
+ var designDoc = {
+ _id:"_design/test",
+ language: "javascript",
+ validate_doc_update: "(" + (function (newDoc, oldDoc, userCtx) {
+ // docs should have an author field.
+ if (!newDoc._deleted && !newDoc.author) {
+ throw {forbidden:
+ "Documents must have an author field"};
+ }
+ if (oldDoc && oldDoc.author != userCtx.name) {
+ throw {unauthorized:
+ "You are not the author of this document. You jerk."};
+ }
+ }).toString() + ")"
+ }
+
+ // Save a document normally
+ var userDb = new CouchDB("test_suite_db",
+ {"WWW-Authenticate": "X-Couch-Test-Auth Damien Katz:pecan pie"}
+ );
+
+ T(userDb.save({_id:"testdoc", foo:1, author:"Damien Katz"}).ok);
+
+ // Attempt to save the design as a non-admin
+ try {
+ userDb.save(designDoc);
+ T(false && "Can't get here. Should have thrown an error on design doc");
+ } catch (e) {
+ T(e.error == "unauthorized");
+ T(userDb.last_req.status == 401);
+ }
+
+ // add user as admin
+ db.setAdmins(["Damien Katz"]);
+
+ T(userDb.save(designDoc).ok);
+
+ // update the document
+ var doc = userDb.open("testdoc");
+ doc.foo=2;
+ T(userDb.save(doc).ok);
+
+ // Save a document that's missing an author field.
+ try {
+ userDb.save({foo:1});
+ T(false && "Can't get here. Should have thrown an error 2");
+ } catch (e) {
+ T(e.error == "forbidden");
+ T(userDb.last_req.status == 403);
+ }
+
+ // Now attempt to update the document as a different user, Jan
+ var user2Db = new CouchDB("test_suite_db",
+ {"WWW-Authenticate": "X-Couch-Test-Auth Jan Lehnardt:apple"}
+ );
+
+ var doc = user2Db.open("testdoc");
+ doc.foo=3;
+ try {
+ user2Db.save(doc);
+ T(false && "Can't get here. Should have thrown an error 3");
+ } catch (e) {
+ T(e.error == "unauthorized");
+ T(user2Db.last_req.status == 401);
+ }
+
+ // Now have Damien change the author to Jan
+ doc = userDb.open("testdoc");
+ doc.author="Jan Lehnardt";
+ T(userDb.save(doc).ok);
+
+ // Now update the document as Jan
+ doc = user2Db.open("testdoc");
+ doc.foo = 3;
+ T(user2Db.save(doc).ok);
+
+ // Damien can't delete it
+ try {
+ userDb.deleteDoc(doc);
+ T(false && "Can't get here. Should have thrown an error 4");
+ } catch (e) {
+ T(e.error == "unauthorized");
+ T(userDb.last_req.status == 401);
+ }
+
+ // Now delete document
+ T(user2Db.deleteDoc(doc).ok);
+ });
+};
diff --git a/share/www/script/test/show_documents.js b/share/www/script/test/show_documents.js
new file mode 100644
index 00000000..4837c18d
--- /dev/null
+++ b/share/www/script/test/show_documents.js
@@ -0,0 +1,282 @@
+// 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.show_documents = function(debug) {
+ var db = new CouchDB("test_suite_db");
+ db.deleteDb();
+ db.createDb();
+ if (debug) debugger;
+
+ var designDoc = {
+ _id:"_design/template",
+ language: "javascript",
+ shows: {
+ "hello" : stringFun(function(doc) {
+ if (doc) {
+ return "Hello World";
+ } else {
+ return "Empty World";
+ }
+ }),
+ "just-name" : stringFun(function(doc, req) {
+ return {
+ body : "Just " + doc.name
+ };
+ }),
+ "req-info" : stringFun(function(doc, req) {
+ return {
+ json : req
+ }
+ }),
+ "xml-type" : stringFun(function(doc, req) {
+ return {
+ "headers" : {
+ "Content-Type" : "application/xml"
+ },
+ "body" : new XML('<xml><node foo="bar"/></xml>')
+ }
+ }),
+ "no-set-etag" : stringFun(function(doc, req) {
+ return {
+ headers : {
+ "Etag" : "skipped"
+ },
+ "body" : "something"
+ }
+ }),
+ "accept-switch" : stringFun(function(doc, req) {
+ if (req.headers["Accept"].match(/image/)) {
+ return {
+ // a 16x16 px version of the CouchDB logo
+ "base64" :
+["iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAsV",
+"BMVEUAAAD////////////////////////5ur3rEBn////////////////wDBL/",
+"AADuBAe9EB3IEBz/7+//X1/qBQn2AgP/f3/ilpzsDxfpChDtDhXeCA76AQH/v7",
+"/84eLyWV/uc3bJPEf/Dw/uw8bRWmP1h4zxSlD6YGHuQ0f6g4XyQkXvCA36MDH6",
+"wMH/z8/yAwX64ODeh47BHiv/Ly/20dLQLTj98PDXWmP/Pz//39/wGyJ7Iy9JAA",
+"AADHRSTlMAbw8vf08/bz+Pv19jK/W3AAAAg0lEQVR4Xp3LRQ4DQRBD0QqTm4Y5",
+"zMxw/4OleiJlHeUtv2X6RbNO1Uqj9g0RMCuQO0vBIg4vMFeOpCWIWmDOw82fZx",
+"vaND1c8OG4vrdOqD8YwgpDYDxRgkSm5rwu0nQVBJuMg++pLXZyr5jnc1BaH4GT",
+"LvEliY253nA3pVhQqdPt0f/erJkMGMB8xucAAAAASUVORK5CYII="].join(''),
+ headers : {
+ "Content-Type" : "image/png",
+ "Vary" : "Accept" // we set this for proxy caches
+ }
+ };
+ } else {
+ return {
+ "body" : "accepting text requests",
+ headers : {
+ "Content-Type" : "text/html",
+ "Vary" : "Accept"
+ }
+ };
+ }
+ }),
+ "respondWith" : stringFun(function(doc, req) {
+ registerType("foo", "application/foo","application/x-foo");
+ return respondWith(req, {
+ html : function() {
+ return {
+ body:"Ha ha, you said \"" + doc.word + "\"."
+ };
+ },
+ xml : function() {
+ var xml = new XML('<xml><node/></xml>');
+ // Becase Safari can't stand to see that dastardly
+ // E4X outside of a string. Outside of tests you
+ // can just use E4X literals.
+ this.eval('xml.node.@foo = doc.word');
+ return {
+ body: xml
+ };
+ },
+ foo : function() {
+ return {
+ body: "foofoo"
+ };
+ },
+ fallback : "html"
+ });
+ })
+ }
+ };
+ T(db.save(designDoc).ok);
+
+ var doc = {"word":"plankton", "name":"Rusty"}
+ var resp = db.save(doc);
+ T(resp.ok);
+ var docid = resp.id;
+
+ // show error
+ var xhr = CouchDB.request("GET", "/test_suite_db/_show/");
+ T(xhr.status == 404);
+ T(JSON.parse(xhr.responseText).reason == "Invalid path.");
+
+ // hello template world
+ xhr = CouchDB.request("GET", "/test_suite_db/_show/template/hello/"+docid);
+ T(xhr.responseText == "Hello World");
+
+ // hello template world (no docid)
+ xhr = CouchDB.request("GET", "/test_suite_db/_show/template/hello");
+ T(xhr.responseText == "Empty World");
+
+
+ // show with doc
+ xhr = CouchDB.request("GET", "/test_suite_db/_show/template/just-name/"+docid);
+ T(xhr.responseText == "Just Rusty");
+
+ // show with missing doc
+ xhr = CouchDB.request("GET", "/test_suite_db/_show/template/just-name/missingdoc");
+ T(xhr.status == 404);
+ var resp = JSON.parse(xhr.responseText);
+ T(resp.error == "not_found");
+ T(resp.reason == "missing");
+
+ // show with missing func
+ xhr = CouchDB.request("GET", "/test_suite_db/_show/template/missing/"+docid);
+ T(xhr.status == 404);
+
+ // missing design doc
+ xhr = CouchDB.request("GET", "/test_suite_db/_show/missingdoc/just-name/"+docid);
+ T(xhr.status == 404);
+ var resp = JSON.parse(xhr.responseText);
+ T(resp.error == "not_found");
+
+ // query parameters
+ xhr = CouchDB.request("GET", "/test_suite_db/_show/template/req-info/"+docid+"?foo=bar", {
+ headers: {
+ "Accept": "text/html;text/plain;*/*",
+ "X-Foo" : "bar"
+ }
+ });
+ var resp = JSON.parse(xhr.responseText);
+ T(equals(resp.headers["X-Foo"], "bar"));
+ T(equals(resp.query, {foo:"bar"}));
+ T(equals(resp.verb, "GET"));
+ T(equals(resp.path[4], docid));
+ T(equals(resp.info.db_name, "test_suite_db"));
+
+ // returning a content-type
+ xhr = CouchDB.request("GET", "/test_suite_db/_show/template/xml-type/"+docid);
+ T("application/xml" == xhr.getResponseHeader("Content-Type"));
+ T("Accept" == xhr.getResponseHeader("Vary"));
+
+ // accept header switching
+ // different mime has different etag
+
+ xhr = CouchDB.request("GET", "/test_suite_db/_show/template/accept-switch/"+docid, {
+ headers: {"Accept": "text/html;text/plain;*/*"}
+ });
+ T("text/html" == xhr.getResponseHeader("Content-Type"));
+ T("Accept" == xhr.getResponseHeader("Vary"));
+ var etag = xhr.getResponseHeader("etag");
+
+ xhr = CouchDB.request("GET", "/test_suite_db/_show/template/accept-switch/"+docid, {
+ headers: {"Accept": "image/png;*/*"}
+ });
+ T(xhr.responseText.match(/PNG/))
+ T("image/png" == xhr.getResponseHeader("Content-Type"));
+ var etag2 = xhr.getResponseHeader("etag");
+ T(etag2 != etag);
+
+ // proper etags
+ // show with doc
+ xhr = CouchDB.request("GET", "/test_suite_db/_show/template/just-name/"+docid);
+ // extract the ETag header values
+ etag = xhr.getResponseHeader("etag");
+ // get again with etag in request
+ xhr = CouchDB.request("GET", "/test_suite_db/_show/template/just-name/"+docid, {
+ headers: {"if-none-match": etag}
+ });
+ // should be 304
+ T(xhr.status == 304);
+
+ // update the doc
+ doc.name = "Crusty";
+ resp = db.save(doc);
+ T(resp.ok);
+ // req with same etag
+ xhr = CouchDB.request("GET", "/test_suite_db/_show/template/just-name/"+docid, {
+ headers: {"if-none-match": etag}
+ });
+ // status is 200
+ T(xhr.status == 200);
+
+ // get new etag and request again
+ etag = xhr.getResponseHeader("etag");
+ xhr = CouchDB.request("GET", "/test_suite_db/_show/template/just-name/"+docid, {
+ headers: {"if-none-match": etag}
+ });
+ // should be 304
+ T(xhr.status == 304);
+
+ // update design doc (but not function)
+ designDoc.isChanged = true;
+ T(db.save(designDoc).ok);
+
+ xhr = CouchDB.request("GET", "/test_suite_db/_show/template/just-name/"+docid, {
+ headers: {"if-none-match": etag}
+ });
+ // should be 304
+ T(xhr.status == 304);
+
+ // update design doc function
+ designDoc.shows["just-name"] = (function(doc, req) {
+ return {
+ body : "Just old " + doc.name
+ };
+ }).toString();
+ T(db.save(designDoc).ok);
+
+ xhr = CouchDB.request("GET", "/test_suite_db/_show/template/just-name/"+docid, {
+ headers: {"if-none-match": etag}
+ });
+ // status is 200
+ T(xhr.status == 200);
+
+
+ // JS can't set etag
+ xhr = CouchDB.request("GET", "/test_suite_db/_show/template/no-set-etag/"+docid);
+ // extract the ETag header values
+ etag = xhr.getResponseHeader("etag");
+ T(etag != "skipped")
+
+ // test the respondWith mime matcher
+ xhr = CouchDB.request("GET", "/test_suite_db/_show/template/respondWith/"+docid, {
+ headers: {
+ "Accept": 'text/html,application/atom+xml; q=0.9'
+ }
+ });
+ T(xhr.getResponseHeader("Content-Type") == "text/html");
+ T(xhr.responseText == "Ha ha, you said \"plankton\".");
+
+ // now with xml
+ xhr = CouchDB.request("GET", "/test_suite_db/_show/template/respondWith/"+docid, {
+ headers: {
+ "Accept": 'application/xml'
+ }
+ });
+ T(xhr.getResponseHeader("Content-Type") == "application/xml");
+ T(xhr.responseText.match(/node/));
+ T(xhr.responseText.match(/plankton/));
+
+ // registering types works
+ xhr = CouchDB.request("GET", "/test_suite_db/_show/template/respondWith/"+docid, {
+ headers: {
+ "Accept": "application/x-foo"
+ }
+ });
+ T(xhr.getResponseHeader("Content-Type") == "application/x-foo");
+ T(xhr.responseText.match(/foofoo/));
+};
diff --git a/share/www/script/test/utf8.js b/share/www/script/test/utf8.js
new file mode 100644
index 00000000..5ba29496
--- /dev/null
+++ b/share/www/script/test/utf8.js
@@ -0,0 +1,41 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+couchTests.utf8 = function(debug) {
+ var db = new CouchDB("test_suite_db");
+ db.deleteDb();
+ db.createDb();
+ if (debug) debugger;
+
+ var texts = [];
+
+ texts[0] = "1. Ascii: hello"
+ texts[1] = "2. Russian: На берегу пустынных волн"
+ texts[2] = "3. Math: ∮ E⋅da = Q, n → ∞, ∑ f(i) = ∏ g(i),"
+ texts[3] = "4. Geek: STARGΛ̊TE SG-1"
+ texts[4] = "5. Braille: ⡌⠁⠧⠑ ⠼⠁⠒ ⡍⠜⠇⠑⠹⠰⠎ ⡣⠕⠌"
+
+ // check that we can save a reload with full fidelity
+ for (var i=0; i<texts.length; i++) {
+ T(db.save({_id:i.toString(), text:texts[i]}).ok);
+ }
+
+ for (var i=0; i<texts.length; i++) {
+ T(db.open(i.toString()).text == texts[i]);
+ }
+
+ // check that views and key collation don't blow up
+ var rows = db.query(function(doc) { emit(null, doc.text) }).rows;
+ for (var i=0; i<texts.length; i++) {
+ T(rows[i].value == texts[i]);
+ }
+};
diff --git a/share/www/script/test/uuids.js b/share/www/script/test/uuids.js
new file mode 100644
index 00000000..6f701884
--- /dev/null
+++ b/share/www/script/test/uuids.js
@@ -0,0 +1,62 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+couchTests.uuids = function(debug) {
+ var testHashBustingHeaders = function(xhr) {
+ T(xhr.getResponseHeader("Cache-Control").match(/no-cache/));
+ T(xhr.getResponseHeader("Pragma") == "no-cache");
+
+ var currentTime = new Date();
+ var expiresHeader = Date.parse(xhr.getResponseHeader("Expires"));
+ var dateHeader = Date.parse(xhr.getResponseHeader("Date"));
+
+ T(expiresHeader < currentTime);
+ T(currentTime - dateHeader < 3000);
+ };
+
+ var db = new CouchDB("test_suite_db");
+ db.deleteDb();
+ db.createDb();
+ if (debug) debugger;
+
+ // a single UUID without an explicit count
+ var xhr = CouchDB.request("GET", "/_uuids");
+ T(xhr.status == 200);
+ var result = JSON.parse(xhr.responseText);
+ T(result.uuids.length == 1);
+ var first = result.uuids[0];
+ testHashBustingHeaders(xhr);
+
+ // a single UUID with an explicit count
+ xhr = CouchDB.request("GET", "/_uuids?count=1");
+ T(xhr.status == 200);
+ result = JSON.parse(xhr.responseText);
+ T(result.uuids.length == 1);
+ var second = result.uuids[0];
+ T(first != second);
+
+ // no collisions with 1,000 UUIDs
+ xhr = CouchDB.request("GET", "/_uuids?count=1000");
+ T(xhr.status == 200);
+ result = JSON.parse(xhr.responseText);
+ T( result.uuids.length == 1000 );
+ var seen = {};
+ for(var i in result.uuids) {
+ var id = result.uuids[i];
+ T(seen[id] === undefined);
+ seen[id] = 1;
+ }
+
+ // ensure we return a 405 on POST
+ xhr = CouchDB.request("POST", "/_uuids?count=1000");
+ T(xhr.status == 405);
+};
diff --git a/share/www/script/test/view_collation.js b/share/www/script/test/view_collation.js
new file mode 100644
index 00000000..bcb63404
--- /dev/null
+++ b/share/www/script/test/view_collation.js
@@ -0,0 +1,88 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+couchTests.view_collation = function(debug) {
+ var db = new CouchDB("test_suite_db");
+ db.deleteDb();
+ db.createDb();
+ if (debug) debugger;
+
+ // NOTE, the values are already in their correct sort order. Consider this
+ // a specification of collation of json types.
+
+ var values = [];
+
+ // special values sort before all other types
+ values.push(null);
+ values.push(false);
+ values.push(true);
+
+ // then numbers
+ values.push(1);
+ values.push(2);
+ values.push(3.0);
+ values.push(4);
+
+ // then text, case sensitive
+ values.push("a");
+ values.push("A");
+ values.push("aa");
+ values.push("b");
+ values.push("B");
+ values.push("ba");
+ values.push("bb");
+
+ // then arrays. compared element by element until different.
+ // Longer arrays sort after their prefixes
+ values.push(["a"]);
+ values.push(["b"]);
+ values.push(["b","c"]);
+ values.push(["b","c", "a"]);
+ values.push(["b","d"]);
+ values.push(["b","d", "e"]);
+
+ // then object, compares each key value in the list until different.
+ // larger objects sort after their subset objects.
+ values.push({a:1});
+ values.push({a:2});
+ values.push({b:1});
+ values.push({b:2});
+ values.push({b:2, a:1}); // Member order does matter for collation.
+ // CouchDB preserves member order
+ // but doesn't require that clients will.
+ // (this test might fail if used with a js engine
+ // that doesn't preserve order)
+ values.push({b:2, c:2});
+
+ for (var i=0; i<values.length; i++) {
+ db.save({_id:(i).toString(), foo:values[i]});
+ }
+
+ var queryFun = function(doc) { emit(doc.foo, null); };
+ var rows = db.query(queryFun).rows;
+ for (i=0; i<values.length; i++) {
+ T(equals(rows[i].key, values[i]));
+ }
+
+ // everything has collated correctly. Now to check the descending output
+ rows = db.query(queryFun, null, {descending: true}).rows;
+ for (i=0; i<values.length; i++) {
+ T(equals(rows[i].key, values[values.length - 1 -i]));
+ }
+
+ // now check the key query args
+ for (i=1; i<values.length; i++) {
+ var queryOptions = {key:values[i]};
+ rows = db.query(queryFun, null, queryOptions).rows;
+ T(rows.length == 1 && equals(rows[0].key, values[i]));
+ }
+};
diff --git a/share/www/script/test/view_conflicts.js b/share/www/script/test/view_conflicts.js
new file mode 100644
index 00000000..959471c2
--- /dev/null
+++ b/share/www/script/test/view_conflicts.js
@@ -0,0 +1,49 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+couchTests.view_conflicts = function(debug) {
+ var dbA = new CouchDB("test_suite_db_a");
+ dbA.deleteDb();
+ dbA.createDb();
+ var dbB = new CouchDB("test_suite_db_b");
+ dbB.deleteDb();
+ dbB.createDb();
+ if (debug) debugger;
+
+ var docA = {_id: "foo", bar: 42};
+ T(dbA.save(docA).ok);
+ CouchDB.replicate(dbA.name, dbB.name);
+
+ var docB = dbB.open("foo");
+ docB.bar = 43;
+ dbB.save(docB);
+ docA.bar = 41;
+ dbA.save(docA);
+ CouchDB.replicate(dbA.name, dbB.name);
+
+ var doc = dbB.open("foo", {conflicts: true});
+ T(doc._conflicts.length == 1);
+ var conflictRev = doc._conflicts[0];
+ if (doc.bar == 41) { // A won
+ T(conflictRev == docB._rev);
+ } else { // B won
+ T(doc.bar == 43);
+ T(conflictRev == docA._rev);
+ }
+
+ var results = dbB.query(function(doc) {
+ if (doc._conflicts) {
+ emit(doc._id, doc._conflicts);
+ }
+ });
+ T(results.rows[0].value[0] == conflictRev);
+};
diff --git a/share/www/script/test/view_errors.js b/share/www/script/test/view_errors.js
new file mode 100644
index 00000000..a8a9be7d
--- /dev/null
+++ b/share/www/script/test/view_errors.js
@@ -0,0 +1,43 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+couchTests.view_errors = function(debug) {
+ var db = new CouchDB("test_suite_db");
+ db.deleteDb();
+ db.createDb();
+ if (debug) debugger;
+
+ var doc = {integer: 1, string: "1", array: [1, 2, 3]};
+ T(db.save(doc).ok);
+
+ // emitting a key value that is undefined should result in that row not
+ // being included in the view results
+ var results = db.query(function(doc) {
+ emit(doc.undef, null);
+ });
+ T(results.total_rows == 0);
+
+ // if a view function throws an exception, its results are not included in
+ // the view index, but the view does not itself raise an error
+ var results = db.query(function(doc) {
+ doc.undef(); // throws an error
+ });
+ T(results.total_rows == 0);
+
+ // if a view function includes an undefined value in the emitted key or
+ // value, an error is logged and the result is not included in the view
+ // index, and the view itself does not raise an error
+ var results = db.query(function(doc) {
+ emit([doc._id, doc.undef], null);
+ });
+ T(results.total_rows == 0);
+};
diff --git a/share/www/script/test/view_include_docs.js b/share/www/script/test/view_include_docs.js
new file mode 100644
index 00000000..dd8ebb44
--- /dev/null
+++ b/share/www/script/test/view_include_docs.js
@@ -0,0 +1,111 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+couchTests.view_include_docs = function(debug) {
+ var db = new CouchDB("test_suite_db");
+ db.deleteDb();
+ db.createDb();
+ if (debug) debugger;
+
+ var docs = makeDocs(0, 100);
+ T(db.bulkSave(docs).ok);
+
+ var designDoc = {
+ _id:"_design/test",
+ language: "javascript",
+ views: {
+ all_docs: {
+ map: "function(doc) { emit(doc.integer, doc.string) }"
+ },
+ with_prev: {
+ map: "function(doc){if(doc.prev) emit(doc._id,{'_rev':doc.prev}); else emit(doc._id,{'_rev':doc._rev});}"
+ },
+ summate: {
+ map:"function (doc) {emit(doc.integer, doc.integer)};",
+ reduce:"function (keys, values) { return sum(values); };"
+ }
+ }
+ }
+ T(db.save(designDoc).ok);
+
+ var resp = db.view('test/all_docs', {include_docs: true, limit: 2});
+ T(resp.rows.length == 2);
+ T(resp.rows[0].id == "0");
+ T(resp.rows[0].doc._id == "0");
+ T(resp.rows[1].id == "1");
+ T(resp.rows[1].doc._id == "1");
+
+ resp = db.view('test/all_docs', {include_docs: true}, [29, 74]);
+ T(resp.rows.length == 2);
+ T(resp.rows[0].doc._id == "29");
+ T(resp.rows[1].doc.integer == 74);
+
+ resp = db.allDocs({limit: 2, skip: 1, include_docs: true});
+ T(resp.rows.length == 2);
+ T(resp.rows[0].doc.integer == 1);
+ T(resp.rows[1].doc.integer == 10);
+
+ resp = db.allDocs({include_docs: true}, ['not_a_doc']);
+ T(resp.rows.length == 1);
+ T(!resp.rows[0].doc);
+
+ resp = db.allDocs({include_docs: true}, ["1", "foo"]);
+ T(resp.rows.length == 2);
+ T(resp.rows[0].doc.integer == 1);
+ T(!resp.rows[1].doc);
+
+ resp = db.allDocs({include_docs: true, limit: 0});
+ T(resp.rows.length == 0);
+
+ // No reduce support
+ try {
+ resp = db.view('test/summate', {include_docs: true});
+ alert(JSON.stringify(resp));
+ T(0==1);
+ } catch (e) {
+ T(e.error == 'query_parse_error');
+ }
+
+ // Reduce support when reduce=false
+ resp = db.view('test/summate', {reduce: false, include_docs: true});
+ T(resp.rows.length == 100);
+
+ // Check emitted _rev controls things
+ resp = db.allDocs({include_docs: true}, ["0"]);
+ var before = resp.rows[0].doc;
+ var after = db.open("0");
+ after.integer = 100
+ after.prev = after._rev;
+ db.save(after);
+ after = db.open("0");
+ T(after._rev != after.prev);
+ T(after.integer == 100);
+
+ // should emit the previous revision
+ resp = db.view("test/with_prev", {include_docs: true}, ["0"]);
+ T(resp.rows[0].doc._id == "0");
+ T(resp.rows[0].doc._rev == before._rev);
+ T(!resp.rows[0].doc.prev);
+ T(resp.rows[0].doc.integer == 0);
+
+ var xhr = CouchDB.request("POST", "/test_suite_db/_compact");
+ T(xhr.status == 202)
+ while (db.info().compact_running) {}
+
+ resp = db.view("test/with_prev", {include_docs: true}, ["0", "23"]);
+ T(resp.rows.length == 2);
+ T(resp.rows[0].key == "0");
+ T(resp.rows[0].id == "0");
+ T(!resp.rows[0].doc);
+ T(resp.rows[0].error == "missing");
+ T(resp.rows[1].doc.integer == 23);
+};
diff --git a/share/www/script/test/view_multi_key_all_docs.js b/share/www/script/test/view_multi_key_all_docs.js
new file mode 100644
index 00000000..f036db11
--- /dev/null
+++ b/share/www/script/test/view_multi_key_all_docs.js
@@ -0,0 +1,54 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+couchTests.view_multi_key_all_docs = function(debug) {
+ var db = new CouchDB("test_suite_db");
+ db.deleteDb();
+ db.createDb();
+ if (debug) debugger;
+
+ var docs = makeDocs(0, 100);
+ T(db.bulkSave(docs).ok);
+
+ var keys = ["10","15","30","37","50"];
+ var rows = db.allDocs({},keys).rows;
+ T(rows.length == keys.length);
+ for(var i=0; i<rows.length; i++)
+ T(rows[i].id == keys[i]);
+
+ rows = db.allDocs({limit: 1}, keys).rows;
+ T(rows.length == 1);
+ T(rows[0].id == keys[0]);
+
+ rows = db.allDocs({skip: 2}, keys).rows;
+ T(rows.length == 3);
+ for(var i=0; i<rows.length; i++)
+ T(rows[i].id == keys[i+2]);
+
+ rows = db.allDocs({descending: "true"}, keys).rows;
+ T(rows.length == keys.length);
+ for(var i=0; i<rows.length; i++)
+ T(rows[i].id == keys[keys.length-i-1]);
+
+ rows = db.allDocs({descending: "true", skip: 3, limit:1}, keys).rows;
+ T(rows.length == 1);
+ T(rows[0].id == keys[1]);
+
+ // Check we get invalid rows when the key doesn't exist
+ rows = db.allDocs({}, [1, "i_dont_exist", "0"]).rows;
+ T(rows.length == 3);
+ T(rows[0].error == "not_found");
+ T(!rows[0].id);
+ T(rows[1].error == "not_found");
+ T(!rows[1].id);
+ T(rows[2].id == rows[2].key && rows[2].key == "0");
+};
diff --git a/share/www/script/test/view_multi_key_design.js b/share/www/script/test/view_multi_key_design.js
new file mode 100644
index 00000000..2464ff1c
--- /dev/null
+++ b/share/www/script/test/view_multi_key_design.js
@@ -0,0 +1,138 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+couchTests.view_multi_key_design = function(debug) {
+ var db = new CouchDB("test_suite_db");
+ db.deleteDb();
+ db.createDb();
+ if (debug) debugger;
+
+ var docs = makeDocs(0, 100);
+ T(db.bulkSave(docs).ok);
+
+ var designDoc = {
+ _id:"_design/test",
+ language: "javascript",
+ views: {
+ all_docs: {
+ map: "function(doc) { emit(doc.integer, doc.string) }"
+ },
+ multi_emit: {
+ map: "function(doc) {for(var i = 0 ; i < 3 ; i++) { emit(i, doc.integer) ; } }"
+ },
+ summate: {
+ map:"function (doc) {emit(doc.integer, doc.integer)};",
+ reduce:"function (keys, values) { return sum(values); };"
+ }
+ }
+ }
+ T(db.save(designDoc).ok);
+
+ // Test that missing keys work too
+ var keys = [101,30,15,37,50]
+ var reduce = db.view("test/summate",{group:true},keys).rows;
+ T(reduce.length == keys.length-1); // 101 is missing
+ for(var i=0; i<reduce.length; i++) {
+ T(keys.indexOf(reduce[i].key) != -1);
+ T(reduce[i].key == reduce[i].value);
+ }
+
+ // First, the goods:
+ var keys = [10,15,30,37,50];
+ var rows = db.view("test/all_docs",{},keys).rows;
+ for(var i=0; i<rows.length; i++) {
+ T(keys.indexOf(rows[i].key) != -1);
+ T(rows[i].key == rows[i].value);
+ }
+
+ var reduce = db.view("test/summate",{group:true},keys).rows;
+ T(reduce.length == keys.length);
+ for(var i=0; i<reduce.length; i++) {
+ T(keys.indexOf(reduce[i].key) != -1);
+ T(reduce[i].key == reduce[i].value);
+ }
+
+ // Test that invalid parameter combinations get rejected
+ var badargs = [{startkey:0}, {endkey:0}, {key: 0}, {group_level: 2}];
+ for(var i in badargs)
+ {
+ try {
+ db.view("test/all_docs",badargs[i],keys);
+ T(0==1);
+ } catch (e) {
+ T(e.error == "query_parse_error");
+ }
+ }
+
+ try {
+ db.view("test/summate",{},keys);
+ T(0==1);
+ } catch (e) {
+ T(e.error == "query_parse_error");
+ }
+
+ // Test that a map & reduce containing func support keys when reduce=false
+ resp = db.view("test/summate", {reduce: false}, keys);
+ T(resp.rows.length == 5);
+
+ // Check that limiting by startkey_docid and endkey_docid get applied
+ // as expected.
+ var curr = db.view("test/multi_emit", {startkey_docid: 21, endkey_docid: 23}, [0, 2]).rows;
+ var exp_key = [ 0, 0, 0, 2, 2, 2] ;
+ var exp_val = [21, 22, 23, 21, 22, 23] ;
+ T(curr.length == 6);
+ for( var i = 0 ; i < 6 ; i++)
+ {
+ T(curr[i].key == exp_key[i]);
+ T(curr[i].value == exp_val[i]);
+ }
+
+ // Check limit works
+ curr = db.view("test/all_docs", {limit: 1}, keys).rows;
+ T(curr.length == 1);
+ T(curr[0].key == 10);
+
+ // Check offset works
+ curr = db.view("test/multi_emit", {skip: 1}, [0]).rows;
+ T(curr.length == 99);
+ T(curr[0].value == 1);
+
+ // Check that dir works
+ curr = db.view("test/multi_emit", {descending: "true"}, [1]).rows;
+ T(curr.length == 100);
+ T(curr[0].value == 99);
+ T(curr[99].value == 0);
+
+ // Check a couple combinations
+ curr = db.view("test/multi_emit", {descending: "true", skip: 3, limit: 2}, [2]).rows;
+ T(curr.length, 2);
+ T(curr[0].value == 96);
+ T(curr[1].value == 95);
+
+ curr = db.view("test/multi_emit", {skip: 2, limit: 3, startkey_docid: "13"}, [0]).rows;
+ T(curr.length == 3);
+ T(curr[0].value == 15);
+ T(curr[1].value == 16);
+ T(curr[2].value == 17);
+
+ curr = db.view("test/multi_emit",
+ {skip: 1, limit: 5, startkey_docid: "25", endkey_docid: "27"}, [1]).rows;
+ T(curr.length == 2);
+ T(curr[0].value == 26);
+ T(curr[1].value == 27);
+
+ curr = db.view("test/multi_emit",
+ {skip: 1, limit: 5, startkey_docid: "28", endkey_docid: "26", descending: "true"}, [1]).rows;
+ T(curr.length == 2);
+ T(curr[0].value == 27);
+ T(curr[1].value == 26);
+};
diff --git a/share/www/script/test/view_multi_key_temp.js b/share/www/script/test/view_multi_key_temp.js
new file mode 100644
index 00000000..28cd42b4
--- /dev/null
+++ b/share/www/script/test/view_multi_key_temp.js
@@ -0,0 +1,37 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+couchTests.view_multi_key_temp = function(debug) {
+ var db = new CouchDB("test_suite_db");
+ db.deleteDb();
+ db.createDb();
+ if (debug) debugger;
+
+ var docs = makeDocs(0, 100);
+ T(db.bulkSave(docs).ok);
+
+ var queryFun = function(doc) { emit(doc.integer, doc.integer) };
+ var reduceFun = function (keys, values) { return sum(values); };
+
+ var keys = [10,15,30,37,50];
+ var rows = db.query(queryFun, null, {}, keys).rows;
+ for(var i=0; i<rows.length; i++) {
+ T(keys.indexOf(rows[i].key) != -1);
+ T(rows[i].key == rows[i].value);
+ }
+
+ var reduce = db.query(queryFun, reduceFun, {group:true}, keys).rows;
+ for(var i=0; i<reduce.length; i++) {
+ T(keys.indexOf(reduce[i].key) != -1);
+ T(reduce[i].key == reduce[i].value);
+ }
+};
diff --git a/share/www/script/test/view_pagination.js b/share/www/script/test/view_pagination.js
new file mode 100644
index 00000000..7d466f19
--- /dev/null
+++ b/share/www/script/test/view_pagination.js
@@ -0,0 +1,122 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+couchTests.view_pagination = function(debug) {
+ var db = new CouchDB("test_suite_db");
+ db.deleteDb();
+ db.createDb();
+ if (debug) debugger;
+
+ var docs = makeDocs(0, 100);
+ T(db.bulkSave(docs).ok);
+
+ var queryFun = function(doc) { emit(doc.integer, null) };
+ var i;
+
+ // page through the view ascending
+ for (i = 0; i < docs.length; i += 10) {
+ var queryResults = db.query(queryFun, null, {
+ startkey: i,
+ startkey_docid: i,
+ limit: 10
+ });
+ T(queryResults.rows.length == 10)
+ T(queryResults.total_rows == docs.length)
+ T(queryResults.offset == i)
+ var j;
+ for (j = 0; j < 10;j++) {
+ T(queryResults.rows[j].key == i + j);
+ }
+ }
+
+ // page through the view descending
+ for (i = docs.length - 1; i >= 0; i -= 10) {
+ var queryResults = db.query(queryFun, null, {
+ startkey: i,
+ startkey_docid: i,
+ descending: true,
+ limit: 10
+ });
+ T(queryResults.rows.length == 10)
+ T(queryResults.total_rows == docs.length)
+ T(queryResults.offset == docs.length - i - 1)
+ var j;
+ for (j = 0; j < 10; j++) {
+ T(queryResults.rows[j].key == i - j);
+ }
+ }
+
+ // ignore decending=false. CouchDB should just ignore that.
+ for (i = 0; i < docs.length; i += 10) {
+ var queryResults = db.query(queryFun, null, {
+ startkey: i,
+ startkey_docid: i,
+ descending: false,
+ limit: 10
+ });
+ T(queryResults.rows.length == 10)
+ T(queryResults.total_rows == docs.length)
+ T(queryResults.offset == i)
+ var j;
+ for (j = 0; j < 10;j++) {
+ T(queryResults.rows[j].key == i + j);
+ }
+ }
+
+ // test endkey_docid
+ var queryResults = db.query(function(doc) { emit(null, null);}, null, {
+ startkey: null,
+ startkey_docid: 1,
+ endkey: null,
+ endkey_docid: 40
+ });
+
+ T(queryResults.rows.length == 35)
+ T(queryResults.total_rows == docs.length)
+ T(queryResults.offset == 1)
+ T(queryResults.rows[0].id == "1");
+ T(queryResults.rows[1].id == "10");
+ T(queryResults.rows[2].id == "11");
+ T(queryResults.rows[3].id == "12");
+ T(queryResults.rows[4].id == "13");
+ T(queryResults.rows[5].id == "14");
+ T(queryResults.rows[6].id == "15");
+ T(queryResults.rows[7].id == "16");
+ T(queryResults.rows[8].id == "17");
+ T(queryResults.rows[9].id == "18");
+ T(queryResults.rows[10].id == "19");
+ T(queryResults.rows[11].id == "2");
+ T(queryResults.rows[12].id == "20");
+ T(queryResults.rows[13].id == "21");
+ T(queryResults.rows[14].id == "22");
+ T(queryResults.rows[15].id == "23");
+ T(queryResults.rows[16].id == "24");
+ T(queryResults.rows[17].id == "25");
+ T(queryResults.rows[18].id == "26");
+ T(queryResults.rows[19].id == "27");
+ T(queryResults.rows[20].id == "28");
+ T(queryResults.rows[21].id == "29");
+ T(queryResults.rows[22].id == "3");
+ T(queryResults.rows[23].id == "30");
+ T(queryResults.rows[24].id == "31");
+ T(queryResults.rows[25].id == "32");
+ T(queryResults.rows[26].id == "33");
+ T(queryResults.rows[27].id == "34");
+ T(queryResults.rows[28].id == "35");
+ T(queryResults.rows[29].id == "36");
+ T(queryResults.rows[30].id == "37");
+ T(queryResults.rows[31].id == "38");
+ T(queryResults.rows[32].id == "39");
+ T(queryResults.rows[33].id == "4");
+ T(queryResults.rows[34].id == "40");
+
+ };
diff --git a/share/www/script/test/view_sandboxing.js b/share/www/script/test/view_sandboxing.js
new file mode 100644
index 00000000..779b1d9d
--- /dev/null
+++ b/share/www/script/test/view_sandboxing.js
@@ -0,0 +1,52 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+couchTests.view_sandboxing = function(debug) {
+ var db = new CouchDB("test_suite_db");
+ db.deleteDb();
+ db.createDb();
+ if (debug) debugger;
+
+ var doc = {integer: 1, string: "1", array: [1, 2, 3]};
+ T(db.save(doc).ok);
+/*
+ // make sure that attempting to change the document throws an error
+ var results = db.query(function(doc) {
+ doc.integer = 2;
+ emit(null, doc);
+ });
+ T(results.total_rows == 0);
+
+ var results = db.query(function(doc) {
+ doc.array[0] = 0;
+ emit(null, doc);
+ });
+ T(results.total_rows == 0);
+*/
+ // make sure that a view cannot invoke interpreter internals such as the
+ // garbage collector
+ var results = db.query(function(doc) {
+ gc();
+ emit(null, doc);
+ });
+ T(results.total_rows == 0);
+
+ // make sure that a view cannot access the map_funs array defined used by
+ // the view server
+ var results = db.query(function(doc) { map_funs.push(1); emit(null, doc) });
+ T(results.total_rows == 0);
+
+ // make sure that a view cannot access the map_results array defined used by
+ // the view server
+ var results = db.query(function(doc) { map_results.push(1); emit(null, doc) });
+ T(results.total_rows == 0);
+};
diff --git a/share/www/script/test/view_xml.js b/share/www/script/test/view_xml.js
new file mode 100644
index 00000000..c7a0472c
--- /dev/null
+++ b/share/www/script/test/view_xml.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.view_xml = function(debug) {
+ var db = new CouchDB("test_suite_db");
+ db.deleteDb();
+ db.createDb();
+ if (debug) debugger;
+
+ db.save({content: "<doc><title id='xml'>Testing XML</title></doc>"});
+ db.save({content: "<doc><title id='e4x'>Testing E4X</title></doc>"});
+
+ var results = db.query(
+ "function(doc) {\n" +
+ " var xml = new XML(doc.content);\n" +
+ " emit(xml.title.text(), null);\n" +
+ "}");
+ T(results.total_rows == 2);
+ T(results.rows[0].key == "Testing E4X");
+ T(results.rows[1].key == "Testing XML");
+
+ var results = db.query(
+ "function(doc) {\n" +
+ " var xml = new XML(doc.content);\n" +
+ " emit(xml.title.@id, null);\n" +
+ "}");
+ T(results.total_rows == 2);
+ T(results.rows[0].key == "e4x");
+ T(results.rows[1].key == "xml");
+};