http://git-wip-us.apache.org/repos/asf/couchdb/blob/6a4893aa/test/javascript/tests/attachments_multipart.js ---------------------------------------------------------------------- diff --git a/test/javascript/tests/attachments_multipart.js b/test/javascript/tests/attachments_multipart.js new file mode 100644 index 0000000..6f924a7 --- /dev/null +++ b/test/javascript/tests/attachments_multipart.js @@ -0,0 +1,416 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +couchTests.attachments_multipart= function(debug) { + var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); + db.deleteDb(); + db.createDb(); + if (debug) debugger; + + // mime multipart + + var xhr = CouchDB.request("PUT", "/test_suite_db/multipart", { + headers: {"Content-Type": "multipart/related;boundary=\"abc123\""}, + body: + "--abc123\r\n" + + "content-type: application/json\r\n" + + "\r\n" + + JSON.stringify({ + "body":"This is a body.", + "_attachments":{ + "foo.txt": { + "follows":true, + "content_type":"application/test", + "length":21 + }, + "bar.txt": { + "follows":true, + "content_type":"application/test", + "length":20 + }, + "baz.txt": { + "follows":true, + "content_type":"text/plain", + "length":19 + } + } + }) + + "\r\n--abc123\r\n" + + "\r\n" + + "this is 21 chars long" + + "\r\n--abc123\r\n" + + "\r\n" + + "this is 20 chars lon" + + "\r\n--abc123\r\n" + + "\r\n" + + "this is 19 chars lo" + + "\r\n--abc123--epilogue" + }); + + var result = JSON.parse(xhr.responseText); + + T(result.ok); + + + + TEquals(201, xhr.status, "should send 201 Accepted"); + + xhr = CouchDB.request("GET", "/test_suite_db/multipart/foo.txt"); + + T(xhr.responseText == "this is 21 chars long"); + + xhr = CouchDB.request("GET", "/test_suite_db/multipart/bar.txt"); + + T(xhr.responseText == "this is 20 chars lon"); + + xhr = CouchDB.request("GET", "/test_suite_db/multipart/baz.txt"); + + T(xhr.responseText == "this is 19 chars lo"); + + // now edit an attachment + + var doc = db.open("multipart", {att_encoding_info: true}); + var firstrev = doc._rev; + + T(doc._attachments["foo.txt"].stub == true); + T(doc._attachments["bar.txt"].stub == true); + T(doc._attachments["baz.txt"].stub == true); + TEquals("undefined", typeof doc._attachments["foo.txt"].encoding); + TEquals("undefined", typeof doc._attachments["bar.txt"].encoding); + TEquals("gzip", doc._attachments["baz.txt"].encoding); + + //lets change attachment bar + delete doc._attachments["bar.txt"].stub; // remove stub member (or could set to false) + delete doc._attachments["bar.txt"].digest; // remove the digest (it's for the gzip form) + doc._attachments["bar.txt"].length = 18; + doc._attachments["bar.txt"].follows = true; + //lets delete attachment baz: + delete doc._attachments["baz.txt"]; + + var xhr = CouchDB.request("PUT", "/test_suite_db/multipart", { + headers: {"Content-Type": "multipart/related;boundary=\"abc123\""}, + body: + "--abc123\r\n" + + "content-type: application/json\r\n" + + "\r\n" + + JSON.stringify(doc) + + "\r\n--abc123\r\n" + + "\r\n" + + "this is 18 chars l" + + "\r\n--abc123--" + }); + TEquals(201, xhr.status); + + xhr = CouchDB.request("GET", "/test_suite_db/multipart/bar.txt"); + + T(xhr.responseText == "this is 18 chars l"); + + xhr = CouchDB.request("GET", "/test_suite_db/multipart/baz.txt"); + T(xhr.status == 404); + + // now test receiving multipart docs + + function getBoundary(xhr) { + var ctype = CouchDB.xhrheader(xhr, "Content-Type"); + var ctypeArgs = ctype.split("; ").slice(1); + var boundary = null; + for(var i=0; i<ctypeArgs.length; i++) { + if (ctypeArgs[i].indexOf("boundary=") == 0) { + boundary = ctypeArgs[i].split("=")[1]; + if (boundary.charAt(0) == '"') { + // stringified boundary, parse as json + // (will maybe not if there are escape quotes) + boundary = JSON.parse(boundary); + } + } + } + return boundary; + } + + function parseMultipart(xhr) { + var boundary = getBoundary(xhr); + var mimetext = CouchDB.xhrbody(xhr); + // strip off leading boundary + var leading = "--" + boundary + "\r\n"; + var last = "\r\n--" + boundary + "--"; + + // strip off leading and trailing boundary + var leadingIdx = mimetext.indexOf(leading) + leading.length; + var trailingIdx = mimetext.indexOf(last); + mimetext = mimetext.slice(leadingIdx, trailingIdx); + + // now split the sections + var sections = mimetext.split(new RegExp("\\r\\n--" + boundary)); + + // spilt out the headers for each section + for(var i=0; i < sections.length; i++) { + var section = sections[i]; + var headerEndIdx = section.indexOf("\r\n\r\n"); + var headersraw = section.slice(0, headerEndIdx).split(/\r\n/); + var body = section.slice(headerEndIdx + 4); + var headers = {}; + for(var j=0; j<headersraw.length; j++) { + var tmp = headersraw[j].split(": "); + headers[tmp[0]] = tmp[1]; + } + sections[i] = {"headers":headers, "body":body}; + } + + return sections; + } + + + xhr = CouchDB.request("GET", "/test_suite_db/multipart?attachments=true", + {headers:{"accept": "multipart/related,*/*;"}}); + + T(xhr.status == 200); + + // parse out the multipart + var sections = parseMultipart(xhr); + TEquals("790", xhr.getResponseHeader("Content-Length"), + "Content-Length should be correct"); + T(sections.length == 3); + // The first section is the json doc. Check it's content-type. + // Each part carries their own meta data. + TEquals("application/json", sections[0].headers['Content-Type'], + "Content-Type should be application/json for section[0]"); + TEquals("application/test", sections[1].headers['Content-Type'], + "Content-Type should be application/test for section[1]"); + TEquals("application/test", sections[2].headers['Content-Type'], + "Content-Type should be application/test for section[2]"); + + TEquals("21", sections[1].headers['Content-Length'], + "Content-Length should be 21 section[1]"); + TEquals("18", sections[2].headers['Content-Length'], + "Content-Length should be 18 section[2]"); + + TEquals('attachment; filename="foo.txt"', sections[1].headers['Content-Disposition'], + "Content-Disposition should be foo.txt section[1]"); + TEquals('attachment; filename="bar.txt"', sections[2].headers['Content-Disposition'], + "Content-Disposition should be bar.txt section[2]"); + + var doc = JSON.parse(sections[0].body); + + T(doc._attachments['foo.txt'].follows == true); + T(doc._attachments['bar.txt'].follows == true); + + T(sections[1].body == "this is 21 chars long"); + TEquals("this is 18 chars l", sections[2].body, "should be 18 chars long"); + + // now get attachments incrementally (only the attachments changes since + // a certain rev). + + xhr = CouchDB.request("GET", "/test_suite_db/multipart?atts_since=[\"" + firstrev + "\"]", + {headers:{"accept": "multipart/related, */*"}}); + + T(xhr.status == 200); + + var sections = parseMultipart(xhr); + + T(sections.length == 2); + + var doc = JSON.parse(sections[0].body); + + T(doc._attachments['foo.txt'].stub == true); + T(doc._attachments['bar.txt'].follows == true); + + TEquals("this is 18 chars l", sections[1].body, "should be 18 chars long 2"); + + // try the atts_since parameter together with the open_revs parameter + xhr = CouchDB.request( + "GET", + '/test_suite_db/multipart?open_revs=["' + + doc._rev + '"]&atts_since=["' + firstrev + '"]', + {headers: {"accept": "multipart/mixed"}} + ); + + T(xhr.status === 200); + + sections = parseMultipart(xhr); + // 1 section, with a multipart/related Content-Type + T(sections.length === 1); + T(sections[0].headers['Content-Type'].indexOf('multipart/related;') === 0); + + var innerSections = parseMultipart(sections[0]); + // 2 inner sections: a document body section plus an attachment data section + T(innerSections.length === 2); + T(innerSections[0].headers['Content-Type'] === 'application/json'); + + doc = JSON.parse(innerSections[0].body); + + T(doc._attachments['foo.txt'].stub === true); + T(doc._attachments['bar.txt'].follows === true); + + T(innerSections[1].body === "this is 18 chars l"); + + // try it with a rev that doesn't exist (should get all attachments) + + xhr = CouchDB.request("GET", "/test_suite_db/multipart?atts_since=[\"1-2897589\"]", + {headers:{"accept": "multipart/related,*/*;"}}); + + T(xhr.status == 200); + + var sections = parseMultipart(xhr); + + T(sections.length == 3); + + var doc = JSON.parse(sections[0].body); + + T(doc._attachments['foo.txt'].follows == true); + T(doc._attachments['bar.txt'].follows == true); + + T(sections[1].body == "this is 21 chars long"); + TEquals("this is 18 chars l", sections[2].body, "should be 18 chars long 3"); + // try it with a rev that doesn't exist, and one that does + + xhr = CouchDB.request("GET", "/test_suite_db/multipart?atts_since=[\"1-2897589\",\"" + firstrev + "\"]", + {headers:{"accept": "multipart/related,*/*;"}}); + + T(xhr.status == 200); + + var sections = parseMultipart(xhr); + + T(sections.length == 2); + + var doc = JSON.parse(sections[0].body); + + T(doc._attachments['foo.txt'].stub == true); + T(doc._attachments['bar.txt'].follows == true); + + TEquals("this is 18 chars l", sections[1].body, "should be 18 chars long 4"); + + // check that with the document multipart/mixed API it's possible to receive + // attachments in compressed form (if they're stored in compressed form) + + var server_config = [ + { + section: "attachments", + key: "compression_level", + value: "8" + }, + { + section: "attachments", + key: "compressible_types", + value: "text/plain" + } + ]; + + function testMultipartAttCompression() { + var doc = { _id: "foobar" }; + var lorem = + CouchDB.request("GET", "/_utils/script/test/lorem.txt").responseText; + var helloData = "hello world"; + + TEquals(true, db.save(doc).ok); + + var firstRev = doc._rev; + var xhr = CouchDB.request( + "PUT", + "/" + db.name + "/" + doc._id + "/data.bin?rev=" + firstRev, + { + body: helloData, + headers: {"Content-Type": "application/binary"} + } + ); + TEquals(201, xhr.status); + + var secondRev = db.open(doc._id)._rev; + xhr = CouchDB.request( + "PUT", + "/" + db.name + "/" + doc._id + "/lorem.txt?rev=" + secondRev, + { + body: lorem, + headers: {"Content-Type": "text/plain"} + } + ); + TEquals(201, xhr.status); + + var thirdRev = db.open(doc._id)._rev; + + xhr = CouchDB.request( + "GET", + '/' + db.name + '/' + doc._id + '?open_revs=["' + thirdRev + '"]', + { + headers: { + "Accept": "multipart/mixed", + "X-CouchDB-Send-Encoded-Atts": "true" + } + } + ); + TEquals(200, xhr.status); + + var sections = parseMultipart(xhr); + // 1 section, with a multipart/related Content-Type + TEquals(1, sections.length); + TEquals(0, + sections[0].headers['Content-Type'].indexOf('multipart/related;')); + + var innerSections = parseMultipart(sections[0]); + // 3 inner sections: a document body section plus 2 attachment data sections + TEquals(3, innerSections.length); + TEquals('application/json', innerSections[0].headers['Content-Type']); + + doc = JSON.parse(innerSections[0].body); + + TEquals(true, doc._attachments['lorem.txt'].follows); + TEquals("gzip", doc._attachments['lorem.txt'].encoding); + TEquals(true, doc._attachments['data.bin'].follows); + T(doc._attachments['data.bin'] !== "gzip"); + + if (innerSections[1].body === helloData) { + T(innerSections[2].body !== lorem); + } else if (innerSections[2].body === helloData) { + T(innerSections[1].body !== lorem); + } else { + T(false, "Could not found data.bin attachment data"); + } + + // now test that it works together with the atts_since parameter + + xhr = CouchDB.request( + "GET", + '/' + db.name + '/' + doc._id + '?open_revs=["' + thirdRev + '"]' + + '&atts_since=["' + secondRev + '"]', + { + headers: { + "Accept": "multipart/mixed", + "X-CouchDB-Send-Encoded-Atts": "true" + } + } + ); + TEquals(200, xhr.status); + + sections = parseMultipart(xhr); + // 1 section, with a multipart/related Content-Type + TEquals(1, sections.length); + TEquals(0, + sections[0].headers['Content-Type'].indexOf('multipart/related;')); + + innerSections = parseMultipart(sections[0]); + // 2 inner sections: a document body section plus 1 attachment data section + TEquals(2, innerSections.length); + TEquals('application/json', innerSections[0].headers['Content-Type']); + + doc = JSON.parse(innerSections[0].body); + + TEquals(true, doc._attachments['lorem.txt'].follows); + TEquals("gzip", doc._attachments['lorem.txt'].encoding); + TEquals("undefined", typeof doc._attachments['data.bin'].follows); + TEquals(true, doc._attachments['data.bin'].stub); + T(innerSections[1].body !== lorem); + } + + run_on_modified_server(server_config, testMultipartAttCompression); + + // cleanup + db.deleteDb(); +};
http://git-wip-us.apache.org/repos/asf/couchdb/blob/6a4893aa/test/javascript/tests/auth_cache.js ---------------------------------------------------------------------- diff --git a/test/javascript/tests/auth_cache.js b/test/javascript/tests/auth_cache.js new file mode 100644 index 0000000..39b9887 --- /dev/null +++ b/test/javascript/tests/auth_cache.js @@ -0,0 +1,269 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +couchTests.auth_cache = function(debug) { + + if (debug) debugger; + + // Simple secret key generator + function generateSecret(length) { + var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + + "0123456789+/"; + var secret = ''; + for (var i = 0; i < length; i++) { + secret += tab.charAt(Math.floor(Math.random() * 64)); + } + return secret; + } + + var authDb = new CouchDB("test_suite_users", {"X-Couch-Full-Commit":"false"}); + var server_config = [ + { + section: "couch_httpd_auth", + key: "authentication_db", + value: authDb.name + }, + { + section: "couch_httpd_auth", + key: "auth_cache_size", + value: "3" + }, + { + section: "httpd", + key: "authentication_handlers", + value: "{couch_httpd_auth, default_authentication_handler}" + }, + { + section: "couch_httpd_auth", + key: "secret", + value: generateSecret(64) + } + ]; + + + function hits() { + var hits = CouchDB.requestStats(["couchdb", "auth_cache_hits"], true); + return hits.value || 0; + } + + + function misses() { + var misses = CouchDB.requestStats(["couchdb", "auth_cache_misses"], true); + return misses.value || 0; + } + + + function testFun() { + var hits_before, + misses_before, + hits_after, + misses_after; + + var fdmanana = CouchDB.prepareUserDoc({ + name: "fdmanana", + roles: ["dev"] + }, "qwerty"); + + T(authDb.save(fdmanana).ok); + + var chris = CouchDB.prepareUserDoc({ + name: "chris", + roles: ["dev", "mafia", "white_costume"] + }, "the_god_father"); + + T(authDb.save(chris).ok); + + var joe = CouchDB.prepareUserDoc({ + name: "joe", + roles: ["erlnager"] + }, "functional"); + + T(authDb.save(joe).ok); + + var johndoe = CouchDB.prepareUserDoc({ + name: "johndoe", + roles: ["user"] + }, "123456"); + + T(authDb.save(johndoe).ok); + + hits_before = hits(); + misses_before = misses(); + + T(CouchDB.login("fdmanana", "qwerty").ok); + T(CouchDB.logout().ok); + + hits_after = hits(); + misses_after = misses(); + + T(misses_after === (misses_before + 1)); + T(hits_after === hits_before); + + hits_before = hits_after; + misses_before = misses_after; + + T(CouchDB.login("fdmanana", "qwerty").ok); + T(CouchDB.logout().ok); + + hits_after = hits(); + misses_after = misses(); + + T(misses_after === misses_before); + T(hits_after === (hits_before + 1)); + + hits_before = hits_after; + misses_before = misses_after; + + T(CouchDB.login("chris", "the_god_father").ok); + T(CouchDB.logout().ok); + + hits_after = hits(); + misses_after = misses(); + + T(misses_after === (misses_before + 1)); + T(hits_after === hits_before); + + hits_before = hits_after; + misses_before = misses_after; + + T(CouchDB.login("joe", "functional").ok); + T(CouchDB.logout().ok); + + hits_after = hits(); + misses_after = misses(); + + T(misses_after === (misses_before + 1)); + T(hits_after === hits_before); + + hits_before = hits_after; + misses_before = misses_after; + + T(CouchDB.login("johndoe", "123456").ok); + T(CouchDB.logout().ok); + + hits_after = hits(); + misses_after = misses(); + + T(misses_after === (misses_before + 1)); + T(hits_after === hits_before); + + hits_before = hits_after; + misses_before = misses_after; + + T(CouchDB.login("joe", "functional").ok); + T(CouchDB.logout().ok); + + hits_after = hits(); + misses_after = misses(); + + // it's an MRU cache, joe was removed from cache to add johndoe + T(misses_after === (misses_before + 1)); + T(hits_after === hits_before); + + hits_before = hits_after; + misses_before = misses_after; + + T(CouchDB.login("fdmanana", "qwerty").ok); + T(CouchDB.logout().ok); + + hits_after = hits(); + misses_after = misses(); + + T(misses_after === misses_before); + T(hits_after === (hits_before + 1)); + + hits_before = hits_after; + misses_before = misses_after; + + fdmanana.password = "foobar"; + T(authDb.save(fdmanana).ok); + + // cache was refreshed + T(CouchDB.login("fdmanana", "qwerty").error === "unauthorized"); + T(CouchDB.login("fdmanana", "foobar").ok); + T(CouchDB.logout().ok); + + hits_after = hits(); + misses_after = misses(); + + T(misses_after === misses_before); + T(hits_after === (hits_before + 2)); + + hits_before = hits_after; + misses_before = misses_after; + + // and yet another update + fdmanana.password = "javascript"; + T(authDb.save(fdmanana).ok); + + // cache was refreshed + T(CouchDB.login("fdmanana", "foobar").error === "unauthorized"); + T(CouchDB.login("fdmanana", "javascript").ok); + T(CouchDB.logout().ok); + + hits_after = hits(); + misses_after = misses(); + + T(misses_after === misses_before); + T(hits_after === (hits_before + 2)); + + T(authDb.deleteDoc(fdmanana).ok); + + hits_before = hits_after; + misses_before = misses_after; + + T(CouchDB.login("fdmanana", "javascript").error === "unauthorized"); + + hits_after = hits(); + misses_after = misses(); + + T(misses_after === misses_before); + T(hits_after === (hits_before + 1)); + + // login, compact authentication DB, login again and verify that + // there was a cache hit + hits_before = hits_after; + misses_before = misses_after; + + T(CouchDB.login("johndoe", "123456").ok); + T(CouchDB.logout().ok); + + hits_after = hits(); + misses_after = misses(); + + T(misses_after === (misses_before + 1)); + T(hits_after === hits_before); + + T(authDb.compact().ok); + + while (authDb.info().compact_running); + + hits_before = hits_after; + misses_before = misses_after; + + T(CouchDB.login("johndoe", "123456").ok); + T(CouchDB.logout().ok); + + hits_after = hits(); + misses_after = misses(); + + T(misses_after === misses_before); + T(hits_after === (hits_before + 1)); + } + + + authDb.deleteDb(); + run_on_modified_server(server_config, testFun); + + // cleanup + authDb.deleteDb(); +} http://git-wip-us.apache.org/repos/asf/couchdb/blob/6a4893aa/test/javascript/tests/basics.js ---------------------------------------------------------------------- diff --git a/test/javascript/tests/basics.js b/test/javascript/tests/basics.js new file mode 100644 index 0000000..993456c --- /dev/null +++ b/test/javascript/tests/basics.js @@ -0,0 +1,290 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +// Do some basic tests. +couchTests.basics = function(debug) { + var result = JSON.parse(CouchDB.request("GET", "/").responseText); + T(result.couchdb == "Welcome"); + + var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); + db.deleteDb(); + + // bug COUCHDB-100: DELETE on non-existent DB returns 500 instead of 404 + db.deleteDb(); + + db.createDb(); + + // PUT on existing DB should return 412 instead of 500 + xhr = CouchDB.request("PUT", "/test_suite_db/"); + T(xhr.status == 412); + if (debug) debugger; + + // creating a new DB should return Location header + // and it should work for dbs with slashes (COUCHDB-411) + var dbnames = ["test_suite_db", "test_suite_db%2Fwith_slashes"]; + dbnames.forEach(function(dbname) { + xhr = CouchDB.request("DELETE", "/" + dbname); + xhr = CouchDB.request("PUT", "/" + dbname); + TEquals(dbname, + xhr.getResponseHeader("Location").substr(-dbname.length), + "should return Location header to newly created document"); + TEquals(CouchDB.protocol, + xhr.getResponseHeader("Location").substr(0, CouchDB.protocol.length), + "should return absolute Location header to newly created document"); + }); + + // Get the database info, check the db_name + T(db.info().db_name == "test_suite_db"); + T(CouchDB.allDbs().indexOf("test_suite_db") != -1); + + // Get the database info, check the doc_count + T(db.info().doc_count == 0); + + // create a document and save it to the database + var doc = {_id:"0",a:1,b:1}; + var result = db.save(doc); + + T(result.ok==true); // return object has an ok member with a value true + T(result.id); // the _id of the document is set. + T(result.rev); // the revision id of the document is set. + + // Verify the input doc is now set with the doc id and rev + // (for caller convenience). + T(doc._id == result.id && doc._rev == result.rev); + + var id = result.id; // save off the id for later + + // make sure the revs_info status is good + var doc = db.open(id, {revs_info:true}); + T(doc._revs_info[0].status == "available"); + + // make sure you can do a seq=true option + var doc = db.open(id, {local_seq:true}); + T(doc._local_seq == 1); + + + // Create some more documents. + // Notice the use of the ok member on the return result. + T(db.save({_id:"1",a:2,b:4}).ok); + T(db.save({_id:"2",a:3,b:9}).ok); + T(db.save({_id:"3",a:4,b:16}).ok); + + // Check the database doc count + T(db.info().doc_count == 4); + + // COUCHDB-954 + var oldRev = db.save({_id:"COUCHDB-954", a:1}).rev; + var newRev = db.save({_id:"COUCHDB-954", _rev:oldRev}).rev; + + // test behavior of open_revs with explicit revision list + var result = db.open("COUCHDB-954", {open_revs:[oldRev,newRev]}); + T(result.length == 2, "should get two revisions back"); + T(result[0].ok); + T(result[1].ok); + + // latest=true suppresses non-leaf revisions + var result = db.open("COUCHDB-954", {open_revs:[oldRev,newRev], latest:true}); + T(result.length == 1, "should only get the child revision with latest=true"); + T(result[0].ok._rev == newRev, "should get the child and not the parent"); + + // latest=true returns a child when you ask for a parent + var result = db.open("COUCHDB-954", {open_revs:[oldRev], latest:true}); + T(result[0].ok._rev == newRev, "should get child when we requested parent"); + + // clean up after ourselves + db.save({_id:"COUCHDB-954", _rev:newRev, _deleted:true}); + + // Test a simple map functions + + // create a map function that selects all documents whose "a" member + // has a value of 4, and then returns the document's b value. + var mapFunction = function(doc){ + if (doc.a==4) + emit(null, doc.b); + }; + + var results = db.query(mapFunction); + + // verify only one document found and the result value (doc.b). + T(results.total_rows == 1 && results.rows[0].value == 16); + + // reopen document we saved earlier + var existingDoc = db.open(id); + + T(existingDoc.a==1); + + //modify and save + existingDoc.a=4; + db.save(existingDoc); + + // redo the map query + results = db.query(mapFunction); + + // the modified document should now be in the results. + T(results.total_rows == 2); + + // write 2 more documents + T(db.save({a:3,b:9}).ok); + T(db.save({a:4,b:16}).ok); + + results = db.query(mapFunction); + + // 1 more document should now be in the result. + T(results.total_rows == 3); + T(db.info().doc_count == 6); + + var reduceFunction = function(keys, values){ + return sum(values); + }; + + results = db.query(mapFunction, reduceFunction); + + T(results.rows[0].value == 33); + + // delete a document + T(db.deleteDoc(existingDoc).ok); + + // make sure we can't open the doc + T(db.open(existingDoc._id) == null); + + results = db.query(mapFunction); + + // 1 less document should now be in the results. + T(results.total_rows == 2); + T(db.info().doc_count == 5); + + // make sure we can still open the old rev of the deleted doc + T(db.open(existingDoc._id, {rev: existingDoc._rev}) != null); + // make sure restart works + T(db.ensureFullCommit().ok); + restartServer(); + + // make sure we can still open + T(db.open(existingDoc._id, {rev: existingDoc._rev}) != null); + + // test that the POST response has a Location header + var xhr = CouchDB.request("POST", "/test_suite_db", { + body: JSON.stringify({"foo":"bar"}), + headers: {"Content-Type": "application/json"} + }); + var resp = JSON.parse(xhr.responseText); + T(resp.ok); + var loc = xhr.getResponseHeader("Location"); + T(loc, "should have a Location header"); + var locs = loc.split('/'); + T(locs[locs.length-1] == resp.id); + T(locs[locs.length-2] == "test_suite_db"); + + // test that that POST's with an _id aren't overriden with a UUID. + var xhr = CouchDB.request("POST", "/test_suite_db", { + headers: {"Content-Type": "application/json"}, + body: JSON.stringify({"_id": "oppossum", "yar": "matey"}) + }); + var resp = JSON.parse(xhr.responseText); + T(resp.ok); + T(resp.id == "oppossum"); + var doc = db.open("oppossum"); + T(doc.yar == "matey"); + + // document put's should return a Location header + var xhr = CouchDB.request("PUT", "/test_suite_db/newdoc", { + body: JSON.stringify({"a":1}) + }); + TEquals("/test_suite_db/newdoc", + xhr.getResponseHeader("Location").substr(-21), + "should return Location header to newly created document"); + TEquals(CouchDB.protocol, + xhr.getResponseHeader("Location").substr(0, CouchDB.protocol.length), + "should return absolute Location header to newly created document"); + + // deleting a non-existent doc should be 404 + xhr = CouchDB.request("DELETE", "/test_suite_db/doc-does-not-exist"); + T(xhr.status == 404); + + // Check for invalid document members + var bad_docs = [ + ["goldfish", {"_zing": 4}], + ["zebrafish", {"_zoom": "hello"}], + ["mudfish", {"zane": "goldfish", "_fan": "something smells delicious"}], + ["tastyfish", {"_bing": {"wha?": "soda can"}}] + ]; + var test_doc = function(info) { + var data = JSON.stringify(info[1]); + xhr = CouchDB.request("PUT", "/test_suite_db/" + info[0], {body: data}); + T(xhr.status == 500); + result = JSON.parse(xhr.responseText); + T(result.error == "doc_validation"); + + xhr = CouchDB.request("POST", "/test_suite_db/", { + headers: {"Content-Type": "application/json"}, + body: data + }); + T(xhr.status == 500); + result = JSON.parse(xhr.responseText); + T(result.error == "doc_validation"); + }; + bad_docs.forEach(test_doc); + + // Check some common error responses. + // PUT body not an object + xhr = CouchDB.request("PUT", "/test_suite_db/bar", {body: "[]"}); + T(xhr.status == 400); + result = JSON.parse(xhr.responseText); + T(result.error == "bad_request"); + T(result.reason == "Document must be a JSON object"); + + // Body of a _bulk_docs is not an object + xhr = CouchDB.request("POST", "/test_suite_db/_bulk_docs", {body: "[]"}); + T(xhr.status == 400); + result = JSON.parse(xhr.responseText); + T(result.error == "bad_request"); + T(result.reason == "Request body must be a JSON object"); + + // Body of an _all_docs multi-get is not a {"key": [...]} structure. + xhr = CouchDB.request("POST", "/test_suite_db/_all_docs", {body: "[]"}); + T(xhr.status == 400); + result = JSON.parse(xhr.responseText); + T(result.error == "bad_request"); + T(result.reason == "Request body must be a JSON object"); + var data = "{\"keys\": 1}"; + xhr = CouchDB.request("POST", "/test_suite_db/_all_docs", {body:data}); + T(xhr.status == 400); + result = JSON.parse(xhr.responseText); + T(result.error == "bad_request"); + T(result.reason == "`keys` member must be a array."); + + // oops, the doc id got lost in code nirwana + xhr = CouchDB.request("DELETE", "/test_suite_db/?rev=foobarbaz"); + TEquals(400, xhr.status, "should return a bad request"); + result = JSON.parse(xhr.responseText); + TEquals("bad_request", result.error); + TEquals("You tried to DELETE a database with a ?rev= parameter. Did you mean to DELETE a document instead?", result.reason); + + // On restart, a request for creating a database that already exists can + // not override the existing database file + db = new CouchDB("test_suite_foobar"); + db.deleteDb(); + xhr = CouchDB.request("PUT", "/" + db.name); + TEquals(201, xhr.status); + + TEquals(true, db.save({"_id": "doc1"}).ok); + TEquals(true, db.ensureFullCommit().ok); + + TEquals(1, db.info().doc_count); + + restartServer(); + + xhr = CouchDB.request("PUT", "/" + db.name); + TEquals(412, xhr.status); + + TEquals(1, db.info().doc_count); +}; http://git-wip-us.apache.org/repos/asf/couchdb/blob/6a4893aa/test/javascript/tests/batch_save.js ---------------------------------------------------------------------- diff --git a/test/javascript/tests/batch_save.js b/test/javascript/tests/batch_save.js new file mode 100644 index 0000000..a1b0019 --- /dev/null +++ b/test/javascript/tests/batch_save.js @@ -0,0 +1,48 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +couchTests.batch_save = function(debug) { + var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); + db.deleteDb(); + db.createDb(); + if (debug) debugger; + + var i + for(i=0; i < 100; i++) { + T(db.save({_id:i.toString(),a:i,b:i}, {batch : "ok"}).ok); + + // test that response is 202 Accepted + T(db.last_req.status == 202); + } + + for(i=0; i < 100; i++) { + // attempt to save the same document a bunch of times + T(db.save({_id:"foo",a:i,b:i}, {batch : "ok"}).ok); + + // test that response is 202 Accepted + T(db.last_req.status == 202); + } + + while(db.allDocs().total_rows != 101){}; + + // repeat the tests for POST + for(i=0; i < 100; i++) { + var resp = db.request("POST", db.uri + "?batch=ok", { + headers: {"Content-Type": "application/json"}, + body: JSON.stringify({a:1}) + }); + T(JSON.parse(resp.responseText).ok); + } + + while(db.allDocs().total_rows != 201){}; + +}; http://git-wip-us.apache.org/repos/asf/couchdb/blob/6a4893aa/test/javascript/tests/bulk_docs.js ---------------------------------------------------------------------- diff --git a/test/javascript/tests/bulk_docs.js b/test/javascript/tests/bulk_docs.js new file mode 100644 index 0000000..27a97c8 --- /dev/null +++ b/test/javascript/tests/bulk_docs.js @@ -0,0 +1,124 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +couchTests.bulk_docs = function(debug) { + var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); + db.deleteDb(); + db.createDb(); + if (debug) debugger; + + var docs = makeDocs(5); + + // Create the docs + var results = db.bulkSave(docs); + + T(results.length == 5); + for (var i = 0; i < 5; i++) { + T(results[i].id == docs[i]._id); + T(results[i].rev); + // Update the doc + docs[i].string = docs[i].string + ".00"; + } + + // Save the docs + results = db.bulkSave(docs); + T(results.length == 5); + for (i = 0; i < 5; i++) { + T(results[i].id == i.toString()); + + // set the delete flag to delete the docs in the next step + docs[i]._deleted = true; + } + + // now test a bulk update with a conflict + // open and save + var doc = db.open("0"); + db.save(doc); + + // Now bulk delete the docs + results = db.bulkSave(docs); + + // doc "0" should be a conflict + T(results.length == 5); + T(results[0].id == "0"); + T(results[0].error == "conflict"); + T(typeof results[0].rev === "undefined"); // no rev member when a conflict + + // but the rest are not + for (i = 1; i < 5; i++) { + T(results[i].id == i.toString()); + T(results[i].rev); + T(db.open(docs[i]._id) == null); + } + + // now force a conflict to to save + + // save doc 0, this will cause a conflict when we save docs[0] + var doc = db.open("0"); + docs[0] = db.open("0"); + db.save(doc); + + docs[0].shooby = "dooby"; + + // Now save the bulk docs, When we use all_or_nothing, we don't get conflict + // checking, all docs are saved regardless of conflict status, or none are + // saved. + results = db.bulkSave(docs,{all_or_nothing:true}); + T(results.error === undefined); + + var doc = db.open("0", {conflicts:true}); + var docConflict = db.open("0", {rev:doc._conflicts[0]}); + + T(doc.shooby == "dooby" || docConflict.shooby == "dooby"); + + // verify creating a document with no id returns a new id + var req = CouchDB.request("POST", "/test_suite_db/_bulk_docs", { + body: JSON.stringify({"docs": [{"foo":"bar"}]}) + }); + results = JSON.parse(req.responseText); + + T(results[0].id != ""); + T(results[0].rev != ""); + + + // Regression test for failure on update/delete + var newdoc = {"_id": "foobar", "body": "baz"}; + T(db.save(newdoc).ok); + var update = {"_id": newdoc._id, "_rev": newdoc._rev, "body": "blam"}; + var torem = {"_id": newdoc._id, "_rev": newdoc._rev, "_deleted": true}; + results = db.bulkSave([update, torem]); + T(results[0].error == "conflict" || results[1].error == "conflict"); + + + // verify that sending a request with no docs causes error thrown + var req = CouchDB.request("POST", "/test_suite_db/_bulk_docs", { + body: JSON.stringify({"doc": [{"foo":"bar"}]}) + }); + + T(req.status == 400 ); + result = JSON.parse(req.responseText); + T(result.error == "bad_request"); + T(result.reason == "Missing JSON list of 'docs'"); + + // jira-911 + db.deleteDb(); + db.createDb(); + docs = []; + docs.push({"_id":"0", "a" : 0}); + docs.push({"_id":"1", "a" : 1}); + docs.push({"_id":"1", "a" : 2}); + docs.push({"_id":"3", "a" : 3}); + results = db.bulkSave(docs); + T(results[1].id == "1"); + T(results[1].error == undefined); + T(results[2].error == "conflict"); +}; http://git-wip-us.apache.org/repos/asf/couchdb/blob/6a4893aa/test/javascript/tests/changes.js ---------------------------------------------------------------------- diff --git a/test/javascript/tests/changes.js b/test/javascript/tests/changes.js new file mode 100644 index 0000000..d5a4236 --- /dev/null +++ b/test/javascript/tests/changes.js @@ -0,0 +1,738 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +function jsonp(obj) { + T(jsonp_flag == 0); + T(obj.results.length == 1 && obj.last_seq == 1, "jsonp"); + jsonp_flag = 1; +} + +couchTests.changes = function(debug) { + var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"true"}); + db.deleteDb(); + db.createDb(); + if (debug) debugger; + + var req = CouchDB.request("GET", "/test_suite_db/_changes"); + var resp = JSON.parse(req.responseText); + + T(resp.results.length == 0 && resp.last_seq == 0, "empty db"); + var docFoo = {_id:"foo", bar:1}; + T(db.save(docFoo).ok); + T(db.ensureFullCommit().ok); + T(db.open(docFoo._id)._id == docFoo._id); + + req = CouchDB.request("GET", "/test_suite_db/_changes"); + var resp = JSON.parse(req.responseText); + + T(resp.last_seq == 1); + T(resp.results.length == 1, "one doc db"); + T(resp.results[0].changes[0].rev == docFoo._rev); + + // test with callback + + run_on_modified_server( + [{section: "httpd", + key: "allow_jsonp", + value: "true"}], + function() { + var xhr = CouchDB.request("GET", "/test_suite_db/_changes?callback=jsonp"); + T(xhr.status == 200); + jsonp_flag = 0; + eval(xhr.responseText); + T(jsonp_flag == 1); + }); + + req = CouchDB.request("GET", "/test_suite_db/_changes?feed=continuous&timeout=10"); + var lines = req.responseText.split("\n"); + T(JSON.parse(lines[0]).changes[0].rev == docFoo._rev); + T(JSON.parse(lines[1]).last_seq == 1); + + var xhr; + + try { + xhr = CouchDB.newXhr(); + } catch (err) { + } + + // poor man's browser detection + var is_safari = false; + if(typeof(navigator) == "undefined") { + is_safari = true; // For CouchHTTP based runners + } else if(navigator.userAgent.match(/AppleWebKit/)) { + is_safari = true; + }; + if (!is_safari && xhr) { + // Only test the continuous stuff if we have a real XHR object + // with real async support. + + // WebKit (last checked on nightly #47686) does fail on processing + // the async-request properly while javascript is executed. + + xhr.open("GET", CouchDB.proxyUrl("/test_suite_db/_changes?feed=continuous&timeout=500"), true); + xhr.send(""); + + var docBar = {_id:"bar", bar:1}; + db.save(docBar); + + var lines, change1, change2; + waitForSuccess(function() { + lines = xhr.responseText.split("\n"); + change1 = JSON.parse(lines[0]); + change2 = JSON.parse(lines[1]); + if (change2.seq != 2) { + throw "bad seq, try again"; + } + return true; + }, "bar-only"); + + T(change1.seq == 1); + T(change1.id == "foo"); + + T(change2.seq == 2); + T(change2.id == "bar"); + T(change2.changes[0].rev == docBar._rev); + + + var docBaz = {_id:"baz", baz:1}; + db.save(docBaz); + + var change3; + waitForSuccess(function() { + lines = xhr.responseText.split("\n"); + change3 = JSON.parse(lines[2]); + if (change3.seq != 3) { + throw "bad seq, try again"; + } + return true; + }); + + T(change3.seq == 3); + T(change3.id == "baz"); + T(change3.changes[0].rev == docBaz._rev); + + + xhr = CouchDB.newXhr(); + + //verify the heartbeat newlines are sent + xhr.open("GET", CouchDB.proxyUrl("/test_suite_db/_changes?feed=continuous&heartbeat=10&timeout=500"), true); + xhr.send(""); + + var str; + waitForSuccess(function() { + str = xhr.responseText; + if (str.charAt(str.length - 1) != "\n" || str.charAt(str.length - 2) != "\n") { + throw("keep waiting"); + } + return true; + }, "heartbeat"); + + T(str.charAt(str.length - 1) == "\n"); + T(str.charAt(str.length - 2) == "\n"); + + // otherwise we'll continue to receive heartbeats forever + xhr.abort(); + + // test Server Sent Event (eventsource) + if (!!window.EventSource) { + var source = new EventSource( + "/test_suite_db/_changes?feed=eventsource"); + var results = []; + var sourceListener = function(e) { + var data = JSON.parse(e.data); + results.push(data); + }; + + source.addEventListener('message', sourceListener , false); + + waitForSuccess(function() { + if (results.length != 3) { + throw "bad seq, try again"; + } + return true; + }); + + source.removeEventListener('message', sourceListener, false); + + T(results[0].seq == 1); + T(results[0].id == "foo"); + + T(results[1].seq == 2); + T(results[1].id == "bar"); + T(results[1].changes[0].rev == docBar._rev); + } + + // test that we receive EventSource heartbeat events + if (!!window.EventSource) { + var source = new EventSource( + "/test_suite_db/_changes?feed=eventsource&heartbeat=10"); + + var count_heartbeats = 0; + source.addEventListener('heartbeat', function () { count_heartbeats = count_heartbeats + 1; } , false); + + waitForSuccess(function() { + if (count_heartbeats < 3) { + throw "keep waiting"; + } + return true; + }, "eventsource-heartbeat"); + + T(count_heartbeats >= 3); + source.close(); + } + + // test longpolling + xhr = CouchDB.newXhr(); + + xhr.open("GET", CouchDB.proxyUrl("/test_suite_db/_changes?feed=longpoll"), true); + xhr.send(""); + + waitForSuccess(function() { + lines = xhr.responseText.split("\n"); + if (lines[5] != '"last_seq":3}') { + throw("still waiting"); + } + return true; + }, "last_seq"); + + xhr = CouchDB.newXhr(); + + xhr.open("GET", CouchDB.proxyUrl("/test_suite_db/_changes?feed=longpoll&since=3"), true); + xhr.send(""); + + var docBarz = {_id:"barz", bar:1}; + db.save(docBarz); + + var parse_changes_line = function(line) { + if (line.charAt(line.length-1) == ",") { + var linetrimmed = line.substring(0, line.length-1); + } else { + var linetrimmed = line; + } + return JSON.parse(linetrimmed); + }; + + waitForSuccess(function() { + lines = xhr.responseText.split("\n"); + if (lines[3] != '"last_seq":4}') { + throw("still waiting"); + } + return true; + }, "change_lines"); + + var change = parse_changes_line(lines[1]); + T(change.seq == 4); + T(change.id == "barz"); + T(change.changes[0].rev == docBarz._rev); + T(lines[3]=='"last_seq":4}'); + + + // test since=now + xhr = CouchDB.newXhr(); + + xhr.open("GET", "/test_suite_db/_changes?feed=longpoll&since=now", true); + xhr.send(""); + + var docBarz = {_id:"barzzzz", bar:1}; + db.save(docBarz); + + var parse_changes_line = function(line) { + if (line.charAt(line.length-1) == ",") { + var linetrimmed = line.substring(0, line.length-1); + } else { + var linetrimmed = line; + } + return JSON.parse(linetrimmed); + }; + + waitForSuccess(function() { + lines = xhr.responseText.split("\n"); + if (lines[3] != '"last_seq":5}') { + throw("still waiting"); + } + return true; + }, "change_lines"); + + var change = parse_changes_line(lines[1]); + T(change.seq == 5); + T(change.id == "barzzzz"); + T(change.changes[0].rev == docBarz._rev); + T(lines[3]=='"last_seq":5}'); + + + } + + // test the filtered changes + var ddoc = { + _id : "_design/changes_filter", + "filters" : { + "bop" : "function(doc, req) { return (doc.bop);}", + "dynamic" : stringFun(function(doc, req) { + var field = req.query.field; + return doc[field]; + }), + "userCtx" : stringFun(function(doc, req) { + return doc.user && (doc.user == req.userCtx.name); + }), + "conflicted" : "function(doc, req) { return (doc._conflicts);}" + }, + options : { + local_seq : true + }, + views : { + local_seq : { + map : "function(doc) {emit(doc._local_seq, null)}" + }, + blah: { + map : 'function(doc) {' + + ' if (doc._id == "blah") {' + + ' emit(null, null);' + + ' }' + + '}' + } + } + }; + + db.save(ddoc); + + var req = CouchDB.request("GET", "/test_suite_db/_changes?filter=changes_filter/bop"); + var resp = JSON.parse(req.responseText); + T(resp.results.length == 0); + + db.save({"bop" : "foom"}); + db.save({"bop" : false}); + + var req = CouchDB.request("GET", "/test_suite_db/_changes?filter=changes_filter/bop"); + var resp = JSON.parse(req.responseText); + T(resp.results.length == 1, "filtered/bop"); + + req = CouchDB.request("GET", "/test_suite_db/_changes?filter=changes_filter/dynamic&field=woox"); + resp = JSON.parse(req.responseText); + T(resp.results.length == 0); + + req = CouchDB.request("GET", "/test_suite_db/_changes?filter=changes_filter/dynamic&field=bop"); + resp = JSON.parse(req.responseText); + T(resp.results.length == 1, "changes_filter/dynamic&field=bop"); + + if (!is_safari && xhr) { // full test requires parallel connections + // filter with longpoll + // longpoll filters full history when run without a since seq + xhr = CouchDB.newXhr(); + xhr.open("GET", CouchDB.proxyUrl("/test_suite_db/_changes?feed=longpoll&filter=changes_filter/bop"), false); + xhr.send(""); + var resp = JSON.parse(xhr.responseText); + T(resp.last_seq == 8); + // longpoll waits until a matching change before returning + xhr = CouchDB.newXhr(); + xhr.open("GET", CouchDB.proxyUrl("/test_suite_db/_changes?feed=longpoll&since=7&filter=changes_filter/bop"), true); + xhr.send(""); + db.save({"_id":"falsy", "bop" : ""}); // empty string is falsy + db.save({"_id":"bingo","bop" : "bingo"}); + + waitForSuccess(function() { + resp = JSON.parse(xhr.responseText); + return true; + }, "longpoll-since"); + + T(resp.last_seq == 10); + T(resp.results && resp.results.length > 0 && resp.results[0]["id"] == "bingo", "filter the correct update"); + xhr.abort(); + + var timeout = 500; + var last_seq = 11; + while (true) { + + // filter with continuous + xhr = CouchDB.newXhr(); + xhr.open("GET", CouchDB.proxyUrl("/test_suite_db/_changes?feed=continuous&filter=changes_filter/bop&timeout="+timeout), true); + xhr.send(""); + + db.save({"_id":"rusty", "bop" : "plankton"}); + T(xhr.readyState != 4, "test client too slow"); + var rusty = db.open("rusty", {cache_bust : new Date()}); + T(rusty._id == "rusty"); + + waitForSuccess(function() { // throws an error after 5 seconds + if (xhr.readyState != 4) { + throw("still waiting"); + } + return true; + }, "continuous-rusty"); + lines = xhr.responseText.split("\n"); + var good = false; + try { + JSON.parse(lines[3]); + good = true; + } catch(e) { + } + if (good) { + T(JSON.parse(lines[1]).id == "bingo", lines[1]); + T(JSON.parse(lines[2]).id == "rusty", lines[2]); + T(JSON.parse(lines[3]).last_seq == last_seq, lines[3]); + break; + } else { + xhr.abort(); + db.deleteDoc(rusty); + timeout = timeout * 2; + last_seq = last_seq + 2; + } + } + } + // error conditions + + // non-existing design doc + var req = CouchDB.request("GET", + "/test_suite_db/_changes?filter=nothingtosee/bop"); + TEquals(404, req.status, "should return 404 for non existant design doc"); + + // non-existing filter + var req = CouchDB.request("GET", + "/test_suite_db/_changes?filter=changes_filter/movealong"); + TEquals(404, req.status, "should return 404 for non existant filter fun"); + + // both + var req = CouchDB.request("GET", + "/test_suite_db/_changes?filter=nothingtosee/movealong"); + TEquals(404, req.status, + "should return 404 for non existant design doc and filter fun"); + + // changes get all_docs style with deleted docs + var doc = {a:1}; + db.save(doc); + db.deleteDoc(doc); + var req = CouchDB.request("GET", + "/test_suite_db/_changes?filter=changes_filter/bop&style=all_docs"); + var resp = JSON.parse(req.responseText); + var expect = (!is_safari && xhr) ? 3: 1; + TEquals(expect, resp.results.length, "should return matching rows"); + + // test filter on view function (map) + // + T(db.save({"_id":"blah", "bop" : "plankton"}).ok); + var req = CouchDB.request("GET", "/test_suite_db/_changes?filter=_view&view=changes_filter/blah"); + var resp = JSON.parse(req.responseText); + T(resp.results.length === 1); + T(resp.results[0].id === "blah"); + + + // test for userCtx + run_on_modified_server( + [{section: "httpd", + key: "authentication_handlers", + value: "{couch_httpd_auth, special_test_authentication_handler}"}, + {section:"httpd", + key: "WWW-Authenticate", + value: "X-Couch-Test-Auth"}], + + function() { + var authOpts = {"headers":{"WWW-Authenticate": "X-Couch-Test-Auth Chris Anderson:mp3"}}; + + var req = CouchDB.request("GET", "/_session", authOpts); + var resp = JSON.parse(req.responseText); + + T(db.save({"user" : "Noah Slater"}).ok); + var req = CouchDB.request("GET", "/test_suite_db/_changes?filter=changes_filter/userCtx", authOpts); + var resp = JSON.parse(req.responseText); + T(resp.results.length == 0); + + var docResp = db.save({"user" : "Chris Anderson"}); + T(docResp.ok); + T(db.ensureFullCommit().ok); + req = CouchDB.request("GET", "/test_suite_db/_changes?filter=changes_filter/userCtx", authOpts); + resp = JSON.parse(req.responseText); + T(resp.results.length == 1, "userCtx"); + T(resp.results[0].id == docResp.id); + } + ); + + req = CouchDB.request("GET", "/test_suite_db/_changes?limit=1"); + resp = JSON.parse(req.responseText); + TEquals(1, resp.results.length); + + //filter includes _conflicts + var id = db.save({'food' : 'pizza'}).id; + db.bulkSave([{_id: id, 'food' : 'pasta'}], {all_or_nothing:true}); + + req = CouchDB.request("GET", "/test_suite_db/_changes?filter=changes_filter/conflicted"); + resp = JSON.parse(req.responseText); + T(resp.results.length == 1, "filter=changes_filter/conflicted"); + + // test with erlang filter function + run_on_modified_server([{ + section: "native_query_servers", + key: "erlang", + value: "{couch_native_process, start_link, []}" + }], function() { + var erl_ddoc = { + _id: "_design/erlang", + language: "erlang", + filters: { + foo: + 'fun({Doc}, Req) -> ' + + ' case couch_util:get_value(<<"value">>, Doc) of' + + ' undefined -> false;' + + ' Value -> (Value rem 2) =:= 0;' + + ' _ -> false' + + ' end ' + + 'end.' + } + }; + + db.deleteDb(); + db.createDb(); + T(db.save(erl_ddoc).ok); + + var req = CouchDB.request("GET", "/test_suite_db/_changes?filter=erlang/foo"); + var resp = JSON.parse(req.responseText); + T(resp.results.length === 0); + + T(db.save({_id: "doc1", value : 1}).ok); + T(db.save({_id: "doc2", value : 2}).ok); + T(db.save({_id: "doc3", value : 3}).ok); + T(db.save({_id: "doc4", value : 4}).ok); + + var req = CouchDB.request("GET", "/test_suite_db/_changes?filter=erlang/foo"); + var resp = JSON.parse(req.responseText); + T(resp.results.length === 2); + T(resp.results[0].id === "doc2"); + T(resp.results[1].id === "doc4"); + + // test filtering on docids + // + + var options = { + headers: {"Content-Type": "application/json"}, + body: JSON.stringify({"doc_ids": ["something", "anotherthing", "andmore"]}) + }; + + var req = CouchDB.request("POST", "/test_suite_db/_changes?filter=_doc_ids", options); + var resp = JSON.parse(req.responseText); + T(resp.results.length === 0); + + T(db.save({"_id":"something", "bop" : "plankton"}).ok); + var req = CouchDB.request("POST", "/test_suite_db/_changes?filter=_doc_ids", options); + var resp = JSON.parse(req.responseText); + T(resp.results.length === 1); + T(resp.results[0].id === "something"); + + T(db.save({"_id":"anotherthing", "bop" : "plankton"}).ok); + var req = CouchDB.request("POST", "/test_suite_db/_changes?filter=_doc_ids", options); + var resp = JSON.parse(req.responseText); + T(resp.results.length === 2); + T(resp.results[0].id === "something"); + T(resp.results[1].id === "anotherthing"); + + var docids = JSON.stringify(["something", "anotherthing", "andmore"]), + req = CouchDB.request("GET", "/test_suite_db/_changes?filter=_doc_ids&doc_ids="+docids, options); + var resp = JSON.parse(req.responseText); + T(resp.results.length === 2); + T(resp.results[0].id === "something"); + T(resp.results[1].id === "anotherthing"); + + var req = CouchDB.request("GET", "/test_suite_db/_changes?filter=_design"); + var resp = JSON.parse(req.responseText); + T(resp.results.length === 1); + T(resp.results[0].id === "_design/erlang"); + + + if (!is_safari && xhr) { + // filter docids with continuous + xhr = CouchDB.newXhr(); + xhr.open("POST", CouchDB.proxyUrl("/test_suite_db/_changes?feed=continuous&timeout=500&since=7&filter=_doc_ids"), true); + xhr.setRequestHeader("Content-Type", "application/json"); + + xhr.send(options.body); + + T(db.save({"_id":"andmore", "bop" : "plankton"}).ok); + + waitForSuccess(function() { + if (xhr.readyState != 4) { + throw("still waiting"); + } + return true; + }, "andmore-only"); + + var line = JSON.parse(xhr.responseText.split("\n")[0]); + T(line.seq == 8); + T(line.id == "andmore"); + } + }); + + // COUCHDB-1037 - empty result for ?limit=1&filter=foo/bar in some cases + T(db.deleteDb()); + T(db.createDb()); + + ddoc = { + _id: "_design/testdocs", + filters: { + testdocsonly: (function(doc, req) { + return (typeof doc.integer === "number"); + }).toString() + } + }; + T(db.save(ddoc)); + + ddoc = { + _id: "_design/foobar", + foo: "bar" + }; + T(db.save(ddoc)); + + db.bulkSave(makeDocs(0, 5)); + + req = CouchDB.request("GET", "/" + db.name + "/_changes"); + resp = JSON.parse(req.responseText); + TEquals(7, resp.last_seq); + TEquals(7, resp.results.length); + + req = CouchDB.request( + "GET", "/"+ db.name + "/_changes?limit=1&filter=testdocs/testdocsonly"); + resp = JSON.parse(req.responseText); + TEquals(3, resp.last_seq); + TEquals(1, resp.results.length); + TEquals("0", resp.results[0].id); + + req = CouchDB.request( + "GET", "/" + db.name + "/_changes?limit=2&filter=testdocs/testdocsonly"); + resp = JSON.parse(req.responseText); + TEquals(4, resp.last_seq); + TEquals(2, resp.results.length); + TEquals("0", resp.results[0].id); + TEquals("1", resp.results[1].id); + + TEquals(0, CouchDB.requestStats(['couchdb', 'httpd', 'clients_requesting_changes'], true).value); + CouchDB.request("GET", "/" + db.name + "/_changes"); + TEquals(0, CouchDB.requestStats(['couchdb', 'httpd', 'clients_requesting_changes'], true).value); + + // COUCHDB-1256 + T(db.deleteDb()); + T(db.createDb()); + + T(db.save({"_id":"foo", "a" : 123}).ok); + T(db.save({"_id":"bar", "a" : 456}).ok); + + options = { + headers: {"Content-Type": "application/json"}, + body: JSON.stringify({"_rev":"1-cc609831f0ca66e8cd3d4c1e0d98108a", "a":456}) + }; + req = CouchDB.request("PUT", "/" + db.name + "/foo?new_edits=false", options); + + req = CouchDB.request("GET", "/" + db.name + "/_changes?style=all_docs"); + resp = JSON.parse(req.responseText); + + TEquals(3, resp.last_seq); + TEquals(2, resp.results.length); + + req = CouchDB.request("GET", "/" + db.name + "/_changes?style=all_docs&since=2"); + resp = JSON.parse(req.responseText); + + TEquals(3, resp.last_seq); + TEquals(1, resp.results.length); + TEquals(2, resp.results[0].changes.length); + + // COUCHDB-1852 + T(db.deleteDb()); + T(db.createDb()); + + // create 4 documents... this assumes the update sequnce will start from 0 and get to 4 + db.save({"bop" : "foom"}); + db.save({"bop" : "foom"}); + db.save({"bop" : "foom"}); + db.save({"bop" : "foom"}); + + // simulate an EventSource request with a Last-Event-ID header + req = CouchDB.request("GET", "/test_suite_db/_changes?feed=eventsource&timeout=0&since=0", + {"headers": {"Accept": "text/event-stream", "Last-Event-ID": "2"}}); + + // "parse" the eventsource response and collect only the "id: ..." lines + var changes = req.responseText.split('\n') + .map(function (el) { + return el.split(":").map(function (el) { return el.trim()}); + }) + .filter(function (el) { return (el[0] === "id"); }) + + // make sure we only got 2 changes, and they are update_seq=3 and update_seq=4 + T(changes.length === 2); + T(changes[0][1] === "3"); + T(changes[1][1] === "4"); + + // COUCHDB-1923 + T(db.deleteDb()); + T(db.createDb()); + + var attachmentData = "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="; + + db.bulkSave(makeDocs(20, 30, { + _attachments:{ + "foo.txt": { + content_type:"text/plain", + data: attachmentData + }, + "bar.txt": { + content_type:"text/plain", + data: attachmentData + } + } + })); + + var mapFunction = function(doc) { + var count = 0; + + for(var idx in doc._attachments) { + count = count + 1; + } + + emit(parseInt(doc._id), count); + }; + + var req = CouchDB.request("GET", "/test_suite_db/_changes?include_docs=true"); + var resp = JSON.parse(req.responseText); + + T(resp.results.length == 10); + T(resp.results[0].doc._attachments['foo.txt'].stub === true); + T(resp.results[0].doc._attachments['foo.txt'].data === undefined); + T(resp.results[0].doc._attachments['foo.txt'].encoding === undefined); + T(resp.results[0].doc._attachments['foo.txt'].encoded_length === undefined); + T(resp.results[0].doc._attachments['bar.txt'].stub === true); + T(resp.results[0].doc._attachments['bar.txt'].data === undefined); + T(resp.results[0].doc._attachments['bar.txt'].encoding === undefined); + T(resp.results[0].doc._attachments['bar.txt'].encoded_length === undefined); + + var req = CouchDB.request("GET", "/test_suite_db/_changes?include_docs=true&attachments=true"); + var resp = JSON.parse(req.responseText); + + T(resp.results.length == 10); + T(resp.results[0].doc._attachments['foo.txt'].stub === undefined); + T(resp.results[0].doc._attachments['foo.txt'].data === attachmentData); + T(resp.results[0].doc._attachments['foo.txt'].encoding === undefined); + T(resp.results[0].doc._attachments['foo.txt'].encoded_length === undefined); + T(resp.results[0].doc._attachments['bar.txt'].stub === undefined); + T(resp.results[0].doc._attachments['bar.txt'].data == attachmentData); + T(resp.results[0].doc._attachments['bar.txt'].encoding === undefined); + T(resp.results[0].doc._attachments['bar.txt'].encoded_length === undefined); + + var req = CouchDB.request("GET", "/test_suite_db/_changes?include_docs=true&att_encoding_info=true"); + var resp = JSON.parse(req.responseText); + + T(resp.results.length == 10); + T(resp.results[0].doc._attachments['foo.txt'].stub === true); + T(resp.results[0].doc._attachments['foo.txt'].data === undefined); + T(resp.results[0].doc._attachments['foo.txt'].encoding === "gzip"); + T(resp.results[0].doc._attachments['foo.txt'].encoded_length === 47); + T(resp.results[0].doc._attachments['bar.txt'].stub === true); + T(resp.results[0].doc._attachments['bar.txt'].data === undefined); + T(resp.results[0].doc._attachments['bar.txt'].encoding === "gzip"); + T(resp.results[0].doc._attachments['bar.txt'].encoded_length === 47); + + // cleanup + db.deleteDb(); +}; http://git-wip-us.apache.org/repos/asf/couchdb/blob/6a4893aa/test/javascript/tests/coffee.js ---------------------------------------------------------------------- diff --git a/test/javascript/tests/coffee.js b/test/javascript/tests/coffee.js new file mode 100644 index 0000000..9306124 --- /dev/null +++ b/test/javascript/tests/coffee.js @@ -0,0 +1,67 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +// test basic coffeescript functionality +couchTests.coffee = function(debug) { + var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); + db.deleteDb(); + db.createDb(); + if (debug) debugger; + + var ddoc = { + _id: "_design/coffee", + language: "coffeescript", + views: { + myview: { + map: '(doc) -> if doc.foo\n emit(doc.foo, 1)', + reduce: '(keys, values, rereduce) ->\n sum = 0\n for x in values\n sum = sum + x\n sum' + } + }, + shows: { + myshow: '(doc) ->\n "Foo #{doc.foo}"' + }, + lists: { + mylist: '(head, req) ->\n while row = getRow()\n send("Foo #{row.value}")\n return "Foo"' + }, + filters: { + filter: "(doc) ->\n doc.foo" + } + }; + + db.save(ddoc); + + var docs = [ + {_id:"a", foo: 100}, + {foo:1}, + {foo:1}, + {foo:2}, + {foo:2}, + {bar:1}, + {bar:1}, + {bar:2}, + {bar:2} + ]; + + db.bulkSave(docs); + + var res = db.view("coffee/myview"); + TEquals(5, res.rows[0].value, "should sum up values"); + + var res = CouchDB.request("GET", "/" + db.name + "/_design/coffee/_show/myshow/a"); + TEquals("Foo 100", res.responseText, "should show 100"); + + var res = CouchDB.request("GET", "/" + db.name + "/_design/coffee/_list/mylist/myview"); + TEquals("Foo 5Foo", res.responseText, "should list"); + + var changes = db.changes({filter: "coffee/filter"}); + TEquals(5, changes.results.length, "should have changes"); +}; http://git-wip-us.apache.org/repos/asf/couchdb/blob/6a4893aa/test/javascript/tests/compact.js ---------------------------------------------------------------------- diff --git a/test/javascript/tests/compact.js b/test/javascript/tests/compact.js new file mode 100644 index 0000000..68c83b3 --- /dev/null +++ b/test/javascript/tests/compact.js @@ -0,0 +1,65 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +couchTests.compact = function(debug) { + var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); + db.deleteDb(); + db.createDb(); + if (debug) debugger; + var docs = makeDocs(0, 20); + db.bulkSave(docs); + + var binAttDoc = { + _id: "bin_doc", + _attachments:{ + "foo.txt": { + content_type:"text/plain", + data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ=" + } + } + }; + + T(db.save(binAttDoc).ok); + + var originalsize = db.info().disk_size; + var originaldatasize = db.info().data_size; + var start_time = db.info().instance_start_time; + + TEquals("number", typeof originaldatasize, "data_size is a number"); + T(originaldatasize < originalsize, "data size is < then db file size"); + + for(var i in docs) { + db.deleteDoc(docs[i]); + } + T(db.ensureFullCommit().ok); + var deletesize = db.info().disk_size; + T(deletesize > originalsize); + T(db.setDbProperty("_revs_limit", 666).ok); + + T(db.compact().ok); + T(db.last_req.status == 202); + // compaction isn't instantaneous, loop until done + while (db.info().compact_running) {}; + T(db.info().instance_start_time == start_time); + T(db.getDbProperty("_revs_limit") === 666); + + T(db.ensureFullCommit().ok); + restartServer(); + var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc/foo.txt"); + T(xhr.responseText == "This is a base64 encoded text"); + T(xhr.getResponseHeader("Content-Type") == "text/plain"); + T(db.info().doc_count == 1); + T(db.info().disk_size < deletesize); + TEquals("number", typeof db.info().data_size, "data_size is a number"); + T(db.info().data_size < db.info().disk_size, "data size is < then db file size"); + +}; http://git-wip-us.apache.org/repos/asf/couchdb/blob/6a4893aa/test/javascript/tests/config.js ---------------------------------------------------------------------- diff --git a/test/javascript/tests/config.js b/test/javascript/tests/config.js new file mode 100644 index 0000000..37b339b --- /dev/null +++ b/test/javascript/tests/config.js @@ -0,0 +1,211 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +couchTests.config = function(debug) { + var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); + db.deleteDb(); + db.createDb(); + if (debug) debugger; + + // test that /_config returns all the settings + var xhr = CouchDB.request("GET", "/_config"); + var config = JSON.parse(xhr.responseText); + + /* + if we run on standard ports, we can't extract + the number from the URL. Instead we try to guess + from the protocol what port we are running on. + If we can't guess, we don't test for the port. + Overengineering FTW. + */ + var server_port = CouchDB.host.split(':'); + if(server_port.length == 1 && CouchDB.inBrowser) { + if(CouchDB.protocol == "http://") { + port = "80"; + } + if(CouchDB.protocol == "https://") { + port = "443"; + } + } else { + port = server_port.pop(); + } + + if(CouchDB.protocol == "http://") { + config_port = config.httpd.port; + } + if(CouchDB.protocol == "https://") { + config_port = config.ssl.port; + } + + if(port && config_port != "0") { + TEquals(config_port, port, "ports should match"); + } + + T(config.couchdb.database_dir); + T(config.daemons.httpd); + T(config.httpd_global_handlers._config); + // T(config.log.level); + T(config.query_servers.javascript); + + // test that settings can be altered, and that an undefined whitelist allows any change + TEquals(undefined, config.httpd.config_whitelist, "Default whitelist is empty"); + xhr = CouchDB.request("PUT", "/_config/test/foo",{ + body : JSON.stringify("bar"), + headers: {"X-Couch-Persist": "false"} + }); + T(xhr.status == 200); + xhr = CouchDB.request("GET", "/_config/test"); + config = JSON.parse(xhr.responseText); + T(config.foo == "bar"); + + // you can get a single key + xhr = CouchDB.request("GET", "/_config/test/foo"); + config = JSON.parse(xhr.responseText); + T(config == "bar"); + + // Server-side password hashing, and raw updates disabling that. + var password_plain = 's3cret'; + var password_hashed = null; + + xhr = CouchDB.request("PUT", "/_config/admins/administrator",{ + body : JSON.stringify(password_plain), + headers: {"X-Couch-Persist": "false"} + }); + TEquals(200, xhr.status, "Create an admin in the config"); + + T(CouchDB.login("administrator", password_plain).ok); + + xhr = CouchDB.request("GET", "/_config/admins/administrator"); + password_hashed = JSON.parse(xhr.responseText); + T(password_hashed.match(/^-pbkdf2-/) || password_hashed.match(/^-hashed-/), + "Admin password is hashed"); + + xhr = CouchDB.request("PUT", "/_config/admins/administrator?raw=nothanks",{ + body : JSON.stringify(password_hashed), + headers: {"X-Couch-Persist": "false"} + }); + TEquals(400, xhr.status, "CouchDB rejects an invalid 'raw' option"); + + xhr = CouchDB.request("PUT", "/_config/admins/administrator?raw=true",{ + body : JSON.stringify(password_hashed), + headers: {"X-Couch-Persist": "false"} + }); + TEquals(200, xhr.status, "Set an raw, pre-hashed admin password"); + + xhr = CouchDB.request("PUT", "/_config/admins/administrator?raw=false",{ + body : JSON.stringify(password_hashed), + headers: {"X-Couch-Persist": "false"} + }); + TEquals(200, xhr.status, "Set an admin password with raw=false"); + + // The password is literally the string "-pbkdf2-abcd...". + T(CouchDB.login("administrator", password_hashed).ok); + + xhr = CouchDB.request("GET", "/_config/admins/administrator"); + T(password_hashed != JSON.parse(xhr.responseText), + "Hashed password was not stored as a raw string"); + + xhr = CouchDB.request("DELETE", "/_config/admins/administrator",{ + headers: {"X-Couch-Persist": "false"} + }); + TEquals(200, xhr.status, "Delete an admin from the config"); + T(CouchDB.logout().ok); + + // Non-term whitelist values allow further modification of the whitelist. + xhr = CouchDB.request("PUT", "/_config/httpd/config_whitelist",{ + body : JSON.stringify("!This is an invalid Erlang term!"), + headers: {"X-Couch-Persist": "false"} + }); + TEquals(200, xhr.status, "Set config whitelist to an invalid Erlang term"); + xhr = CouchDB.request("DELETE", "/_config/httpd/config_whitelist",{ + headers: {"X-Couch-Persist": "false"} + }); + TEquals(200, xhr.status, "Modify whitelist despite it being invalid syntax"); + + // Non-list whitelist values allow further modification of the whitelist. + xhr = CouchDB.request("PUT", "/_config/httpd/config_whitelist",{ + body : JSON.stringify("{[yes, a_valid_erlang_term, but_unfortunately, not_a_list]}"), + headers: {"X-Couch-Persist": "false"} + }); + TEquals(200, xhr.status, "Set config whitelist to an non-list term"); + xhr = CouchDB.request("DELETE", "/_config/httpd/config_whitelist",{ + headers: {"X-Couch-Persist": "false"} + }); + TEquals(200, xhr.status, "Modify whitelist despite it not being a list"); + + // Keys not in the whitelist may not be modified. + xhr = CouchDB.request("PUT", "/_config/httpd/config_whitelist",{ + body : JSON.stringify("[{httpd,config_whitelist}, {test,foo}]"), + headers: {"X-Couch-Persist": "false"} + }); + TEquals(200, xhr.status, "Set config whitelist to something valid"); + + ["PUT", "DELETE"].forEach(function(method) { + ["test/not_foo", "not_test/foo", "neither_test/nor_foo"].forEach(function(pair) { + var path = "/_config/" + pair; + var test_name = method + " to " + path + " disallowed: not whitelisted"; + + xhr = CouchDB.request(method, path, { + body : JSON.stringify("Bummer! " + test_name), + headers: {"X-Couch-Persist": "false"} + }); + TEquals(400, xhr.status, test_name); + }); + }); + + // Keys in the whitelist may be modified. + ["PUT", "DELETE"].forEach(function(method) { + xhr = CouchDB.request(method, "/_config/test/foo",{ + body : JSON.stringify(method + " to whitelisted config variable"), + headers: {"X-Couch-Persist": "false"} + }); + TEquals(200, xhr.status, "Keys in the whitelist may be modified"); + }); + + // Non-2-tuples in the whitelist are ignored + xhr = CouchDB.request("PUT", "/_config/httpd/config_whitelist",{ + body : JSON.stringify("[{httpd,config_whitelist}, these, {are}, {nOt, 2, tuples}," + + " [so], [they, will], [all, become, noops], {test,foo}]"), + headers: {"X-Couch-Persist": "false"} + }); + TEquals(200, xhr.status, "Set config whitelist with some inert values"); + ["PUT", "DELETE"].forEach(function(method) { + xhr = CouchDB.request(method, "/_config/test/foo",{ + body : JSON.stringify(method + " to whitelisted config variable"), + headers: {"X-Couch-Persist": "false"} + }); + TEquals(200, xhr.status, "Update whitelisted variable despite invalid entries"); + }); + + // Atoms, binaries, and strings suffice as whitelist sections and keys. + ["{test,foo}", '{"test","foo"}', '{<<"test">>,<<"foo">>}'].forEach(function(pair) { + xhr = CouchDB.request("PUT", "/_config/httpd/config_whitelist",{ + body : JSON.stringify("[{httpd,config_whitelist}, " + pair + "]"), + headers: {"X-Couch-Persist": "false"} + }); + TEquals(200, xhr.status, "Set config whitelist to include " + pair); + + var pair_format = {"t":"tuple", '"':"string", "<":"binary"}[pair[1]]; + ["PUT", "DELETE"].forEach(function(method) { + xhr = CouchDB.request(method, "/_config/test/foo",{ + body : JSON.stringify(method + " with " + pair_format), + headers: {"X-Couch-Persist": "false"} + }); + TEquals(200, xhr.status, "Whitelist works with " + pair_format); + }); + }); + + xhr = CouchDB.request("DELETE", "/_config/httpd/config_whitelist",{ + headers: {"X-Couch-Persist": "false"} + }); + TEquals(200, xhr.status, "Reset config whitelist to undefined"); +}; http://git-wip-us.apache.org/repos/asf/couchdb/blob/6a4893aa/test/javascript/tests/conflicts.js ---------------------------------------------------------------------- diff --git a/test/javascript/tests/conflicts.js b/test/javascript/tests/conflicts.js new file mode 100644 index 0000000..79266ab --- /dev/null +++ b/test/javascript/tests/conflicts.js @@ -0,0 +1,119 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +// Do some edit conflict detection tests +couchTests.conflicts = function(debug) { + var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); + db.deleteDb(); + db.createDb(); + if (debug) debugger; + + // create a doc and save + var doc = {_id:"foo",a:1,b:1}; + T(db.save(doc).ok); + + // reopen + var doc2 = db.open(doc._id); + + // ensure the revisions are the same + T(doc._id == doc2._id && doc._rev == doc2._rev); + + // edit the documents. + doc.a = 2; + doc2.a = 3; + + // save one document + T(db.save(doc).ok); + + // save the other document + try { + db.save(doc2); // this should generate a conflict exception + T("no save conflict 1" && false); // we shouldn't hit here + } catch (e) { + T(e.error == "conflict"); + } + + var changes = db.changes(); + + T(changes.results.length == 1); + + // Now clear out the _rev member and save. This indicates this document is + // new, not based on an existing revision. + doc2._rev = undefined; + try { + db.save(doc2); // this should generate a conflict exception + T("no save conflict 2" && false); // we shouldn't hit here + } catch (e) { + T(e.error == "conflict"); + } + + // Make a few bad requests, specifying conflicting revs + // ?rev doesn't match body + var xhr = CouchDB.request("PUT", "/test_suite_db/foo?rev=1-foobar", { + body : JSON.stringify(doc) + }); + T(xhr.status == 400); + + // If-Match doesn't match body + xhr = CouchDB.request("PUT", "/test_suite_db/foo", { + headers: {"If-Match": "1-foobar"}, + body: JSON.stringify(doc) + }); + T(xhr.status == 400); + + // ?rev= doesn't match If-Match + xhr = CouchDB.request("PUT", "/test_suite_db/foo?rev=1-boobaz", { + headers: {"If-Match": "1-foobar"}, + body: JSON.stringify(doc2) + }); + T(xhr.status == 400); + + // Now update the document using ?rev= + xhr = CouchDB.request("PUT", "/test_suite_db/foo?rev=" + doc._rev, { + body: JSON.stringify(doc) + }); + T(xhr.status == 201); + + // reopen + var doc = db.open(doc._id); + + // Now delete the document from the database + T(db.deleteDoc(doc).ok); + + T(db.save(doc2).ok); // we can save a new document over a deletion without + // knowing the deletion rev. + + // Verify COUCHDB-1178 + var r1 = {"_id":"doc","foo":"bar"}; + var r2 = {"_id":"doc","foo":"baz","_rev":"1-4c6114c65e295552ab1019e2b046b10e"}; + var r3 = {"_id":"doc","foo":"bam","_rev":"2-cfcd6781f13994bde69a1c3320bfdadb"}; + var r4 = {"_id":"doc","foo":"bat","_rev":"3-cc2f3210d779aef595cd4738be0ef8ff"}; + + T(db.save({"_id":"_design/couchdb-1178","validate_doc_update":"function(){}"}).ok); + T(db.save(r1).ok); + T(db.save(r2).ok); + T(db.save(r3).ok); + + T(db.compact().ok); + while (db.info().compact_running) {}; + + TEquals({"_id":"doc", + "_rev":"3-cc2f3210d779aef595cd4738be0ef8ff", + "foo":"bam", + "_revisions":{"start":3, + "ids":["cc2f3210d779aef595cd4738be0ef8ff", + "cfcd6781f13994bde69a1c3320bfdadb", + "4c6114c65e295552ab1019e2b046b10e"]}}, + db.open("doc", {"revs": true})); + TEquals([], db.bulkSave([r4, r3, r2], {"new_edits":false}), "no failures"); + +}; http://git-wip-us.apache.org/repos/asf/couchdb/blob/6a4893aa/test/javascript/tests/content_negotiation.js ---------------------------------------------------------------------- diff --git a/test/javascript/tests/content_negotiation.js b/test/javascript/tests/content_negotiation.js new file mode 100644 index 0000000..36e7dfb --- /dev/null +++ b/test/javascript/tests/content_negotiation.js @@ -0,0 +1,39 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +couchTests.content_negotiation = function(debug) { + var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); + db.deleteDb(); + db.createDb(); + if (debug) debugger; + var xhr; + + // with no accept header + var req = CouchDB.newXhr(); + req.open("GET", CouchDB.proxyUrl("/test_suite_db/"), false); + req.send(""); + TEquals("text/plain; charset=utf-8", req.getResponseHeader("Content-Type")); + + // make sure JSON responses end in a newline + var text = req.responseText; + TEquals("\n", text[text.length-1]); + + xhr = CouchDB.request("GET", "/test_suite_db/", { + headers: {"Accept": "text/html; text/plain;*/*"} + }); + TEquals("text/plain; charset=utf-8", xhr.getResponseHeader("Content-Type")); + + xhr = CouchDB.request("GET", "/test_suite_db/", { + headers: {"Accept": "application/json"} + }); + TEquals("application/json", xhr.getResponseHeader("Content-Type")); +};
