diff options
Diffstat (limited to 'share/www/script/test')
44 files changed, 2559 insertions, 350 deletions
diff --git a/share/www/script/test/attachment_names.js b/share/www/script/test/attachment_names.js index d90c24c4..988dd2d2 100644 --- a/share/www/script/test/attachment_names.js +++ b/share/www/script/test/attachment_names.js @@ -24,7 +24,7 @@ couchTests.attachment_names = function(debug) { data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ=" } } - } + }; // inline attachments try { @@ -72,7 +72,7 @@ couchTests.attachment_names = function(debug) { data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ=" } } - } + }; try { db.save(binAttDoc); diff --git a/share/www/script/test/attachment_paths.js b/share/www/script/test/attachment_paths.js index a2a0f69c..3f6ffb7c 100644 --- a/share/www/script/test/attachment_paths.js +++ b/share/www/script/test/attachment_paths.js @@ -33,7 +33,7 @@ couchTests.attachment_paths = function(debug) { data: "V2UgbGlrZSBwZXJjZW50IHR3byBGLg==" } } - } + }; T(db.save(binAttDoc).ok); @@ -73,7 +73,10 @@ couchTests.attachment_paths = function(debug) { 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"); + TEquals("text/plain;charset=utf-8", // thank you Safari + binAttDoc._attachments["foo/bar2.txt"].content_type.toLowerCase(), + "correct content-type" + ); T(binAttDoc._attachments["foo/bar2.txt"].length == 30); //// now repeat the while thing with a design doc @@ -92,7 +95,7 @@ couchTests.attachment_paths = function(debug) { data: "V2UgbGlrZSBwZXJjZW50IHR3byBGLg==" } } - } + }; T(db.save(binAttDoc).ok); @@ -141,7 +144,10 @@ couchTests.attachment_paths = function(debug) { 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"); + TEquals("text/plain;charset=utf-8", // thank you Safari + binAttDoc._attachments["foo/bar2.txt"].content_type.toLowerCase(), + "correct content-type" + ); T(binAttDoc._attachments["foo/bar2.txt"].length == 30); } }; diff --git a/share/www/script/test/attachment_views.js b/share/www/script/test/attachment_views.js index fd30dcfc..a92a8ad0 100644 --- a/share/www/script/test/attachment_views.js +++ b/share/www/script/test/attachment_views.js @@ -68,11 +68,11 @@ couchTests.attachment_views= function(debug) { } emit(parseInt(doc._id), count); - } + }; var reduceFunction = function(key, values) { return sum(values); - } + }; var result = db.query(mapFunction, reduceFunction); diff --git a/share/www/script/test/attachments.js b/share/www/script/test/attachments.js index 36f5a5ad..e16c384f 100644 --- a/share/www/script/test/attachments.js +++ b/share/www/script/test/attachments.js @@ -24,7 +24,7 @@ couchTests.attachments= function(debug) { data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ=" } } - } + }; var save_response = db.save(binAttDoc); T(save_response.ok); @@ -43,7 +43,7 @@ couchTests.attachments= function(debug) { data: "" } } - } + }; T(db.save(binAttDoc2).ok); @@ -68,12 +68,12 @@ couchTests.attachments= function(debug) { T(binAttDoc2._attachments["foo.txt"] !== undefined); T(binAttDoc2._attachments["foo2.txt"] !== undefined); - T(binAttDoc2._attachments["foo2.txt"].content_type == "text/plain;charset=utf-8"); + TEqualsIgnoreCase("text/plain;charset=utf-8", binAttDoc2._attachments["foo2.txt"].content_type); T(binAttDoc2._attachments["foo2.txt"].length == 30); var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc2/foo2.txt"); T(xhr.responseText == "This is no base64 encoded text"); - T(xhr.getResponseHeader("Content-Type") == "text/plain;charset=utf-8"); + TEqualsIgnoreCase("text/plain;charset=utf-8", xhr.getResponseHeader("Content-Type")); // test without rev, should fail var xhr = CouchDB.request("DELETE", "/test_suite_db/bin_doc2/foo2.txt"); @@ -96,7 +96,7 @@ couchTests.attachments= function(debug) { 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"); + TEqualsIgnoreCase("text/plain;charset=utf-8", xhr.getResponseHeader("Content-Type")); var xhr = CouchDB.request("PUT", "/test_suite_db/bin_doc3/attachment.txt", { headers:{"Content-Type":"text/plain;charset=utf-8"}, @@ -113,11 +113,11 @@ couchTests.attachments= function(debug) { 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"); + TEqualsIgnoreCase("text/plain;charset=utf-8", xhr.getResponseHeader("Content-Type")); var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc3/attachment.txt?rev=" + rev); T(xhr.responseText == bin_data); - T(xhr.getResponseHeader("Content-Type") == "text/plain;charset=utf-8"); + TEqualsIgnoreCase("text/plain;charset=utf-8", xhr.getResponseHeader("Content-Type")); var xhr = CouchDB.request("DELETE", "/test_suite_db/bin_doc3/attachment.txt?rev=" + rev); T(xhr.status == 200); @@ -129,7 +129,7 @@ couchTests.attachments= function(debug) { var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc3/attachment.txt?rev=" + rev); T(xhr.status == 200); T(xhr.responseText == bin_data); - T(xhr.getResponseHeader("Content-Type") == "text/plain;charset=utf-8"); + TEqualsIgnoreCase("text/plain;charset=utf-8", xhr.getResponseHeader("Content-Type")); // empty attachments var xhr = CouchDB.request("PUT", "/test_suite_db/bin_doc4/attachment.txt", { @@ -156,7 +156,7 @@ couchTests.attachments= function(debug) { // Attachment sparseness COUCHDB-220 - var docs = [] + var docs = []; for (var i = 0; i < 5; i++) { var doc = { _id: (i).toString(), @@ -166,8 +166,8 @@ couchTests.attachments= function(debug) { data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ=" } } - } - docs.push(doc) + }; + docs.push(doc); } var saved = db.bulkSave(docs); @@ -210,7 +210,7 @@ couchTests.attachments= function(debug) { var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc5/lorem.txt"); T(xhr.responseText == lorem); - T(xhr.getResponseHeader("Content-Type") == "text/plain;charset=utf-8"); + TEqualsIgnoreCase("text/plain;charset=utf-8", xhr.getResponseHeader("Content-Type")); // test large inline attachment too var lorem_b64 = CouchDB.request("GET", "/_utils/script/test/lorem_b64.txt").responseText; @@ -244,4 +244,30 @@ couchTests.attachments= function(debug) { body: "THIS IS AN ATTACHMENT. BOOYA!" }); TEquals(400, xhr.status, "should return error code 400 Bad Request"); + + // test COUCHDB-809 - stubs should only require the 'stub' field + var bin_doc6 = { + _id: "bin_doc6", + _attachments:{ + "foo.txt": { + content_type:"text/plain", + data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ=" + } + } + }; + T(db.save(bin_doc6).ok); + // stub out the attachment + bin_doc6._attachments["foo.txt"] = { stub: true }; + T(db.save(bin_doc6).ok == true); + + // wrong rev pos specified + + // stub out the attachment with the wrong revpos + bin_doc6._attachments["foo.txt"] = { stub: true, revpos: 10}; + try { + T(db.save(bin_doc6).ok == true); + T(false && "Shouldn't get here!"); + } catch (e) { + T(e.error == "missing_stub"); + } }; diff --git a/share/www/script/test/attachments_multipart.js b/share/www/script/test/attachments_multipart.js index 2b79e559..fecf9d01 100644 --- a/share/www/script/test/attachments_multipart.js +++ b/share/www/script/test/attachments_multipart.js @@ -58,7 +58,7 @@ couchTests.attachments_multipart= function(debug) { var result = JSON.parse(xhr.responseText); - T(result.ok) + T(result.ok); @@ -193,7 +193,7 @@ couchTests.attachments_multipart= function(debug) { // a certain rev). xhr = CouchDB.request("GET", "/test_suite_db/multipart?atts_since=[\"" + firstrev + "\"]", - {headers:{"accept": "multipart/related,*/*;"}}); + {headers:{"accept": "multipart/related, */*"}}); T(xhr.status == 200); diff --git a/share/www/script/test/auth_cache.js b/share/www/script/test/auth_cache.js new file mode 100644 index 00000000..e48f7370 --- /dev/null +++ b/share/www/script/test/auth_cache.js @@ -0,0 +1,280 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +couchTests.auth_cache = function(debug) { + + if (debug) debugger; + + // Simple secret key generator + function generateSecret(length) { + var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + + "0123456789+/"; + var secret = ''; + for (var i = 0; i < length; i++) { + secret += tab.charAt(Math.floor(Math.random() * 64)); + } + return secret; + } + + var authDb = new CouchDB("test_suite_users", {"X-Couch-Full-Commit":"false"}); + var server_config = [ + { + section: "couch_httpd_auth", + key: "authentication_db", + value: authDb.name + }, + { + section: "couch_httpd_auth", + key: "auth_cache_size", + value: "3" + }, + { + section: "httpd", + key: "authentication_handlers", + value: "{couch_httpd_auth, default_authentication_handler}" + }, + { + section: "couch_httpd_auth", + key: "secret", + value: generateSecret(64) + } + ]; + + + function hits() { + var hits = CouchDB.requestStats("couchdb", "auth_cache_hits", true); + return hits.current || 0; + } + + + function misses() { + var misses = CouchDB.requestStats("couchdb", "auth_cache_misses", true); + return misses.current || 0; + } + + + function testFun() { + var hits_before, + misses_before, + hits_after, + misses_after; + + var fdmanana = CouchDB.prepareUserDoc({ + name: "fdmanana", + roles: ["dev"] + }, "qwerty"); + + T(authDb.save(fdmanana).ok); + + var chris = CouchDB.prepareUserDoc({ + name: "chris", + roles: ["dev", "mafia", "white_costume"] + }, "the_god_father"); + + T(authDb.save(chris).ok); + + var joe = CouchDB.prepareUserDoc({ + name: "joe", + roles: ["erlnager"] + }, "functional"); + + T(authDb.save(joe).ok); + + var johndoe = CouchDB.prepareUserDoc({ + name: "johndoe", + roles: ["user"] + }, "123456"); + + T(authDb.save(johndoe).ok); + + hits_before = hits(); + misses_before = misses(); + + T(CouchDB.login("fdmanana", "qwerty").ok); + + hits_after = hits(); + misses_after = misses(); + + T(misses_after === (misses_before + 1)); + T(hits_after === hits_before); + + hits_before = hits_after; + misses_before = misses_after; + + T(CouchDB.logout().ok); + T(CouchDB.login("fdmanana", "qwerty").ok); + + hits_after = hits(); + misses_after = misses(); + + T(misses_after === misses_before); + T(hits_after === (hits_before + 1)); + + hits_before = hits_after; + misses_before = misses_after; + + T(CouchDB.logout().ok); + T(CouchDB.login("chris", "the_god_father").ok); + + hits_after = hits(); + misses_after = misses(); + + T(misses_after === (misses_before + 1)); + T(hits_after === hits_before); + + hits_before = hits_after; + misses_before = misses_after; + + T(CouchDB.logout().ok); + T(CouchDB.login("joe", "functional").ok); + + hits_after = hits(); + misses_after = misses(); + + T(misses_after === (misses_before + 1)); + T(hits_after === hits_before); + + hits_before = hits_after; + misses_before = misses_after; + + T(CouchDB.logout().ok); + T(CouchDB.login("johndoe", "123456").ok); + + hits_after = hits(); + misses_after = misses(); + + T(misses_after === (misses_before + 1)); + T(hits_after === hits_before); + + hits_before = hits_after; + misses_before = misses_after; + + T(CouchDB.logout().ok); + T(CouchDB.login("joe", "functional").ok); + + hits_after = hits(); + misses_after = misses(); + + // it's an MRU cache, joe was removed from cache to add johndoe + T(misses_after === (misses_before + 1)); + T(hits_after === hits_before); + + hits_before = hits_after; + misses_before = misses_after; + + T(CouchDB.logout().ok); + T(CouchDB.login("fdmanana", "qwerty").ok); + + hits_after = hits(); + misses_after = misses(); + + T(misses_after === misses_before); + T(hits_after === (hits_before + 1)); + + hits_before = hits_after; + misses_before = misses_after; + + var new_salt = CouchDB.newUuids(1)[0]; + var new_passwd = hex_sha1("foobar" + new_salt); + fdmanana.salt = new_salt; + fdmanana.password_sha = new_passwd; + + T(authDb.save(fdmanana).ok); + T(CouchDB.logout().ok); + + // cache was refreshed + T(CouchDB.login("fdmanana", "qwerty").error === "unauthorized"); + T(CouchDB.login("fdmanana", "foobar").ok); + + hits_after = hits(); + misses_after = misses(); + + T(misses_after === misses_before); + T(hits_after === (hits_before + 2)); + + T(CouchDB.logout().ok); + + hits_before = hits_after; + misses_before = misses_after; + + // and yet another update + new_salt = CouchDB.newUuids(1)[0]; + new_passwd = hex_sha1("javascript" + new_salt); + fdmanana.salt = new_salt; + fdmanana.password_sha = new_passwd; + + T(authDb.save(fdmanana).ok); + T(CouchDB.logout().ok); + + // cache was refreshed + T(CouchDB.login("fdmanana", "foobar").error === "unauthorized"); + T(CouchDB.login("fdmanana", "javascript").ok); + + hits_after = hits(); + misses_after = misses(); + + T(misses_after === misses_before); + T(hits_after === (hits_before + 2)); + + T(authDb.deleteDoc(fdmanana).ok); + T(CouchDB.logout().ok); + + hits_before = hits_after; + misses_before = misses_after; + + T(CouchDB.login("fdmanana", "javascript").error === "unauthorized"); + + hits_after = hits(); + misses_after = misses(); + + T(misses_after === misses_before); + T(hits_after === (hits_before + 1)); + + // login, compact authentication DB, login again and verify that + // there was a cache hit + hits_before = hits_after; + misses_before = misses_after; + + T(CouchDB.login("johndoe", "123456").ok); + + hits_after = hits(); + misses_after = misses(); + + T(misses_after === (misses_before + 1)); + T(hits_after === hits_before); + + T(CouchDB.logout().ok); + T(authDb.compact().ok); + + while (authDb.info().compact_running); + + hits_before = hits_after; + misses_before = misses_after; + + T(CouchDB.login("johndoe", "123456").ok); + + hits_after = hits(); + misses_after = misses(); + + T(misses_after === misses_before); + T(hits_after === (hits_before + 1)); + + T(CouchDB.logout().ok); + } + + + authDb.deleteDb(); + run_on_modified_server(server_config, testFun); + + // cleanup + authDb.deleteDb(); +}
\ No newline at end of file diff --git a/share/www/script/test/basics.js b/share/www/script/test/basics.js index 0f9ac44f..8885ba6e 100644 --- a/share/www/script/test/basics.js +++ b/share/www/script/test/basics.js @@ -45,7 +45,7 @@ couchTests.basics = function(debug) { // Get the database info, check the db_name T(db.info().db_name == "test_suite_db"); - T(CouchDB.allDbs().indexOf("test_suite_db") != -1) + T(CouchDB.allDbs().indexOf("test_suite_db") != -1); // Get the database info, check the doc_count T(db.info().doc_count == 0); @@ -91,13 +91,13 @@ couchTests.basics = function(debug) { emit(null, doc.b); }; - results = db.query(mapFunction); + var results = db.query(mapFunction); // verify only one document found and the result value (doc.b). T(results.total_rows == 1 && results.rows[0].value == 16); // reopen document we saved earlier - existingDoc = db.open(id); + var existingDoc = db.open(id); T(existingDoc.a==1); @@ -152,7 +152,8 @@ couchTests.basics = function(debug) { // test that the POST response has a Location header var xhr = CouchDB.request("POST", "/test_suite_db", { - body: JSON.stringify({"foo":"bar"}) + body: JSON.stringify({"foo":"bar"}), + headers: {"Content-Type": "application/json"} }); var resp = JSON.parse(xhr.responseText); T(resp.ok); @@ -164,6 +165,7 @@ couchTests.basics = function(debug) { // test that that POST's with an _id aren't overriden with a UUID. var xhr = CouchDB.request("POST", "/test_suite_db", { + headers: {"Content-Type": "application/json"}, body: JSON.stringify({"_id": "oppossum", "yar": "matey"}) }); var resp = JSON.parse(xhr.responseText); @@ -189,12 +191,12 @@ couchTests.basics = function(debug) { T(xhr.status == 404); // Check for invalid document members - bad_docs = [ + var bad_docs = [ ["goldfish", {"_zing": 4}], ["zebrafish", {"_zoom": "hello"}], ["mudfish", {"zane": "goldfish", "_fan": "something smells delicious"}], ["tastyfish", {"_bing": {"wha?": "soda can"}}] - ] + ]; var test_doc = function(info) { var data = JSON.stringify(info[1]); xhr = CouchDB.request("PUT", "/test_suite_db/" + info[0], {body: data}); @@ -202,7 +204,10 @@ couchTests.basics = function(debug) { result = JSON.parse(xhr.responseText); T(result.error == "doc_validation"); - xhr = CouchDB.request("POST", "/test_suite_db/", {body: data}); + xhr = CouchDB.request("POST", "/test_suite_db/", { + headers: {"Content-Type": "application/json"}, + body: data + }); T(xhr.status == 500); result = JSON.parse(xhr.responseText); T(result.error == "doc_validation"); diff --git a/share/www/script/test/batch_save.js b/share/www/script/test/batch_save.js index 1c8a2be9..a1b00192 100644 --- a/share/www/script/test/batch_save.js +++ b/share/www/script/test/batch_save.js @@ -36,7 +36,10 @@ couchTests.batch_save = function(debug) { // repeat the tests for POST for(i=0; i < 100; i++) { - var resp = db.request("POST", db.uri + "?batch=ok", {body: JSON.stringify({a:1})}); + var resp = db.request("POST", db.uri + "?batch=ok", { + headers: {"Content-Type": "application/json"}, + body: JSON.stringify({a:1}) + }); T(JSON.parse(resp.responseText).ok); } diff --git a/share/www/script/test/bulk_docs.js b/share/www/script/test/bulk_docs.js index 346aea83..9095e6b3 100644 --- a/share/www/script/test/bulk_docs.js +++ b/share/www/script/test/bulk_docs.js @@ -51,12 +51,12 @@ couchTests.bulk_docs = function(debug) { T(results.length == 5); T(results[0].id == "0"); T(results[0].error == "conflict"); - T(results[0].rev === undefined); // no rev member when a conflict + T(typeof results[0].rev === "undefined"); // no rev member when a conflict // but the rest are not for (i = 1; i < 5; i++) { T(results[i].id == i.toString()); - T(results[i].rev) + T(results[i].rev); T(db.open(docs[i]._id) == null); } @@ -64,7 +64,7 @@ couchTests.bulk_docs = function(debug) { // save doc 0, this will cause a conflict when we save docs[0] var doc = db.open("0"); - docs[0] = db.open("0") + docs[0] = db.open("0"); db.save(doc); docs[0].shooby = "dooby"; @@ -93,8 +93,8 @@ couchTests.bulk_docs = function(debug) { // Regression test for failure on update/delete var newdoc = {"_id": "foobar", "body": "baz"}; T(db.save(newdoc).ok); - update = {"_id": newdoc._id, "_rev": newdoc._rev, "body": "blam"}; - torem = {"_id": newdoc._id, "_rev": newdoc._rev, "_deleted": true}; + var update = {"_id": newdoc._id, "_rev": newdoc._rev, "body": "blam"}; + var torem = {"_id": newdoc._id, "_rev": newdoc._rev, "_deleted": true}; results = db.bulkSave([update, torem]); T(results[0].error == "conflict" || results[1].error == "conflict"); }; diff --git a/share/www/script/test/changes.js b/share/www/script/test/changes.js index 0cbf3bd6..50649508 100644 --- a/share/www/script/test/changes.js +++ b/share/www/script/test/changes.js @@ -17,7 +17,7 @@ function jsonp(obj) { } couchTests.changes = function(debug) { - var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); + var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"true"}); db.deleteDb(); db.createDb(); if (debug) debugger; @@ -29,20 +29,28 @@ couchTests.changes = function(debug) { var docFoo = {_id:"foo", bar:1}; T(db.save(docFoo).ok); T(db.ensureFullCommit().ok); + T(db.open(docFoo._id)._id == docFoo._id); req = CouchDB.request("GET", "/test_suite_db/_changes"); var resp = JSON.parse(req.responseText); - T(resp.results.length == 1 && resp.last_seq==1, "one doc db") + T(resp.last_seq == 1); + T(resp.results.length == 1, "one doc db") T(resp.results[0].changes[0].rev == docFoo._rev) // test with callback - var xhr = CouchDB.request("GET", "/test_suite_db/_changes?callback=jsonp"); - T(xhr.status == 200); - jsonp_flag = 0; - eval(xhr.responseText); - T(jsonp_flag == 1); - + + run_on_modified_server( + [{section: "httpd", + key: "allow_jsonp", + value: "true"}], + function() { + var xhr = CouchDB.request("GET", "/test_suite_db/_changes?callback=jsonp"); + T(xhr.status == 200); + jsonp_flag = 0; + eval(xhr.responseText); + T(jsonp_flag == 1); + }); req = CouchDB.request("GET", "/test_suite_db/_changes?feed=continuous&timeout=10"); var lines = req.responseText.split("\n"); @@ -70,98 +78,109 @@ couchTests.changes = function(debug) { // WebKit (last checked on nightly #47686) does fail on processing // the async-request properly while javascript is executed. - var sleep = function(msecs) { - // by making a slow sync request, we allow the waiting XHR request data - // to be received. - var req = CouchDB.request("GET", "/_sleep?time=" + msecs); - T(JSON.parse(req.responseText).ok == true); - } - - xhr.open("GET", "/test_suite_db/_changes?feed=continuous", true); + xhr.open("GET", "/test_suite_db/_changes?feed=continuous&timeout=500", true); xhr.send(""); var docBar = {_id:"bar", bar:1}; db.save(docBar); + + var lines, change1, change2; + waitForSuccess(function() { + lines = xhr.responseText.split("\n"); + change1 = JSON.parse(lines[0]); + change2 = JSON.parse(lines[1]); + if (change2.seq != 2) { + throw "bad seq, try again" + } + }, "bar-only"); - sleep(100); - var lines = xhr.responseText.split("\n"); - - var change = JSON.parse(lines[0]); - - T(change.seq == 1) - T(change.id == "foo") - - change = JSON.parse(lines[1]); - - T(change.seq == 2) - T(change.id == "bar") - T(change.changes[0].rev == docBar._rev) - + T(change1.seq == 1) + T(change1.id == "foo") + + T(change2.seq == 2) + T(change2.id == "bar") + T(change2.changes[0].rev == docBar._rev) + + var docBaz = {_id:"baz", baz:1}; db.save(docBaz); - sleep(100); - var lines = xhr.responseText.split("\n"); - - change = JSON.parse(lines[2]); - - T(change.seq == 3); - T(change.id == "baz"); - T(change.changes[0].rev == docBaz._rev); + var change3; + waitForSuccess(function() { + lines = xhr.responseText.split("\n"); + change3 = JSON.parse(lines[2]); + if (change3.seq != 3) { + throw "bad seq, try again" + } + }); + + T(change3.seq == 3); + T(change3.id == "baz"); + T(change3.changes[0].rev == docBaz._rev); xhr = CouchDB.newXhr(); //verify the hearbeat newlines are sent - xhr.open("GET", "/test_suite_db/_changes?feed=continuous&heartbeat=10", true); + xhr.open("GET", "/test_suite_db/_changes?feed=continuous&heartbeat=10&timeout=500", true); xhr.send(""); - - sleep(100); - - var str = xhr.responseText; + + var str; + waitForSuccess(function() { + str = xhr.responseText; + if (str.charAt(str.length - 1) != "\n" || str.charAt(str.length - 2) != "\n") { + throw("keep waiting"); + } + }, "heartbeat"); T(str.charAt(str.length - 1) == "\n") T(str.charAt(str.length - 2) == "\n") + // otherwise we'll continue to receive heartbeats forever + xhr.abort(); // test longpolling xhr = CouchDB.newXhr(); xhr.open("GET", "/test_suite_db/_changes?feed=longpoll", true); xhr.send(""); - - sleep(100); - var lines = xhr.responseText.split("\n"); - T(lines[5]=='"last_seq":3}'); - + + waitForSuccess(function() { + lines = xhr.responseText.split("\n"); + if (lines[5] != '"last_seq":3}') { + throw("still waiting"); + } + }, "last_seq"); + xhr = CouchDB.newXhr(); xhr.open("GET", "/test_suite_db/_changes?feed=longpoll&since=3", true); xhr.send(""); - sleep(100); - var docBarz = {_id:"barz", bar:1}; db.save(docBarz); - - sleep(100); - - var lines = xhr.responseText.split("\n"); - + var parse_changes_line = function(line) { if (line.charAt(line.length-1) == ",") { - line = line.substring(0, line.length-1); + var linetrimmed = line.substring(0, line.length-1); + } else { + var linetrimmed = line } - return JSON.parse(line); + return JSON.parse(linetrimmed); } - - change = parse_changes_line(lines[1]); - + + waitForSuccess(function() { + lines = xhr.responseText.split("\n"); + if (lines[3] != '"last_seq":4}') { + throw("still waiting"); + } + }, "change_lines"); + + var change = parse_changes_line(lines[1]); T(change.seq == 4); T(change.id == "barz"); T(change.changes[0].rev == docBarz._rev); T(lines[3]=='"last_seq":4}'); - } // test the filtered changes @@ -175,7 +194,16 @@ couchTests.changes = function(debug) { }), "userCtx" : stringFun(function(doc, req) { return doc.user && (doc.user == req.userCtx.name); - }) + }), + "conflicted" : "function(doc, req) { return (doc._conflicts);}", + }, + options : { + local_seq : true + }, + views : { + local_seq : { + map : "function(doc) {emit(doc._local_seq, null)}" + } } } @@ -190,7 +218,7 @@ couchTests.changes = function(debug) { var req = CouchDB.request("GET", "/test_suite_db/_changes?filter=changes_filter/bop"); var resp = JSON.parse(req.responseText); - T(resp.results.length == 1); + T(resp.results.length == 1, "filtered/bop"); req = CouchDB.request("GET", "/test_suite_db/_changes?filter=changes_filter/dynamic&field=woox"); resp = JSON.parse(req.responseText); @@ -198,15 +226,14 @@ couchTests.changes = function(debug) { req = CouchDB.request("GET", "/test_suite_db/_changes?filter=changes_filter/dynamic&field=bop"); resp = JSON.parse(req.responseText); - T(resp.results.length == 1); + T(resp.results.length == 1, "changes_filter/dynamic&field=bop"); if (!is_safari && xhr) { // full test requires parallel connections // filter with longpoll // longpoll filters full history when run without a since seq xhr = CouchDB.newXhr(); - xhr.open("GET", "/test_suite_db/_changes?feed=longpoll&filter=changes_filter/bop", true); + xhr.open("GET", "/test_suite_db/_changes?feed=longpoll&filter=changes_filter/bop", false); xhr.send(""); - sleep(100); var resp = JSON.parse(xhr.responseText); T(resp.last_seq == 7); // longpoll waits until a matching change before returning @@ -215,22 +242,53 @@ couchTests.changes = function(debug) { xhr.send(""); db.save({"_id":"falsy", "bop" : ""}); // empty string is falsy db.save({"_id":"bingo","bop" : "bingo"}); - sleep(100); - var resp = JSON.parse(xhr.responseText); + + waitForSuccess(function() { + resp = JSON.parse(xhr.responseText); + }, "longpoll-since"); + T(resp.last_seq == 9); T(resp.results && resp.results.length > 0 && resp.results[0]["id"] == "bingo", "filter the correct update"); - - // filter with continuous - xhr = CouchDB.newXhr(); - xhr.open("GET", "/test_suite_db/_changes?feed=continuous&filter=changes_filter/bop&timeout=200", true); - xhr.send(""); - db.save({"_id":"rusty", "bop" : "plankton"}); - T(db.ensureFullCommit().ok); - sleep(300); - var lines = xhr.responseText.split("\n"); - T(JSON.parse(lines[1]).id == "bingo", lines[1]); - T(JSON.parse(lines[2]).id == "rusty", lines[2]); - T(JSON.parse(lines[3]).last_seq == 10, lines[3]); + xhr.abort(); + + timeout = 500; + last_seq = 10 + while (true) { + + // filter with continuous + xhr = CouchDB.newXhr(); + xhr.open("GET", "/test_suite_db/_changes?feed=continuous&filter=changes_filter/bop&timeout="+timeout, true); + xhr.send(""); + + db.save({"_id":"rusty", "bop" : "plankton"}); + T(xhr.readyState != 4, "test client too slow"); + var rusty = db.open("rusty", {cache_bust : new Date()}); + T(rusty._id == "rusty"); + + waitForSuccess(function() { // throws an error after 5 seconds + if (xhr.readyState != 4) { + throw("still waiting") + } + }, "continuous-rusty"); + lines = xhr.responseText.split("\n"); + try { + JSON.parse(lines[3]) + good = true; + } catch(e) { + good = false; + } + if (good) { + T(JSON.parse(lines[1]).id == "bingo", lines[1]); + T(JSON.parse(lines[2]).id == "rusty", lines[2]); + T(JSON.parse(lines[3]).last_seq == last_seq, lines[3]); + break; + } else { + xhr.abort(); + db.deleteDoc(rusty); + timeout = timeout * 2; + last_seq = last_seq + 2; + } + } } // error conditions @@ -257,7 +315,8 @@ couchTests.changes = function(debug) { var req = CouchDB.request("GET", "/test_suite_db/_changes?filter=changes_filter/bop&style=all_docs"); var resp = JSON.parse(req.responseText); - TEquals(3, resp.results.length, "should return matching rows"); + var expect = (!is_safari && xhr) ? 3: 1; + TEquals(expect, resp.results.length, "should return matching rows"); // test for userCtx run_on_modified_server( @@ -286,9 +345,58 @@ couchTests.changes = function(debug) { resp = JSON.parse(req.responseText); T(resp.results.length == 1, "userCtx"); T(resp.results[0].id == docResp.id); - }); - - req = CouchDB.request("GET", "/test_suite_db/_changes?limit=1"); - resp = JSON.parse(req.responseText); - TEquals(1, resp.results.length) + } + ); + + req = CouchDB.request("GET", "/test_suite_db/_changes?limit=1"); + resp = JSON.parse(req.responseText); + TEquals(1, resp.results.length) + + //filter includes _conflicts + var id = db.save({'food' : 'pizza'}).id; + db.bulkSave([{_id: id, 'food' : 'pasta'}], {all_or_nothing:true}); + + req = CouchDB.request("GET", "/test_suite_db/_changes?filter=changes_filter/conflicted"); + resp = JSON.parse(req.responseText); + T(resp.results.length == 1, "filter=changes_filter/conflicted"); + + // test with erlang filter function + run_on_modified_server([{ + section: "native_query_servers", + key: "erlang", + value: "{couch_native_process, start_link, []}" + }], function() { + var erl_ddoc = { + _id: "_design/erlang", + language: "erlang", + filters: { + foo: + 'fun({Doc}, Req) -> ' + + ' Value = couch_util:get_value(<<"value">>, Doc),' + + ' (Value rem 2) =:= 0' + + 'end.' + } + }; + + db.deleteDb(); + db.createDb(); + T(db.save(erl_ddoc).ok); + + var req = CouchDB.request("GET", "/test_suite_db/_changes?filter=erlang/foo"); + var resp = JSON.parse(req.responseText); + T(resp.results.length === 0); + + T(db.save({_id: "doc1", value : 1}).ok); + T(db.save({_id: "doc2", value : 2}).ok); + T(db.save({_id: "doc3", value : 3}).ok); + T(db.save({_id: "doc4", value : 4}).ok); + + var req = CouchDB.request("GET", "/test_suite_db/_changes?filter=erlang/foo"); + var resp = JSON.parse(req.responseText); + T(resp.results.length === 2); + T(resp.results[0].id === "doc2"); + T(resp.results[1].id === "doc4"); + }); + }; + diff --git a/share/www/script/test/compact.js b/share/www/script/test/compact.js index f63bfc57..805a3b08 100644 --- a/share/www/script/test/compact.js +++ b/share/www/script/test/compact.js @@ -26,11 +26,12 @@ couchTests.compact = function(debug) { data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ=" } } - } + }; T(db.save(binAttDoc).ok); var originalsize = db.info().disk_size; + var start_time = db.info().instance_start_time; for(var i in docs) { db.deleteDoc(docs[i]); @@ -38,17 +39,20 @@ couchTests.compact = function(debug) { T(db.ensureFullCommit().ok); var deletesize = db.info().disk_size; T(deletesize > originalsize); + T(db.setDbProperty("_revs_limit", 666).ok); T(db.compact().ok); T(db.last_req.status == 202); // compaction isn't instantaneous, loop until done while (db.info().compact_running) {}; + T(db.info().instance_start_time == start_time); + T(db.getDbProperty("_revs_limit") === 666); T(db.ensureFullCommit().ok); restartServer(); var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc/foo.txt"); - T(xhr.responseText == "This is a base64 encoded text") - T(xhr.getResponseHeader("Content-Type") == "text/plain") + T(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/conflicts.js b/share/www/script/test/conflicts.js index b8b93946..7258bc31 100644 --- a/share/www/script/test/conflicts.js +++ b/share/www/script/test/conflicts.js @@ -44,7 +44,7 @@ couchTests.conflicts = function(debug) { var changes = db.changes(); - T( changes.results.length == 1) + T(changes.results.length == 1); // Now clear out the _rev member and save. This indicates this document is // new, not based on an existing revision. diff --git a/share/www/script/test/content_negotiation.js b/share/www/script/test/content_negotiation.js index 171dbb3d..c79df948 100644 --- a/share/www/script/test/content_negotiation.js +++ b/share/www/script/test/content_negotiation.js @@ -17,11 +17,14 @@ couchTests.content_negotiation = function(debug) { if (debug) debugger; var xhr; - xhr = CouchDB.request("GET", "/test_suite_db/"); - TEquals("text/plain;charset=utf-8", xhr.getResponseHeader("Content-Type")); + // with no accept header + var req = CouchDB.newXhr(); + req.open("GET", "/test_suite_db/", false); + req.send(""); + TEquals("text/plain;charset=utf-8", req.getResponseHeader("Content-Type")); // make sure JSON responses end in a newline - var text = xhr.responseText; + var text = req.responseText; TEquals("\n", text[text.length-1]); xhr = CouchDB.request("GET", "/test_suite_db/", { diff --git a/share/www/script/test/cookie_auth.js b/share/www/script/test/cookie_auth.js index 9eadfee0..ef915602 100644 --- a/share/www/script/test/cookie_auth.js +++ b/share/www/script/test/cookie_auth.js @@ -46,39 +46,39 @@ couchTests.cookie_auth = function(debug) { // Create a user var jasonUserDoc = CouchDB.prepareUserDoc({ - username: "Jason Davies", + name: "Jason Davies", roles: ["dev"] }, password); T(usersDb.save(jasonUserDoc).ok); var checkDoc = usersDb.open(jasonUserDoc._id); - T(checkDoc.username == "Jason Davies"); + T(checkDoc.name == "Jason Davies"); var jchrisUserDoc = CouchDB.prepareUserDoc({ - username: "jchris@apache.org" + name: "jchris@apache.org" }, "funnybone"); T(usersDb.save(jchrisUserDoc).ok); // make sure we cant create duplicate users var duplicateJchrisDoc = CouchDB.prepareUserDoc({ - username: "jchris@apache.org" + name: "jchris@apache.org" }, "eh, Boo-Boo?"); try { - usersDb.save(duplicateJchrisDoc) + usersDb.save(duplicateJchrisDoc); T(false && "Can't create duplicate user names. Should have thrown an error."); } catch (e) { T(e.error == "conflict"); T(usersDb.last_req.status == 409); } - // we can't create _usernames + // we can't create _names var underscoreUserDoc = CouchDB.prepareUserDoc({ - username: "_why" + name: "_why" }, "copperfield"); try { - usersDb.save(underscoreUserDoc) + usersDb.save(underscoreUserDoc); T(false && "Can't create underscore user names. Should have thrown an error."); } catch (e) { T(e.error == "forbidden"); @@ -87,46 +87,47 @@ couchTests.cookie_auth = function(debug) { // we can't create docs with malformed ids var badIdDoc = CouchDB.prepareUserDoc({ - username: "foo" + name: "foo" }, "bar"); badIdDoc._id = "org.apache.couchdb:w00x"; try { - usersDb.save(badIdDoc) + usersDb.save(badIdDoc); T(false && "Can't create malformed docids. Should have thrown an error."); } catch (e) { T(e.error == "forbidden"); T(usersDb.last_req.status == 403); } - - try { - usersDb.save(underscoreUserDoc) - T(false && "Can't create underscore user names. Should have thrown an error."); - } catch (e) { - T(e.error == "forbidden"); - T(usersDb.last_req.status == 403); - } // login works T(CouchDB.login('Jason Davies', password).ok); - T(CouchDB.session().name == 'Jason Davies'); + T(CouchDB.session().userCtx.name == 'Jason Davies'); // update one's own credentials document jasonUserDoc.foo=2; T(usersDb.save(jasonUserDoc).ok); + T(CouchDB.session().userCtx.roles.indexOf("_admin") == -1); + // can't delete another users doc unless you are admin + try { + usersDb.deleteDoc(jchrisUserDoc); + T(false && "Can't delete other users docs. Should have thrown an error."); + } catch (e) { + T(e.error == "forbidden"); + T(usersDb.last_req.status == 403); + } // TODO should login() throw an exception here? T(!CouchDB.login('Jason Davies', "2.71828").ok); T(!CouchDB.login('Robert Allen Zimmerman', 'd00d').ok); // a failed login attempt should log you out - T(CouchDB.session().name != 'Jason Davies'); + T(CouchDB.session().userCtx.name != 'Jason Davies'); // test redirect - xhr = CouchDB.request("POST", "/_session?next=/", { + var xhr = CouchDB.request("POST", "/_session?next=/", { headers: {"Content-Type": "application/x-www-form-urlencoded"}, - body: "username=Jason%20Davies&password="+encodeURIComponent(password) + body: "name=Jason%20Davies&password="+encodeURIComponent(password) }); // should this be a redirect code instead of 200? // The cURL adapter is returning the expected 302 here. @@ -134,23 +135,23 @@ couchTests.cookie_auth = function(debug) { // to follow the redirect, ie, the browser follows and does a // GET on the returned Location if (xhr.status == 200) { - T(/Welcome/.test(xhr.responseText)) + T(/Welcome/.test(xhr.responseText)); } else { - T(xhr.status == 302) - T(xhr.getResponseHeader("Location")) + T(xhr.status == 302); + T(xhr.getResponseHeader("Location")); } // test users db validations // // test that you can't update docs unless you are logged in as the user (or are admin) T(CouchDB.login("jchris@apache.org", "funnybone").ok); - T(CouchDB.session().name == "jchris@apache.org"); - T(CouchDB.session().roles.length == 0); + T(CouchDB.session().userCtx.name == "jchris@apache.org"); + T(CouchDB.session().userCtx.roles.length == 0); jasonUserDoc.foo=3; try { - usersDb.save(jasonUserDoc) + usersDb.save(jasonUserDoc); T(false && "Can't update someone else's user doc. Should have thrown an error."); } catch (e) { T(e.error == "forbidden"); @@ -161,7 +162,7 @@ couchTests.cookie_auth = function(debug) { jchrisUserDoc.roles = ["foo"]; try { - usersDb.save(jchrisUserDoc) + usersDb.save(jchrisUserDoc); T(false && "Can't set roles unless you are admin. Should have thrown an error."); } catch (e) { T(e.error == "forbidden"); @@ -169,7 +170,7 @@ couchTests.cookie_auth = function(debug) { } T(CouchDB.logout().ok); - T(CouchDB.session().roles[0] == "_admin"); + T(CouchDB.session().userCtx.roles[0] == "_admin"); jchrisUserDoc.foo = ["foo"]; T(usersDb.save(jchrisUserDoc).ok); @@ -178,7 +179,7 @@ couchTests.cookie_auth = function(debug) { jchrisUserDoc.roles = ["_bar"]; try { - usersDb.save(jchrisUserDoc) + usersDb.save(jchrisUserDoc); T(false && "Can't add system roles to user's db. Should have thrown an error."); } catch (e) { T(e.error == "forbidden"); @@ -187,24 +188,24 @@ couchTests.cookie_auth = function(debug) { // make sure the foo role has been applied T(CouchDB.login("jchris@apache.org", "funnybone").ok); - T(CouchDB.session().name == "jchris@apache.org"); - T(CouchDB.session().roles.indexOf("_admin") == -1); - T(CouchDB.session().roles.indexOf("foo") != -1); + T(CouchDB.session().userCtx.name == "jchris@apache.org"); + T(CouchDB.session().userCtx.roles.indexOf("_admin") == -1); + T(CouchDB.session().userCtx.roles.indexOf("foo") != -1); // now let's make jchris a server admin T(CouchDB.logout().ok); - T(CouchDB.session().roles[0] == "_admin"); - T(CouchDB.session().name == null); + T(CouchDB.session().userCtx.roles[0] == "_admin"); + T(CouchDB.session().userCtx.name == null); // set the -hashed- password so the salt matches // todo ask on the ML about this run_on_modified_server([{section: "admins", key: "jchris@apache.org", value: "funnybone"}], function() { T(CouchDB.login("jchris@apache.org", "funnybone").ok); - T(CouchDB.session().name == "jchris@apache.org"); - T(CouchDB.session().roles.indexOf("_admin") != -1); + T(CouchDB.session().userCtx.name == "jchris@apache.org"); + T(CouchDB.session().userCtx.roles.indexOf("_admin") != -1); // test that jchris still has the foo role - T(CouchDB.session().roles.indexOf("foo") != -1); + T(CouchDB.session().userCtx.roles.indexOf("foo") != -1); // should work even when user doc has no password jchrisUserDoc = usersDb.open(jchrisUserDoc._id); @@ -214,13 +215,13 @@ couchTests.cookie_auth = function(debug) { T(CouchDB.logout().ok); T(CouchDB.login("jchris@apache.org", "funnybone").ok); var s = CouchDB.session(); - T(s.name == "jchris@apache.org"); - T(s.roles.indexOf("_admin") != -1); + T(s.userCtx.name == "jchris@apache.org"); + T(s.userCtx.roles.indexOf("_admin") != -1); // test session info - T(s.info.authenticated == "{couch_httpd_auth, cookie_authentication_handler}"); - T(s.info.user_db == "test_suite_users"); + T(s.info.authenticated == "cookie"); + T(s.info.authentication_db == "test_suite_users"); // test that jchris still has the foo role - T(CouchDB.session().roles.indexOf("foo") != -1); + T(CouchDB.session().userCtx.roles.indexOf("foo") != -1); }); } finally { diff --git a/share/www/script/test/delayed_commits.js b/share/www/script/test/delayed_commits.js index d0c87182..dbb072fb 100644 --- a/share/www/script/test/delayed_commits.js +++ b/share/www/script/test/delayed_commits.js @@ -122,4 +122,33 @@ couchTests.delayed_commits = function(debug) { } }); + + // Test that a conflict can't cause delayed commits to fail + run_on_modified_server( + [{section: "couchdb", + key: "delayed_commits", + value: "true"}], + + function() { + //First save a document and commit it + T(db.save({_id:"6",a:2,b:4}).ok); + T(db.ensureFullCommit().ok); + //Generate a conflict + try { + db.save({_id:"6",a:2,b:4}); + } catch( e) { + T(e.error == "conflict"); + } + //Wait for the delayed commit interval to pass + var time = new Date(); + while(new Date() - time < 2000); + //Save a new doc + T(db.save({_id:"7",a:2,b:4}).ok); + //Wait for the delayed commit interval to pass + var time = new Date(); + while(new Date() - time < 2000); + //Crash the server and make sure the last doc was written + restartServer(); + T(db.open("7") != null); + }); }; diff --git a/share/www/script/test/design_docs.js b/share/www/script/test/design_docs.js index 9318d2bc..a24167b2 100644 --- a/share/www/script/test/design_docs.js +++ b/share/www/script/test/design_docs.js @@ -38,6 +38,13 @@ function() { var designDoc = { _id:"_design/test", // turn off couch.js id escaping? language: "javascript", + whatever : { + stringzone : "exports.string = 'plankton';", + commonjs : { + whynot : "exports.test = require('../stringzone'); exports.foo = require('whatever/stringzone');", + upper : "exports.testing = require('./whynot').test.string.toUpperCase()+module.id+require('./whynot').foo.string" + } + }, views: { all_docs_twice: {map: "function(doc) { emit(doc.integer, null); emit(doc.integer, null) }"}, no_docs: {map: "function(doc) {}"}, @@ -50,9 +57,12 @@ function() { reduce:"function (keys, values) { return \"" + makebigstring(16) + "\"; };"} }, shows: { - simple: "function() {return 'ok'};" + simple: "function() {return 'ok'};", + requirey : "function() { var lib = require('whatever/commonjs/upper'); return lib.testing; };", + circular : "function() { var lib = require('whatever/commonjs/upper'); return JSON.stringify(this); };" } - } + }; + var xhr = CouchDB.request("PUT", "/test_suite_db_a/_design/test", {body: JSON.stringify(designDoc)}); var resp = JSON.parse(xhr.responseText); @@ -74,15 +84,46 @@ function() { T(xhr.status == 200); TEquals("ok", xhr.responseText, 'query server used wrong ddoc'); + // test commonjs require + var xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_show/requirey"); + T(xhr.status == 200); + TEquals("PLANKTONwhatever/commonjs/upperplankton", xhr.responseText); + + var xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_show/circular"); + T(xhr.status == 200); + TEquals("javascript", JSON.parse(xhr.responseText).language); + + var prev_view_sig = db.designInfo("_design/test").view_index.signature; + + db.bulkSave(makeDocs(1, numDocs + 1)); + // test that we get design doc info back var dinfo = db.designInfo("_design/test"); TEquals("test", dinfo.name); var vinfo = dinfo.view_index; TEquals(51, vinfo.disk_size); TEquals(false, vinfo.compact_running); - TEquals("3f88e53b303e2342e49a66c538c30679", vinfo.signature); + // test that GET /db/_design/test/_info + // hasn't triggered an update of the views + TEquals(prev_view_sig, vinfo.signature, 'ddoc sig'); + for (var loop = 0; loop < 2; loop++) { + T(db.view("test/all_docs_twice", {stale: "ok"}).total_rows === 0); + T(db.view("test/single_doc", {stale: "ok"}).total_rows === 0); + T(db.view("test/summate", {stale: "ok"}).rows.length === 0); + T(db.ensureFullCommit().ok); + restartServer(); + }; - db.bulkSave(makeDocs(1, numDocs + 1)); + // test that POST /db/_view_cleanup + // doesn't trigger an update of the views + T(db.viewCleanup().ok); + for (var loop = 0; loop < 2; loop++) { + T(db.view("test/all_docs_twice", {stale: "ok"}).total_rows == 0); + T(db.view("test/single_doc", {stale: "ok"}).total_rows == 0); + T(db.view("test/summate", {stale: "ok"}).rows.length == 0); + T(db.ensureFullCommit().ok); + restartServer(); + }; // test that the _all_docs view returns correctly with keys var results = db.allDocs({startkey:"_design", endkey:"_design0"}); @@ -93,9 +134,9 @@ function() { 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.view("test/no_docs").total_rows == 0); + T(db.view("test/single_doc").total_rows == 1); T(db.ensureFullCommit().ok); restartServer(); }; diff --git a/share/www/script/test/erlang_views.js b/share/www/script/test/erlang_views.js index e9e0363f..7eddab40 100644 --- a/share/www/script/test/erlang_views.js +++ b/share/www/script/test/erlang_views.js @@ -29,8 +29,8 @@ couchTests.erlang_views = function(debug) { T(db.save(doc).ok); var mfun = 'fun({Doc}) -> ' + - ' K = proplists:get_value(<<"integer">>, Doc, null), ' + - ' V = proplists:get_value(<<"string">>, Doc, null), ' + + ' K = couch_util:get_value(<<"integer">>, Doc, null), ' + + ' V = couch_util:get_value(<<"string">>, Doc, null), ' + ' Emit(K, V) ' + 'end.'; @@ -44,7 +44,7 @@ couchTests.erlang_views = function(debug) { // check simple reduction - another doc with same key. var doc = {_id: "2", integer: 1, string: "str2"}; T(db.save(doc).ok); - rfun = "fun(Keys, Values, ReReduce) -> length(Values) end." + rfun = "fun(Keys, Values, ReReduce) -> length(Values) end."; results = db.query(mfun, rfun, null, null, "erlang"); T(results.rows[0].value == 2); @@ -55,9 +55,9 @@ couchTests.erlang_views = function(debug) { shows: { simple: 'fun(Doc, {Req}) -> ' + - ' {Info} = proplists:get_value(<<"info">>, Req, {[]}), ' + - ' Purged = proplists:get_value(<<"purge_seq">>, Info, -1), ' + - ' Verb = proplists:get_value(<<"method">>, Req, <<"not_get">>), ' + + ' {Info} = couch_util:get_value(<<"info">>, Req, {[]}), ' + + ' Purged = couch_util:get_value(<<"purge_seq">>, Info, -1), ' + + ' Verb = couch_util:get_value(<<"method">>, Req, <<"not_get">>), ' + ' R = list_to_binary(io_lib:format("~b - ~s", [Purged, Verb])), ' + ' {[{<<"code">>, 200}, {<<"headers">>, {[]}}, {<<"body">>, R}]} ' + 'end.' @@ -67,7 +67,7 @@ couchTests.erlang_views = function(debug) { 'fun(Head, {Req}) -> ' + ' Send(<<"head">>), ' + ' Fun = fun({Row}, _) -> ' + - ' Val = proplists:get_value(<<"value">>, Row, -1), ' + + ' Val = couch_util:get_value(<<"value">>, Row, -1), ' + ' Send(list_to_binary(integer_to_list(Val))), ' + ' {ok, nil} ' + ' end, ' + @@ -117,10 +117,10 @@ couchTests.erlang_views = function(debug) { T(db.bulkSave(docs).length, 250, "Saved big doc set."); var mfun = 'fun({Doc}) -> ' + - 'Words = proplists:get_value(<<"words">>, Doc), ' + + 'Words = couch_util:get_value(<<"words">>, Doc), ' + 'lists:foreach(fun({Word}) -> ' + - 'WordString = proplists:get_value(<<"word">>, Word), ' + - 'Count = proplists:get_value(<<"count">>, Word), ' + + 'WordString = couch_util:get_value(<<"word">>, Word), ' + + 'Count = couch_util:get_value(<<"count">>, Word), ' + 'Emit(WordString , Count) ' + 'end, Words) ' + 'end.'; diff --git a/share/www/script/test/etags_views.js b/share/www/script/test/etags_views.js index a12734f8..7e1537bd 100644 --- a/share/www/script/test/etags_views.js +++ b/share/www/script/test/etags_views.js @@ -38,7 +38,7 @@ couchTests.etags_views = function(debug) { }) } } - } + }; T(db.save(designDoc).ok); var xhr; var docs = makeDocs(0, 10); diff --git a/share/www/script/test/jsonp.js b/share/www/script/test/jsonp.js index dfd6a0df..6bec63ab 100644 --- a/share/www/script/test/jsonp.js +++ b/share/www/script/test/jsonp.js @@ -32,38 +32,51 @@ couchTests.jsonp = function(debug) { db.deleteDb(); db.createDb(); if (debug) debugger; - + var doc = {_id:"0",a:0,b:0}; T(db.save(doc).ok); + + // callback param is ignored unless jsonp is configured + var xhr = CouchDB.request("GET", "/test_suite_db/0?callback=jsonp_not_configured"); + JSON.parse(xhr.responseText); - // Test unchunked callbacks. - var xhr = CouchDB.request("GET", "/test_suite_db/0?callback=jsonp_no_chunk"); - T(xhr.status == 200); - jsonp_flag = 0; - eval(xhr.responseText); - T(jsonp_flag == 1); - xhr = CouchDB.request("GET", "/test_suite_db/0?callback=foo\""); - T(xhr.status == 400); + run_on_modified_server( + [{section: "httpd", + key: "allow_jsonp", + value: "true"}], + function() { - // Test chunked responses - var doc = {_id:"1",a:1,b:1}; - T(db.save(doc).ok); + // Test unchunked callbacks. + var xhr = CouchDB.request("GET", "/test_suite_db/0?callback=jsonp_no_chunk"); + T(xhr.status == 200); + jsonp_flag = 0; + eval(xhr.responseText); + T(jsonp_flag == 1); + xhr = CouchDB.request("GET", "/test_suite_db/0?callback=foo\""); + T(xhr.status == 400); + + // Test chunked responses + var doc = {_id:"1",a:1,b:1}; + T(db.save(doc).ok); - var designDoc = { - _id:"_design/test", - language: "javascript", - views: { - all_docs: {map: "function(doc) {if(doc.a) emit(null, doc.a);}"} + var designDoc = { + _id:"_design/test", + language: "javascript", + views: { + all_docs: {map: "function(doc) {if(doc.a) emit(null, doc.a);}"} + } } - } - T(db.save(designDoc).ok); + T(db.save(designDoc).ok); + + var url = "/test_suite_db/_design/test/_view/all_docs?callback=jsonp_chunk"; + xhr = CouchDB.request("GET", url); + T(xhr.status == 200); + jsonp_flag = 0; + eval(xhr.responseText); + T(jsonp_flag == 1); + xhr = CouchDB.request("GET", url + "\'"); + T(xhr.status == 400); + }); + - var url = "/test_suite_db/_design/test/_view/all_docs?callback=jsonp_chunk"; - xhr = CouchDB.request("GET", url); - T(xhr.status == 200); - jsonp_flag = 0; - eval(xhr.responseText); - T(jsonp_flag == 1); - xhr = CouchDB.request("GET", url + "\'"); - T(xhr.status == 400); }; diff --git a/share/www/script/test/list_views.js b/share/www/script/test/list_views.js index 54214c2c..44afa899 100644 --- a/share/www/script/test/list_views.js +++ b/share/www/script/test/list_views.js @@ -178,7 +178,7 @@ couchTests.list_views = function(debug) { 'fun(Head, {Req}) -> ' + ' Send(<<"[">>), ' + ' Fun = fun({Row}, Sep) -> ' + - ' Val = proplists:get_value(<<"key">>, Row, 23), ' + + ' Val = couch_util:get_value(<<"key">>, Row, 23), ' + ' Send(list_to_binary(Sep ++ integer_to_list(Val))), ' + ' {ok, ","} ' + ' end, ' + @@ -220,10 +220,13 @@ couchTests.list_views = function(debug) { T(etag1 != etag2, "POST to map _list generates key-depdendent ETags"); // test the richness of the arguments - xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/basicJSON/basicView"); + xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/basicJSON/basicView?update_seq=true"); T(xhr.status == 200, "standard get should be 200"); var resp = JSON.parse(xhr.responseText); - TEquals(resp.head, {total_rows:10, offset:0}); + TEquals(10, resp.head.total_rows); + TEquals(0, resp.head.offset); + TEquals(11, resp.head.update_seq); + T(resp.rows.length == 10); TEquals(resp.rows[0], {"id": "0","key": 0,"value": "0"}); @@ -343,13 +346,21 @@ couchTests.list_views = function(debug) { // T(xhr.getResponseHeader("Content-Type") == "text/plain"); T(xhr.responseText.match(/^head 0 1 2 tail$/) && "basic stop"); - xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/stopIter2/basicView"); + xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/stopIter2/basicView", { + headers : { + "Accept" : "text/html" + } + }); T(xhr.responseText.match(/^head 0 1 2 tail$/) && "stop 2"); // aborting iteration with reduce var xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/stopIter/withReduce?group=true"); T(xhr.responseText.match(/^head 0 1 2 tail$/) && "reduce stop"); - xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/stopIter2/withReduce?group=true"); + xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/stopIter2/withReduce?group=true", { + headers : { + "Accept" : "text/html" + } + }); T(xhr.responseText.match(/^head 0 1 2 tail$/) && "reduce stop 2"); // with accept headers for HTML @@ -383,7 +394,7 @@ couchTests.list_views = function(debug) { T(/LastKey: 0/.test(xhr.responseText)); // Test we do multi-key requests on lists and views in separate docs. - var url = "/test_suite_db/_design/lists/_list/simpleForm/views/basicView" + var url = "/test_suite_db/_design/lists/_list/simpleForm/views/basicView"; xhr = CouchDB.request("POST", url, { body: '{"keys":[-2,-4,-5,-7]}' }); diff --git a/share/www/script/test/method_override.js b/share/www/script/test/method_override.js new file mode 100644 index 00000000..0bb4c61f --- /dev/null +++ b/share/www/script/test/method_override.js @@ -0,0 +1,40 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +// Allow broken HTTP clients to fake a full method vocabulary with an X-HTTP-METHOD-OVERRIDE header +couchTests.method_override = function(debug) { + var result = JSON.parse(CouchDB.request("GET", "/").responseText); + T(result.couchdb == "Welcome"); + + var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); + db.deleteDb(); + + db.createDb(); + + var doc = {bob : "connie"}; + xhr = CouchDB.request("POST", "/test_suite_db/fnord", {body: JSON.stringify(doc), headers:{"X-HTTP-Method-Override" : "PUT"}}); + T(xhr.status == 201); + + doc = db.open("fnord"); + T(doc.bob == "connie"); + + xhr = CouchDB.request("POST", "/test_suite_db/fnord?rev=" + doc._rev, {headers:{"X-HTTP-Method-Override" : "DELETE"}}); + T(xhr.status == 200); + + xhr = CouchDB.request("GET", "/test_suite_db/fnord2", {body: JSON.stringify(doc), headers:{"X-HTTP-Method-Override" : "PUT"}}); + // Method Override is ignored when original Method isn't POST + T(xhr.status == 404); + + doc = db.open("fnord"); + T(doc == null); + +}; diff --git a/share/www/script/test/oauth.js b/share/www/script/test/oauth.js index d55d13e8..b439b4db 100644 --- a/share/www/script/test/oauth.js +++ b/share/www/script/test/oauth.js @@ -94,8 +94,18 @@ couchTests.oauth = function(debug) { headers: {"X-Couch-Persist": "false"}, body: JSON.stringify(testadminPassword) }); - - CouchDB.request("GET", "/_sleep?time=50"); + var i = 0; + waitForSuccess(function() { + //loop until the couch server has processed the password + i += 1; + var xhr = CouchDB.request("GET", "http://" + host + "/_config/admins/testadmin?foo="+i,{ + headers: { + "Authorization": adminBasicAuthHeaderValue() + }}); + if (xhr.responseText.indexOf("\"-hashed-") != 0) { + throw("still waiting"); + } + }, "wait-for-admin"); CouchDB.newUuids(2); // so we have one to make the salt @@ -116,7 +126,7 @@ couchTests.oauth = function(debug) { // Create a user var jasonUserDoc = CouchDB.prepareUserDoc({ - username: "jason", + name: "jason", roles: ["test"] }, "testpassword"); T(usersDb.save(jasonUserDoc).ok); diff --git a/share/www/script/test/proxyauth.js b/share/www/script/test/proxyauth.js new file mode 100644 index 00000000..91e2f221 --- /dev/null +++ b/share/www/script/test/proxyauth.js @@ -0,0 +1,130 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + + + +couchTests.proxyauth = function(debug) { + // this test proxy authentification handler + + var usersDb = new CouchDB("test_suite_users", {"X-Couch-Full-Commit":"false"}); + var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); + + if (debug) debugger; + + // Simple secret key generator + function generateSecret(length) { + var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + var secret = ''; + for (var i=0; i<length; i++) { + secret += tab.charAt(Math.floor(Math.random() * 64)); + } + return secret; + } + + var secret = generateSecret(64); + + function TestFun() { + usersDb.deleteDb(); + usersDb.createDb(); + db.deleteDb(); + db.createDb(); + + var benoitcUserDoc = CouchDB.prepareUserDoc({ + name: "benoitc@apache.org" + }, "test"); + T(usersDb.save(benoitcUserDoc).ok); + + T(CouchDB.session().userCtx.name == null); + + // test that you can use basic auth aginst the users db + var s = CouchDB.session({ + headers : { + "Authorization" : "Basic YmVub2l0Y0BhcGFjaGUub3JnOnRlc3Q=" + } + }); + T(s.userCtx.name == "benoitc@apache.org"); + T(s.info.authenticated == "default"); + + CouchDB.logout(); + + var headers = { + "X-Auth-CouchDB-UserName": "benoitc@apache.org", + "X-Auth-CouchDB-Roles": "test", + "X-Auth-CouchDB-Token": hex_hmac_sha1(secret, "benoitc@apache.org") + }; + + var designDoc = { + _id:"_design/test", + language: "javascript", + + shows: { + "welcome": stringFun(function(doc,req) { + return "Welcome " + req.userCtx["name"]; + }), + "role": stringFun(function(doc, req) { + return req.userCtx['roles'][0]; + }) + } + }; + + db.save(designDoc); + + var req = CouchDB.request("GET", "/test_suite_db/_design/test/_show/welcome", + {headers: headers}); + T(req.responseText == "Welcome benoitc@apache.org"); + + req = CouchDB.request("GET", "/test_suite_db/_design/test/_show/role", + {headers: headers}); + T(req.responseText == "test"); + + var xhr = CouchDB.request("PUT", "/_config/couch_httpd_auth/proxy_use_secret",{ + body : JSON.stringify("true"), + headers: {"X-Couch-Persist": "false"} + }); + T(xhr.status == 200); + + req = CouchDB.request("GET", "/test_suite_db/_design/test/_show/welcome", + {headers: headers}); + T(req.responseText == "Welcome benoitc@apache.org"); + + req = CouchDB.request("GET", "/test_suite_db/_design/test/_show/role", + {headers: headers}); + T(req.responseText == "test"); + + } + + run_on_modified_server( + [{section: "httpd", + key: "authentication_handlers", + value:"{couch_httpd_auth, proxy_authentification_handler}, {couch_httpd_auth, default_authentication_handler}"}, + {section: "couch_httpd_auth", + key: "authentication_db", + value: "test_suite_users"}, + {section: "couch_httpd_auth", + key: "secret", + value: secret}, + {section: "couch_httpd_auth", + key: "x_auth_username", + value: "X-Auth-CouchDB-UserName"}, + {section: "couch_httpd_auth", + key: "x_auth_roles", + value: "X-Auth-CouchDB-Roles"}, + {section: "couch_httpd_auth", + key: "x_auth_token", + value: "X-Auth-CouchDB-Token"}, + {section: "couch_httpd_auth", + key: "proxy_use_secret", + value: "false"}], + TestFun + ); + +}
\ No newline at end of file diff --git a/share/www/script/test/purge.js b/share/www/script/test/purge.js index ca68b363..af72ea4f 100644 --- a/share/www/script/test/purge.js +++ b/share/www/script/test/purge.js @@ -30,7 +30,7 @@ couchTests.purge = function(debug) { 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); @@ -50,17 +50,19 @@ couchTests.purge = function(debug) { // purge the documents var xhr = CouchDB.request("POST", "/test_suite_db/_purge", { - body: JSON.stringify({"1":[doc1._rev], "2":[doc2._rev]}), + body: JSON.stringify({"1":[doc1._rev], "2":[doc2._rev]}) }); T(xhr.status == 200); + var result = JSON.parse(xhr.responseText); 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); + T(result.purge_seq == newInfo.purge_seq); - var result = JSON.parse(xhr.responseText); T(result.purged["1"][0] == doc1._rev); T(result.purged["2"][0] == doc2._rev); @@ -81,16 +83,18 @@ couchTests.purge = function(debug) { var doc4 = db.open("4"); xhr = CouchDB.request("POST", "/test_suite_db/_purge", { - body: JSON.stringify({"3":[doc3._rev]}), + body: JSON.stringify({"3":[doc3._rev]}) }); T(xhr.status == 200); xhr = CouchDB.request("POST", "/test_suite_db/_purge", { - body: JSON.stringify({"4":[doc4._rev]}), + body: JSON.stringify({"4":[doc4._rev]}) }); T(xhr.status == 200); + result = JSON.parse(xhr.responseText); + T(result.purge_seq == db.info().purge_seq); var rows = db.view("test/all_docs_twice").rows; for (var i = 4; i < numDocs; i++) { diff --git a/share/www/script/test/reader_acl.js b/share/www/script/test/reader_acl.js new file mode 100644 index 00000000..cc249ea4 --- /dev/null +++ b/share/www/script/test/reader_acl.js @@ -0,0 +1,198 @@ +// 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.reader_acl = function(debug) { + // this tests read access control + + var usersDb = new CouchDB("test_suite_users", {"X-Couch-Full-Commit":"false"}); + var secretDb = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); + function testFun() { + try { + usersDb.deleteDb(); + usersDb.createDb(); + secretDb.deleteDb(); + secretDb.createDb(); + + // create a user with top-secret-clearance + var jchrisUserDoc = CouchDB.prepareUserDoc({ + name: "jchris@apache.org", + roles : ["top-secret"] + }, "funnybone"); + T(usersDb.save(jchrisUserDoc).ok); + usersDb.ensureFullCommit(); + + T(CouchDB.session().userCtx.name == null); + + // set secret db to be read controlled + T(secretDb.save({_id:"baz",foo:"bar"}).ok); + T(secretDb.open("baz").foo == "bar"); + + T(secretDb.setSecObj({ + "readers" : { + roles : ["super-secret-club"], + names : ["joe","barb"] + } + }).ok); + } finally { + CouchDB.logout(); + } + } + + // split into 2 funs so we can test restart behavior + function testFun2() { + try { + // can't read it as jchris b/c he's missing the needed role + T(CouchDB.login("jchris@apache.org", "funnybone").ok); + T(CouchDB.session().userCtx.name == "jchris@apache.org"); + + try { + secretDb.open("baz"); + T(false && "can't open a doc from a secret db") ; + } catch(e) { + T(true) + } + + CouchDB.logout(); + + // make anyone with the top-secret role an admin + // db admins are automatically readers + T(secretDb.setSecObj({ + "admins" : { + roles : ["top-secret"], + names : [] + }, + "readers" : { + roles : ["super-secret-club"], + names : ["joe","barb"] + } + }).ok); + + + T(CouchDB.login("jchris@apache.org", "funnybone").ok); + + // db admin can read + T(secretDb.open("baz").foo == "bar"); + + // and run temp views + TEquals(secretDb.query(function(doc) { + emit(null, null) + }).total_rows, 1); + + CouchDB.logout(); + T(CouchDB.session().userCtx.roles.indexOf("_admin") != -1); + + // admin now adds the top-secret role to the db's readers + // and removes db-admins + T(secretDb.setSecObj({ + "admins" : { + roles : [], + names : [] + }, + "readers" : { + roles : ["super-secret-club", "top-secret"], + names : ["joe","barb"] + } + }).ok); + + // server _admin can always read + T(secretDb.open("baz").foo == "bar"); + + // and run temp views + TEquals(secretDb.query(function(doc) { + emit(null, null) + }).total_rows, 1); + + T(secretDb.save({ + "_id" : "_design/foo", + views : { + bar : { + map : "function(doc){emit(null, null)}" + } + } + }).ok) + + // now top-secret users can read too + T(CouchDB.login("jchris@apache.org", "funnybone").ok); + T(CouchDB.session().userCtx.roles.indexOf("_admin") == -1); + T(secretDb.open("baz").foo == "bar"); + // readers can query stored views + T(secretDb.view("foo/bar").total_rows == 1); + + // readers can't do temp views + try { + var results = secretDb.query(function(doc) { + emit(null, null); + }); + T(false && "temp view should be admin only"); + } catch (e) { + T(true && "temp view is admin only"); + } + + + CouchDB.logout(); + + // can't set non string reader names or roles + try { + secretDb.setSecObj({ + "readers" : { + roles : ["super-secret-club", {"top-secret":"awesome"}], + names : ["joe","barb"] + } + }) + T(false && "only string roles"); + } catch (e) {} + + try { + secretDb.setSecObj({ + "readers" : { + roles : ["super-secret-club", {"top-secret":"awesome"}], + names : ["joe",22] + } + }); + T(false && "only string names"); + } catch (e) {} + + try { + secretDb.setSecObj({ + "readers" : { + roles : ["super-secret-club", {"top-secret":"awesome"}], + names : "joe" + } + }); + T(false && "only lists of names"); + } catch (e) {} + } finally { + CouchDB.logout(); + } + }; + + run_on_modified_server( + [{section: "httpd", + key: "authentication_handlers", + value: "{couch_httpd_auth, cookie_authentication_handler}, {couch_httpd_auth, default_authentication_handler}"}, + {section: "couch_httpd_auth", + key: "authentication_db", value: "test_suite_users"}], + testFun + ); + + // security changes will always commit synchronously + restartServer(); + + run_on_modified_server( + [{section: "httpd", + key: "authentication_handlers", + value: "{couch_httpd_auth, cookie_authentication_handler}, {couch_httpd_auth, default_authentication_handler}"}, + {section: "couch_httpd_auth", + key: "authentication_db", value: "test_suite_users"}], + testFun2 + ); +} diff --git a/share/www/script/test/recreate_doc.js b/share/www/script/test/recreate_doc.js index a6a64ac0..05843558 100644 --- a/share/www/script/test/recreate_doc.js +++ b/share/www/script/test/recreate_doc.js @@ -51,7 +51,7 @@ couchTests.recreate_doc = function(debug) { data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ=" } } - } + }; try { // same as before, but with binary db.save(binAttDoc); diff --git a/share/www/script/test/reduce.js b/share/www/script/test/reduce.js index 9c80fa7f..979a0292 100644 --- a/share/www/script/test/reduce.js +++ b/share/www/script/test/reduce.js @@ -15,14 +15,15 @@ couchTests.reduce = function(debug) { db.deleteDb(); db.createDb(); if (debug) debugger; - var numDocs = 500 + var numDocs = 500; var docs = makeDocs(1,numDocs + 1); db.bulkSave(docs); var summate = function(N) {return (N+1)*N/2;}; var map = function (doc) { emit(doc.integer, doc.integer); - 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)); @@ -69,7 +70,7 @@ couchTests.reduce = function(debug) { T(db.info().doc_count == ((i - 1) * 10 * 11) + ((j + 1) * 11)); } - map = function (doc) {emit(doc.keys, 1)}; + map = function (doc) { emit(doc.keys, 1); }; reduce = function (keys, values) { return sum(values); }; var results = db.query(map, reduce, {group:true}); @@ -107,7 +108,7 @@ couchTests.reduce = function(debug) { db.createDb(); - var map = function (doc) {emit(doc.val, doc.val)}; + 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; diff --git a/share/www/script/test/reduce_builtin.js b/share/www/script/test/reduce_builtin.js index 7938a0cf..c9d41fa4 100644 --- a/share/www/script/test/reduce_builtin.js +++ b/share/www/script/test/reduce_builtin.js @@ -16,22 +16,37 @@ couchTests.reduce_builtin = function(debug) { db.createDb(); if (debug) debugger; - var numDocs = 500 + var numDocs = 500; var docs = makeDocs(1,numDocs + 1); db.bulkSave(docs); var summate = function(N) {return (N+1)*N/2;}; + var sumsqr = function(N) { + var acc = 0; + for (var i=1; i<=N; ++i) { + acc += i*i; + } + return acc; + }; + // this is the same test as the reduce.js test // only we'll let CouchDB run reduce in Erlang var map = function (doc) { emit(doc.integer, doc.integer); - emit(doc.integer, doc.integer)}; + emit(doc.integer, doc.integer); + }; var result = db.query(map, "_sum"); T(result.rows[0].value == 2*summate(numDocs)); result = db.query(map, "_count"); T(result.rows[0].value == 1000); + result = db.query(map, "_stats"); + T(result.rows[0].value.sum == 2*summate(numDocs)); + T(result.rows[0].value.count == 1000); + T(result.rows[0].value.min == 1); + T(result.rows[0].value.max == 500); + T(result.rows[0].value.sumsqr == 2*sumsqr(numDocs)); result = db.query(map, "_sum", {startkey: 4, endkey: 4}); T(result.rows[0].value == 8); @@ -58,6 +73,26 @@ couchTests.reduce_builtin = function(debug) { T(result.rows[0].value == 2*(summate(numDocs-i) - summate(i-1))); } + // test for trailing characters after builtin functions, desired behaviour + // is to disregard any trailing characters + // I think the behavior should be a prefix test, so that even "_statsorama" + // or "_stats\nare\awesome" should work just as "_stats" does. - JChris + + var trailing = ["\u000a", "orama", "\nare\nawesome", " ", " \n "]; + + for(var i=0; i < trailing.length; i++) { + result = db.query(map, "_sum" + trailing[i]); + T(result.rows[0].value == 2*summate(numDocs)); + result = db.query(map, "_count" + trailing[i]); + T(result.rows[0].value == 1000); + result = db.query(map, "_stats" + trailing[i]); + T(result.rows[0].value.sum == 2*summate(numDocs)); + T(result.rows[0].value.count == 1000); + T(result.rows[0].value.min == 1); + T(result.rows[0].value.max == 500); + T(result.rows[0].value.sumsqr == 2*sumsqr(numDocs)); + } + db.deleteDb(); db.createDb(); @@ -81,7 +116,7 @@ couchTests.reduce_builtin = function(debug) { T(db.info().doc_count == ((i - 1) * 10 * 11) + ((j + 1) * 11)); } - map = function (doc) {emit(doc.keys, 1)}; + map = function (doc) { emit(doc.keys, 1); }; // with emitted values being 1, count should be the same as sum var builtins = ["_sum", "_count"]; diff --git a/share/www/script/test/replication.js b/share/www/script/test/replication.js index 78678937..7cc1f823 100644 --- a/share/www/script/test/replication.js +++ b/share/www/script/test/replication.js @@ -22,14 +22,14 @@ couchTests.replication = function(debug) { 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", {"X-Couch-Full-Commit":"false"}); var dbB = new CouchDB("test_suite_db_b", {"X-Couch-Full-Commit":"false"}); var numDocs = 10; var xhr; for (var testPair = 0; testPair < dbPairs.length; testPair++) { - var A = dbPairs[testPair].source - var B = dbPairs[testPair].target + var A = dbPairs[testPair].source; + var B = dbPairs[testPair].target; dbA.deleteDb(); dbA.createDb(); @@ -41,7 +41,7 @@ couchTests.replication = function(debug) { 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. }; @@ -165,20 +165,20 @@ couchTests.replication = function(debug) { this.afterAB1 = function(dbA, dbB) { var xhr = CouchDB.request("GET", "/test_suite_db_a/bin_doc/foo%2Bbar.txt"); - T(xhr.responseText == "This is a base64 encoded text") + T(xhr.responseText == "This is a base64 encoded text"); xhr = CouchDB.request("GET", "/test_suite_db_b/bin_doc/foo%2Bbar.txt"); - T(xhr.responseText == "This is a base64 encoded text") + T(xhr.responseText == "This is a base64 encoded text"); // and the design-doc xhr = CouchDB.request("GET", "/test_suite_db_a/_design/with_bin/foo%2Bbar.txt"); - T(xhr.responseText == "This is a base64 encoded text") + T(xhr.responseText == "This is a base64 encoded text"); xhr = CouchDB.request("GET", "/test_suite_db_b/_design/with_bin/foo%2Bbar.txt"); - T(xhr.responseText == "This is a base64 encoded text") + T(xhr.responseText == "This is a base64 encoded text"); }; }, @@ -209,8 +209,8 @@ couchTests.replication = function(debug) { var docB = dbB.open("foo", {conflicts: true, deleted_conflicts: true}); // We should have no conflicts this time - T(docA._conflicts === undefined) - T(docB._conflicts === undefined); + T(typeof docA._conflicts === "undefined"); + T(typeof docB._conflicts === "undefined"); // They show up as deleted conflicts instead T(docA._deleted_conflicts[0] == docB._deleted_conflicts[0]); @@ -229,7 +229,7 @@ couchTests.replication = function(debug) { var seqA = result.source_last_seq; T(0 == result.history[0].start_last_seq); - T(result.history[1] === undefined) + T(typeof result.history[1] === "undefined"); for(test in repTests) { if(repTests[test].afterAB1) repTests[test].afterAB1(dbA, dbB); @@ -239,7 +239,7 @@ couchTests.replication = function(debug) { var seqB = result.source_last_seq; T(0 == result.history[0].start_last_seq); - T(result.history[1] === undefined) + T(typeof result.history[1] === "undefined"); for(test in repTests) { if(repTests[test].afterBA1) repTests[test].afterBA1(dbA, dbB); @@ -252,7 +252,7 @@ couchTests.replication = function(debug) { T(seqA < result2.source_last_seq); T(seqA == result2.history[0].start_last_seq); - T(result2.history[1].end_last_seq == seqA) + T(result2.history[1].end_last_seq == seqA); seqA = result2.source_last_seq; @@ -260,11 +260,11 @@ couchTests.replication = function(debug) { if(repTests[test].afterAB2) repTests[test].afterAB2(dbA, dbB); } - result = CouchDB.replicate(B, A) + result = CouchDB.replicate(B, A); T(seqB < result.source_last_seq); T(seqB == result.history[0].start_last_seq); - T(result.history[1].end_last_seq == seqB) + T(result.history[1].end_last_seq == seqB); seqB = result.source_last_seq; @@ -301,4 +301,386 @@ couchTests.replication = function(debug) { }); TEquals("test_suite_db_b", dbB.info().db_name, "Target database should exist"); + + // continuous + var continuousResult = CouchDB.replicate(dbA.name, "test_suite_db_b", { + body: {"continuous": true} + }); + T(continuousResult.ok); + T(continuousResult._local_id); + + var cancelResult = CouchDB.replicate(dbA.name, "test_suite_db_b", { + body: {"cancel": true} + }); + T(cancelResult.ok); + T(continuousResult._local_id == cancelResult._local_id); + + try { + var cancelResult2 = CouchDB.replicate(dbA.name, "test_suite_db_b", { + body: {"cancel": true} + }); + } catch (e) { + T(e.error == "not_found"); + } + // test replication object option doc_ids + + var dbA = new CouchDB("test_suite_rep_docs_db_a", {"X-Couch-Full-Commit":"false"}); + var dbB = new CouchDB("test_suite_rep_docs_db_b", {"X-Couch-Full-Commit":"false"}); + + dbA.deleteDb(); + dbA.createDb(); + + var all_docs = [ + { + _id: "foo1", + value: "a" + }, + { + _id: "foo2", + value: "b" + }, + { + _id: "foo3", + value: "c" + }, + { + _id: "slashed/foo", + value: "s" + }, + { + _id: "_design/foobar", + language: "javascript", + value: "I am a design doc", + filters: { + idfilter: (function(doc, req) { + return doc.value == Number(req.filter_value); + }).toString() + }, + views: { + countview: (function(doc) { + emit(doc.value, 1); + }).toString() + } + } + ]; + + for (var i = 0; i < all_docs.length; i++) { + T(dbA.save(all_docs[i]).ok); + } + + var dbPairs = [ + {source:"test_suite_rep_docs_db_a", + target:"test_suite_rep_docs_db_b"}, + {source:"test_suite_rep_docs_db_a", + target:"http://" + host + "/test_suite_rep_docs_db_b"}, + {source:"http://" + host + "/test_suite_rep_docs_db_a", + target:"test_suite_rep_docs_db_b"}, + {source:"http://" + host + "/test_suite_rep_docs_db_a", + target:"http://" + host + "/test_suite_rep_docs_db_b"} + ]; + + var target_doc_ids = [ + ["foo1", "foo3", "foo666"], + ["foo1", "foo666"], + ["foo666", "foo2"], + ["foo2", "foo9999", "foo1"], + ["foo3", "slashed/foo"], + ["foo3", "slashed%2Ffoo"], + ["foo1", "_design/foobar"], + ["foo1", "foo1001", "_design%2Ffoobar"] + ]; + + for (var i = 0; i < dbPairs.length; i++) { + var src_db = dbPairs[i].source; + var tgt_db = dbPairs[i].target; + + for (var j = 0; j < target_doc_ids.length; j++) { + var doc_ids = target_doc_ids[j]; + var valid_doc_ids = []; + var invalid_doc_ids = []; + + $.each(doc_ids, function(index, id) { + var found = false; + + for (var k = 0; k < all_docs.length; k++) { + var doc = all_docs[k]; + + if (id === doc._id) { + found = true; + break; + } + } + + if (found) { + valid_doc_ids.push(id); + } else { + invalid_doc_ids.push(id); + } + }); + + dbB.deleteDb(); + dbB.createDb(); + + var repResult = CouchDB.replicate(src_db, tgt_db, { + body: {"doc_ids": doc_ids} + }); + + T(repResult.ok); + T(repResult.docs_written === valid_doc_ids.length); + T(repResult.docs_read === valid_doc_ids.length); + T(repResult.doc_write_failures === 0); + + for (var k = 0; k < all_docs.length; k++) { + var doc = all_docs[k]; + var tgt_doc = dbB.open(doc._id); + + if ($.inArray(doc._id, doc_ids) >= 0) { + T(tgt_doc !== null); + T(tgt_doc.value === doc.value); + } else { + T(tgt_doc === null); + } + } + + for (var k = 0; k < invalid_doc_ids.length; k++) { + var tgt_doc = dbB.open(invalid_doc_ids[k]); + + T(tgt_doc === null); + } + } + } + + // test filtered replication + + var sourceDb = new CouchDB( + "test_suite_filtered_rep_db_a", {"X-Couch-Full-Commit":"false"} + ); + + sourceDb.deleteDb(); + sourceDb.createDb(); + + T(sourceDb.save({_id:"foo1",value:1}).ok); + T(sourceDb.save({_id:"foo2",value:2}).ok); + T(sourceDb.save({_id:"foo3",value:3}).ok); + T(sourceDb.save({_id:"foo4",value:4}).ok); + T(sourceDb.save({ + "_id": "_design/mydesign", + "language" : "javascript", + "filters" : { + "myfilter" : (function(doc, req) { + if (doc.value < Number(req.query.maxvalue)) { + return true; + } else { + return false; + } + }).toString() + } + }).ok); + + var dbPairs = [ + {source:"test_suite_filtered_rep_db_a", + target:"test_suite_filtered_rep_db_b"}, + {source:"test_suite_filtered_rep_db_a", + target:"http://" + host + "/test_suite_filtered_rep_db_b"}, + {source:"http://" + host + "/test_suite_filtered_rep_db_a", + target:"test_suite_filtered_rep_db_b"}, + {source:"http://" + host + "/test_suite_filtered_rep_db_a", + target:"http://" + host + "/test_suite_filtered_rep_db_b"} + ]; + + for (var i = 0; i < dbPairs.length; i++) { + var targetDb = new CouchDB("test_suite_filtered_rep_db_b"); + targetDb.deleteDb(); + targetDb.createDb(); + + var dbA = dbPairs[i].source; + var dbB = dbPairs[i].target; + + var repResult = CouchDB.replicate(dbA, dbB, { + body: { + "filter" : "mydesign/myfilter", + "query_params" : { + "maxvalue": "3" + } + } + }); + + T(repResult.ok); + T($.isArray(repResult.history)); + T(repResult.history.length === 1); + T(repResult.history[0].docs_written === 2); + T(repResult.history[0].docs_read === 2); + T(repResult.history[0].doc_write_failures === 0); + + var docFoo1 = targetDb.open("foo1"); + T(docFoo1 !== null); + T(docFoo1.value === 1); + + var docFoo2 = targetDb.open("foo2"); + T(docFoo2 !== null); + T(docFoo2.value === 2); + + var docFoo3 = targetDb.open("foo3"); + T(docFoo3 === null); + + var docFoo4 = targetDb.open("foo4"); + T(docFoo4 === null); + } + + // test for COUCHDB-868 - design docs' attachments not getting replicated + // when doing a pull replication with HTTP basic auth + dbA = new CouchDB("test_suite_db_a"); + dbB = new CouchDB("test_suite_db_b"); + var usersDb = new CouchDB("test_suite_auth"); + var lorem = CouchDB.request( + "GET", "/_utils/script/test/lorem.txt").responseText; + var lorem_b64 = CouchDB.request( + "GET", "/_utils/script/test/lorem_b64.txt").responseText; + + usersDb.deleteDb(); + usersDb.createDb(); + dbA.deleteDb(); + dbA.createDb(); + dbB.deleteDb(); + dbB.createDb(); + + var atts_ddoc = { + _id: "_design/i_have_atts", + language: "javascript" + }; + T(dbA.save(atts_ddoc).ok); + + var rev = atts_ddoc._rev; + var att_1_name = "lorem.txt"; + var att_2_name = "lorem.dat"; + var xhr = CouchDB.request( + "PUT", "/" + dbA.name + "/" + atts_ddoc._id + "/" + att_1_name + "?rev=" + rev, { + headers: {"Content-Type": "text/plain;charset=utf-8"}, + body: lorem + }); + rev = JSON.parse(xhr.responseText).rev; + T(xhr.status === 201); + xhr = CouchDB.request( + "PUT", "/" + dbA.name + "/" + atts_ddoc._id + "/" + att_2_name + "?rev=" + rev, { + headers: {"Content-Type": "application/data"}, + body: lorem_b64 + }); + T(xhr.status === 201); + + var fdmananaUserDoc = CouchDB.prepareUserDoc({ + name: "fdmanana", + roles: ["reader"] + }, "qwerty"); + T(usersDb.save(fdmananaUserDoc).ok); + + T(dbA.setSecObj({ + admins: { + names: [], + roles: ["admin"] + }, + readers: { + names: [], + roles: ["reader"] + } + }).ok); + T(dbB.setSecObj({ + admins: { + names: ["fdmanana"], + roles: [] + } + }).ok); + + var server_config = [ + { + section: "couch_httpd_auth", + key: "authentication_db", + value: usersDb.name + }, + // to prevent admin party mode + { + section: "admins", + key: "joe", + value: "erlang" + } + ]; + + var test_fun = function() { + T(CouchDB.login("fdmanana", "qwerty").ok); + T(CouchDB.session().userCtx.name === "fdmanana"); + T(CouchDB.session().userCtx.roles.indexOf("_admin") === -1); + + var repResult = CouchDB.replicate( + "http://fdmanana:qwerty@" + host + "/" + dbA.name, + dbB.name + ); + T(repResult.ok === true); + T(repResult.history instanceof Array); + T(repResult.history.length === 1); + T(repResult.history[0].docs_written === 1); + T(repResult.history[0].docs_read === 1); + T(repResult.history[0].doc_write_failures === 0); + + var atts_ddoc_copy = dbB.open(atts_ddoc._id); + T(atts_ddoc_copy !== null); + T(typeof atts_ddoc_copy._attachments === "object"); + T(atts_ddoc_copy._attachments !== null); + T(att_1_name in atts_ddoc_copy._attachments); + T(att_2_name in atts_ddoc_copy._attachments); + + var xhr = CouchDB.request("GET", "/" + dbB.name + "/" + atts_ddoc._id + "/" + att_1_name); + T(xhr.status === 200); + T(xhr.responseText === lorem); + + xhr = CouchDB.request("GET", "/" + dbB.name + "/" + atts_ddoc._id + "/" + att_2_name); + T(xhr.status === 200); + T(xhr.responseText === lorem_b64); + + CouchDB.logout(); + T(CouchDB.login("joe", "erlang").ok); + T(dbA.setSecObj({ + admins: { + names: [], + roles: ["bar"] + }, + readers: { + names: [], + roles: ["foo"] + } + }).ok); + T(dbB.deleteDb().ok === true); + T(dbB.createDb().ok === true); + T(dbB.setSecObj({ + admins: { + names: ["fdmanana"], + roles: [] + } + }).ok); + CouchDB.logout(); + + T(CouchDB.login("fdmanana", "qwerty").ok); + T(CouchDB.session().userCtx.name === "fdmanana"); + T(CouchDB.session().userCtx.roles.indexOf("_admin") === -1); + try { + repResult = CouchDB.replicate( + "http://fdmanana:qwerty@" + host + "/" + dbA.name, + dbB.name + ); + T(false, "replication should have failed"); + } catch(x) { + T(x.error === "unauthorized"); + } + + atts_ddoc_copy = dbB.open(atts_ddoc._id); + T(atts_ddoc_copy === null); + + CouchDB.logout(); + T(CouchDB.login("joe", "erlang").ok); + }; + + run_on_modified_server(server_config, test_fun); + + // cleanup + dbA.deleteDb(); + dbB.deleteDb(); + usersDb.deleteDb(); }; diff --git a/share/www/script/test/rewrite.js b/share/www/script/test/rewrite.js new file mode 100644 index 00000000..ff2d3822 --- /dev/null +++ b/share/www/script/test/rewrite.js @@ -0,0 +1,380 @@ +// 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.rewrite = function(debug) { + // this test _rewrite handler + + + var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); + db.deleteDb(); + db.createDb(); + + + if (debug) debugger; + run_on_modified_server( + [{section: "httpd", + key: "authentication_handlers", + value: "{couch_httpd_auth, special_test_authentication_handler}"}, + {section:"httpd", + key: "WWW-Authenticate", + value: "X-Couch-Test-Auth"}], + + function(){ + var designDoc = { + _id:"_design/test", + language: "javascript", + _attachments:{ + "foo.txt": { + content_type:"text/plain", + data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ=" + } + }, + rewrites: [ + { + "from": "foo", + "to": "foo.txt" + }, + { + "from": "foo2", + "to": "foo.txt", + "method": "GET" + }, + { + "from": "hello/:id", + "to": "_update/hello/:id", + "method": "PUT" + }, + { + "from": "/welcome", + "to": "_show/welcome" + }, + { + "from": "/welcome/:name", + "to": "_show/welcome", + "query": { + "name": ":name" + } + }, + { + "from": "/welcome2", + "to": "_show/welcome", + "query": { + "name": "user" + } + }, + { + "from": "/welcome3/:name", + "to": "_update/welcome2/:name", + "method": "PUT" + }, + { + "from": "/welcome3/:name", + "to": "_show/welcome2/:name", + "method": "GET" + }, + { + "from": "simpleForm/basicView", + "to": "_list/simpleForm/basicView", + }, + { + "from": "simpleForm/basicViewFixed", + "to": "_list/simpleForm/basicView", + "query": { + "startkey": 3, + "endkey": 8 + } + }, + { + "from": "simpleForm/basicViewPath/:start/:end", + "to": "_list/simpleForm/basicView", + "query": { + "startkey": ":start", + "endkey": ":end" + } + }, + { + "from": "simpleForm/complexView", + "to": "_list/simpleForm/complexView", + "query": { + "key": [1, 2] + } + }, + { + "from": "simpleForm/complexView2", + "to": "_list/simpleForm/complexView", + "query": { + "key": ["test", {}] + } + }, + { + "from": "simpleForm/complexView3", + "to": "_list/simpleForm/complexView", + "query": { + "key": ["test", ["test", "essai"]] + } + }, + { + "from": "simpleForm/complexView4", + "to": "_list/simpleForm/complexView2", + "query": { + "key": {"c": 1} + } + }, + { + "from": "simpleForm/complexView5/:a/:b", + "to": "_list/simpleForm/complexView3", + "query": { + "key": [":a", ":b"] + } + }, + { + "from": "simpleForm/complexView6", + "to": "_list/simpleForm/complexView3", + "query": { + "key": [":a", ":b"] + } + } + ], + lists: { + simpleForm: stringFun(function(head, req) { + log("simpleForm"); + send('<ul>'); + var row, row_number = 0, prevKey, firstKey = null; + while (row = getRow()) { + row_number += 1; + if (!firstKey) firstKey = row.key; + prevKey = row.key; + send('\n<li>Key: '+row.key + +' Value: '+row.value + +' LineNo: '+row_number+'</li>'); + } + return '</ul><p>FirstKey: '+ firstKey + ' LastKey: '+ prevKey+'</p>'; + }), + }, + shows: { + "welcome": stringFun(function(doc,req) { + return "Welcome " + req.query["name"]; + }), + "welcome2": stringFun(function(doc, req) { + return "Welcome " + doc.name; + }), + }, + updates: { + "hello" : stringFun(function(doc, req) { + if (!doc) { + if (req.id) { + return [{ + _id : req.id + }, "New World"] + } + return [null, "Empty World"]; + } + doc.world = "hello"; + doc.edited_by = req.userCtx; + return [doc, "hello doc"]; + }), + "welcome2": stringFun(function(doc, req) { + if (!doc) { + if (req.id) { + return [{ + _id: req.id, + name: req.id + }, "New World"] + } + return [null, "Empty World"]; + } + return [doc, "hello doc"]; + }) + }, + views : { + basicView : { + map : stringFun(function(doc) { + if (doc.integer) { + emit(doc.integer, doc.string); + } + + }) + }, + complexView: { + map: stringFun(function(doc) { + if (doc.type == "complex") { + emit([doc.a, doc.b], doc.string); + } + }) + }, + complexView2: { + map: stringFun(function(doc) { + if (doc.type == "complex") { + emit(doc.a, doc.string); + } + }) + }, + complexView3: { + map: stringFun(function(doc) { + if (doc.type == "complex") { + emit(doc.b, doc.string); + } + }) + } + } + } + + db.save(designDoc); + + var docs = makeDocs(0, 10); + db.bulkSave(docs); + + var docs2 = [ + {"a": 1, "b": 1, "string": "doc 1", "type": "complex"}, + {"a": 1, "b": 2, "string": "doc 2", "type": "complex"}, + {"a": "test", "b": {}, "string": "doc 3", "type": "complex"}, + {"a": "test", "b": ["test", "essai"], "string": "doc 4", "type": "complex"}, + {"a": {"c": 1}, "b": "", "string": "doc 5", "type": "complex"} + ]; + + db.bulkSave(docs2); + + // test simple rewriting + + req = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/foo"); + T(req.responseText == "This is a base64 encoded text"); + T(req.getResponseHeader("Content-Type") == "text/plain"); + + req = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/foo2"); + T(req.responseText == "This is a base64 encoded text"); + T(req.getResponseHeader("Content-Type") == "text/plain"); + + + // test POST + // hello update world + + var doc = {"word":"plankton", "name":"Rusty"} + var resp = db.save(doc); + T(resp.ok); + var docid = resp.id; + + xhr = CouchDB.request("PUT", "/test_suite_db/_design/test/_rewrite/hello/"+docid); + T(xhr.status == 201); + T(xhr.responseText == "hello doc"); + T(/charset=utf-8/.test(xhr.getResponseHeader("Content-Type"))) + + doc = db.open(docid); + T(doc.world == "hello"); + + req = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/welcome?name=user"); + T(req.responseText == "Welcome user"); + + req = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/welcome/user"); + T(req.responseText == "Welcome user"); + + req = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/welcome2"); + T(req.responseText == "Welcome user"); + + xhr = CouchDB.request("PUT", "/test_suite_db/_design/test/_rewrite/welcome3/test"); + T(xhr.status == 201); + T(xhr.responseText == "New World"); + T(/charset=utf-8/.test(xhr.getResponseHeader("Content-Type"))); + + xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/welcome3/test"); + T(xhr.responseText == "Welcome test"); + + + // get with query params + xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/simpleForm/basicView?startkey=3&endkey=8"); + T(xhr.status == 200, "with query params"); + T(!(/Key: 1/.test(xhr.responseText))); + T(/FirstKey: 3/.test(xhr.responseText)); + T(/LastKey: 8/.test(xhr.responseText)); + + xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/simpleForm/basicViewFixed"); + T(xhr.status == 200, "with query params"); + T(!(/Key: 1/.test(xhr.responseText))); + T(/FirstKey: 3/.test(xhr.responseText)); + T(/LastKey: 8/.test(xhr.responseText)); + + // get with query params + xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/simpleForm/basicViewFixed?startkey=4"); + T(xhr.status == 200, "with query params"); + T(!(/Key: 1/.test(xhr.responseText))); + T(/FirstKey: 3/.test(xhr.responseText)); + T(/LastKey: 8/.test(xhr.responseText)); + + // get with query params + xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/simpleForm/basicViewPath/3/8"); + T(xhr.status == 200, "with query params"); + T(!(/Key: 1/.test(xhr.responseText))); + T(/FirstKey: 3/.test(xhr.responseText)); + T(/LastKey: 8/.test(xhr.responseText)); + + // get with query params + xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/simpleForm/complexView"); + T(xhr.status == 200, "with query params"); + T(/FirstKey: [1, 2]/.test(xhr.responseText)); + + xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/simpleForm/complexView2"); + T(xhr.status == 200, "with query params"); + T(/Value: doc 3/.test(xhr.responseText)); + + xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/simpleForm/complexView3"); + T(xhr.status == 200, "with query params"); + T(/Value: doc 4/.test(xhr.responseText)); + + xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/simpleForm/complexView4"); + T(xhr.status == 200, "with query params"); + T(/Value: doc 5/.test(xhr.responseText)); + + xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/simpleForm/complexView5/test/essai"); + T(xhr.status == 200, "with query params"); + T(/Value: doc 4/.test(xhr.responseText)); + + xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/simpleForm/complexView6?a=test&b=essai"); + T(xhr.status == 200, "with query params"); + T(/Value: doc 4/.test(xhr.responseText)); + + // test path relative to server + designDoc.rewrites.push({ + "from": "uuids", + "to": "../../../_uuids" + }); + T(db.save(designDoc).ok); + + var xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/uuids"); + T(xhr.status == 500); + var result = JSON.parse(xhr.responseText); + T(result.error == "insecure_rewrite_rule"); + + run_on_modified_server( + [{section: "httpd", + key: "secure_rewrites", + value: "false"}], + function() { + var xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/uuids?cache=bust"); + T(xhr.status == 200); + var result = JSON.parse(xhr.responseText); + T(result.uuids.length == 1); + var first = result.uuids[0]; + }); + }); + + // test invalid rewrites + // string + var ddoc = { + _id: "_design/invalid", + rewrites: "[{\"from\":\"foo\",\"to\":\"bar\"}]" + } + db.save(ddoc); + var res = CouchDB.request("GET", "/test_suite_db/_design/invalid/_rewrite/foo"); + TEquals(400, res.status, "should return 400"); + +} diff --git a/share/www/script/test/security_validation.js b/share/www/script/test/security_validation.js index d07195e1..dd3b202e 100644 --- a/share/www/script/test/security_validation.js +++ b/share/www/script/test/security_validation.js @@ -13,7 +13,7 @@ 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 + // specifically for this testing. It is a WWW-Authenticate scheme named // X-Couch-Test-Auth, and the user names and passwords are hard coded // on the server-side. // @@ -21,7 +21,7 @@ couchTests.security_validation = function(debug) { // 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 + // 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. @@ -69,7 +69,13 @@ couchTests.security_validation = function(debug) { var designDoc = { _id:"_design/test", language: "javascript", - validate_doc_update: "(" + (function (newDoc, oldDoc, userCtx) { + validate_doc_update: "(" + (function (newDoc, oldDoc, userCtx, secObj) { + if (secObj.admin_override) { + if (userCtx.roles.indexOf('_admin') != -1) { + // user is admin, they can do anything + return true; + } + } // docs should have an author field. if (!newDoc._deleted && !newDoc.author) { throw {forbidden: @@ -99,13 +105,27 @@ couchTests.security_validation = function(debug) { } // set user as the admin - T(db.setDbProperty("_admins", ["Damien Katz"]).ok); + T(db.setSecObj({ + admins : {names : ["Damien Katz"]} + }).ok); T(userDb.save(designDoc).ok); - // test the _whoami endpoint + var user2Db = new CouchDB("test_suite_db", + {"WWW-Authenticate": "X-Couch-Test-Auth Jan Lehnardt:apple"} + ); + // Attempt to save the design as a non-admin (in replication scenario) + try { + user2Db.save(designDoc, {new_edits : false}); + T(false && "Can't get here. Should have thrown an error on design doc"); + } catch (e) { + T(e.error == "unauthorized"); + T(user2Db.last_req.status == 401); + } + + // test the _session API var resp = userDb.request("GET", "/_session"); - var user = JSON.parse(resp.responseText) + var user = JSON.parse(resp.responseText).userCtx; T(user.name == "Damien Katz"); // test that the roles are listed properly TEquals(user.roles, []); @@ -116,20 +136,23 @@ couchTests.security_validation = function(debug) { 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); + // Save a document that's missing an author field (before and after compaction) + for (var i=0; i<2; i++) { + 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); + } + // compact. + T(db.compact().ok); + T(db.last_req.status == 202); + // compaction isn't instantaneous, loop until done + while (db.info().compact_running) {}; } // 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 { @@ -158,6 +181,37 @@ couchTests.security_validation = function(debug) { T(e.error == "unauthorized"); T(userDb.last_req.status == 401); } + + // admin must save with author field unless admin override + var resp = db.request("GET", "/_session"); + var user = JSON.parse(resp.responseText).userCtx; + T(user.name == null); + // test that we are admin + TEquals(user.roles, ["_admin"]); + + // can't save the doc even though we are admin + var doc = db.open("testdoc"); + doc.foo=3; + try { + db.save(doc); + T(false && "Can't get here. Should have thrown an error 3"); + } catch (e) { + T(e.error == "unauthorized"); + T(db.last_req.status == 401); + } + + // now turn on admin override + T(db.setDbProperty("_security", {admin_override : true}).ok); + T(db.save(doc).ok); + + // try to do something lame + try { + db.setDbProperty("_security", ["foo"]); + T(false && "can't do this"); + } catch(e) {} + + // go back to normal + T(db.setDbProperty("_security", {admin_override : false}).ok); // Now delete document T(user2Db.deleteDoc(doc).ok); @@ -188,7 +242,6 @@ couchTests.security_validation = function(debug) { T(db.open("booboo") == null); T(db.open("foofoo") == null); - // Now test replication var AuthHeaders = {"WWW-Authenticate": "X-Couch-Test-Auth Christopher Lenz:dog food"}; var host = CouchDB.host; diff --git a/share/www/script/test/show_documents.js b/share/www/script/test/show_documents.js index f484e029..e06bcadc 100644 --- a/share/www/script/test/show_documents.js +++ b/share/www/script/test/show_documents.js @@ -25,7 +25,11 @@ couchTests.show_documents = function(debug) { if (doc) { return "Hello World"; } else { - return "Empty World"; + if(req.id) { + return "New World"; + } else { + return "Empty World"; + } } }), "just-name" : stringFun(function(doc, req) { @@ -50,18 +54,28 @@ couchTests.show_documents = function(debug) { json : req } }), + "show-deleted" : stringFun(function(doc, req) { + if(doc) { + return doc._id; + } else { + return "No doc " + req.id; + } + }), "render-error" : stringFun(function(doc, req) { return noSuchVariable; }), "empty" : stringFun(function(doc, req) { return ""; }), + "fail" : stringFun(function(doc, req) { + return doc._id; + }), "xml-type" : stringFun(function(doc, req) { return { "headers" : { "Content-Type" : "application/xml" }, - "body" : new XML('<xml><node foo="bar"/></xml>') + "body" : new XML('<xml><node foo="bar"/></xml>').toXMLString() } }), "no-set-etag" : stringFun(function(doc, req) { @@ -72,6 +86,25 @@ couchTests.show_documents = function(debug) { "body" : "something" } }), + "list-api" : stringFun(function(doc, req) { + start({"X-Couch-Test-Header": "Yeah"}); + send("Hey"); + }), + "list-api-mix" : stringFun(function(doc, req) { + start({"X-Couch-Test-Header": "Yeah"}); + send("Hey "); + return "Dude"; + }), + "list-api-mix-with-header" : stringFun(function(doc, req) { + start({"X-Couch-Test-Header": "Yeah"}); + send("Hey "); + return { + headers: { + "X-Couch-Test-Header-Awesome": "Oh Yeah!" + }, + body: "Dude" + }; + }), "accept-switch" : stringFun(function(doc, req) { if (req.headers["Accept"].match(/image/)) { return { @@ -114,12 +147,16 @@ couchTests.show_documents = function(debug) { // E4X outside of a string. Outside of tests you // can just use E4X literals. eval('xml.node.@foo = doc.word'); - return xml; + log('xml: '+xml.toSource()); + return xml.toXMLString(); }); provides("foo", function() { return "foofoo"; }); + }), + "withSlash": stringFun(function(doc, req) { + return { json: doc } }) } }; @@ -157,7 +194,7 @@ couchTests.show_documents = function(debug) { T(xhr.responseText == ""); // // hello template world (non-existing docid) - xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/hello/nonExistingDoc"); + xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/fail/nonExistingDoc"); T(xhr.status == 404); var resp = JSON.parse(xhr.responseText); T(resp.error == "not_found"); @@ -169,8 +206,7 @@ couchTests.show_documents = function(debug) { // show with missing doc xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/just-name/missingdoc"); T(xhr.status == 404); - var resp = JSON.parse(xhr.responseText); - T(resp.error == "not_found"); + TEquals("No such doc", xhr.responseText); // show with missing func xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/missing/"+docid); @@ -342,4 +378,37 @@ couchTests.show_documents = function(debug) { xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/json/foo"); TEquals(1, JSON.parse(xhr.responseText)._conflicts.length); + var doc3 = {_id:"a/b/c", a:1}; + db.save(doc3); + xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/withSlash/a/b/c"); + T(xhr.status == 200); + + // hello template world (non-existing docid) + xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/hello/nonExistingDoc"); + T(xhr.responseText == "New World"); + + // test list() compatible API + xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/list-api/foo"); + T(xhr.responseText == "Hey"); + TEquals("Yeah", xhr.getResponseHeader("X-Couch-Test-Header"), "header should be cool"); + + xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/list-api-mix/foo"); + T(xhr.responseText == "Hey Dude"); + TEquals("Yeah", xhr.getResponseHeader("X-Couch-Test-Header"), "header should be cool"); + + xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/list-api-mix-with-header/foo"); + T(xhr.responseText == "Hey Dude"); + TEquals("Yeah", xhr.getResponseHeader("X-Couch-Test-Header"), "header should be cool"); + TEquals("Oh Yeah!", xhr.getResponseHeader("X-Couch-Test-Header-Awesome"), "header should be cool"); + + // test deleted docs + var doc = {_id:"testdoc",foo:1}; + db.save(doc); + var xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/show-deleted/testdoc"); + TEquals("testdoc", xhr.responseText, "should return 'testdoc'"); + + db.deleteDoc(doc); + var xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/show-deleted/testdoc"); + TEquals("No doc testdoc", xhr.responseText, "should return 'no doc testdoc'"); + }; diff --git a/share/www/script/test/stats.js b/share/www/script/test/stats.js index 793b390d..6fb0fbba 100644 --- a/share/www/script/test/stats.js +++ b/share/www/script/test/stats.js @@ -30,7 +30,7 @@ couchTests.stats = function(debug) { _id:"_design/test", // turn off couch.js id escaping? language: "javascript", views: { - all_docs: {map: "function(doc) {emit(doc.integer, null);}"}, + all_docs: {map: "function(doc) {emit(doc.integer, null);}"} } }; db.save(designDoc); @@ -81,19 +81,17 @@ couchTests.stats = function(debug) { var pre_dbs = getStat("couchdb", "open_databases").current || 0; var pre_files = getStat("couchdb", "open_os_files").current || 0; - // We have to make sure that as we open the max'th database - // that we've waited for more than 1 second since opening - // the first database so that any delayed commits will be - // flushed. var triggered = false; var db = null; for(var i = 0; i < max*2; i++) { - try { - db = newDb("test_suite_db_" + i, true); - } catch(e) { - triggered = true; - CouchDB.request("GET", "/_sleep?time=1500"); - db = newDb("test_suite_db_" + i, true); + while (true) { + try { + db = newDb("test_suite_db_" + i, true); + break; + } catch(e) { + // all_dbs_active error! + triggered = true; + } } // Trigger a delayed commit @@ -162,12 +160,15 @@ couchTests.stats = function(debug) { runTest("couchdb", "database_writes", { run: function(db) { - CouchDB.request("POST", "/test_suite_db", {body: '{"a": "1"}'}) + CouchDB.request("POST", "/test_suite_db", { + headers: {"Content-Type": "application/json"}, + body: '{"a": "1"}' + }); }, test: function(before, after) { TEquals(before+1, after, "POST'ing new docs increments doc writes."); } - }) + }); runTest("couchdb", "database_writes", { setup: function(db) {db.save({"_id": "test"});}, @@ -246,7 +247,7 @@ couchTests.stats = function(debug) { }); runTest("httpd", "temporary_view_reads", { - run: function(db) {db.query(function(doc) {emit(doc._id)})}, + run: function(db) { db.query(function(doc) { emit(doc._id); }); }, test: function(before, after) { TEquals(before+1, after, "Temporary views have their own counter."); } @@ -260,7 +261,7 @@ couchTests.stats = function(debug) { }); runTest("httpd", "view_reads", { - run: function(db) {db.query(function(doc) {emit(doc._id)});}, + run: function(db) { db.query(function(doc) { emit(doc._id); }); }, test: function(before, after) { TEquals(before, after, "Temporary views don't affect permanent views."); } diff --git a/share/www/script/test/update_documents.js b/share/www/script/test/update_documents.js index 87fc7352..da68d621 100644 --- a/share/www/script/test/update_documents.js +++ b/share/www/script/test/update_documents.js @@ -68,10 +68,13 @@ couchTests.update_documents = function(debug) { "headers" : { "Content-Type" : "application/xml" }, - "body" : xml + "body" : xml.toXMLString() }; return [doc, resp]; + }), + "get-uuid" : stringFun(function(doc, req) { + return [null, req.uuid]; }) } }; @@ -123,7 +126,7 @@ couchTests.update_documents = function(debug) { // bump counter xhr = CouchDB.request("PUT", "/test_suite_db/_design/update/_update/bump-counter/"+docid, { - headers : {"X-Couch-Full-Commit":"false"} + headers : {"X-Couch-Full-Commit":"true"} }); T(xhr.status == 201); T(xhr.responseText == "<h1>bumped it!</h1>"); @@ -135,12 +138,16 @@ couchTests.update_documents = function(debug) { headers : {"X-Couch-Full-Commit":"true"} }); + var NewRev = xhr.getResponseHeader("X-Couch-Update-NewRev"); doc = db.open(docid); + T(doc['_rev'] == NewRev); + + T(doc.counter == 2); // parse xml xhr = CouchDB.request("PUT", "/test_suite_db/_design/update/_update/xml/"+docid, { - headers : {"X-Couch-Full-Commit":"false"}, + headers : {"X-Couch-Full-Commit":"true"}, "body" : '<xml><foo>bar</foo></xml>' }); T(xhr.status == 201); @@ -148,5 +155,10 @@ couchTests.update_documents = function(debug) { doc = db.open(docid); T(doc.via_xml == "bar"); + + // Server provides UUID when POSTing without an ID in the URL + xhr = CouchDB.request("POST", "/test_suite_db/_design/update/_update/get-uuid/"); + T(xhr.status == 200); + T(xhr.responseText.length == 32); }; diff --git a/share/www/script/test/users_db.js b/share/www/script/test/users_db.js index 2cf63fcf..1e13e5d7 100644 --- a/share/www/script/test/users_db.js +++ b/share/www/script/test/users_db.js @@ -24,44 +24,101 @@ couchTests.users_db = function(debug) { // to determine the actual users db name. function testFun() { - usersDb.deleteDb(); - // test that the validation function is installed var ddoc = usersDb.open("_design/_auth"); T(ddoc.validate_doc_update); // test that you can login as a user using basic auth var jchrisUserDoc = CouchDB.prepareUserDoc({ - username: "jchris@apache.org" + name: "jchris@apache.org" }, "funnybone"); T(usersDb.save(jchrisUserDoc).ok); - T(CouchDB.session().name == null); + T(CouchDB.session().userCtx.name == null); + + // test that you can use basic auth aginst the users db var s = CouchDB.session({ headers : { + // base64_encode("jchris@apache.org:funnybone") "Authorization" : "Basic amNocmlzQGFwYWNoZS5vcmc6ZnVubnlib25l" } }); - T(s.name == "jchris@apache.org"); - T(s.user_doc._id == "org.couchdb.user:jchris@apache.org") - T(s.info.authenticated == "{couch_httpd_auth, default_authentication_handler}"); - T(s.info.user_db == "test_suite_users"); - TEquals(["{couch_httpd_oauth, oauth_authentication_handler}", - "{couch_httpd_auth, cookie_authentication_handler}", - "{couch_httpd_auth, default_authentication_handler}"], s.info.handlers); + T(s.userCtx.name == "jchris@apache.org"); + T(s.info.authenticated == "default"); + T(s.info.authentication_db == "test_suite_users"); + TEquals(["oauth", "cookie", "default"], s.info.authentication_handlers); var s = CouchDB.session({ headers : { - "Authorization" : "Basic Xzpf" // username and pass of _:_ + "Authorization" : "Basic Xzpf" // name and pass of _:_ } }); T(s.name == null); - T(s.info.authenticated == "{couch_httpd_auth, default_authentication_handler}"); + T(s.info.authenticated == "default"); + + + // ok, now create a conflicting edit on the jchris doc, and make sure there's no login. + var jchrisUser2 = JSON.parse(JSON.stringify(jchrisUserDoc)); + jchrisUser2.foo = "bar"; + T(usersDb.save(jchrisUser2).ok); + try { + usersDb.save(jchrisUserDoc); + T(false && "should be an update conflict") + } catch(e) { + T(true); + } + // save as bulk with new_edits=false to force conflict save + var resp = usersDb.bulkSave([jchrisUserDoc],{all_or_nothing : true}); + + var jchrisWithConflict = usersDb.open(jchrisUserDoc._id, {conflicts : true}); + T(jchrisWithConflict._conflicts.length == 1) + + // no login with conflicted user doc + try { + var s = CouchDB.session({ + headers : { + "Authorization" : "Basic amNocmlzQGFwYWNoZS5vcmc6ZnVubnlib25l" + } + }); + T(false && "this will throw") + } catch(e) { + T(e.error == "unauthorized") + T(/conflict/.test(e.reason)) + } + + // you can delete a user doc + s = CouchDB.session().userCtx; + T(s.name == null); + T(s.roles.indexOf("_admin") !== -1); + T(usersDb.deleteDoc(jchrisWithConflict).ok); + + // you can't change doc from type "user" + jchrisUserDoc = usersDb.open(jchrisUserDoc._id); + jchrisUserDoc.type = "not user"; + try { + usersDb.save(jchrisUserDoc); + T(false && "should only allow us to save doc when type == 'user'"); + } catch(e) { + T(e.reason == "doc.type must be user"); + } + jchrisUserDoc.type = "user"; + + // "roles" must be an array + jchrisUserDoc.roles = "not an array"; + try { + usersDb.save(jchrisUserDoc); + T(false && "should only allow us to save doc when roles is an array"); + } catch(e) { + T(e.reason == "doc.roles must be an array"); + } + jchrisUserDoc.roles = []; }; - + + usersDb.deleteDb(); run_on_modified_server( [{section: "couch_httpd_auth", - key: "authentication_db", value: "test_suite_users"}], + key: "authentication_db", value: usersDb.name}], testFun ); + usersDb.deleteDb(); // cleanup -}
\ No newline at end of file +} diff --git a/share/www/script/test/uuids.js b/share/www/script/test/uuids.js index 4de7ce90..fc33a105 100644 --- a/share/www/script/test/uuids.js +++ b/share/www/script/test/uuids.js @@ -93,12 +93,20 @@ couchTests.uuids = function(debug) { xhr = CouchDB.request("GET", "/_uuids?count=1000"); T(xhr.status == 200); result = JSON.parse(xhr.responseText); - for(var i = 1; i < result.uuids.length; i++) { - T(result.uuids[i].length == 32); - var u1 = result.uuids[i-1].substr(0, 13); - var u2 = result.uuids[i].substr(0, 13); - T(u1 < u2, "UTC uuids are roughly ordered."); + T(result.uuids[1].length == 32); + + // no collisions + var seen = {}; + for(var i in result.uuids) { + var id = result.uuids[i]; + T(seen[id] === undefined); + seen[id] = 1; } + + // roughly ordered + var u1 = result.uuids[1].substr(0, 13); + var u2 = result.uuids[result.uuids.length-1].substr(0, 13); + T(u1 < u2, "UTC uuids are only roughly ordered, so this assertion may fail occasionally. Don't sweat it."); }; run_on_modified_server([{ diff --git a/share/www/script/test/view_collation.js b/share/www/script/test/view_collation.js index f4ae4a15..b01a5c50 100644 --- a/share/www/script/test/view_collation.js +++ b/share/www/script/test/view_collation.js @@ -90,27 +90,27 @@ couchTests.view_collation = function(debug) { // the inclusive_end=true functionality is limited to endkey currently // if you need inclusive_start=false for startkey, please do implement. ;) var rows = db.query(queryFun, null, {endkey : "b", inclusive_end:true}).rows; - T(rows[rows.length-1].key == "b") + T(rows[rows.length-1].key == "b"); // descending=true var rows = db.query(queryFun, null, {endkey : "b", descending:true, inclusive_end:true}).rows; - T(rows[rows.length-1].key == "b") + T(rows[rows.length-1].key == "b"); // test inclusive_end=false var rows = db.query(queryFun, null, {endkey : "b", inclusive_end:false}).rows; - T(rows[rows.length-1].key == "aa") + T(rows[rows.length-1].key == "aa"); // descending=true var rows = db.query(queryFun, null, {endkey : "b", descending:true, inclusive_end:false}).rows; - T(rows[rows.length-1].key == "B") + T(rows[rows.length-1].key == "B"); var rows = db.query(queryFun, null, { endkey : "b", endkey_docid: "10", inclusive_end:false}).rows; - T(rows[rows.length-1].key == "aa") + T(rows[rows.length-1].key == "aa"); var rows = db.query(queryFun, null, { endkey : "b", endkey_docid: "11", inclusive_end:false}).rows; - T(rows[rows.length-1].key == "b") + T(rows[rows.length-1].key == "b"); }; diff --git a/share/www/script/test/view_collation_raw.js b/share/www/script/test/view_collation_raw.js index 08f37fae..31624cdb 100644 --- a/share/www/script/test/view_collation_raw.js +++ b/share/www/script/test/view_collation_raw.js @@ -97,27 +97,27 @@ couchTests.view_collation_raw = function(debug) { // the inclusive_end=true functionality is limited to endkey currently // if you need inclusive_start=false for startkey, please do implement. ;) var rows = db.view("test/test", {endkey : "b", inclusive_end:true}).rows; - T(rows[rows.length-1].key == "b") + T(rows[rows.length-1].key == "b"); // descending=true var rows = db.view("test/test", {endkey : "b", descending:true, inclusive_end:true}).rows; - T(rows[rows.length-1].key == "b") + T(rows[rows.length-1].key == "b"); // test inclusive_end=false var rows = db.view("test/test", {endkey : "b", inclusive_end:false}).rows; - T(rows[rows.length-1].key == "aa") + T(rows[rows.length-1].key == "aa"); // descending=true var rows = db.view("test/test", {endkey : "b", descending:true, inclusive_end:false}).rows; - T(rows[rows.length-1].key == "ba") + T(rows[rows.length-1].key == "ba"); var rows = db.view("test/test", { endkey : "b", endkey_docid: "10", inclusive_end:false}).rows; - T(rows[rows.length-1].key == "aa") + T(rows[rows.length-1].key == "aa"); var rows = db.view("test/test", { endkey : "b", endkey_docid: "11", inclusive_end:false}).rows; - T(rows[rows.length-1].key == "aa") + T(rows[rows.length-1].key == "aa"); }; diff --git a/share/www/script/test/view_compaction.js b/share/www/script/test/view_compaction.js new file mode 100644 index 00000000..a11fb7bd --- /dev/null +++ b/share/www/script/test/view_compaction.js @@ -0,0 +1,104 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +couchTests.view_compaction = function(debug) { + + if (debug) debugger; + + var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit": "true"}); + + db.deleteDb(); + db.createDb(); + + var ddoc = { + _id: "_design/foo", + language: "javascript", + views: { + view1: { + map: "function(doc) { emit(doc._id, doc.value) }" + }, + view2: { + map: "function(doc) { emit(doc._id, doc.value); }", + reduce: "function(keys, values, rereduce) { return sum(values); }" + } + } + }; + T(db.save(ddoc).ok); + + var docs = makeDocs(0, 1000); + db.bulkSave(docs); + + var resp = db.view('foo/view1', {}); + T(resp.rows.length === 1000); + + resp = db.view('foo/view2', {}); + T(resp.rows.length === 1); + + resp = db.designInfo("_design/foo"); + T(resp.view_index.update_seq === 1001); + + + // update docs + for (var i = 0; i < docs.length; i++) { + docs[i].integer = docs[i].integer + 1; + } + db.bulkSave(docs); + + + resp = db.view('foo/view1', {}); + T(resp.rows.length === 1000); + + resp = db.view('foo/view2', {}); + T(resp.rows.length === 1); + + resp = db.designInfo("_design/foo"); + T(resp.view_index.update_seq === 2001); + + + // update docs again... + for (var i = 0; i < docs.length; i++) { + docs[i].integer = docs[i].integer + 2; + } + db.bulkSave(docs); + + + resp = db.view('foo/view1', {}); + T(resp.rows.length === 1000); + + resp = db.view('foo/view2', {}); + T(resp.rows.length === 1); + + resp = db.designInfo("_design/foo"); + T(resp.view_index.update_seq === 3001); + + var disk_size_before_compact = resp.view_index.disk_size; + + // compact view group + var xhr = CouchDB.request("POST", "/" + db.name + "/_compact" + "/foo"); + T(JSON.parse(xhr.responseText).ok === true); + + resp = db.designInfo("_design/foo"); + while (resp.view_index.compact_running === true) { + resp = db.designInfo("_design/foo"); + } + + + resp = db.view('foo/view1', {}); + T(resp.rows.length === 1000); + + resp = db.view('foo/view2', {}); + T(resp.rows.length === 1); + + resp = db.designInfo("_design/foo"); + T(resp.view_index.update_seq === 3001); + T(resp.view_index.disk_size < disk_size_before_compact); +};
\ No newline at end of file diff --git a/share/www/script/test/view_errors.js b/share/www/script/test/view_errors.js index 0f90c46f..c05000b7 100644 --- a/share/www/script/test/view_errors.js +++ b/share/www/script/test/view_errors.js @@ -16,8 +16,6 @@ couchTests.view_errors = function(debug) { db.createDb(); if (debug) debugger; - - run_on_modified_server( [{section: "couchdb", key: "os_process_timeout", @@ -26,12 +24,13 @@ couchTests.view_errors = function(debug) { 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 + // emitting a key value that is undefined should result in that row + // being included in the view results as null var results = db.query(function(doc) { emit(doc.undef, null); }); - T(results.total_rows == 0); + T(results.total_rows == 1); + T(results.rows[0].key == null); // if a view function throws an exception, its results are not included in // the view index, but the view does not itself raise an error @@ -41,13 +40,13 @@ couchTests.view_errors = function(debug) { 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 + // value, it is treated as null var results = db.query(function(doc) { emit([doc._id, doc.undef], null); }); - T(results.total_rows == 0); - + T(results.total_rows == 1); + T(results.rows[0].key[1] == null); + // querying a view with invalid params should give a resonable error message var xhr = CouchDB.request("POST", "/test_suite_db/_temp_view?startkey=foo", { headers: {"Content-Type": "application/json"}, @@ -57,14 +56,14 @@ couchTests.view_errors = function(debug) { }); T(JSON.parse(xhr.responseText).error == "bad_request"); - // views should ignore Content-Type, like the rest of CouchDB + // content type must be json var xhr = CouchDB.request("POST", "/test_suite_db/_temp_view", { headers: {"Content-Type": "application/x-www-form-urlencoded"}, body: JSON.stringify({language: "javascript", map : "function(doc){}" }) }); - T(xhr.status == 200); + T(xhr.status == 415); var map = function (doc) {emit(doc.integer, doc.integer);}; @@ -75,9 +74,6 @@ couchTests.view_errors = function(debug) { T(e.error == "query_parse_error"); } - // reduce=false on map views doesn't work, so group=true will - // never throw for temp reduce views. - var designDoc = { _id:"_design/test", language: "javascript", @@ -105,6 +101,15 @@ couchTests.view_errors = function(debug) { db.view("test/no_reduce", {group: true}); T(0 == 1); } catch(e) { + T(db.last_req.status == 400); + T(e.error == "query_parse_error"); + } + + try { + db.view("test/no_reduce", {group_level: 1}); + T(0 == 1); + } catch(e) { + T(db.last_req.status == 400); T(e.error == "query_parse_error"); } @@ -116,10 +121,23 @@ couchTests.view_errors = function(debug) { T(e.error == "query_parse_error"); } + db.view("test/no_reduce", {reduce: false}); + TEquals(200, db.last_req.status, "reduce=false for map views (without" + + " group or group_level) is allowed"); + try { db.view("test/with_reduce", {group: true, reduce: false}); T(0 == 1); } catch(e) { + T(db.last_req.status == 400); + T(e.error == "query_parse_error"); + } + + try { + db.view("test/with_reduce", {group_level: 1, reduce: false}); + T(0 == 1); + } catch(e) { + T(db.last_req.status == 400); T(e.error == "query_parse_error"); } diff --git a/share/www/script/test/view_multi_key_design.js b/share/www/script/test/view_multi_key_design.js index 5a2f645d..c39e73d9 100644 --- a/share/www/script/test/view_multi_key_design.js +++ b/share/www/script/test/view_multi_key_design.js @@ -34,11 +34,11 @@ couchTests.view_multi_key_design = function(debug) { 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 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++) { @@ -81,7 +81,7 @@ couchTests.view_multi_key_design = function(debug) { } // Test that a map & reduce containing func support keys when reduce=false - resp = db.view("test/summate", {reduce: false}, keys); + var resp = db.view("test/summate", {reduce: false}, keys); T(resp.rows.length == 5); // Check that limiting by startkey_docid and endkey_docid get applied diff --git a/share/www/script/test/view_sandboxing.js b/share/www/script/test/view_sandboxing.js index 9f893b28..02951d9f 100644 --- a/share/www/script/test/view_sandboxing.js +++ b/share/www/script/test/view_sandboxing.js @@ -42,11 +42,99 @@ couchTests.view_sandboxing = function(debug) { // 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) }); + 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) }); + var results = db.query(function(doc) { map_results.push(1); emit(null, doc); }); T(results.total_rows == 0); + + // test for COUCHDB-925 + // altering 'doc' variable in map function affects other map functions + var ddoc = { + _id: "_design/foobar", + language: "javascript", + views: { + view1: { + map: + (function(doc) { + if (doc.values) { + doc.values = [666]; + } + if (doc.tags) { + doc.tags.push("qwerty"); + } + if (doc.tokens) { + doc.tokens["c"] = 3; + } + }).toString() + }, + view2: { + map: + (function(doc) { + if (doc.values) { + emit(doc._id, doc.values); + } + if (doc.tags) { + emit(doc._id, doc.tags); + } + if (doc.tokens) { + emit(doc._id, doc.tokens); + } + }).toString() + } + } + }; + var doc1 = { + _id: "doc1", + values: [1, 2, 3] + }; + var doc2 = { + _id: "doc2", + tags: ["foo", "bar"], + tokens: {a: 1, b: 2} + }; + + db.deleteDb(); + db.createDb(); + T(db.save(ddoc).ok); + T(db.save(doc1).ok); + T(db.save(doc2).ok); + + var view1Results = db.view( + "foobar/view1", {bypass_cache: Math.round(Math.random() * 1000)}); + var view2Results = db.view( + "foobar/view2", {bypass_cache: Math.round(Math.random() * 1000)}); + + TEquals(0, view1Results.rows.length, "view1 has 0 rows"); + TEquals(3, view2Results.rows.length, "view2 has 3 rows"); + + TEquals(doc1._id, view2Results.rows[0].key); + TEquals(doc2._id, view2Results.rows[1].key); + TEquals(doc2._id, view2Results.rows[2].key); + + // https://bugzilla.mozilla.org/show_bug.cgi?id=449657 + TEquals(3, view2Results.rows[0].value.length, + "Warning: installed SpiderMonkey version doesn't allow sealing of arrays"); + if (view2Results.rows[0].value.length === 3) { + TEquals(1, view2Results.rows[0].value[0]); + TEquals(2, view2Results.rows[0].value[1]); + TEquals(3, view2Results.rows[0].value[2]); + } + + TEquals(1, view2Results.rows[1].value["a"]); + TEquals(2, view2Results.rows[1].value["b"]); + TEquals('undefined', typeof view2Results.rows[1].value["c"], + "doc2.tokens object was not sealed"); + + TEquals(2, view2Results.rows[2].value.length, + "Warning: installed SpiderMonkey version doesn't allow sealing of arrays"); + if (view2Results.rows[2].value.length === 2) { + TEquals("foo", view2Results.rows[2].value[0]); + TEquals("bar", view2Results.rows[2].value[1]); + } + + // cleanup + db.deleteDb(); }; diff --git a/share/www/script/test/view_update_seq.js b/share/www/script/test/view_update_seq.js new file mode 100644 index 00000000..9757caa1 --- /dev/null +++ b/share/www/script/test/view_update_seq.js @@ -0,0 +1,89 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +couchTests.view_update_seq = function(debug) { + var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"true"}); + db.deleteDb(); + db.createDb(); + if (debug) debugger; + + T(db.info().update_seq == 0); + + var resp = db.allDocs({update_seq:true}); + + T(resp.rows.length == 0); + T(resp.update_seq == 0); + + var designDoc = { + _id:"_design/test", + language: "javascript", + views: { + all_docs: { + map: "function(doc) { emit(doc.integer, doc.string) }" + }, + summate: { + map:"function (doc) {emit(doc.integer, doc.integer)};", + reduce:"function (keys, values) { return sum(values); };" + } + } + }; + T(db.save(designDoc).ok); + + T(db.info().update_seq == 1); + + resp = db.allDocs({update_seq:true}); + + T(resp.rows.length == 1); + T(resp.update_seq == 1); + + var docs = makeDocs(0, 100); + db.bulkSave(docs); + + resp = db.allDocs({limit: 1}); + T(resp.rows.length == 1); + T(!resp.update_seq, "all docs"); + + resp = db.allDocs({limit: 1, update_seq:true}); + T(resp.rows.length == 1); + T(resp.update_seq == 101); + + resp = db.view('test/all_docs', {limit: 1, update_seq:true}); + T(resp.rows.length == 1); + T(resp.update_seq == 101); + + resp = db.view('test/all_docs', {limit: 1, update_seq:false}); + T(resp.rows.length == 1); + T(!resp.update_seq, "view"); + + resp = db.view('test/summate', {update_seq:true}); + T(resp.rows.length == 1); + T(resp.update_seq == 101); + + db.save({"id":"0"}); + resp = db.view('test/all_docs', {limit: 1,stale: "ok", update_seq:true}); + T(resp.rows.length == 1); + T(resp.update_seq == 101); + + resp = db.view('test/all_docs', {limit: 1, update_seq:true}); + T(resp.rows.length == 1); + T(resp.update_seq == 102); + + resp = db.view('test/all_docs',{update_seq:true},["0","1"]); + T(resp.update_seq == 102); + + resp = db.view('test/all_docs',{update_seq:true},["0","1"]); + T(resp.update_seq == 102); + + resp = db.view('test/summate',{group:true, update_seq:true},["0","1"]); + T(resp.update_seq == 102); + +}; diff --git a/share/www/script/test/view_xml.js b/share/www/script/test/view_xml.js index 451fb6a8..3403b47c 100644 --- a/share/www/script/test/view_xml.js +++ b/share/www/script/test/view_xml.js @@ -22,7 +22,7 @@ couchTests.view_xml = function(debug) { var results = db.query( "function(doc) {\n" + " var xml = new XML(doc.content);\n" + - " emit(xml.title.text(), null);\n" + + " emit(xml.title.text().toXMLString(), null);\n" + "}"); T(results.total_rows == 2); T(results.rows[0].key == "Testing E4X"); @@ -31,7 +31,7 @@ couchTests.view_xml = function(debug) { var results = db.query( "function(doc) {\n" + " var xml = new XML(doc.content);\n" + - " emit(xml.title.@id, null);\n" + + " emit(xml.title.@id.toXMLString(), null);\n" + "}"); T(results.total_rows == 2); T(results.rows[0].key == "e4x"); |