http://git-wip-us.apache.org/repos/asf/couchdb/blob/6a4893aa/test/javascript/tests/rev_stemming.js ---------------------------------------------------------------------- diff --git a/test/javascript/tests/rev_stemming.js b/test/javascript/tests/rev_stemming.js new file mode 100644 index 0000000..954da79 --- /dev/null +++ b/test/javascript/tests/rev_stemming.js @@ -0,0 +1,110 @@ +// 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.rev_stemming = function(debug) { + var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); + db.deleteDb(); + db.createDb(); + var db = new CouchDB("test_suite_db_a", {"X-Couch-Full-Commit":"false"}); + db.deleteDb(); + db.createDb(); + var dbB = new CouchDB("test_suite_db_b", {"X-Couch-Full-Commit":"false"}); + dbB.deleteDb(); + dbB.createDb(); + if (debug) debugger; + + var newLimit = 5; + + T(db.getDbProperty("_revs_limit") == 1000); + + // Make an invalid request to _revs_limit + // Should return 400 + var xhr = CouchDB.request("PUT", "/test_suite_db/_revs_limit", {body:"\"foo\""}); + T(xhr.status == 400); + var result = JSON.parse(xhr.responseText); + T(result.error == "bad_request"); + T(result.reason == "Rev limit has to be an integer"); + + var doc = {_id:"foo",foo:0} + for( var i=0; i < newLimit + 1; i++) { + doc.foo++; + T(db.save(doc).ok); + } + var doc0 = db.open("foo", {revs:true}); + T(doc0._revisions.ids.length == newLimit + 1); + + var docBar = {_id:"bar",foo:0} + for( var i=0; i < newLimit + 1; i++) { + docBar.foo++; + T(db.save(docBar).ok); + } + T(db.open("bar", {revs:true})._revisions.ids.length == newLimit + 1); + + T(db.setDbProperty("_revs_limit", newLimit).ok); + + for( var i=0; i < newLimit + 1; i++) { + doc.foo++; + T(db.save(doc).ok); + } + doc0 = db.open("foo", {revs:true}); + T(doc0._revisions.ids.length == newLimit); + + + // If you replicate after you make more edits than the limit, you'll + // cause a spurious edit conflict. + CouchDB.replicate("test_suite_db_a", "test_suite_db_b"); + var docB1 = dbB.open("foo",{conflicts:true}) + T(docB1._conflicts == null); + + for( var i=0; i < newLimit - 1; i++) { + doc.foo++; + T(db.save(doc).ok); + } + + // one less edit than limit, no conflict + CouchDB.replicate("test_suite_db_a", "test_suite_db_b"); + var docB1 = dbB.open("foo",{conflicts:true}) + T(docB1._conflicts == null); + + //now we hit the limit + for( var i=0; i < newLimit; i++) { + doc.foo++; + T(db.save(doc).ok); + } + + CouchDB.replicate("test_suite_db_a", "test_suite_db_b"); + + var docB2 = dbB.open("foo",{conflicts:true}); + + // we have a conflict, but the previous replicated rev is always the losing + // conflict + T(docB2._conflicts[0] == docB1._rev) + + // We having already updated bar before setting the limit, so it's still got + // a long rev history. compact to stem the revs. + + T(db.open("bar", {revs:true})._revisions.ids.length == newLimit + 1); + + T(db.compact().ok); + + // compaction isn't instantaneous, loop until done + while (db.info().compact_running) {}; + + // force reload because ETags don't honour compaction + var req = db.request("GET", "/test_suite_db_a/bar?revs=true", { + headers:{"if-none-match":"pommes"} + }); + + var finalDoc = JSON.parse(req.responseText); + TEquals(newLimit, finalDoc._revisions.ids.length, + "should return a truncated revision list"); +};
http://git-wip-us.apache.org/repos/asf/couchdb/blob/6a4893aa/test/javascript/tests/rewrite.js ---------------------------------------------------------------------- diff --git a/test/javascript/tests/rewrite.js b/test/javascript/tests/rewrite.js new file mode 100644 index 0000000..5c56fa5 --- /dev/null +++ b/test/javascript/tests/rewrite.js @@ -0,0 +1,505 @@ +// 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) { + if (debug) debugger; + var dbNames = ["test_suite_db", "test_suite_db/with_slashes"]; + for (var i=0; i < dbNames.length; i++) { + var db = new CouchDB(dbNames[i]); + var dbName = encodeURIComponent(dbNames[i]); + db.deleteDb(); + db.createDb(); + + + 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": "/welcome4/*", + "to" : "_show/welcome3", + "query": { + "name": "*" + } + }, + { + "from": "/welcome5/*", + "to" : "_show/*", + "query": { + "name": "*" + } + }, + { + "from": "basicView", + "to": "_view/basicView", + }, + { + "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" + }, + "formats": { + "start": "int", + "end": "int" + } + }, + { + "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"] + } + }, + { + "from": "simpleForm/complexView7/:a/:b", + "to": "_view/complexView3", + "query": { + "key": [":a", ":b"], + "include_docs": ":doc" + }, + "format": { + "doc": "bool" + } + + }, + { + "from": "/", + "to": "_view/basicView", + }, + { + "from": "/db/*", + "to": "../../*" + } + ], + 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; + }), + "welcome3": stringFun(function(doc,req) { + return "Welcome " + req.query["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", "/"+dbName+"/_design/test/_rewrite/foo"); + T(req.responseText == "This is a base64 encoded text"); + T(req.getResponseHeader("Content-Type") == "text/plain"); + + req = CouchDB.request("GET", "/"+dbName+"/_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", "/"+dbName+"/_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", "/"+dbName+"/_design/test/_rewrite/welcome?name=user"); + T(req.responseText == "Welcome user"); + + req = CouchDB.request("GET", "/"+dbName+"/_design/test/_rewrite/welcome/user"); + T(req.responseText == "Welcome user"); + + req = CouchDB.request("GET", "/"+dbName+"/_design/test/_rewrite/welcome2"); + T(req.responseText == "Welcome user"); + + xhr = CouchDB.request("PUT", "/"+dbName+"/_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", "/"+dbName+"/_design/test/_rewrite/welcome3/test"); + T(xhr.responseText == "Welcome test"); + + req = CouchDB.request("GET", "/"+dbName+"/_design/test/_rewrite/welcome4/user"); + T(req.responseText == "Welcome user"); + + req = CouchDB.request("GET", "/"+dbName+"/_design/test/_rewrite/welcome5/welcome3"); + T(req.responseText == "Welcome welcome3"); + + xhr = CouchDB.request("GET", "/"+dbName+"/_design/test/_rewrite/basicView"); + T(xhr.status == 200, "view call"); + T(/{"total_rows":9/.test(xhr.responseText)); + + xhr = CouchDB.request("GET", "/"+dbName+"/_design/test/_rewrite/"); + T(xhr.status == 200, "view call"); + T(/{"total_rows":9/.test(xhr.responseText)); + + + // get with query params + xhr = CouchDB.request("GET", "/"+dbName+"/_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", "/"+dbName+"/_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", "/"+dbName+"/_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", "/"+dbName+"/_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", "/"+dbName+"/_design/test/_rewrite/simpleForm/complexView"); + T(xhr.status == 200, "with query params"); + T(/FirstKey: [1, 2]/.test(xhr.responseText)); + + xhr = CouchDB.request("GET", "/"+dbName+"/_design/test/_rewrite/simpleForm/complexView2"); + T(xhr.status == 200, "with query params"); + T(/Value: doc 3/.test(xhr.responseText)); + + xhr = CouchDB.request("GET", "/"+dbName+"/_design/test/_rewrite/simpleForm/complexView3"); + T(xhr.status == 200, "with query params"); + T(/Value: doc 4/.test(xhr.responseText)); + + xhr = CouchDB.request("GET", "/"+dbName+"/_design/test/_rewrite/simpleForm/complexView4"); + T(xhr.status == 200, "with query params"); + T(/Value: doc 5/.test(xhr.responseText)); + + xhr = CouchDB.request("GET", "/"+dbName+"/_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", "/"+dbName+"/_design/test/_rewrite/simpleForm/complexView6?a=test&b=essai"); + T(xhr.status == 200, "with query params"); + T(/Value: doc 4/.test(xhr.responseText)); + + xhr = CouchDB.request("GET", "/"+dbName+"/_design/test/_rewrite/simpleForm/complexView7/test/essai?doc=true"); + T(xhr.status == 200, "with query params"); + var result = JSON.parse(xhr.responseText); + T(typeof(result.rows[0].doc) === "object"); + + // COUCHDB-2031 - path normalization versus qs params + xhr = CouchDB.request("GET", "/"+dbName+"/_design/test/_rewrite/db/_design/test?meta=true"); + T(xhr.status == 200, "path normalization works with qs params"); + var result = JSON.parse(xhr.responseText); + T(result['_id'] == "_design/test"); + T(typeof(result['_revs_info']) === "object"); + + // test path relative to server + designDoc.rewrites.push({ + "from": "uuids", + "to": "../../../_uuids" + }); + T(db.save(designDoc).ok); + + var xhr = CouchDB.request("GET", "/"+dbName+"/_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", "/"+dbName+"/_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", "/"+dbName+"/_design/invalid/_rewrite/foo"); + TEquals(400, res.status, "should return 400"); + + var ddoc_requested_path = { + _id: "_design/requested_path", + rewrites:[ + {"from": "show", "to": "_show/origin/0"}, + {"from": "show_rewritten", "to": "_rewrite/show"} + ], + shows: { + origin: stringFun(function(doc, req) { + return req.headers["x-couchdb-requested-path"]; + })} + }; + + db.save(ddoc_requested_path); + var url = "/"+dbName+"/_design/requested_path/_rewrite/show"; + var res = CouchDB.request("GET", url); + TEquals(url, res.responseText, "should return the original url"); + + var url = "/"+dbName+"/_design/requested_path/_rewrite/show_rewritten"; + var res = CouchDB.request("GET", url); + TEquals(url, res.responseText, "returned the original url"); + + var ddoc_loop = { + _id: "_design/loop", + rewrites: [{ "from": "loop", "to": "_rewrite/loop"}] + }; + db.save(ddoc_loop); + + // Assert loop detection + run_on_modified_server( + [{section: "httpd", + key: "rewrite_limit", + value: "2"}], + function(){ + var url = "/"+dbName+"/_design/loop/_rewrite/loop"; + var xhr = CouchDB.request("GET", url); + TEquals(400, xhr.status); + }); + + // Assert serial execution is not spuriously counted as loop + run_on_modified_server( + [{section: "httpd", + key: "rewrite_limit", + value: "2"}, + {section: "httpd", + key: "secure_rewrites", + value: "false"}], + function(){ + var url = "/"+dbName+"/_design/test/_rewrite/foo"; + for (var i=0; i < 5; i++) { + var xhr = CouchDB.request("GET", url); + TEquals(200, xhr.status); + } + }); + } +} http://git-wip-us.apache.org/repos/asf/couchdb/blob/6a4893aa/test/javascript/tests/security_validation.js ---------------------------------------------------------------------- diff --git a/test/javascript/tests/security_validation.js b/test/javascript/tests/security_validation.js new file mode 100644 index 0000000..14e5d04 --- /dev/null +++ b/test/javascript/tests/security_validation.js @@ -0,0 +1,338 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +couchTests.security_validation = function(debug) { + // This tests couchdb's security and validation features. This does + // not test authentication, except to use test authentication code made + // specifically for this testing. It is a WWW-Authenticate scheme named + // X-Couch-Test-Auth, and the user names and passwords are hard coded + // on the server-side. + // + // We could have used Basic authentication, however the XMLHttpRequest + // implementation for Firefox and Safari, and probably other browsers are + // broken (Firefox always prompts the user on 401 failures, Safari gives + // odd security errors when using different name/passwords, perhaps due + // to cross site scripting prevention). These problems essentially make Basic + // authentication testing in the browser impossible. But while hard to + // test automated in the browser, Basic auth may still useful for real + // world use where these bugs/behaviors don't matter. + // + // So for testing purposes we are using this custom X-Couch-Test-Auth. + // It's identical to Basic auth, except it doesn't even base64 encode + // the "username:password" string, it's sent completely plain text. + // Firefox and Safari both deal with this correctly (which is to say + // they correctly do nothing special). + + var db = new CouchDB("test_suite_db", {"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 () { + // try saving document using the wrong credentials + var wrongPasswordDb = new CouchDB("test_suite_db", + {"WWW-Authenticate": "X-Couch-Test-Auth Damien Katz:foo"} + ); + + try { + wrongPasswordDb.save({foo:1,author:"Damien Katz"}); + T(false && "Can't get here. Should have thrown an error 1"); + } catch (e) { + T(e.error == "unauthorized"); + T(wrongPasswordDb.last_req.status == 401); + } + + // test force basic login + var resp = wrongPasswordDb.request("GET", "/_session?basic=true"); + var err = JSON.parse(resp.responseText); + T(err.error == "unauthorized"); + T(resp.status == 401); + + // Create the design doc that will run custom validation code + var designDoc = { + _id:"_design/test", + language: "javascript", + validate_doc_update: stringFun(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: + "Documents must have an author field"}; + } + if (oldDoc && oldDoc.author != userCtx.name) { + throw {unauthorized: + "You are not the author of this document. You jerk."}; + } + }) + } + + // Save a document normally + var userDb = new CouchDB("test_suite_db", + {"WWW-Authenticate": "X-Couch-Test-Auth Damien Katz:pecan pie"} + ); + + T(userDb.save({_id:"testdoc", foo:1, author:"Damien Katz"}).ok); + + // Attempt to save the design as a non-admin + try { + userDb.save(designDoc); + T(false && "Can't get here. Should have thrown an error on design doc"); + } catch (e) { + T(e.error == "unauthorized"); + T(userDb.last_req.status == 401); + } + + // set user as the admin + T(db.setSecObj({ + admins : {names : ["Damien Katz"]} + }).ok); + + T(userDb.save(designDoc).ok); + + 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) + designDoc.foo = "bar"; + designDoc._rev = "2-642e20f96624a0aae6025b4dba0c6fb2"; + 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).userCtx; + T(user.name == "Damien Katz"); + // test that the roles are listed properly + TEquals(user.roles, []); + + + // update the document + var doc = userDb.open("testdoc"); + doc.foo=2; + T(userDb.save(doc).ok); + + // Save a document that's missing an author field (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 doc = user2Db.open("testdoc"); + doc.foo=3; + try { + user2Db.save(doc); + T(false && "Can't get here. Should have thrown an error 3"); + } catch (e) { + T(e.error == "unauthorized"); + T(user2Db.last_req.status == 401); + } + + // Now have Damien change the author to Jan + doc = userDb.open("testdoc"); + doc.author="Jan Lehnardt"; + T(userDb.save(doc).ok); + + // Now update the document as Jan + doc = user2Db.open("testdoc"); + doc.foo = 3; + T(user2Db.save(doc).ok); + + // Damien can't delete it + try { + userDb.deleteDoc(doc); + T(false && "Can't get here. Should have thrown an error 4"); + } catch (e) { + T(e.error == "unauthorized"); + T(userDb.last_req.status == 401); + } + + // 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); + + // now test bulk docs + var docs = [{_id:"bahbah",author:"Damien Katz",foo:"bar"},{_id:"fahfah",foo:"baz"}]; + + // Create the docs + var results = db.bulkSave(docs); + + T(results[0].rev) + T(results[0].error == undefined) + T(results[1].rev === undefined) + T(results[1].error == "forbidden") + + T(db.open("bahbah")); + T(db.open("fahfah") == null); + + + // now all or nothing with a failure + var docs = [{_id:"booboo",author:"Damien Katz",foo:"bar"},{_id:"foofoo",foo:"baz"}]; + + // Create the docs + var results = db.bulkSave(docs, {all_or_nothing:true}); + + T(results.errors.length == 1); + T(results.errors[0].error == "forbidden"); + 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; + var dbPairs = [ + {source:"test_suite_db_a", + target:"test_suite_db_b"}, + + {source:"test_suite_db_a", + target:{url: CouchDB.protocol + host + "/test_suite_db_b", + headers: AuthHeaders}}, + + {source:{url:CouchDB.protocol + host + "/test_suite_db_a", + headers: AuthHeaders}, + target:"test_suite_db_b"}, + + {source:{url:CouchDB.protocol + host + "/test_suite_db_a", + headers: AuthHeaders}, + target:{url:CouchDB.protocol + host + "/test_suite_db_b", + headers: AuthHeaders}}, + ] + var adminDbA = new CouchDB("test_suite_db_a", {"X-Couch-Full-Commit":"false"}); + var adminDbB = new CouchDB("test_suite_db_b", {"X-Couch-Full-Commit":"false"}); + var dbA = new CouchDB("test_suite_db_a", + {"WWW-Authenticate": "X-Couch-Test-Auth Christopher Lenz:dog food"}); + var dbB = new CouchDB("test_suite_db_b", + {"WWW-Authenticate": "X-Couch-Test-Auth Christopher Lenz:dog food"}); + var xhr; + for (var testPair = 0; testPair < dbPairs.length; testPair++) { + var A = dbPairs[testPair].source + var B = dbPairs[testPair].target + + adminDbA.deleteDb(); + adminDbA.createDb(); + adminDbB.deleteDb(); + adminDbB.createDb(); + + // save and replicate a documents that will and will not pass our design + // doc validation function. + dbA.save({_id:"foo1",value:"a",author:"Noah Slater"}); + dbA.save({_id:"foo2",value:"a",author:"Christopher Lenz"}); + dbA.save({_id:"bad1",value:"a"}); + + T(CouchDB.replicate(A, B, {headers:AuthHeaders}).ok); + T(CouchDB.replicate(B, A, {headers:AuthHeaders}).ok); + + T(dbA.open("foo1")); + T(dbB.open("foo1")); + T(dbA.open("foo2")); + T(dbB.open("foo2")); + + // save the design doc to dbA + delete designDoc._rev; // clear rev from previous saves + adminDbA.save(designDoc); + + // no affect on already saved docs + T(dbA.open("bad1")); + + // Update some docs on dbB. Since the design hasn't replicated, anything + // is allowed. + + // this edit will fail validation on replication to dbA (no author) + T(dbB.save({_id:"bad2",value:"a"}).ok); + + // this edit will fail security on replication to dbA (wrong author + // replicating the change) + var foo1 = dbB.open("foo1"); + foo1.value = "b"; + dbB.save(foo1); + + // this is a legal edit + var foo2 = dbB.open("foo2"); + foo2.value = "b"; + dbB.save(foo2); + + var results = CouchDB.replicate(B, A, {headers:AuthHeaders}); + + T(results.ok); + + T(results.history[0].docs_written == 1); + T(results.history[0].doc_write_failures == 2); + + // bad2 should not be on dbA + T(dbA.open("bad2") == null); + + // The edit to foo1 should not have replicated. + T(dbA.open("foo1").value == "a"); + + // The edit to foo2 should have replicated. + T(dbA.open("foo2").value == "b"); + } + }); +}; http://git-wip-us.apache.org/repos/asf/couchdb/blob/6a4893aa/test/javascript/tests/show_documents.js ---------------------------------------------------------------------- diff --git a/test/javascript/tests/show_documents.js b/test/javascript/tests/show_documents.js new file mode 100644 index 0000000..618925f --- /dev/null +++ b/test/javascript/tests/show_documents.js @@ -0,0 +1,420 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +couchTests.show_documents = function(debug) { + var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); + db.deleteDb(); + db.createDb(); + if (debug) debugger; + + var designDoc = { + _id:"_design/template", + language: "javascript", + shows: { + "hello" : stringFun(function(doc, req) { + log("hello fun"); + if (doc) { + return "Hello World"; + } else { + if(req.id) { + return "New World"; + } else { + return "Empty World"; + } + } + }), + "just-name" : stringFun(function(doc, req) { + if (doc) { + return { + body : "Just " + doc.name + }; + } else { + return { + body : "No such doc", + code : 404 + }; + } + }), + "json" : stringFun(function(doc, req) { + return { + json : doc + } + }), + "req-info" : stringFun(function(doc, req) { + return { + 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; + }), + "no-set-etag" : stringFun(function(doc, req) { + return { + headers : { + "Etag" : "skipped" + }, + "body" : "something" + } + }), + "list-api" : stringFun(function(doc, req) { + start({"X-Couch-Test-Header": "Yeah"}); + send("Hey"); + }), + "list-api-provides" : stringFun(function(doc, req) { + provides("text", function(){ + send("foo, "); + send("bar, "); + send("baz!"); + }) + }), + "list-api-provides-and-return" : stringFun(function(doc, req) { + provides("text", function(){ + send("4, "); + send("5, "); + send("6, "); + return "7!"; + }) + send("1, "); + send("2, "); + return "3, "; + }), + "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 { + // a 16x16 px version of the CouchDB logo + "base64" : +["iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAsV", +"BMVEUAAAD////////////////////////5ur3rEBn////////////////wDBL/", +"AADuBAe9EB3IEBz/7+//X1/qBQn2AgP/f3/ilpzsDxfpChDtDhXeCA76AQH/v7", +"/84eLyWV/uc3bJPEf/Dw/uw8bRWmP1h4zxSlD6YGHuQ0f6g4XyQkXvCA36MDH6", +"wMH/z8/yAwX64ODeh47BHiv/Ly/20dLQLTj98PDXWmP/Pz//39/wGyJ7Iy9JAA", +"AADHRSTlMAbw8vf08/bz+Pv19jK/W3AAAAg0lEQVR4Xp3LRQ4DQRBD0QqTm4Y5", +"zMxw/4OleiJlHeUtv2X6RbNO1Uqj9g0RMCuQO0vBIg4vMFeOpCWIWmDOw82fZx", +"vaND1c8OG4vrdOqD8YwgpDYDxRgkSm5rwu0nQVBJuMg++pLXZyr5jnc1BaH4GT", +"LvEliY253nA3pVhQqdPt0f/erJkMGMB8xucAAAAASUVORK5CYII="].join(''), + headers : { + "Content-Type" : "image/png", + "Vary" : "Accept" // we set this for proxy caches + } + }; + } else { + return { + "body" : "accepting text requests", + headers : { + "Content-Type" : "text/html", + "Vary" : "Accept" + } + }; + } + }), + "provides" : stringFun(function(doc, req) { + registerType("foo", "application/foo","application/x-foo"); + + provides("html", function() { + return "Ha ha, you said \"" + doc.word + "\"."; + }); + + provides("foo", function() { + return "foofoo"; + }); + }), + "withSlash": stringFun(function(doc, req) { + return { json: doc } + }), + "secObj": stringFun(function(doc, req) { + return { json: req.secObj }; + }) + } + }; + T(db.save(designDoc).ok); + + var doc = {"word":"plankton", "name":"Rusty"} + var resp = db.save(doc); + T(resp.ok); + var docid = resp.id; + + // show error + var xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/"); + T(xhr.status == 404, 'Should be missing'); + T(JSON.parse(xhr.responseText).reason == "Invalid path."); + + // hello template world + xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/hello/"+docid); + T(xhr.responseText == "Hello World", "hello"); + T(/charset=utf-8/.test(xhr.getResponseHeader("Content-Type"))); + + + // Fix for COUCHDB-379 + T(equals(xhr.getResponseHeader("Server").substr(0,7), "CouchDB")); + + // // error stacktraces + // xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/render-error/"+docid); + // T(JSON.parse(xhr.responseText).error == "render_error"); + + // hello template world (no docid) + xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/hello"); + T(xhr.responseText == "Empty World"); + + // hello template world (no docid) + xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/empty"); + T(xhr.responseText == ""); + + // // hello template world (non-existing docid) + 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"); + + // show with doc + xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/just-name/"+docid); + T(xhr.responseText == "Just Rusty"); + + // show with missing doc + xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/just-name/missingdoc"); + T(xhr.status == 404); + TEquals("No such doc", xhr.responseText); + + // show with missing func + xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/missing/"+docid); + T(xhr.status == 404, "function is missing"); + + // missing design doc + xhr = CouchDB.request("GET", "/test_suite_db/_design/missingddoc/_show/just-name/"+docid); + T(xhr.status == 404); + var resp = JSON.parse(xhr.responseText); + T(resp.error == "not_found"); + + // query parameters + xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/req-info/"+docid+"?foo=bar", { + headers: { + "Accept": "text/html;text/plain;*/*", + "X-Foo" : "bar" + } + }); + var resp = JSON.parse(xhr.responseText); + T(equals(resp.headers["X-Foo"], "bar")); + T(equals(resp.query, {foo:"bar"})); + T(equals(resp.method, "GET")); + T(equals(resp.path[5], docid)); + T(equals(resp.info.db_name, "test_suite_db")); + + // accept header switching + // different mime has different etag + xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/accept-switch/"+docid, { + headers: {"Accept": "text/html;text/plain;*/*"} + }); + var ct = xhr.getResponseHeader("Content-Type"); + T(/text\/html/.test(ct)) + T("Accept" == xhr.getResponseHeader("Vary")); + var etag = xhr.getResponseHeader("etag"); + + xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/accept-switch/"+docid, { + headers: {"Accept": "image/png;*/*"} + }); + T(xhr.responseText.match(/PNG/)) + T("image/png" == xhr.getResponseHeader("Content-Type")); + var etag2 = xhr.getResponseHeader("etag"); + T(etag2 != etag); + + // proper etags + // show with doc + xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/just-name/"+docid); + // extract the ETag header values + etag = xhr.getResponseHeader("etag"); + // get again with etag in request + xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/just-name/"+docid, { + headers: {"if-none-match": etag} + }); + // should be 304 + T(xhr.status == 304); + + // update the doc + doc.name = "Crusty"; + resp = db.save(doc); + T(resp.ok); + // req with same etag + xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/just-name/"+docid, { + headers: {"if-none-match": etag} + }); + // status is 200 + T(xhr.status == 200); + + // get new etag and request again + etag = xhr.getResponseHeader("etag"); + xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/just-name/"+docid, { + headers: {"if-none-match": etag} + }); + // should be 304 + T(xhr.status == 304); + + // update design doc (but not function) + designDoc.isChanged = true; + T(db.save(designDoc).ok); + + xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/just-name/"+docid, { + headers: {"if-none-match": etag} + }); + // should not be 304 if we change the doc + T(xhr.status != 304, "changed ddoc"); + + // update design doc function + designDoc.shows["just-name"] = stringFun(function(doc, req) { + return { + body : "Just old " + doc.name + }; + }); + T(db.save(designDoc).ok); + + xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/just-name/"+docid, { + headers: {"if-none-match": etag} + }); + // status is 200 + T(xhr.status == 200); + + + // JS can't set etag + xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/no-set-etag/"+docid); + // extract the ETag header values + etag = xhr.getResponseHeader("etag"); + T(etag != "skipped") + + // test the provides mime matcher + xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/provides/"+docid, { + headers: { + "Accept": 'text/html,application/atom+xml; q=0.9' + } + }); + var ct = xhr.getResponseHeader("Content-Type"); + T(/charset=utf-8/.test(ct)) + T(/text\/html/.test(ct)) + T(xhr.responseText == "Ha ha, you said \"plankton\"."); + + // registering types works + xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/provides/"+docid, { + headers: { + "Accept": "application/x-foo" + } + }); + T(xhr.getResponseHeader("Content-Type") == "application/x-foo"); + T(xhr.responseText.match(/foofoo/)); + + // test the provides mime matcher without a match + xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/provides/"+docid, { + headers: { + "Accept": 'text/monkeys' + } + }); + var rs = JSON.parse(xhr.responseText); + T(rs.error == "not_acceptable") + + + // test inclusion of conflict state + var doc1 = {_id:"foo", a:1}; + var doc2 = {_id:"foo", a:2}; + db.save(doc1); + + // create the conflict with an all_or_nothing bulk docs request + var docs = [doc2]; + db.bulkSave(docs, {all_or_nothing:true}); + + 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"); + + // test list() compatible API with provides function + xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/list-api-provides/foo?format=text"); + TEquals(xhr.responseText, "foo, bar, baz!", "should join chunks to response body"); + + // should keep next result order: chunks + return value + provided chunks + provided return value + xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/list-api-provides-and-return/foo?format=text"); + TEquals(xhr.responseText, "1, 2, 3, 4, 5, 6, 7!", "should not break 1..7 range"); + + 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'"); + + + 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() { + T(db.setDbProperty("_security", {foo: true}).ok); + T(db.save({_id:"testdoc",foo:1}).ok); + + xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/secObj"); + var resp = JSON.parse(xhr.responseText); + T(resp.foo == true); + } + ); + +}; http://git-wip-us.apache.org/repos/asf/couchdb/blob/6a4893aa/test/javascript/tests/stats.js ---------------------------------------------------------------------- diff --git a/test/javascript/tests/stats.js b/test/javascript/tests/stats.js new file mode 100644 index 0000000..87440b3 --- /dev/null +++ b/test/javascript/tests/stats.js @@ -0,0 +1,348 @@ +// 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.stats = function(debug) { + + function newDb(name, doSetup) { + var db = new CouchDB(name, {"X-Couch-Full-Commit": "false"}); + if(doSetup) { + db.deleteDb(); + db.createDb(); + } + return db; + }; + + function getStat(path) { + var stat = CouchDB.requestStats(path, true); + return stat ? stat.value : null; + }; + + function doView(db) { + var designDoc = { + _id:"_design/test", // turn off couch.js id escaping? + language: "javascript", + views: { + all_docs: {map: "function(doc) {emit(doc.integer, null);}"} + } + }; + db.save(designDoc); + db.view("test/all_docs"); + }; + + function runTest(path, funcs) { + var db = newDb("test_suite_db", true); + if(funcs.setup) funcs.setup(db); + var before = getStat(path); + if(funcs.run) funcs.run(db); + var after = getStat(path); + if(funcs.test) funcs.test(before, after); + } + + if (debug) debugger; + + (function() { + var db = newDb("test_suite_db"); + db.deleteDb(); + + var before = getStat(["couchdb", "open_databases"]); + db.createDb(); + var after = getStat(["couchdb", "open_databases"]); + TEquals(before+1, after, "Creating a db increments open db count."); + })(); + + runTest(["couchdb", "open_databases"], { + setup: function() {restartServer();}, + run: function(db) {db.open("123");}, + test: function(before, after) { + TEquals(before+1, after, "Opening a db increments open db count."); + } + }); + + runTest(["couchdb", "open_databases"], { + run: function(db) {db.deleteDb();}, + test: function(before, after) { + TEquals(before-1, after, "Deleting a db decrements open db count."); + } + }); + + (function() { + restartServer(); + var max = 5; + + var testFun = function() { + var pre_dbs = getStat(["couchdb", "open_databases"]) || 0; + var pre_files = getStat(["couchdb", "open_os_files"]) || 0; + + var triggered = false; + var db = null; + for(var i = 0; i < max*2; i++) { + while (true) { + try { + db = newDb("test_suite_db_" + i, true); + break; + } catch(e) { + // all_dbs_active error! + triggered = true; + } + } + + // Trigger a delayed commit + db.save({_id: "" + i, "lang": "Awesome!"}); + } + T(triggered, "We managed to force a all_dbs_active error."); + + var open_dbs = getStat(["couchdb", "open_databases"]); + TEquals(open_dbs > 0, true, "We actually opened some dbs."); + TEquals(max, open_dbs, "We only have max db's open."); + + for(var i = 0; i < max * 2; i++) { + newDb("test_suite_db_" + i).deleteDb(); + } + + var post_dbs = getStat(["couchdb", "open_databases"]); + var post_files = getStat(["couchdb", "open_os_files"]); + TEquals(pre_dbs, post_dbs, "We have the same number of open dbs."); + TEquals(pre_files, post_files, "We have the same number of open files."); + }; + + run_on_modified_server( + [{section: "couchdb", key: "max_dbs_open", value: "5"}], + testFun + ); + })(); + + // Just fetching the before value is the extra +1 in test + runTest(["couchdb", "httpd", "requests"], { + run: function() {CouchDB.request("GET", "/");}, + test: function(before, after) { + TEquals(before+2, after, "Request counts are incremented properly."); + } + }); + + runTest(["couchdb", "database_reads"], { + setup: function(db) {db.save({"_id": "test"});}, + run: function(db) {db.open("test");}, + test: function(before, after) { + TEquals(before+1, after, "Reading a doc increments docs reads."); + } + }); + + runTest(["couchdb", "database_reads"], { + setup: function(db) {db.save({"_id": "test"});}, + run: function(db) {db.request("GET", "/");}, + test: function(before, after) { + TEquals(before, after, "Only doc reads increment doc reads."); + } + }); + + runTest(["couchdb", "database_reads"], { + setup: function(db) {db.save({"_id": "test"});}, + run: function(db) {db.open("test", {"open_revs": "all"});}, + test: function(before, after) { + TEquals(before+1, after, "Reading doc revs increments docs reads."); + } + }); + + runTest(["couchdb", "database_writes"], { + run: function(db) {db.save({"a": "1"});}, + test: function(before, after) { + TEquals(before+1, after, "Saving docs incrememnts doc writes."); + } + }); + + runTest(["couchdb", "database_writes"], { + run: function(db) { + 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"});}, + run: function(db) {var doc = db.open("test"); db.save(doc);}, + test: function(before, after) { + TEquals(before+1, after, "Updating docs incrememnts doc writes."); + } + }); + + runTest(["couchdb", "database_writes"], { + setup: function(db) {db.save({"_id": "test"});}, + run: function(db) {var doc = db.open("test"); db.deleteDoc(doc);}, + test: function(before, after) { + TEquals(before+1, after, "Deleting docs increments doc writes."); + } + }); + + runTest(["couchdb", "database_writes"], { + setup: function(db) {db.save({"_id": "test"});}, + run: function(db) { + CouchDB.request("COPY", "/test_suite_db/test", { + headers: {"Destination": "copy_of_test"} + }); + }, + test: function(before, after) { + TEquals(before+1, after, "Copying docs increments doc writes."); + } + }); + + runTest(["couchdb", "database_writes"], { + run: function() { + CouchDB.request("PUT", "/test_suite_db/bin_doc2/foo2.txt", { + body: "This is no base64 encoded test", + headers: {"Content-Type": "text/plain;charset=utf-8"} + }); + }, + test: function(before, after) { + TEquals(before+1, after, "Create with attachment increments doc writes."); + } + }); + + runTest(["couchdb", "database_writes"], { + setup: function(db) {db.save({"_id": "test"});}, + run: function(db) { + var doc = db.open("test"); + CouchDB.request("PUT", "/test_suite_db/test/foo2.txt?rev=" + doc._rev, { + body: "This is no base64 encoded text", + headers: {"Content-Type": "text/plainn;charset=utf-8"} + }); + }, + test: function(before, after) { + TEquals(before+1, after, "Adding attachment increments doc writes."); + } + }); + + runTest(["couchdb", "httpd", "bulk_requests"], { + run: function(db) {db.bulkSave(makeDocs(5));}, + test: function(before, after) { + TEquals(before+1, after, "The bulk_requests counter is incremented."); + } + }); + + runTest(["couchdb", "httpd", "view_reads"], { + run: function(db) {doView(db);}, + test: function(before, after) { + TEquals(before+1, after, "Reading a view increments view reads."); + } + }); + + runTest(["couchdb", "httpd", "view_reads"], { + setup: function(db) {db.save({"_id": "test"});}, + run: function(db) {db.open("test");}, + test: function(before, after) { + TEquals(before, after, "Reading a doc doesn't increment view reads."); + } + }); + + runTest(["couchdb", "httpd", "temporary_view_reads"], { + run: function(db) { db.query(function(doc) { emit(doc._id); }); }, + test: function(before, after) { + TEquals(before+1, after, "Temporary views have their own counter."); + } + }); + + runTest(["couchdb", "httpd", "temporary_view_reads"], { + run: function(db) {doView(db);}, + test: function(before, after) { + TEquals(before, after, "Permanent views don't affect temporary views."); + } + }); + + runTest(["couchdb", "httpd", "view_reads"], { + run: function(db) { db.query(function(doc) { emit(doc._id); }); }, + test: function(before, after) { + TEquals(before, after, "Temporary views don't affect permanent views."); + } + }); + + // Relies on getting the stats values being GET requests. + runTest(["couchdb", "httpd_request_methods", "GET"], { + test: function(before, after) { + TEquals(before+1, after, "Get requests are incremented properly."); + } + }); + + runTest(["couchdb", "httpd_request_methods", "GET"], { + run: function() {CouchDB.request("POST", "/");}, + test: function(before, after) { + TEquals(before+1, after, "POST requests don't affect GET counter."); + } + }); + + runTest(["couchdb", "httpd_request_methods", "POST"], { + run: function() {CouchDB.request("POST", "/");}, + test: function(before, after) { + TEquals(before+1, after, "POST requests are incremented properly."); + } + }); + + runTest(["couchdb", "httpd_status_codes", "404"], { + run: function() {CouchDB.request("GET", "/nonexistant_db");}, + test: function(before, after) { + TEquals(before+1, after, "Increments 404 counter on db not found."); + } + }); + + runTest(["couchdb", "httpd_status_codes", "404"], { + run: function() {CouchDB.request("GET", "/");}, + test: function(before, after) { + TEquals(before, after, "Getting DB info doesn't increment 404's"); + } + }); + + var test_metric = function(metric, expected_fields) { + for (var k in metric) { + T(expected_fields.indexOf(k) >= 0, "Unknown property name: " + k); + } + for (var k in expected_fields) { + T(metric[expected_fields[k]] !== undefined, "Missing required property: " + k); + } + }; + + var test_histogram = function(histo) { + test_metric(histo, ["value", "type", "desc"]); + test_metric(histo.value, ["min", "max", "arithmetic_mean", + "geometric_mean", "harmonic_mean", "median", "variance", + "standard_deviation", "skewness", "kurtosis", "percentile", + "histogram", "n"]); + }; + + var test_counter = function(counter) { + test_metric(counter, ["value", "desc", "type"]); + }; + + var test_metrics = function(metrics) { + if (metrics.type === "counter") { + test_counter(metrics); + } else if (metrics.type === "gauge") { + test_counter(metrics); + } else if (metrics.type === "histogram") { + test_histogram(metrics); + } else if (metrics.type === undefined) { + for (var k in metrics) { + test_metrics(metrics[k]); + } + } + }; + + (function() { + var summary = JSON.parse(CouchDB.request("GET", "/_stats", { + headers: {"Accept": "application/json"} + }).responseText); + T(typeof(summary) === 'object'); + test_metrics(summary); + })(); +}; http://git-wip-us.apache.org/repos/asf/couchdb/blob/6a4893aa/test/javascript/tests/update_documents.js ---------------------------------------------------------------------- diff --git a/test/javascript/tests/update_documents.js b/test/javascript/tests/update_documents.js new file mode 100644 index 0000000..bdb7a99 --- /dev/null +++ b/test/javascript/tests/update_documents.js @@ -0,0 +1,235 @@ +// 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.update_documents = function(debug) { + var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); + db.deleteDb(); + db.createDb(); + if (debug) debugger; + + var designDoc = { + _id:"_design/update", + language: "javascript", + updates: { + "hello" : stringFun(function(doc, req) { + log(doc); + log(req); + if (!doc) { + if (req.id) { + return [ + // Creates a new document with the PUT docid, + { _id : req.id, + reqs : [req] }, + // and returns an HTML response to the client. + "<p>New World</p>"]; + }; + // + return [null, "<p>Empty World</p>"]; + }; + // we can update the document inline + doc.world = "hello"; + // we can record aspects of the request or use them in application logic. + doc.reqs && doc.reqs.push(req); + doc.edited_by = req.userCtx; + return [doc, "<p>hello doc</p>"]; + }), + "in-place" : stringFun(function(doc, req) { + var field = req.query.field; + var value = req.query.value; + var message = "set "+field+" to "+value; + doc[field] = value; + return [doc, message]; + }), + "form-update" : stringFun(function(doc, req) { + for (var field in req.form) { + doc[field] = req.form[field]; + } + var message = "updated doc from form"; + return [doc, message]; + }), + "bump-counter" : stringFun(function(doc, req) { + if (!doc.counter) doc.counter = 0; + doc.counter += 1; + var message = "<h1>bumped it!</h1>"; + return [doc, message]; + }), + "error" : stringFun(function(doc, req) { + superFail.badCrash; + }), + "get-uuid" : stringFun(function(doc, req) { + return [null, req.uuid]; + }), + "code-n-bump" : stringFun(function(doc,req) { + if (!doc.counter) doc.counter = 0; + doc.counter += 1; + var message = "<h1>bumped it!</h1>"; + resp = {"code": 302, "body": message} + return [doc, resp]; + }), + "resp-code" : stringFun(function(doc,req) { + resp = {"code": 302} + return [null, resp]; + }), + "resp-code-and-json" : stringFun(function(doc,req) { + resp = {"code": 302, "json": {"ok": true}} + return [{"_id": req["uuid"]}, resp]; + }), + "binary" : stringFun(function(doc, req) { + var resp = { + "headers" : { + "Content-Type" : "application/octet-stream" + }, + "base64" : "aGVsbG8gd29ybGQh" // "hello world!" encoded + }; + return [doc, resp]; + }), + "empty" : stringFun(function(doc, req) { + return [{}, 'oops']; + }) + } + }; + T(db.save(designDoc).ok); + + var doc = {"word":"plankton", "name":"Rusty"} + var resp = db.save(doc); + T(resp.ok); + var docid = resp.id; + + // update error + var xhr = CouchDB.request("POST", "/test_suite_db/_design/update/_update/"); + T(xhr.status == 404, 'Should be missing'); + T(JSON.parse(xhr.responseText).reason == "Invalid path."); + + // hello update world + xhr = CouchDB.request("PUT", "/test_suite_db/_design/update/_update/hello/"+docid); + T(xhr.status == 201); + T(xhr.responseText == "<p>hello doc</p>"); + T(/charset=utf-8/.test(xhr.getResponseHeader("Content-Type"))); + T(equals(docid, xhr.getResponseHeader("X-Couch-Id"))); + + doc = db.open(docid); + T(doc.world == "hello"); + + // Fix for COUCHDB-379 + T(equals(xhr.getResponseHeader("Server").substr(0,7), "CouchDB")); + + // hello update world (no docid) + xhr = CouchDB.request("POST", "/test_suite_db/_design/update/_update/hello"); + T(xhr.status == 200); + T(xhr.responseText == "<p>Empty World</p>"); + + // no GET allowed + xhr = CouchDB.request("GET", "/test_suite_db/_design/update/_update/hello"); + // T(xhr.status == 405); // TODO allow qs to throw error code as well as error message + T(JSON.parse(xhr.responseText).error == "method_not_allowed"); + + // // hello update world (non-existing docid) + xhr = CouchDB.request("GET", "/test_suite_db/nonExistingDoc"); + T(xhr.status == 404); + xhr = CouchDB.request("PUT", "/test_suite_db/_design/update/_update/hello/nonExistingDoc"); + T(xhr.status == 201); + T(xhr.responseText == "<p>New World</p>"); + xhr = CouchDB.request("GET", "/test_suite_db/nonExistingDoc"); + T(xhr.status == 200); + + // in place update + xhr = CouchDB.request("PUT", "/test_suite_db/_design/update/_update/in-place/"+docid+'?field=title&value=test'); + T(xhr.status == 201); + T(xhr.responseText == "set title to test"); + doc = db.open(docid); + T(doc.title == "test"); + + // form update via application/x-www-form-urlencoded + xhr = CouchDB.request("PUT", "/test_suite_db/_design/update/_update/form-update/"+docid, { + headers : {"Content-Type":"application/x-www-form-urlencoded"}, + body : "formfoo=bar&formbar=foo" + }); + TEquals(201, xhr.status); + TEquals("updated doc from form", xhr.responseText); + doc = db.open(docid); + TEquals("bar", doc.formfoo); + TEquals("foo", doc.formbar); + + // bump counter + xhr = CouchDB.request("PUT", "/test_suite_db/_design/update/_update/bump-counter/"+docid, { + headers : {"X-Couch-Full-Commit":"true"} + }); + T(xhr.status == 201); + T(xhr.responseText == "<h1>bumped it!</h1>"); + doc = db.open(docid); + T(doc.counter == 1); + + // _update honors full commit if you need it to + xhr = CouchDB.request("PUT", "/test_suite_db/_design/update/_update/bump-counter/"+docid, { + 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); + + // 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); + + // COUCHDB-1229 - allow slashes in doc ids for update handlers + // /db/_design/doc/_update/handler/doc/id + + var doc = { + _id:"with/slash", + counter:1 + }; + db.save(doc); + xhr = CouchDB.request("PUT", "/test_suite_db/_design/update/_update/bump-counter/with/slash"); + TEquals(201, xhr.status, "should return a 200 status"); + TEquals("<h1>bumped it!</h1>", xhr.responseText, "should report bumping"); + + var doc = db.open("with/slash"); + TEquals(2, doc.counter, "counter should be 2"); + + // COUCHDB-648 - the code in the JSON response should be honored + + xhr = CouchDB.request("PUT", "/test_suite_db/_design/update/_update/code-n-bump/"+docid, { + headers : {"X-Couch-Full-Commit":"true"} + }); + T(xhr.status == 302); + T(xhr.responseText == "<h1>bumped it!</h1>"); + doc = db.open(docid); + T(doc.counter == 3); + + xhr = CouchDB.request("POST", "/test_suite_db/_design/update/_update/resp-code/"); + T(xhr.status == 302); + + xhr = CouchDB.request("POST", "/test_suite_db/_design/update/_update/resp-code-and-json/"); + TEquals(302, xhr.status); + T(JSON.parse(xhr.responseText).ok); + + // base64 response + xhr = CouchDB.request("PUT", "/test_suite_db/_design/update/_update/binary/"+docid, { + headers : {"X-Couch-Full-Commit":"false"}, + body : 'rubbish' + }); + T(xhr.status == 201); + T(xhr.responseText == "hello world!"); + T(/application\/octet-stream/.test(xhr.getResponseHeader("Content-Type"))); + + // Insert doc with empty id + xhr = CouchDB.request("PUT", "/test_suite_db/_design/update/_update/empty/foo"); + TEquals(400, xhr.status); + TEquals("Document id must not be empty", JSON.parse(xhr.responseText).reason); + +}; http://git-wip-us.apache.org/repos/asf/couchdb/blob/6a4893aa/test/javascript/tests/users_db.js ---------------------------------------------------------------------- diff --git a/test/javascript/tests/users_db.js b/test/javascript/tests/users_db.js new file mode 100644 index 0000000..56dae6b --- /dev/null +++ b/test/javascript/tests/users_db.js @@ -0,0 +1,173 @@ +// 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.users_db = function(debug) { + // This tests the users db, especially validations + // this should also test that you can log into the couch + + var usersDb = new CouchDB("test_suite_users", {"X-Couch-Full-Commit":"false"}); + + // test that you can treat "_user" as a db-name + // this can complicate people who try to secure the users db with + // an http proxy and fail to get both the actual db and the _user path + // maybe it's not the right approach... + // hard to know what else to do, as we don't let non-admins inspect the config + // to determine the actual users db name. + + function testFun() { + // 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({ + name: "[email protected]" + }, "funnybone"); + T(usersDb.save(jchrisUserDoc).ok); + + T(CouchDB.session().userCtx.name == null); + + // test that you can use basic auth aginst the users db + var s = CouchDB.session({ + headers : { + // base64_encode("[email protected]:funnybone") + "Authorization" : "Basic amNocmlzQGFwYWNoZS5vcmc6ZnVubnlib25l" + } + }); + T(s.userCtx.name == "[email protected]"); + 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" // name and pass of _:_ + } + }); + T(s.name == null); + 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 = []; + + // "roles" must be an array of strings + jchrisUserDoc.roles = [12]; + try { + usersDb.save(jchrisUserDoc); + T(false && "should only allow us to save doc when roles is an array of strings"); + } catch(e) { + TEquals(e.reason, "doc.roles can only contain strings"); + } + jchrisUserDoc.roles = []; + + // "roles" must exist + delete jchrisUserDoc.roles; + try { + usersDb.save(jchrisUserDoc); + T(false && "should only allow us to save doc when roles exists"); + } catch(e) { + T(e.reason == "doc.roles must exist"); + } + jchrisUserDoc.roles = []; + + // character : is not allowed in usernames + var joeUserDoc = CouchDB.prepareUserDoc({ + name: "joe:erlang" + }, "qwerty"); + try { + usersDb.save(joeUserDoc); + T(false, "shouldn't allow : in usernames"); + } catch(e) { + TEquals("Character `:` is not allowed in usernames.", e.reason); + } + + // test that you can login as a user with a password starting with : + var doc = CouchDB.prepareUserDoc({ + name: "[email protected]" + }, ":bar"); + T(usersDb.save(doc).ok); + + T(CouchDB.session().userCtx.name == null); + + // test that you can use basic auth aginst the users db + var s = CouchDB.session({ + headers : { + // base64_encode("[email protected]::bar") + "Authorization" : "Basic Zm9vQGV4YW1wbGUub3JnOjpiYXI=" + } + }); + T(s.userCtx.name == "[email protected]"); + + }; + + usersDb.deleteDb(); + run_on_modified_server( + [{section: "couch_httpd_auth", + key: "authentication_db", value: usersDb.name}], + testFun + ); + usersDb.deleteDb(); // cleanup + +}
