Return-Path: X-Original-To: apmail-couchdb-commits-archive@www.apache.org Delivered-To: apmail-couchdb-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 5AB76C811 for ; Wed, 10 Dec 2014 11:08:11 +0000 (UTC) Received: (qmail 40986 invoked by uid 500); 10 Dec 2014 11:08:10 -0000 Delivered-To: apmail-couchdb-commits-archive@couchdb.apache.org Received: (qmail 40840 invoked by uid 500); 10 Dec 2014 11:08:09 -0000 Mailing-List: contact commits-help@couchdb.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@couchdb.apache.org Delivered-To: mailing list commits@couchdb.apache.org Received: (qmail 40338 invoked by uid 99); 10 Dec 2014 11:08:09 -0000 Received: from tyr.zones.apache.org (HELO tyr.zones.apache.org) (140.211.11.114) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 10 Dec 2014 11:08:09 +0000 Received: by tyr.zones.apache.org (Postfix, from userid 65534) id 457ADA23218; Wed, 10 Dec 2014 11:08:09 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: kxepal@apache.org To: commits@couchdb.apache.org Date: Wed, 10 Dec 2014 11:08:17 -0000 Message-Id: <3194cfa6590d49f68f586541a8ec0836@git.apache.org> In-Reply-To: References: X-Mailer: ASF-Git Admin Mailer Subject: [10/39] couchdb commit: updated refs/heads/master to 9950caa 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= 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")); +};