summaryrefslogtreecommitdiff
path: root/share/www/script/test
diff options
context:
space:
mode:
Diffstat (limited to 'share/www/script/test')
-rw-r--r--share/www/script/test/attachment_names.js4
-rw-r--r--share/www/script/test/attachment_paths.js14
-rw-r--r--share/www/script/test/attachment_ranges.js134
-rw-r--r--share/www/script/test/attachment_views.js4
-rw-r--r--share/www/script/test/attachments.js50
-rw-r--r--share/www/script/test/attachments_multipart.js46
-rw-r--r--share/www/script/test/auth_cache.js280
-rw-r--r--share/www/script/test/basics.js33
-rw-r--r--share/www/script/test/batch_save.js5
-rw-r--r--share/www/script/test/bulk_docs.js10
-rw-r--r--share/www/script/test/changes.js357
-rw-r--r--share/www/script/test/compact.js10
-rw-r--r--share/www/script/test/config.js106
-rw-r--r--share/www/script/test/conflicts.js2
-rw-r--r--share/www/script/test/content_negotiation.js9
-rw-r--r--share/www/script/test/cookie_auth.js99
-rw-r--r--share/www/script/test/copy_doc.js3
-rw-r--r--share/www/script/test/delayed_commits.js29
-rw-r--r--share/www/script/test/design_docs.js398
-rw-r--r--share/www/script/test/erlang_views.js20
-rw-r--r--share/www/script/test/etags_views.js104
-rw-r--r--share/www/script/test/http.js6
-rw-r--r--share/www/script/test/jsonp.js69
-rw-r--r--share/www/script/test/list_views.js45
-rw-r--r--share/www/script/test/method_override.js40
-rw-r--r--share/www/script/test/oauth.js42
-rw-r--r--share/www/script/test/proxyauth.js130
-rw-r--r--share/www/script/test/purge.js14
-rw-r--r--share/www/script/test/reader_acl.js198
-rw-r--r--share/www/script/test/recreate_doc.js2
-rw-r--r--share/www/script/test/reduce.js9
-rw-r--r--share/www/script/test/reduce_builtin.js66
-rw-r--r--share/www/script/test/replication.js467
-rw-r--r--share/www/script/test/replicator_db.js818
-rw-r--r--share/www/script/test/rewrite.js443
-rw-r--r--share/www/script/test/security_validation.js97
-rw-r--r--share/www/script/test/show_documents.js103
-rw-r--r--share/www/script/test/stats.js31
-rw-r--r--share/www/script/test/update_documents.js22
-rw-r--r--share/www/script/test/users_db.js89
-rw-r--r--share/www/script/test/uuids.js18
-rw-r--r--share/www/script/test/view_collation.js12
-rw-r--r--share/www/script/test/view_collation_raw.js12
-rw-r--r--share/www/script/test/view_compaction.js104
-rw-r--r--share/www/script/test/view_errors.js54
-rw-r--r--share/www/script/test/view_multi_key_all_docs.js37
-rw-r--r--share/www/script/test/view_multi_key_design.js84
-rw-r--r--share/www/script/test/view_pagination.js123
-rw-r--r--share/www/script/test/view_sandboxing.js92
-rw-r--r--share/www/script/test/view_update_seq.js106
-rw-r--r--share/www/script/test/view_xml.js4
51 files changed, 4492 insertions, 562 deletions
diff --git a/share/www/script/test/attachment_names.js b/share/www/script/test/attachment_names.js
index d90c24c4..988dd2d2 100644
--- a/share/www/script/test/attachment_names.js
+++ b/share/www/script/test/attachment_names.js
@@ -24,7 +24,7 @@ couchTests.attachment_names = function(debug) {
data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
}
}
- }
+ };
// inline attachments
try {
@@ -72,7 +72,7 @@ couchTests.attachment_names = function(debug) {
data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
}
}
- }
+ };
try {
db.save(binAttDoc);
diff --git a/share/www/script/test/attachment_paths.js b/share/www/script/test/attachment_paths.js
index a2a0f69c..3f6ffb7c 100644
--- a/share/www/script/test/attachment_paths.js
+++ b/share/www/script/test/attachment_paths.js
@@ -33,7 +33,7 @@ couchTests.attachment_paths = function(debug) {
data: "V2UgbGlrZSBwZXJjZW50IHR3byBGLg=="
}
}
- }
+ };
T(db.save(binAttDoc).ok);
@@ -73,7 +73,10 @@ couchTests.attachment_paths = function(debug) {
T(binAttDoc._attachments["foo/bar.txt"] !== undefined);
T(binAttDoc._attachments["foo%2Fbaz.txt"] !== undefined);
T(binAttDoc._attachments["foo/bar2.txt"] !== undefined);
- T(binAttDoc._attachments["foo/bar2.txt"].content_type == "text/plain;charset=utf-8");
+ TEquals("text/plain;charset=utf-8", // thank you Safari
+ binAttDoc._attachments["foo/bar2.txt"].content_type.toLowerCase(),
+ "correct content-type"
+ );
T(binAttDoc._attachments["foo/bar2.txt"].length == 30);
//// now repeat the while thing with a design doc
@@ -92,7 +95,7 @@ couchTests.attachment_paths = function(debug) {
data: "V2UgbGlrZSBwZXJjZW50IHR3byBGLg=="
}
}
- }
+ };
T(db.save(binAttDoc).ok);
@@ -141,7 +144,10 @@ couchTests.attachment_paths = function(debug) {
T(binAttDoc._attachments["foo/bar.txt"] !== undefined);
T(binAttDoc._attachments["foo/bar2.txt"] !== undefined);
- T(binAttDoc._attachments["foo/bar2.txt"].content_type == "text/plain;charset=utf-8");
+ TEquals("text/plain;charset=utf-8", // thank you Safari
+ binAttDoc._attachments["foo/bar2.txt"].content_type.toLowerCase(),
+ "correct content-type"
+ );
T(binAttDoc._attachments["foo/bar2.txt"].length == 30);
}
};
diff --git a/share/www/script/test/attachment_ranges.js b/share/www/script/test/attachment_ranges.js
new file mode 100644
index 00000000..e1d40eae
--- /dev/null
+++ b/share/www/script/test/attachment_ranges.js
@@ -0,0 +1,134 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+couchTests.attachment_ranges = function(debug) {
+ var db = new CouchDB("test_suite_db", {
+ "X-Couch-Full-Commit": "false"
+ });
+ db.deleteDb();
+ db.createDb();
+
+ if (debug) debugger;
+
+ var binAttDoc = {
+ _id: "bin_doc",
+ _attachments: {
+ "foo.txt": {
+ content_type: "application/octet-stream",
+ data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
+ }
+ }
+ };
+
+ var save_response = db.save(binAttDoc);
+ T(save_response.ok);
+
+ // Fetching the whole entity is a 206.
+ var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc/foo.txt", {
+ headers: {
+ "Range": "bytes=0-28"
+ }
+ });
+ TEquals(206, xhr.status, "fetch 0-28");
+ TEquals("This is a base64 encoded text", xhr.responseText);
+ TEquals("bytes 0-28/29", xhr.getResponseHeader("Content-Range"));
+ TEquals("29", xhr.getResponseHeader("Content-Length"));
+
+ // Fetch the whole entity without an end offset is a 206.
+ var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc/foo.txt", {
+ headers: {
+ "Range": "bytes=0-"
+ }
+ });
+ TEquals(206, xhr.status, "fetch 0-");
+ TEquals("This is a base64 encoded text", xhr.responseText);
+ TEquals("bytes 0-28/29", xhr.getResponseHeader("Content-Range"));
+ TEquals("29", xhr.getResponseHeader("Content-Length"));
+
+ // Badly formed range header is a 200.
+ var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc/foo.txt", {
+ headers: {
+ "Range": "bytes:0-"
+ }
+ });
+ TEquals(200, xhr.status, "fetch with bad range header");
+
+ // Fetch the end of an entity without an end offset is a 206.
+ var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc/foo.txt", {
+ headers: {
+ "Range": "bytes=2-"
+ }
+ });
+ TEquals(206, xhr.status, "fetch 2-");
+ TEquals("is is a base64 encoded text", xhr.responseText);
+ TEquals("bytes 2-28/29", xhr.getResponseHeader("Content-Range"));
+ TEquals("27", xhr.getResponseHeader("Content-Length"));
+
+ // Fetch past the end of the entity is a 206
+ var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc/foo.txt", {
+ headers: {
+ "Range": "bytes=0-29"
+ }
+ });
+ TEquals(206, xhr.status, "fetch 0-29");
+ TEquals("bytes 0-28/29", xhr.getResponseHeader("Content-Range"));
+ TEquals("29", xhr.getResponseHeader("Content-Length"));
+
+ // Fetch first part of entity is a 206
+ var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc/foo.txt", {
+ headers: {
+ "Range": "bytes=0-3"
+ }
+ });
+ TEquals(206, xhr.status, "fetch 0-3");
+ TEquals("This", xhr.responseText);
+ TEquals("4", xhr.getResponseHeader("Content-Length"));
+ TEquals("bytes 0-3/29", xhr.getResponseHeader("Content-Range"));
+
+ // Fetch middle of entity is also a 206
+ var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc/foo.txt", {
+ headers: {
+ "Range": "bytes=10-15"
+ }
+ });
+ TEquals(206, xhr.status, "fetch 10-15");
+ TEquals("base64", xhr.responseText);
+ TEquals("6", xhr.getResponseHeader("Content-Length"));
+ TEquals("bytes 10-15/29", xhr.getResponseHeader("Content-Range"));
+
+ // Fetch end of entity is also a 206
+ var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc/foo.txt", {
+ headers: {
+ "Range": "bytes=-3"
+ }
+ });
+ TEquals(206, xhr.status, "fetch -3");
+ TEquals("ext", xhr.responseText);
+ TEquals("3", xhr.getResponseHeader("Content-Length"));
+ TEquals("bytes 26-28/29", xhr.getResponseHeader("Content-Range"));
+
+ // backward range is 416
+ var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc/foo.txt", {
+ headers: {
+ "Range": "bytes=5-3"
+ }
+ });
+ TEquals(416, xhr.status, "fetch 5-3");
+
+ // range completely outside of entity is 416
+ var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc/foo.txt", {
+ headers: {
+ "Range": "bytes=300-310"
+ }
+ });
+ TEquals(416, xhr.status, "fetch 300-310");
+
+};
diff --git a/share/www/script/test/attachment_views.js b/share/www/script/test/attachment_views.js
index fd30dcfc..a92a8ad0 100644
--- a/share/www/script/test/attachment_views.js
+++ b/share/www/script/test/attachment_views.js
@@ -68,11 +68,11 @@ couchTests.attachment_views= function(debug) {
}
emit(parseInt(doc._id), count);
- }
+ };
var reduceFunction = function(key, values) {
return sum(values);
- }
+ };
var result = db.query(mapFunction, reduceFunction);
diff --git a/share/www/script/test/attachments.js b/share/www/script/test/attachments.js
index 36f5a5ad..b0cfd2c5 100644
--- a/share/www/script/test/attachments.js
+++ b/share/www/script/test/attachments.js
@@ -24,7 +24,7 @@ couchTests.attachments= function(debug) {
data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
}
}
- }
+ };
var save_response = db.save(binAttDoc);
T(save_response.ok);
@@ -68,12 +68,12 @@ couchTests.attachments= function(debug) {
T(binAttDoc2._attachments["foo.txt"] !== undefined);
T(binAttDoc2._attachments["foo2.txt"] !== undefined);
- T(binAttDoc2._attachments["foo2.txt"].content_type == "text/plain;charset=utf-8");
+ TEqualsIgnoreCase("text/plain;charset=utf-8", binAttDoc2._attachments["foo2.txt"].content_type);
T(binAttDoc2._attachments["foo2.txt"].length == 30);
var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc2/foo2.txt");
T(xhr.responseText == "This is no base64 encoded text");
- T(xhr.getResponseHeader("Content-Type") == "text/plain;charset=utf-8");
+ TEqualsIgnoreCase("text/plain;charset=utf-8", xhr.getResponseHeader("Content-Type"));
// test without rev, should fail
var xhr = CouchDB.request("DELETE", "/test_suite_db/bin_doc2/foo2.txt");
@@ -93,10 +93,11 @@ couchTests.attachments= function(debug) {
});
T(xhr.status == 201);
var rev = JSON.parse(xhr.responseText).rev;
+ TEquals('"' + rev + '"', xhr.getResponseHeader("Etag"));
var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc3/attachment.txt");
T(xhr.responseText == bin_data);
- T(xhr.getResponseHeader("Content-Type") == "text/plain;charset=utf-8");
+ TEqualsIgnoreCase("text/plain;charset=utf-8", xhr.getResponseHeader("Content-Type"));
var xhr = CouchDB.request("PUT", "/test_suite_db/bin_doc3/attachment.txt", {
headers:{"Content-Type":"text/plain;charset=utf-8"},
@@ -110,14 +111,15 @@ couchTests.attachments= function(debug) {
});
T(xhr.status == 201);
var rev = JSON.parse(xhr.responseText).rev;
+ TEquals('"' + rev + '"', xhr.getResponseHeader("Etag"));
var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc3/attachment.txt");
T(xhr.responseText == bin_data);
- T(xhr.getResponseHeader("Content-Type") == "text/plain;charset=utf-8");
+ TEqualsIgnoreCase("text/plain;charset=utf-8", xhr.getResponseHeader("Content-Type"));
var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc3/attachment.txt?rev=" + rev);
T(xhr.responseText == bin_data);
- T(xhr.getResponseHeader("Content-Type") == "text/plain;charset=utf-8");
+ TEqualsIgnoreCase("text/plain;charset=utf-8", xhr.getResponseHeader("Content-Type"));
var xhr = CouchDB.request("DELETE", "/test_suite_db/bin_doc3/attachment.txt?rev=" + rev);
T(xhr.status == 200);
@@ -129,7 +131,7 @@ couchTests.attachments= function(debug) {
var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc3/attachment.txt?rev=" + rev);
T(xhr.status == 200);
T(xhr.responseText == bin_data);
- T(xhr.getResponseHeader("Content-Type") == "text/plain;charset=utf-8");
+ TEqualsIgnoreCase("text/plain;charset=utf-8", xhr.getResponseHeader("Content-Type"));
// empty attachments
var xhr = CouchDB.request("PUT", "/test_suite_db/bin_doc4/attachment.txt", {
@@ -156,7 +158,7 @@ couchTests.attachments= function(debug) {
// Attachment sparseness COUCHDB-220
- var docs = []
+ var docs = [];
for (var i = 0; i < 5; i++) {
var doc = {
_id: (i).toString(),
@@ -166,8 +168,8 @@ couchTests.attachments= function(debug) {
data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
}
}
- }
- docs.push(doc)
+ };
+ docs.push(doc);
}
var saved = db.bulkSave(docs);
@@ -210,7 +212,7 @@ couchTests.attachments= function(debug) {
var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc5/lorem.txt");
T(xhr.responseText == lorem);
- T(xhr.getResponseHeader("Content-Type") == "text/plain;charset=utf-8");
+ TEqualsIgnoreCase("text/plain;charset=utf-8", xhr.getResponseHeader("Content-Type"));
// test large inline attachment too
var lorem_b64 = CouchDB.request("GET", "/_utils/script/test/lorem_b64.txt").responseText;
@@ -244,4 +246,30 @@ couchTests.attachments= function(debug) {
body: "THIS IS AN ATTACHMENT. BOOYA!"
});
TEquals(400, xhr.status, "should return error code 400 Bad Request");
+
+ // test COUCHDB-809 - stubs should only require the 'stub' field
+ var bin_doc6 = {
+ _id: "bin_doc6",
+ _attachments:{
+ "foo.txt": {
+ content_type:"text/plain",
+ data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
+ }
+ }
+ };
+ T(db.save(bin_doc6).ok);
+ // stub out the attachment
+ bin_doc6._attachments["foo.txt"] = { stub: true };
+ T(db.save(bin_doc6).ok == true);
+
+ // wrong rev pos specified
+
+ // stub out the attachment with the wrong revpos
+ bin_doc6._attachments["foo.txt"] = { stub: true, revpos: 10};
+ try {
+ T(db.save(bin_doc6).ok == true);
+ T(false && "Shouldn't get here!");
+ } catch (e) {
+ T(e.error == "missing_stub");
+ }
};
diff --git a/share/www/script/test/attachments_multipart.js b/share/www/script/test/attachments_multipart.js
index 2b79e559..96fe344f 100644
--- a/share/www/script/test/attachments_multipart.js
+++ b/share/www/script/test/attachments_multipart.js
@@ -58,7 +58,7 @@ couchTests.attachments_multipart= function(debug) {
var result = JSON.parse(xhr.responseText);
- T(result.ok)
+ T(result.ok);
@@ -115,8 +115,11 @@ couchTests.attachments_multipart= function(debug) {
// now test receiving multipart docs
function getBoundary(xhr) {
- var ctype = xhr.getResponseHeader("Content-Type");
-
+ if (xhr instanceof XMLHttpRequest) {
+ var ctype = xhr.getResponseHeader("Content-Type");
+ } else {
+ var ctype = xhr.headers['Content-Type'];
+ }
var ctypeArgs = ctype.split("; ").slice(1);
var boundary = null;
for(var i=0; i<ctypeArgs.length; i++) {
@@ -134,7 +137,11 @@ couchTests.attachments_multipart= function(debug) {
function parseMultipart(xhr) {
var boundary = getBoundary(xhr);
- var mimetext = xhr.responseText;
+ if (xhr instanceof XMLHttpRequest) {
+ var mimetext = xhr.responseText;
+ } else {
+ var mimetext = xhr.body;
+ }
// strip off leading boundary
var leading = "--" + boundary + "\r\n";
var last = "\r\n--" + boundary + "--";
@@ -193,7 +200,7 @@ couchTests.attachments_multipart= function(debug) {
// a certain rev).
xhr = CouchDB.request("GET", "/test_suite_db/multipart?atts_since=[\"" + firstrev + "\"]",
- {headers:{"accept": "multipart/related,*/*;"}});
+ {headers:{"accept": "multipart/related, */*"}});
T(xhr.status == 200);
@@ -207,7 +214,34 @@ couchTests.attachments_multipart= function(debug) {
T(doc._attachments['bar.txt'].follows == true);
T(sections[1].body == "this is 18 chars l");
-
+
+ // 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\"]",
diff --git a/share/www/script/test/auth_cache.js b/share/www/script/test/auth_cache.js
new file mode 100644
index 00000000..e48f7370
--- /dev/null
+++ b/share/www/script/test/auth_cache.js
@@ -0,0 +1,280 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+couchTests.auth_cache = function(debug) {
+
+ if (debug) debugger;
+
+ // Simple secret key generator
+ function generateSecret(length) {
+ var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" +
+ "0123456789+/";
+ var secret = '';
+ for (var i = 0; i < length; i++) {
+ secret += tab.charAt(Math.floor(Math.random() * 64));
+ }
+ return secret;
+ }
+
+ var authDb = new CouchDB("test_suite_users", {"X-Couch-Full-Commit":"false"});
+ var server_config = [
+ {
+ section: "couch_httpd_auth",
+ key: "authentication_db",
+ value: authDb.name
+ },
+ {
+ section: "couch_httpd_auth",
+ key: "auth_cache_size",
+ value: "3"
+ },
+ {
+ section: "httpd",
+ key: "authentication_handlers",
+ value: "{couch_httpd_auth, default_authentication_handler}"
+ },
+ {
+ section: "couch_httpd_auth",
+ key: "secret",
+ value: generateSecret(64)
+ }
+ ];
+
+
+ function hits() {
+ var hits = CouchDB.requestStats("couchdb", "auth_cache_hits", true);
+ return hits.current || 0;
+ }
+
+
+ function misses() {
+ var misses = CouchDB.requestStats("couchdb", "auth_cache_misses", true);
+ return misses.current || 0;
+ }
+
+
+ function testFun() {
+ var hits_before,
+ misses_before,
+ hits_after,
+ misses_after;
+
+ var fdmanana = CouchDB.prepareUserDoc({
+ name: "fdmanana",
+ roles: ["dev"]
+ }, "qwerty");
+
+ T(authDb.save(fdmanana).ok);
+
+ var chris = CouchDB.prepareUserDoc({
+ name: "chris",
+ roles: ["dev", "mafia", "white_costume"]
+ }, "the_god_father");
+
+ T(authDb.save(chris).ok);
+
+ var joe = CouchDB.prepareUserDoc({
+ name: "joe",
+ roles: ["erlnager"]
+ }, "functional");
+
+ T(authDb.save(joe).ok);
+
+ var johndoe = CouchDB.prepareUserDoc({
+ name: "johndoe",
+ roles: ["user"]
+ }, "123456");
+
+ T(authDb.save(johndoe).ok);
+
+ hits_before = hits();
+ misses_before = misses();
+
+ T(CouchDB.login("fdmanana", "qwerty").ok);
+
+ hits_after = hits();
+ misses_after = misses();
+
+ T(misses_after === (misses_before + 1));
+ T(hits_after === hits_before);
+
+ hits_before = hits_after;
+ misses_before = misses_after;
+
+ T(CouchDB.logout().ok);
+ T(CouchDB.login("fdmanana", "qwerty").ok);
+
+ hits_after = hits();
+ misses_after = misses();
+
+ T(misses_after === misses_before);
+ T(hits_after === (hits_before + 1));
+
+ hits_before = hits_after;
+ misses_before = misses_after;
+
+ T(CouchDB.logout().ok);
+ T(CouchDB.login("chris", "the_god_father").ok);
+
+ hits_after = hits();
+ misses_after = misses();
+
+ T(misses_after === (misses_before + 1));
+ T(hits_after === hits_before);
+
+ hits_before = hits_after;
+ misses_before = misses_after;
+
+ T(CouchDB.logout().ok);
+ T(CouchDB.login("joe", "functional").ok);
+
+ hits_after = hits();
+ misses_after = misses();
+
+ T(misses_after === (misses_before + 1));
+ T(hits_after === hits_before);
+
+ hits_before = hits_after;
+ misses_before = misses_after;
+
+ T(CouchDB.logout().ok);
+ T(CouchDB.login("johndoe", "123456").ok);
+
+ hits_after = hits();
+ misses_after = misses();
+
+ T(misses_after === (misses_before + 1));
+ T(hits_after === hits_before);
+
+ hits_before = hits_after;
+ misses_before = misses_after;
+
+ T(CouchDB.logout().ok);
+ T(CouchDB.login("joe", "functional").ok);
+
+ hits_after = hits();
+ misses_after = misses();
+
+ // it's an MRU cache, joe was removed from cache to add johndoe
+ T(misses_after === (misses_before + 1));
+ T(hits_after === hits_before);
+
+ hits_before = hits_after;
+ misses_before = misses_after;
+
+ T(CouchDB.logout().ok);
+ T(CouchDB.login("fdmanana", "qwerty").ok);
+
+ hits_after = hits();
+ misses_after = misses();
+
+ T(misses_after === misses_before);
+ T(hits_after === (hits_before + 1));
+
+ hits_before = hits_after;
+ misses_before = misses_after;
+
+ var new_salt = CouchDB.newUuids(1)[0];
+ var new_passwd = hex_sha1("foobar" + new_salt);
+ fdmanana.salt = new_salt;
+ fdmanana.password_sha = new_passwd;
+
+ T(authDb.save(fdmanana).ok);
+ T(CouchDB.logout().ok);
+
+ // cache was refreshed
+ T(CouchDB.login("fdmanana", "qwerty").error === "unauthorized");
+ T(CouchDB.login("fdmanana", "foobar").ok);
+
+ hits_after = hits();
+ misses_after = misses();
+
+ T(misses_after === misses_before);
+ T(hits_after === (hits_before + 2));
+
+ T(CouchDB.logout().ok);
+
+ hits_before = hits_after;
+ misses_before = misses_after;
+
+ // and yet another update
+ new_salt = CouchDB.newUuids(1)[0];
+ new_passwd = hex_sha1("javascript" + new_salt);
+ fdmanana.salt = new_salt;
+ fdmanana.password_sha = new_passwd;
+
+ T(authDb.save(fdmanana).ok);
+ T(CouchDB.logout().ok);
+
+ // cache was refreshed
+ T(CouchDB.login("fdmanana", "foobar").error === "unauthorized");
+ T(CouchDB.login("fdmanana", "javascript").ok);
+
+ hits_after = hits();
+ misses_after = misses();
+
+ T(misses_after === misses_before);
+ T(hits_after === (hits_before + 2));
+
+ T(authDb.deleteDoc(fdmanana).ok);
+ T(CouchDB.logout().ok);
+
+ hits_before = hits_after;
+ misses_before = misses_after;
+
+ T(CouchDB.login("fdmanana", "javascript").error === "unauthorized");
+
+ hits_after = hits();
+ misses_after = misses();
+
+ T(misses_after === misses_before);
+ T(hits_after === (hits_before + 1));
+
+ // login, compact authentication DB, login again and verify that
+ // there was a cache hit
+ hits_before = hits_after;
+ misses_before = misses_after;
+
+ T(CouchDB.login("johndoe", "123456").ok);
+
+ hits_after = hits();
+ misses_after = misses();
+
+ T(misses_after === (misses_before + 1));
+ T(hits_after === hits_before);
+
+ T(CouchDB.logout().ok);
+ T(authDb.compact().ok);
+
+ while (authDb.info().compact_running);
+
+ hits_before = hits_after;
+ misses_before = misses_after;
+
+ T(CouchDB.login("johndoe", "123456").ok);
+
+ hits_after = hits();
+ misses_after = misses();
+
+ T(misses_after === misses_before);
+ T(hits_after === (hits_before + 1));
+
+ T(CouchDB.logout().ok);
+ }
+
+
+ authDb.deleteDb();
+ run_on_modified_server(server_config, testFun);
+
+ // cleanup
+ authDb.deleteDb();
+} \ No newline at end of file
diff --git a/share/www/script/test/basics.js b/share/www/script/test/basics.js
index 0f9ac44f..30c27c11 100644
--- a/share/www/script/test/basics.js
+++ b/share/www/script/test/basics.js
@@ -37,15 +37,14 @@ couchTests.basics = function(debug) {
TEquals(dbname,
xhr.getResponseHeader("Location").substr(-dbname.length),
"should return Location header to newly created document");
-
- TEquals("http://",
- xhr.getResponseHeader("Location").substr(0, 7),
+ 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)
+ T(CouchDB.allDbs().indexOf("test_suite_db") != -1);
// Get the database info, check the doc_count
T(db.info().doc_count == 0);
@@ -91,13 +90,13 @@ couchTests.basics = function(debug) {
emit(null, doc.b);
};
- results = db.query(mapFunction);
+ var results = db.query(mapFunction);
// verify only one document found and the result value (doc.b).
T(results.total_rows == 1 && results.rows[0].value == 16);
// reopen document we saved earlier
- existingDoc = db.open(id);
+ var existingDoc = db.open(id);
T(existingDoc.a==1);
@@ -152,18 +151,20 @@ couchTests.basics = function(debug) {
// test that the POST response has a Location header
var xhr = CouchDB.request("POST", "/test_suite_db", {
- body: JSON.stringify({"foo":"bar"})
+ body: JSON.stringify({"foo":"bar"}),
+ headers: {"Content-Type": "application/json"}
});
var resp = JSON.parse(xhr.responseText);
T(resp.ok);
var loc = xhr.getResponseHeader("Location");
T(loc, "should have a Location header");
var locs = loc.split('/');
- T(locs[4] == resp.id);
- T(locs[3] == "test_suite_db");
+ 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);
@@ -179,9 +180,8 @@ couchTests.basics = function(debug) {
TEquals("/test_suite_db/newdoc",
xhr.getResponseHeader("Location").substr(-21),
"should return Location header to newly created document");
-
- TEquals("http://",
- xhr.getResponseHeader("Location").substr(0, 7),
+ 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
@@ -189,12 +189,12 @@ couchTests.basics = function(debug) {
T(xhr.status == 404);
// Check for invalid document members
- bad_docs = [
+ var bad_docs = [
["goldfish", {"_zing": 4}],
["zebrafish", {"_zoom": "hello"}],
["mudfish", {"zane": "goldfish", "_fan": "something smells delicious"}],
["tastyfish", {"_bing": {"wha?": "soda can"}}]
- ]
+ ];
var test_doc = function(info) {
var data = JSON.stringify(info[1]);
xhr = CouchDB.request("PUT", "/test_suite_db/" + info[0], {body: data});
@@ -202,7 +202,10 @@ couchTests.basics = function(debug) {
result = JSON.parse(xhr.responseText);
T(result.error == "doc_validation");
- xhr = CouchDB.request("POST", "/test_suite_db/", {body: data});
+ xhr = CouchDB.request("POST", "/test_suite_db/", {
+ headers: {"Content-Type": "application/json"},
+ body: data
+ });
T(xhr.status == 500);
result = JSON.parse(xhr.responseText);
T(result.error == "doc_validation");
diff --git a/share/www/script/test/batch_save.js b/share/www/script/test/batch_save.js
index 1c8a2be9..a1b00192 100644
--- a/share/www/script/test/batch_save.js
+++ b/share/www/script/test/batch_save.js
@@ -36,7 +36,10 @@ couchTests.batch_save = function(debug) {
// repeat the tests for POST
for(i=0; i < 100; i++) {
- var resp = db.request("POST", db.uri + "?batch=ok", {body: JSON.stringify({a:1})});
+ var resp = db.request("POST", db.uri + "?batch=ok", {
+ headers: {"Content-Type": "application/json"},
+ body: JSON.stringify({a:1})
+ });
T(JSON.parse(resp.responseText).ok);
}
diff --git a/share/www/script/test/bulk_docs.js b/share/www/script/test/bulk_docs.js
index 346aea83..9095e6b3 100644
--- a/share/www/script/test/bulk_docs.js
+++ b/share/www/script/test/bulk_docs.js
@@ -51,12 +51,12 @@ couchTests.bulk_docs = function(debug) {
T(results.length == 5);
T(results[0].id == "0");
T(results[0].error == "conflict");
- T(results[0].rev === undefined); // no rev member when a conflict
+ T(typeof results[0].rev === "undefined"); // no rev member when a conflict
// but the rest are not
for (i = 1; i < 5; i++) {
T(results[i].id == i.toString());
- T(results[i].rev)
+ T(results[i].rev);
T(db.open(docs[i]._id) == null);
}
@@ -64,7 +64,7 @@ couchTests.bulk_docs = function(debug) {
// save doc 0, this will cause a conflict when we save docs[0]
var doc = db.open("0");
- docs[0] = db.open("0")
+ docs[0] = db.open("0");
db.save(doc);
docs[0].shooby = "dooby";
@@ -93,8 +93,8 @@ couchTests.bulk_docs = function(debug) {
// Regression test for failure on update/delete
var newdoc = {"_id": "foobar", "body": "baz"};
T(db.save(newdoc).ok);
- update = {"_id": newdoc._id, "_rev": newdoc._rev, "body": "blam"};
- torem = {"_id": newdoc._id, "_rev": newdoc._rev, "_deleted": true};
+ var update = {"_id": newdoc._id, "_rev": newdoc._rev, "body": "blam"};
+ var torem = {"_id": newdoc._id, "_rev": newdoc._rev, "_deleted": true};
results = db.bulkSave([update, torem]);
T(results[0].error == "conflict" || results[1].error == "conflict");
};
diff --git a/share/www/script/test/changes.js b/share/www/script/test/changes.js
index 0cbf3bd6..7ce3baaa 100644
--- a/share/www/script/test/changes.js
+++ b/share/www/script/test/changes.js
@@ -12,12 +12,12 @@
function jsonp(obj) {
T(jsonp_flag == 0);
- T(obj.results.length == 1 && obj.last_seq==1, "jsonp")
+ 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":"false"});
+ var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"true"});
db.deleteDb();
db.createDb();
if (debug) debugger;
@@ -25,24 +25,32 @@ couchTests.changes = function(debug) {
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")
+ 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.results.length == 1 && resp.last_seq==1, "one doc db")
- T(resp.results[0].changes[0].rev == docFoo._rev)
+ T(resp.last_seq == 1);
+ T(resp.results.length == 1, "one doc db");
+ T(resp.results[0].changes[0].rev == docFoo._rev);
// test with callback
- var xhr = CouchDB.request("GET", "/test_suite_db/_changes?callback=jsonp");
- T(xhr.status == 200);
- jsonp_flag = 0;
- eval(xhr.responseText);
- T(jsonp_flag == 1);
-
+
+ run_on_modified_server(
+ [{section: "httpd",
+ key: "allow_jsonp",
+ value: "true"}],
+ function() {
+ var xhr = CouchDB.request("GET", "/test_suite_db/_changes?callback=jsonp");
+ T(xhr.status == 200);
+ jsonp_flag = 0;
+ eval(xhr.responseText);
+ T(jsonp_flag == 1);
+ });
req = CouchDB.request("GET", "/test_suite_db/_changes?feed=continuous&timeout=10");
var lines = req.responseText.split("\n");
@@ -70,98 +78,111 @@ couchTests.changes = function(debug) {
// WebKit (last checked on nightly #47686) does fail on processing
// the async-request properly while javascript is executed.
- var sleep = function(msecs) {
- // by making a slow sync request, we allow the waiting XHR request data
- // to be received.
- var req = CouchDB.request("GET", "/_sleep?time=" + msecs);
- T(JSON.parse(req.responseText).ok == true);
- }
-
- xhr.open("GET", "/test_suite_db/_changes?feed=continuous", true);
+ xhr.open("GET", "/test_suite_db/_changes?feed=continuous&timeout=500", true);
xhr.send("");
var docBar = {_id:"bar", bar:1};
db.save(docBar);
+
+ var lines, change1, change2;
+ waitForSuccess(function() {
+ lines = xhr.responseText.split("\n");
+ change1 = JSON.parse(lines[0]);
+ change2 = JSON.parse(lines[1]);
+ if (change2.seq != 2) {
+ throw "bad seq, try again";
+ }
+ }, "bar-only");
- sleep(100);
- var lines = xhr.responseText.split("\n");
-
- var change = JSON.parse(lines[0]);
-
- T(change.seq == 1)
- T(change.id == "foo")
-
- change = JSON.parse(lines[1]);
-
- T(change.seq == 2)
- T(change.id == "bar")
- T(change.changes[0].rev == docBar._rev)
-
+ T(change1.seq == 1);
+ T(change1.id == "foo");
+
+ T(change2.seq == 2);
+ T(change2.id == "bar");
+ T(change2.changes[0].rev == docBar._rev);
+
+
var docBaz = {_id:"baz", baz:1};
db.save(docBaz);
- sleep(100);
- var lines = xhr.responseText.split("\n");
-
- change = JSON.parse(lines[2]);
-
- T(change.seq == 3);
- T(change.id == "baz");
- T(change.changes[0].rev == docBaz._rev);
+ var change3;
+ waitForSuccess(function() {
+ lines = xhr.responseText.split("\n");
+ change3 = JSON.parse(lines[2]);
+ if (change3.seq != 3) {
+ throw "bad seq, try again";
+ }
+ });
+
+ T(change3.seq == 3);
+ T(change3.id == "baz");
+ T(change3.changes[0].rev == docBaz._rev);
xhr = CouchDB.newXhr();
//verify the hearbeat newlines are sent
- xhr.open("GET", "/test_suite_db/_changes?feed=continuous&heartbeat=10", true);
+ xhr.open("GET", "/test_suite_db/_changes?feed=continuous&heartbeat=10&timeout=500", true);
xhr.send("");
+
+ var str;
+ waitForSuccess(function() {
+ str = xhr.responseText;
+ if (str.charAt(str.length - 1) != "\n" || str.charAt(str.length - 2) != "\n") {
+ throw("keep waiting");
+ }
+ }, "heartbeat");
- sleep(100);
-
- var str = xhr.responseText;
-
- T(str.charAt(str.length - 1) == "\n")
- T(str.charAt(str.length - 2) == "\n")
+ T(str.charAt(str.length - 1) == "\n");
+ T(str.charAt(str.length - 2) == "\n");
+ // otherwise we'll continue to receive heartbeats forever
+ xhr.abort();
// test longpolling
xhr = CouchDB.newXhr();
xhr.open("GET", "/test_suite_db/_changes?feed=longpoll", true);
xhr.send("");
-
- sleep(100);
- var lines = xhr.responseText.split("\n");
- T(lines[5]=='"last_seq":3}');
-
+
+ waitForSuccess(function() {
+ lines = xhr.responseText.split("\n");
+ if (lines[5] != '"last_seq":3}') {
+ throw("still waiting");
+ }
+ }, "last_seq");
+
xhr = CouchDB.newXhr();
xhr.open("GET", "/test_suite_db/_changes?feed=longpoll&since=3", true);
xhr.send("");
- sleep(100);
-
var docBarz = {_id:"barz", bar:1};
db.save(docBarz);
-
- sleep(100);
-
- var lines = xhr.responseText.split("\n");
-
+
var parse_changes_line = function(line) {
if (line.charAt(line.length-1) == ",") {
- line = line.substring(0, line.length-1);
+ var linetrimmed = line.substring(0, line.length-1);
+ } else {
+ var linetrimmed = line;
}
- return JSON.parse(line);
- }
-
- change = parse_changes_line(lines[1]);
-
+ return JSON.parse(linetrimmed);
+ };
+
+ waitForSuccess(function() {
+ lines = xhr.responseText.split("\n");
+ if (lines[3] != '"last_seq":4}') {
+ throw("still waiting");
+ }
+ }, "change_lines");
+
+ var change = parse_changes_line(lines[1]);
T(change.seq == 4);
T(change.id == "barz");
T(change.changes[0].rev == docBarz._rev);
T(lines[3]=='"last_seq":4}');
-
+
+
}
// test the filtered changes
@@ -175,9 +196,18 @@ couchTests.changes = function(debug) {
}),
"userCtx" : stringFun(function(doc, req) {
return doc.user && (doc.user == req.userCtx.name);
- })
+ }),
+ "conflicted" : "function(doc, req) { return (doc._conflicts);}"
+ },
+ options : {
+ local_seq : true
+ },
+ views : {
+ local_seq : {
+ map : "function(doc) {emit(doc._local_seq, null)}"
+ }
}
- }
+ };
db.save(ddoc);
@@ -190,7 +220,7 @@ couchTests.changes = function(debug) {
var req = CouchDB.request("GET", "/test_suite_db/_changes?filter=changes_filter/bop");
var resp = JSON.parse(req.responseText);
- T(resp.results.length == 1);
+ T(resp.results.length == 1, "filtered/bop");
req = CouchDB.request("GET", "/test_suite_db/_changes?filter=changes_filter/dynamic&field=woox");
resp = JSON.parse(req.responseText);
@@ -198,15 +228,14 @@ couchTests.changes = function(debug) {
req = CouchDB.request("GET", "/test_suite_db/_changes?filter=changes_filter/dynamic&field=bop");
resp = JSON.parse(req.responseText);
- T(resp.results.length == 1);
+ T(resp.results.length == 1, "changes_filter/dynamic&field=bop");
if (!is_safari && xhr) { // full test requires parallel connections
// filter with longpoll
// longpoll filters full history when run without a since seq
xhr = CouchDB.newXhr();
- xhr.open("GET", "/test_suite_db/_changes?feed=longpoll&filter=changes_filter/bop", true);
+ xhr.open("GET", "/test_suite_db/_changes?feed=longpoll&filter=changes_filter/bop", false);
xhr.send("");
- sleep(100);
var resp = JSON.parse(xhr.responseText);
T(resp.last_seq == 7);
// longpoll waits until a matching change before returning
@@ -215,22 +244,53 @@ couchTests.changes = function(debug) {
xhr.send("");
db.save({"_id":"falsy", "bop" : ""}); // empty string is falsy
db.save({"_id":"bingo","bop" : "bingo"});
- sleep(100);
- var resp = JSON.parse(xhr.responseText);
+
+ waitForSuccess(function() {
+ resp = JSON.parse(xhr.responseText);
+ }, "longpoll-since");
+
T(resp.last_seq == 9);
T(resp.results && resp.results.length > 0 && resp.results[0]["id"] == "bingo", "filter the correct update");
-
- // filter with continuous
- xhr = CouchDB.newXhr();
- xhr.open("GET", "/test_suite_db/_changes?feed=continuous&filter=changes_filter/bop&timeout=200", true);
- xhr.send("");
- db.save({"_id":"rusty", "bop" : "plankton"});
- T(db.ensureFullCommit().ok);
- sleep(300);
- var lines = xhr.responseText.split("\n");
- T(JSON.parse(lines[1]).id == "bingo", lines[1]);
- T(JSON.parse(lines[2]).id == "rusty", lines[2]);
- T(JSON.parse(lines[3]).last_seq == 10, lines[3]);
+ xhr.abort();
+
+ var timeout = 500;
+ var last_seq = 10;
+ while (true) {
+
+ // filter with continuous
+ xhr = CouchDB.newXhr();
+ xhr.open("GET", "/test_suite_db/_changes?feed=continuous&filter=changes_filter/bop&timeout="+timeout, true);
+ xhr.send("");
+
+ db.save({"_id":"rusty", "bop" : "plankton"});
+ T(xhr.readyState != 4, "test client too slow");
+ var rusty = db.open("rusty", {cache_bust : new Date()});
+ T(rusty._id == "rusty");
+
+ waitForSuccess(function() { // throws an error after 5 seconds
+ if (xhr.readyState != 4) {
+ throw("still waiting");
+ }
+ }, "continuous-rusty");
+ lines = xhr.responseText.split("\n");
+ 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
@@ -257,7 +317,8 @@ couchTests.changes = function(debug) {
var req = CouchDB.request("GET",
"/test_suite_db/_changes?filter=changes_filter/bop&style=all_docs");
var resp = JSON.parse(req.responseText);
- TEquals(3, resp.results.length, "should return matching rows");
+ var expect = (!is_safari && xhr) ? 3: 1;
+ TEquals(expect, resp.results.length, "should return matching rows");
// test for userCtx
run_on_modified_server(
@@ -286,9 +347,119 @@ couchTests.changes = function(debug) {
resp = JSON.parse(req.responseText);
T(resp.results.length == 1, "userCtx");
T(resp.results[0].id == docResp.id);
- });
-
- req = CouchDB.request("GET", "/test_suite_db/_changes?limit=1");
- resp = JSON.parse(req.responseText);
- TEquals(1, resp.results.length)
+ }
+ );
+
+ req = CouchDB.request("GET", "/test_suite_db/_changes?limit=1");
+ resp = JSON.parse(req.responseText);
+ TEquals(1, resp.results.length);
+
+ //filter includes _conflicts
+ var id = db.save({'food' : 'pizza'}).id;
+ db.bulkSave([{_id: id, 'food' : 'pasta'}], {all_or_nothing:true});
+
+ req = CouchDB.request("GET", "/test_suite_db/_changes?filter=changes_filter/conflicted");
+ resp = JSON.parse(req.responseText);
+ T(resp.results.length == 1, "filter=changes_filter/conflicted");
+
+ // test with erlang filter function
+ run_on_modified_server([{
+ section: "native_query_servers",
+ key: "erlang",
+ value: "{couch_native_process, start_link, []}"
+ }], function() {
+ var erl_ddoc = {
+ _id: "_design/erlang",
+ language: "erlang",
+ filters: {
+ foo:
+ 'fun({Doc}, Req) -> ' +
+ ' Value = couch_util:get_value(<<"value">>, Doc),' +
+ ' (Value rem 2) =:= 0' +
+ 'end.'
+ }
+ };
+
+ db.deleteDb();
+ db.createDb();
+ T(db.save(erl_ddoc).ok);
+
+ var req = CouchDB.request("GET", "/test_suite_db/_changes?filter=erlang/foo");
+ var resp = JSON.parse(req.responseText);
+ T(resp.results.length === 0);
+
+ T(db.save({_id: "doc1", value : 1}).ok);
+ T(db.save({_id: "doc2", value : 2}).ok);
+ T(db.save({_id: "doc3", value : 3}).ok);
+ T(db.save({_id: "doc4", value : 4}).ok);
+
+ var req = CouchDB.request("GET", "/test_suite_db/_changes?filter=erlang/foo");
+ var resp = JSON.parse(req.responseText);
+ T(resp.results.length === 2);
+ T(resp.results[0].id === "doc2");
+ T(resp.results[1].id === "doc4");
+
+ // 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", "/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");
+ }
+ }, "andmore-only");
+
+ var line = JSON.parse(xhr.responseText.split("\n")[0]);
+ T(line.seq == 8);
+ T(line.id == "andmore");
+ }
+
+ });
+
};
+
diff --git a/share/www/script/test/compact.js b/share/www/script/test/compact.js
index f63bfc57..805a3b08 100644
--- a/share/www/script/test/compact.js
+++ b/share/www/script/test/compact.js
@@ -26,11 +26,12 @@ couchTests.compact = function(debug) {
data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
}
}
- }
+ };
T(db.save(binAttDoc).ok);
var originalsize = db.info().disk_size;
+ var start_time = db.info().instance_start_time;
for(var i in docs) {
db.deleteDoc(docs[i]);
@@ -38,17 +39,20 @@ couchTests.compact = function(debug) {
T(db.ensureFullCommit().ok);
var deletesize = db.info().disk_size;
T(deletesize > originalsize);
+ T(db.setDbProperty("_revs_limit", 666).ok);
T(db.compact().ok);
T(db.last_req.status == 202);
// compaction isn't instantaneous, loop until done
while (db.info().compact_running) {};
+ T(db.info().instance_start_time == start_time);
+ T(db.getDbProperty("_revs_limit") === 666);
T(db.ensureFullCommit().ok);
restartServer();
var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc/foo.txt");
- T(xhr.responseText == "This is a base64 encoded text")
- T(xhr.getResponseHeader("Content-Type") == "text/plain")
+ T(xhr.responseText == "This is a base64 encoded text");
+ T(xhr.getResponseHeader("Content-Type") == "text/plain");
T(db.info().doc_count == 1);
T(db.info().disk_size < deletesize);
diff --git a/share/www/script/test/config.js b/share/www/script/test/config.js
index ef74934b..e83ecfd9 100644
--- a/share/www/script/test/config.js
+++ b/share/www/script/test/config.js
@@ -29,19 +29,25 @@ couchTests.config = function(debug) {
*/
var server_port = CouchDB.host.split(':');
if(server_port.length == 1 && CouchDB.inBrowser) {
- var proto = window.location.protocol;
- if(proto == "http:") {
+ if(CouchDB.protocol == "http://") {
port = 80;
}
- if(proto == "https:") {
+ 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) {
- T(config.httpd.port == port);
+ TEquals(config_port, port, "ports should match");
}
T(config.couchdb.database_dir);
@@ -50,7 +56,8 @@ couchTests.config = function(debug) {
T(config.log.level);
T(config.query_servers.javascript);
- // test that settings can be altered
+ // 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"}
@@ -64,4 +71,93 @@ couchTests.config = function(debug) {
xhr = CouchDB.request("GET", "/_config/test/foo");
config = JSON.parse(xhr.responseText);
T(config == "bar");
+
+ // 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");
};
diff --git a/share/www/script/test/conflicts.js b/share/www/script/test/conflicts.js
index b8b93946..7258bc31 100644
--- a/share/www/script/test/conflicts.js
+++ b/share/www/script/test/conflicts.js
@@ -44,7 +44,7 @@ couchTests.conflicts = function(debug) {
var changes = db.changes();
- T( changes.results.length == 1)
+ T(changes.results.length == 1);
// Now clear out the _rev member and save. This indicates this document is
// new, not based on an existing revision.
diff --git a/share/www/script/test/content_negotiation.js b/share/www/script/test/content_negotiation.js
index 171dbb3d..c79df948 100644
--- a/share/www/script/test/content_negotiation.js
+++ b/share/www/script/test/content_negotiation.js
@@ -17,11 +17,14 @@ couchTests.content_negotiation = function(debug) {
if (debug) debugger;
var xhr;
- xhr = CouchDB.request("GET", "/test_suite_db/");
- TEquals("text/plain;charset=utf-8", xhr.getResponseHeader("Content-Type"));
+ // with no accept header
+ var req = CouchDB.newXhr();
+ req.open("GET", "/test_suite_db/", false);
+ req.send("");
+ TEquals("text/plain;charset=utf-8", req.getResponseHeader("Content-Type"));
// make sure JSON responses end in a newline
- var text = xhr.responseText;
+ var text = req.responseText;
TEquals("\n", text[text.length-1]);
xhr = CouchDB.request("GET", "/test_suite_db/", {
diff --git a/share/www/script/test/cookie_auth.js b/share/www/script/test/cookie_auth.js
index 9eadfee0..8ad993cc 100644
--- a/share/www/script/test/cookie_auth.js
+++ b/share/www/script/test/cookie_auth.js
@@ -46,39 +46,39 @@ couchTests.cookie_auth = function(debug) {
// Create a user
var jasonUserDoc = CouchDB.prepareUserDoc({
- username: "Jason Davies",
+ name: "Jason Davies",
roles: ["dev"]
}, password);
T(usersDb.save(jasonUserDoc).ok);
var checkDoc = usersDb.open(jasonUserDoc._id);
- T(checkDoc.username == "Jason Davies");
+ T(checkDoc.name == "Jason Davies");
var jchrisUserDoc = CouchDB.prepareUserDoc({
- username: "jchris@apache.org"
+ name: "jchris@apache.org"
}, "funnybone");
T(usersDb.save(jchrisUserDoc).ok);
// make sure we cant create duplicate users
var duplicateJchrisDoc = CouchDB.prepareUserDoc({
- username: "jchris@apache.org"
+ name: "jchris@apache.org"
}, "eh, Boo-Boo?");
try {
- usersDb.save(duplicateJchrisDoc)
+ usersDb.save(duplicateJchrisDoc);
T(false && "Can't create duplicate user names. Should have thrown an error.");
} catch (e) {
T(e.error == "conflict");
T(usersDb.last_req.status == 409);
}
- // we can't create _usernames
+ // we can't create _names
var underscoreUserDoc = CouchDB.prepareUserDoc({
- username: "_why"
+ name: "_why"
}, "copperfield");
try {
- usersDb.save(underscoreUserDoc)
+ usersDb.save(underscoreUserDoc);
T(false && "Can't create underscore user names. Should have thrown an error.");
} catch (e) {
T(e.error == "forbidden");
@@ -87,46 +87,59 @@ couchTests.cookie_auth = function(debug) {
// we can't create docs with malformed ids
var badIdDoc = CouchDB.prepareUserDoc({
- username: "foo"
+ name: "foo"
}, "bar");
badIdDoc._id = "org.apache.couchdb:w00x";
try {
- usersDb.save(badIdDoc)
+ usersDb.save(badIdDoc);
T(false && "Can't create malformed docids. Should have thrown an error.");
} catch (e) {
T(e.error == "forbidden");
T(usersDb.last_req.status == 403);
}
-
- try {
- usersDb.save(underscoreUserDoc)
- T(false && "Can't create underscore user names. Should have thrown an error.");
- } catch (e) {
- T(e.error == "forbidden");
- T(usersDb.last_req.status == 403);
- }
// login works
T(CouchDB.login('Jason Davies', password).ok);
- T(CouchDB.session().name == 'Jason Davies');
+ T(CouchDB.session().userCtx.name == 'Jason Davies');
+ // JSON login works
+ var xhr = CouchDB.request("POST", "/_session", {
+ headers: {"Content-Type": "application/json"},
+ body: JSON.stringify({
+ name: 'Jason Davies',
+ password: password
+ })
+ });
+
+ T(JSON.parse(xhr.responseText).ok);
+ T(CouchDB.session().userCtx.name == 'Jason Davies');
+
// update one's own credentials document
jasonUserDoc.foo=2;
T(usersDb.save(jasonUserDoc).ok);
+ T(CouchDB.session().userCtx.roles.indexOf("_admin") == -1);
+ // can't delete another users doc unless you are admin
+ try {
+ usersDb.deleteDoc(jchrisUserDoc);
+ T(false && "Can't delete other users docs. Should have thrown an error.");
+ } catch (e) {
+ T(e.error == "forbidden");
+ T(usersDb.last_req.status == 403);
+ }
// TODO should login() throw an exception here?
T(!CouchDB.login('Jason Davies', "2.71828").ok);
T(!CouchDB.login('Robert Allen Zimmerman', 'd00d').ok);
// a failed login attempt should log you out
- T(CouchDB.session().name != 'Jason Davies');
+ T(CouchDB.session().userCtx.name != 'Jason Davies');
// test redirect
xhr = CouchDB.request("POST", "/_session?next=/", {
headers: {"Content-Type": "application/x-www-form-urlencoded"},
- body: "username=Jason%20Davies&password="+encodeURIComponent(password)
+ body: "name=Jason%20Davies&password="+encodeURIComponent(password)
});
// should this be a redirect code instead of 200?
// The cURL adapter is returning the expected 302 here.
@@ -134,23 +147,23 @@ couchTests.cookie_auth = function(debug) {
// to follow the redirect, ie, the browser follows and does a
// GET on the returned Location
if (xhr.status == 200) {
- T(/Welcome/.test(xhr.responseText))
+ T(/Welcome/.test(xhr.responseText));
} else {
- T(xhr.status == 302)
- T(xhr.getResponseHeader("Location"))
+ T(xhr.status == 302);
+ T(xhr.getResponseHeader("Location"));
}
// test users db validations
//
// test that you can't update docs unless you are logged in as the user (or are admin)
T(CouchDB.login("jchris@apache.org", "funnybone").ok);
- T(CouchDB.session().name == "jchris@apache.org");
- T(CouchDB.session().roles.length == 0);
+ T(CouchDB.session().userCtx.name == "jchris@apache.org");
+ T(CouchDB.session().userCtx.roles.length == 0);
jasonUserDoc.foo=3;
try {
- usersDb.save(jasonUserDoc)
+ usersDb.save(jasonUserDoc);
T(false && "Can't update someone else's user doc. Should have thrown an error.");
} catch (e) {
T(e.error == "forbidden");
@@ -161,7 +174,7 @@ couchTests.cookie_auth = function(debug) {
jchrisUserDoc.roles = ["foo"];
try {
- usersDb.save(jchrisUserDoc)
+ usersDb.save(jchrisUserDoc);
T(false && "Can't set roles unless you are admin. Should have thrown an error.");
} catch (e) {
T(e.error == "forbidden");
@@ -169,7 +182,7 @@ couchTests.cookie_auth = function(debug) {
}
T(CouchDB.logout().ok);
- T(CouchDB.session().roles[0] == "_admin");
+ T(CouchDB.session().userCtx.roles[0] == "_admin");
jchrisUserDoc.foo = ["foo"];
T(usersDb.save(jchrisUserDoc).ok);
@@ -178,7 +191,7 @@ couchTests.cookie_auth = function(debug) {
jchrisUserDoc.roles = ["_bar"];
try {
- usersDb.save(jchrisUserDoc)
+ usersDb.save(jchrisUserDoc);
T(false && "Can't add system roles to user's db. Should have thrown an error.");
} catch (e) {
T(e.error == "forbidden");
@@ -187,24 +200,24 @@ couchTests.cookie_auth = function(debug) {
// make sure the foo role has been applied
T(CouchDB.login("jchris@apache.org", "funnybone").ok);
- T(CouchDB.session().name == "jchris@apache.org");
- T(CouchDB.session().roles.indexOf("_admin") == -1);
- T(CouchDB.session().roles.indexOf("foo") != -1);
+ T(CouchDB.session().userCtx.name == "jchris@apache.org");
+ T(CouchDB.session().userCtx.roles.indexOf("_admin") == -1);
+ T(CouchDB.session().userCtx.roles.indexOf("foo") != -1);
// now let's make jchris a server admin
T(CouchDB.logout().ok);
- T(CouchDB.session().roles[0] == "_admin");
- T(CouchDB.session().name == null);
+ T(CouchDB.session().userCtx.roles[0] == "_admin");
+ T(CouchDB.session().userCtx.name == null);
// set the -hashed- password so the salt matches
// todo ask on the ML about this
run_on_modified_server([{section: "admins",
key: "jchris@apache.org", value: "funnybone"}], function() {
T(CouchDB.login("jchris@apache.org", "funnybone").ok);
- T(CouchDB.session().name == "jchris@apache.org");
- T(CouchDB.session().roles.indexOf("_admin") != -1);
+ T(CouchDB.session().userCtx.name == "jchris@apache.org");
+ T(CouchDB.session().userCtx.roles.indexOf("_admin") != -1);
// test that jchris still has the foo role
- T(CouchDB.session().roles.indexOf("foo") != -1);
+ T(CouchDB.session().userCtx.roles.indexOf("foo") != -1);
// should work even when user doc has no password
jchrisUserDoc = usersDb.open(jchrisUserDoc._id);
@@ -214,13 +227,13 @@ couchTests.cookie_auth = function(debug) {
T(CouchDB.logout().ok);
T(CouchDB.login("jchris@apache.org", "funnybone").ok);
var s = CouchDB.session();
- T(s.name == "jchris@apache.org");
- T(s.roles.indexOf("_admin") != -1);
+ T(s.userCtx.name == "jchris@apache.org");
+ T(s.userCtx.roles.indexOf("_admin") != -1);
// test session info
- T(s.info.authenticated == "{couch_httpd_auth, cookie_authentication_handler}");
- T(s.info.user_db == "test_suite_users");
+ T(s.info.authenticated == "cookie");
+ T(s.info.authentication_db == "test_suite_users");
// test that jchris still has the foo role
- T(CouchDB.session().roles.indexOf("foo") != -1);
+ T(CouchDB.session().userCtx.roles.indexOf("foo") != -1);
});
} finally {
diff --git a/share/www/script/test/copy_doc.js b/share/www/script/test/copy_doc.js
index a6de1892..99e3c7fe 100644
--- a/share/www/script/test/copy_doc.js
+++ b/share/www/script/test/copy_doc.js
@@ -36,6 +36,9 @@ couchTests.copy_doc = function(debug) {
});
T(xhr.status == 409); // conflict
+ var xhr = CouchDB.request("COPY", "/test_suite_db/doc_to_be_copied2");
+ T(xhr.status == 400); // bad request (no Destination header)
+
var rev = db.open("doc_to_be_overwritten")._rev;
var xhr = CouchDB.request("COPY", "/test_suite_db/doc_to_be_copied2", {
headers: {"Destination":"doc_to_be_overwritten?rev=" + rev}
diff --git a/share/www/script/test/delayed_commits.js b/share/www/script/test/delayed_commits.js
index d0c87182..dbb072fb 100644
--- a/share/www/script/test/delayed_commits.js
+++ b/share/www/script/test/delayed_commits.js
@@ -122,4 +122,33 @@ couchTests.delayed_commits = function(debug) {
}
});
+
+ // Test that a conflict can't cause delayed commits to fail
+ run_on_modified_server(
+ [{section: "couchdb",
+ key: "delayed_commits",
+ value: "true"}],
+
+ function() {
+ //First save a document and commit it
+ T(db.save({_id:"6",a:2,b:4}).ok);
+ T(db.ensureFullCommit().ok);
+ //Generate a conflict
+ try {
+ db.save({_id:"6",a:2,b:4});
+ } catch( e) {
+ T(e.error == "conflict");
+ }
+ //Wait for the delayed commit interval to pass
+ var time = new Date();
+ while(new Date() - time < 2000);
+ //Save a new doc
+ T(db.save({_id:"7",a:2,b:4}).ok);
+ //Wait for the delayed commit interval to pass
+ var time = new Date();
+ while(new Date() - time < 2000);
+ //Crash the server and make sure the last doc was written
+ restartServer();
+ T(db.open("7") != null);
+ });
};
diff --git a/share/www/script/test/design_docs.js b/share/www/script/test/design_docs.js
index 9318d2bc..f2f63841 100644
--- a/share/www/script/test/design_docs.js
+++ b/share/www/script/test/design_docs.js
@@ -13,139 +13,309 @@
couchTests.design_docs = function(debug) {
var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
var db2 = new CouchDB("test_suite_db_a", {"X-Couch-Full-Commit":"false"});
+
+ if (debug) debugger;
+
db.deleteDb();
db.createDb();
db2.deleteDb();
db2.createDb();
- if (debug) debugger;
- run_on_modified_server(
- [{section: "query_server_config",
+ var server_config = [
+ {
+ section: "query_server_config",
key: "reduce_limit",
- value: "false"}],
-function() {
+ value: "false"
+ }
+ ];
- var numDocs = 500;
+ var testFun = function() {
+ var numDocs = 500;
- function makebigstring(power) {
- var str = "a";
- while(power-- > 0) {
- str = str + str;
+ function makebigstring(power) {
+ var str = "a";
+ while(power-- > 0) {
+ str = str + str;
+ }
+ return str;
}
- return str;
- }
-
- var designDoc = {
- _id:"_design/test", // turn off couch.js id escaping?
- language: "javascript",
- views: {
- all_docs_twice: {map: "function(doc) { emit(doc.integer, null); emit(doc.integer, null) }"},
- no_docs: {map: "function(doc) {}"},
- single_doc: {map: "function(doc) { if (doc._id == \"1\") { emit(1, null) }}"},
- summate: {map:"function (doc) {emit(doc.integer, doc.integer)};",
- reduce:"function (keys, values) { return sum(values); };"},
- summate2: {map:"function (doc) {emit(doc.integer, doc.integer)};",
- reduce:"function (keys, values) { return sum(values); };"},
- huge_src_and_results: {map: "function(doc) { if (doc._id == \"1\") { emit(\"" + makebigstring(16) + "\", null) }}",
- reduce:"function (keys, values) { return \"" + makebigstring(16) + "\"; };"}
- },
- shows: {
- simple: "function() {return 'ok'};"
- }
- }
- var xhr = CouchDB.request("PUT", "/test_suite_db_a/_design/test", {body: JSON.stringify(designDoc)});
- var resp = JSON.parse(xhr.responseText);
-
- TEquals(resp.rev, db.save(designDoc).rev);
-
- // test that editing a show fun on the ddoc results in a change in output
- var xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_show/simple");
- T(xhr.status == 200);
- TEquals(xhr.responseText, "ok");
-
- designDoc.shows.simple = "function() {return 'ko'};"
- T(db.save(designDoc).ok);
-
- var xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_show/simple");
- T(xhr.status == 200);
- TEquals(xhr.responseText, "ko");
-
- var xhr = CouchDB.request("GET", "/test_suite_db_a/_design/test/_show/simple?cache=buster");
- T(xhr.status == 200);
- TEquals("ok", xhr.responseText, 'query server used wrong ddoc');
-
- // test that we get design doc info back
- var dinfo = db.designInfo("_design/test");
- TEquals("test", dinfo.name);
- var vinfo = dinfo.view_index;
- TEquals(51, vinfo.disk_size);
- TEquals(false, vinfo.compact_running);
- TEquals("3f88e53b303e2342e49a66c538c30679", vinfo.signature);
-
- db.bulkSave(makeDocs(1, numDocs + 1));
-
- // test that the _all_docs view returns correctly with keys
- var results = db.allDocs({startkey:"_design", endkey:"_design0"});
- T(results.rows.length == 1);
-
- for (var loop = 0; loop < 2; loop++) {
- var rows = db.view("test/all_docs_twice").rows;
- for (var i = 0; i < numDocs; i++) {
- T(rows[2*i].key == i+1);
- T(rows[(2*i)+1].key == i+1);
- }
- T(db.view("test/no_docs").total_rows == 0)
- T(db.view("test/single_doc").total_rows == 1)
+
+ var designDoc = {
+ _id: "_design/test",
+ language: "javascript",
+ whatever : {
+ stringzone : "exports.string = 'plankton';",
+ commonjs : {
+ whynot : "exports.test = require('../stringzone'); " +
+ "exports.foo = require('whatever/stringzone');",
+ upper : "exports.testing = require('./whynot').test.string.toUpperCase()+" +
+ "module.id+require('./whynot').foo.string"
+ }
+ },
+ views: {
+ all_docs_twice: {
+ map:
+ (function(doc) {
+ emit(doc.integer, null);
+ emit(doc.integer, null);
+ }).toString()
+ },
+ no_docs: {
+ map:
+ (function(doc) {
+ }).toString()
+ },
+ single_doc: {
+ map:
+ (function(doc) {
+ if (doc._id === "1") {
+ emit(1, null);
+ }
+ }).toString()
+ },
+ summate: {
+ map:
+ (function(doc) {
+ emit(doc.integer, doc.integer);
+ }).toString(),
+ reduce:
+ (function(keys, values) {
+ return sum(values);
+ }).toString()
+ },
+ summate2: {
+ map:
+ (function(doc) {
+ emit(doc.integer, doc.integer);
+ }).toString(),
+ reduce:
+ (function(keys, values) {
+ return sum(values);
+ }).toString()
+ },
+ huge_src_and_results: {
+ map:
+ (function(doc) {
+ if (doc._id === "1") {
+ emit(makebigstring(16), null);
+ }
+ }).toString(),
+ reduce:
+ (function(keys, values) {
+ return makebigstring(16);
+ }).toString()
+ },
+ lib : {
+ baz : "exports.baz = 'bam';",
+ foo : {
+ foo : "exports.foo = 'bar';",
+ boom : "exports.boom = 'ok';",
+ zoom : "exports.zoom = 'yeah';"
+ }
+ },
+ commonjs : {
+ map :
+ (function(doc) {
+ emit(null, require('views/lib/foo/boom').boom);
+ }).toString()
+ }
+ },
+ shows: {
+ simple:
+ (function() {
+ return 'ok';
+ }).toString(),
+ requirey:
+ (function() {
+ var lib = require('whatever/commonjs/upper');
+ return lib.testing;
+ }).toString(),
+ circular:
+ (function() {
+ var lib = require('whatever/commonjs/upper');
+ return JSON.stringify(this);
+ }).toString()
+ }
+ }; // designDoc
+
+ var xhr = CouchDB.request(
+ "PUT", "/test_suite_db_a/_design/test", {body: JSON.stringify(designDoc)}
+ );
+ var resp = JSON.parse(xhr.responseText);
+
+ TEquals(resp.rev, db.save(designDoc).rev);
+
+ // test that editing a show fun on the ddoc results in a change in output
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_show/simple");
+ T(xhr.status == 200);
+ TEquals(xhr.responseText, "ok");
+
+ designDoc.shows.simple = (function() {
+ return 'ko';
+ }).toString();
+ T(db.save(designDoc).ok);
+
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_show/simple");
+ T(xhr.status == 200);
+ TEquals(xhr.responseText, "ko");
+
+ xhr = CouchDB.request(
+ "GET", "/test_suite_db_a/_design/test/_show/simple?cache=buster"
+ );
+ T(xhr.status == 200);
+ TEquals("ok", xhr.responseText, 'query server used wrong ddoc');
+
+ // test commonjs require
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_show/requirey");
+ T(xhr.status == 200);
+ TEquals("PLANKTONwhatever/commonjs/upperplankton", xhr.responseText);
+
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_show/circular");
+ T(xhr.status == 200);
+ TEquals("javascript", JSON.parse(xhr.responseText).language);
+
+ var prev_view_sig = db.designInfo("_design/test").view_index.signature;
+ var prev_view_size = db.designInfo("_design/test").view_index.disk_size;
+
+ db.bulkSave(makeDocs(1, numDocs + 1));
T(db.ensureFullCommit().ok);
- restartServer();
- };
-
- // test when language not specified, Javascript is implied
- var designDoc2 = {
- _id:"_design/test2",
- // language: "javascript",
- views: {
- single_doc: {map: "function(doc) { if (doc._id == \"1\") { emit(1, null) }}"}
- }
- };
- T(db.save(designDoc2).ok);
- T(db.view("test2/single_doc").total_rows == 1);
+ // test that we get correct design doc info back,
+ // and also that GET /db/_design/test/_info
+ // hasn't triggered an update of the views
+ db.view("test/summate", {stale: "ok"}); // make sure view group's open
+ for (var i = 0; i < 2; i++) {
+ var dinfo = db.designInfo("_design/test");
+ TEquals("test", dinfo.name);
+ var vinfo = dinfo.view_index;
+ TEquals(prev_view_size, vinfo.disk_size, "view group disk size didn't change");
+ TEquals(false, vinfo.compact_running);
+ TEquals(prev_view_sig, vinfo.signature, 'ddoc sig');
+ // wait some time (there were issues where an update
+ // of the views had been triggered in the background)
+ var start = new Date().getTime();
+ while (new Date().getTime() < start + 2000);
+ TEquals(0, db.view("test/all_docs_twice", {stale: "ok"}).total_rows, 'view info');
+ TEquals(0, db.view("test/single_doc", {stale: "ok"}).total_rows, 'view info');
+ TEquals(0, db.view("test/summate", {stale: "ok"}).rows.length, 'view info');
+ T(db.ensureFullCommit().ok);
+ restartServer();
+ };
+
+ db.bulkSave(makeDocs(numDocs + 1, numDocs * 2 + 1));
+ T(db.ensureFullCommit().ok);
+
+ // open view group
+ db.view("test/summate", {stale: "ok"});
+ // wait so the views can get initialized
+ var start = new Date().getTime();
+ while (new Date().getTime() < start + 2000);
+
+ // test that POST /db/_view_cleanup
+ // doesn't trigger an update of the views
+ var len1 = db.view("test/all_docs_twice", {stale: "ok"}).total_rows;
+ var len2 = db.view("test/single_doc", {stale: "ok"}).total_rows;
+ var len3 = db.view("test/summate", {stale: "ok"}).rows.length;
+ for (i = 0; i < 2; i++) {
+ T(db.viewCleanup().ok);
+ // wait some time (there were issues where an update
+ // of the views had been triggered in the background)
+ start = new Date().getTime();
+ while (new Date().getTime() < start + 2000);
+ TEquals(len1, db.view("test/all_docs_twice", {stale: "ok"}).total_rows, 'view cleanup');
+ TEquals(len2, db.view("test/single_doc", {stale: "ok"}).total_rows, 'view cleanup');
+ TEquals(len3, db.view("test/summate", {stale: "ok"}).rows.length, 'view cleanup');
+ T(db.ensureFullCommit().ok);
+ restartServer();
+ // we'll test whether the view group stays closed
+ // and the views stay uninitialized (they should!)
+ len1 = len2 = len3 = 0;
+ };
+
+ // test commonjs in map functions
+ resp = db.view("test/commonjs", {limit:1});
+ T(resp.rows[0].value == 'ok');
+
+ // test that the _all_docs view returns correctly with keys
+ var results = db.allDocs({startkey:"_design", endkey:"_design0"});
+ T(results.rows.length == 1);
+
+ for (i = 0; i < 2; i++) {
+ var rows = db.view("test/all_docs_twice").rows;
+ for (var j = 0; j < numDocs; j++) {
+ T(rows[2 * j].key == (j + 1));
+ T(rows[(2 * j) + 1].key == (j + 1));
+ };
+ T(db.view("test/no_docs").total_rows == 0);
+ T(db.view("test/single_doc").total_rows == 1);
+ T(db.ensureFullCommit().ok);
+ restartServer();
+ };
- var summate = function(N) {return (N+1)*N/2;};
- var result = db.view("test/summate");
- T(result.rows[0].value == summate(numDocs));
+ // test when language not specified, Javascript is implied
+ var designDoc2 = {
+ _id: "_design/test2",
+ // language: "javascript",
+ views: {
+ single_doc: {
+ map:
+ (function(doc) {
+ if (doc._id === "1") {
+ emit(1, null);
+ }
+ }).toString()
+ }
+ }
+ };
- result = db.view("test/summate", {startkey:4,endkey:4});
- T(result.rows[0].value == 4);
+ T(db.save(designDoc2).ok);
+ T(db.view("test2/single_doc").total_rows == 1);
- result = db.view("test/summate", {startkey:4,endkey:5});
- T(result.rows[0].value == 9);
+ var summate = function(N) {
+ return (N + 1) * (N / 2);
+ };
+ var result = db.view("test/summate");
+ T(result.rows[0].value == summate(numDocs * 2));
- result = db.view("test/summate", {startkey:4,endkey:6});
- T(result.rows[0].value == 15);
+ result = db.view("test/summate", {startkey: 4, endkey: 4});
+ T(result.rows[0].value == 4);
- // Verify that a shared index (view def is an exact copy of "summate")
- // does not confuse the reduce stage
- result = db.view("test/summate2", {startkey:4,endkey:6});
- T(result.rows[0].value == 15);
+ result = db.view("test/summate", {startkey: 4, endkey: 5});
+ T(result.rows[0].value == 9);
- for(var i=1; i<numDocs/2; i+=30) {
- result = db.view("test/summate", {startkey:i,endkey:numDocs-i});
- T(result.rows[0].value == summate(numDocs-i) - summate(i-1));
- }
+ result = db.view("test/summate", {startkey: 4, endkey: 6});
+ T(result.rows[0].value == 15);
- T(db.deleteDoc(designDoc).ok);
- T(db.open(designDoc._id) == null);
- T(db.view("test/no_docs") == null);
+ // test start_key and end_key aliases
+ result = db.view("test/summate", {start_key: 4, end_key: 6});
+ T(result.rows[0].value == 15);
- T(db.ensureFullCommit().ok);
- restartServer();
- T(db.open(designDoc._id) == null);
- T(db.view("test/no_docs") == null);
+ // Verify that a shared index (view def is an exact copy of "summate")
+ // does not confuse the reduce stage
+ result = db.view("test/summate2", {startkey: 4, endkey: 6});
+ T(result.rows[0].value == 15);
- // trigger ddoc cleanup
- T(db.viewCleanup().ok);
+ for(i = 1; i < (numDocs / 2); i += 30) {
+ result = db.view("test/summate", {startkey: i, endkey: (numDocs - i)});
+ T(result.rows[0].value == summate(numDocs - i) - summate(i - 1));
+ }
+
+ T(db.deleteDoc(designDoc).ok);
+ T(db.open(designDoc._id) == null);
+ T(db.view("test/no_docs") == null);
+
+ T(db.ensureFullCommit().ok);
+ restartServer();
+ T(db.open(designDoc._id) == null);
+ T(db.view("test/no_docs") == null);
-});
+ // trigger ddoc cleanup
+ T(db.viewCleanup().ok);
+ }; // enf of testFun
+
+ run_on_modified_server(server_config, testFun);
+
+ // cleanup
+ db.deleteDb();
+ db2.deleteDb();
};
diff --git a/share/www/script/test/erlang_views.js b/share/www/script/test/erlang_views.js
index e9e0363f..7eddab40 100644
--- a/share/www/script/test/erlang_views.js
+++ b/share/www/script/test/erlang_views.js
@@ -29,8 +29,8 @@ couchTests.erlang_views = function(debug) {
T(db.save(doc).ok);
var mfun = 'fun({Doc}) -> ' +
- ' K = proplists:get_value(<<"integer">>, Doc, null), ' +
- ' V = proplists:get_value(<<"string">>, Doc, null), ' +
+ ' K = couch_util:get_value(<<"integer">>, Doc, null), ' +
+ ' V = couch_util:get_value(<<"string">>, Doc, null), ' +
' Emit(K, V) ' +
'end.';
@@ -44,7 +44,7 @@ couchTests.erlang_views = function(debug) {
// check simple reduction - another doc with same key.
var doc = {_id: "2", integer: 1, string: "str2"};
T(db.save(doc).ok);
- rfun = "fun(Keys, Values, ReReduce) -> length(Values) end."
+ rfun = "fun(Keys, Values, ReReduce) -> length(Values) end.";
results = db.query(mfun, rfun, null, null, "erlang");
T(results.rows[0].value == 2);
@@ -55,9 +55,9 @@ couchTests.erlang_views = function(debug) {
shows: {
simple:
'fun(Doc, {Req}) -> ' +
- ' {Info} = proplists:get_value(<<"info">>, Req, {[]}), ' +
- ' Purged = proplists:get_value(<<"purge_seq">>, Info, -1), ' +
- ' Verb = proplists:get_value(<<"method">>, Req, <<"not_get">>), ' +
+ ' {Info} = couch_util:get_value(<<"info">>, Req, {[]}), ' +
+ ' Purged = couch_util:get_value(<<"purge_seq">>, Info, -1), ' +
+ ' Verb = couch_util:get_value(<<"method">>, Req, <<"not_get">>), ' +
' R = list_to_binary(io_lib:format("~b - ~s", [Purged, Verb])), ' +
' {[{<<"code">>, 200}, {<<"headers">>, {[]}}, {<<"body">>, R}]} ' +
'end.'
@@ -67,7 +67,7 @@ couchTests.erlang_views = function(debug) {
'fun(Head, {Req}) -> ' +
' Send(<<"head">>), ' +
' Fun = fun({Row}, _) -> ' +
- ' Val = proplists:get_value(<<"value">>, Row, -1), ' +
+ ' Val = couch_util:get_value(<<"value">>, Row, -1), ' +
' Send(list_to_binary(integer_to_list(Val))), ' +
' {ok, nil} ' +
' end, ' +
@@ -117,10 +117,10 @@ couchTests.erlang_views = function(debug) {
T(db.bulkSave(docs).length, 250, "Saved big doc set.");
var mfun = 'fun({Doc}) -> ' +
- 'Words = proplists:get_value(<<"words">>, Doc), ' +
+ 'Words = couch_util:get_value(<<"words">>, Doc), ' +
'lists:foreach(fun({Word}) -> ' +
- 'WordString = proplists:get_value(<<"word">>, Word), ' +
- 'Count = proplists:get_value(<<"count">>, Word), ' +
+ 'WordString = couch_util:get_value(<<"word">>, Word), ' +
+ 'Count = couch_util:get_value(<<"count">>, Word), ' +
'Emit(WordString , Count) ' +
'end, Words) ' +
'end.';
diff --git a/share/www/script/test/etags_views.js b/share/www/script/test/etags_views.js
index a12734f8..f556d6ac 100644
--- a/share/www/script/test/etags_views.js
+++ b/share/www/script/test/etags_views.js
@@ -11,23 +11,34 @@
// the License.
couchTests.etags_views = function(debug) {
- var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
+ var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"true"});
db.deleteDb();
db.createDb();
if (debug) debugger;
var designDoc = {
- _id:"_design/etags",
+ _id: "_design/etags",
language: "javascript",
views : {
+ fooView: {
+ map: stringFun(function(doc) {
+ if (doc.foo) {
+ emit("bar", 1);
+ }
+ }),
+ },
basicView : {
map : stringFun(function(doc) {
- emit(doc.integer, doc.string);
+ if(doc.integer && doc.string) {
+ emit(doc.integer, doc.string);
+ }
})
},
withReduce : {
map : stringFun(function(doc) {
- emit(doc.integer, doc.string);
+ if(doc.integer && doc.string) {
+ emit(doc.integer, doc.string);
+ }
}),
reduce : stringFun(function(keys, values, rereduce) {
if (rereduce) {
@@ -38,11 +49,11 @@ couchTests.etags_views = function(debug) {
})
}
}
- }
+ };
T(db.save(designDoc).ok);
+ db.bulkSave(makeDocs(0, 10));
+
var xhr;
- var docs = makeDocs(0, 10);
- db.bulkSave(docs);
// verify get w/Etag on map view
xhr = CouchDB.request("GET", "/test_suite_db/_design/etags/_view/basicView");
@@ -52,17 +63,92 @@ couchTests.etags_views = function(debug) {
headers: {"if-none-match": etag}
});
T(xhr.status == 304);
- // TODO GET with keys (when that is available)
+
+ // verify ETag doesn't change when an update
+ // doesn't change the view group's index
+ T(db.save({"_id":"doc1", "foo":"bar"}).ok);
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/etags/_view/basicView");
+ var etag1 = xhr.getResponseHeader("etag");
+ T(etag1 == etag);
+
+ // Verify that purges affect etags
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/etags/_view/fooView");
+ var foo_etag = xhr.getResponseHeader("etag");
+ var doc1 = db.open("doc1");
+ xhr = CouchDB.request("POST", "/test_suite_db/_purge", {
+ body: JSON.stringify({"doc1":[doc1._rev]})
+ });
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/etags/_view/fooView");
+ var etag1 = xhr.getResponseHeader("etag");
+ T(etag1 != foo_etag);
+
+ // Test that _purge didn't affect the other view etags.
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/etags/_view/basicView");
+ var etag1 = xhr.getResponseHeader("etag");
+ T(etag1 == etag);
+
+ // verify different views in the same view group may have different ETags
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/etags/_view/fooView");
+ var etag1 = xhr.getResponseHeader("etag");
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/etags/_view/basicView");
+ var etag2 = xhr.getResponseHeader("etag");
+ T(etag1 != etag2);
+
+ // verify ETag changes when an update changes the view group's index.
+ db.bulkSave(makeDocs(10, 20));
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/etags/_view/basicView");
+ var etag1 = xhr.getResponseHeader("etag");
+ T(etag1 != etag);
+
+ // verify ETag is the same after a restart
+ restartServer();
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/etags/_view/basicView");
+ var etag2 = xhr.getResponseHeader("etag");
+ T(etag1 == etag2);
// reduce view
xhr = CouchDB.request("GET", "/test_suite_db/_design/etags/_view/withReduce");
T(xhr.status == 200);
var etag = xhr.getResponseHeader("etag");
- xhr = CouchDB.request("GET", "/test_suite_db/_design/etags/_view/withReduce", {
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/etags/_view/withReduce",{
headers: {"if-none-match": etag}
});
T(xhr.status == 304);
+ // verify ETag doesn't change when an update
+ // doesn't change the view group's index
+ T(db.save({"_id":"doc3", "foo":"bar"}).ok);
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/etags/_view/withReduce");
+ var etag1 = xhr.getResponseHeader("etag");
+ T(etag1 == etag);
+ // purge
+ var doc3 = db.open("doc3");
+ xhr = CouchDB.request("POST", "/test_suite_db/_purge", {
+ body: JSON.stringify({"doc3":[doc3._rev]})
+ });
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/etags/_view/withReduce");
+ var etag1 = xhr.getResponseHeader("etag");
+ T(etag1 == etag);
+
+ // verify different views in the same view group may have different ETags
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/etags/_view/fooView");
+ var etag1 = xhr.getResponseHeader("etag");
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/etags/_view/withReduce");
+ var etag2 = xhr.getResponseHeader("etag");
+ T(etag1 != etag2);
+
+ // verify ETag changes when an update changes the view group's index
+ db.bulkSave(makeDocs(20, 30));
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/etags/_view/withReduce");
+ var etag1 = xhr.getResponseHeader("etag");
+ T(etag1 != etag);
+
+ // verify ETag is the same after a restart
+ restartServer();
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/etags/_view/withReduce");
+ var etag2 = xhr.getResponseHeader("etag");
+ T(etag1 == etag2);
+
// confirm ETag changes with different POST bodies
xhr = CouchDB.request("POST", "/test_suite_db/_design/etags/_view/basicView",
{body: JSON.stringify({keys:[1]})}
diff --git a/share/www/script/test/http.js b/share/www/script/test/http.js
index 8a2e09b8..5f46af52 100644
--- a/share/www/script/test/http.js
+++ b/share/www/script/test/http.js
@@ -25,7 +25,7 @@ couchTests.http = function(debug) {
var xhr = CouchDB.request("PUT", "/test_suite_db/test", {body: "{}"});
var host = CouchDB.host;
- TEquals("http://" + host + "/test_suite_db/test",
+ TEquals(CouchDB.protocol + host + "/test_suite_db/test",
xhr.getResponseHeader("Location"),
"should include ip address");
@@ -34,7 +34,7 @@ couchTests.http = function(debug) {
headers: {"X-Forwarded-Host": "mysite.com"}
});
- TEquals("http://mysite.com/test_suite_db/test2",
+ TEquals(CouchDB.protocol + "mysite.com/test_suite_db/test2",
xhr.getResponseHeader("Location"),
"should include X-Forwarded-Host");
@@ -47,7 +47,7 @@ couchTests.http = function(debug) {
body: "{}",
headers: {"X-Host": "mysite2.com"}
});
- TEquals("http://mysite2.com/test_suite_db/test3",
+ TEquals(CouchDB.protocol + "mysite2.com/test_suite_db/test3",
xhr.getResponseHeader("Location"),
"should include X-Host");
});
diff --git a/share/www/script/test/jsonp.js b/share/www/script/test/jsonp.js
index dfd6a0df..9aba7189 100644
--- a/share/www/script/test/jsonp.js
+++ b/share/www/script/test/jsonp.js
@@ -32,38 +32,51 @@ couchTests.jsonp = function(debug) {
db.deleteDb();
db.createDb();
if (debug) debugger;
-
+
var doc = {_id:"0",a:0,b:0};
T(db.save(doc).ok);
+
+ // callback param is ignored unless jsonp is configured
+ var xhr = CouchDB.request("GET", "/test_suite_db/0?callback=jsonp_not_configured");
+ JSON.parse(xhr.responseText);
- // Test unchunked callbacks.
- var xhr = CouchDB.request("GET", "/test_suite_db/0?callback=jsonp_no_chunk");
- T(xhr.status == 200);
- jsonp_flag = 0;
- eval(xhr.responseText);
- T(jsonp_flag == 1);
- xhr = CouchDB.request("GET", "/test_suite_db/0?callback=foo\"");
- T(xhr.status == 400);
+ run_on_modified_server(
+ [{section: "httpd",
+ key: "allow_jsonp",
+ value: "true"}],
+ function() {
- // Test chunked responses
- var doc = {_id:"1",a:1,b:1};
- T(db.save(doc).ok);
+ // Test unchunked callbacks.
+ var xhr = CouchDB.request("GET", "/test_suite_db/0?callback=jsonp_no_chunk");
+ T(xhr.status == 200);
+ jsonp_flag = 0;
+ eval(xhr.responseText);
+ T(jsonp_flag == 1);
+ xhr = CouchDB.request("GET", "/test_suite_db/0?callback=foo\"");
+ T(xhr.status == 400);
+
+ // Test chunked responses
+ var doc = {_id:"1",a:1,b:1};
+ T(db.save(doc).ok);
+
+ var designDoc = {
+ _id:"_design/test",
+ language: "javascript",
+ views: {
+ all_docs: {map: "function(doc) {if(doc.a) emit(null, doc.a);}"}
+ }
+ };
+ T(db.save(designDoc).ok);
+
+ var url = "/test_suite_db/_design/test/_view/all_docs?callback=jsonp_chunk";
+ xhr = CouchDB.request("GET", url);
+ T(xhr.status == 200);
+ jsonp_flag = 0;
+ eval(xhr.responseText);
+ T(jsonp_flag == 1);
+ xhr = CouchDB.request("GET", url + "\'");
+ T(xhr.status == 400);
+ });
- var designDoc = {
- _id:"_design/test",
- language: "javascript",
- views: {
- all_docs: {map: "function(doc) {if(doc.a) emit(null, doc.a);}"}
- }
- }
- T(db.save(designDoc).ok);
- var url = "/test_suite_db/_design/test/_view/all_docs?callback=jsonp_chunk";
- xhr = CouchDB.request("GET", url);
- T(xhr.status == 200);
- jsonp_flag = 0;
- eval(xhr.responseText);
- T(jsonp_flag == 1);
- xhr = CouchDB.request("GET", url + "\'");
- T(xhr.status == 400);
};
diff --git a/share/www/script/test/list_views.js b/share/www/script/test/list_views.js
index 54214c2c..e98a212e 100644
--- a/share/www/script/test/list_views.js
+++ b/share/www/script/test/list_views.js
@@ -156,6 +156,9 @@ couchTests.list_views = function(debug) {
var row = getRow();
send(row.doc.integer);
return "tail";
+ }),
+ secObj: stringFun(function(head, req) {
+ return toJSON(req.secObj);
})
}
};
@@ -178,7 +181,7 @@ couchTests.list_views = function(debug) {
'fun(Head, {Req}) -> ' +
' Send(<<"[">>), ' +
' Fun = fun({Row}, Sep) -> ' +
- ' Val = proplists:get_value(<<"key">>, Row, 23), ' +
+ ' Val = couch_util:get_value(<<"key">>, Row, 23), ' +
' Send(list_to_binary(Sep ++ integer_to_list(Val))), ' +
' {ok, ","} ' +
' end, ' +
@@ -201,6 +204,7 @@ couchTests.list_views = function(debug) {
T(xhr.status == 200, "standard get should be 200");
T(/head0123456789tail/.test(xhr.responseText));
+
// test that etags are available
var etag = xhr.getResponseHeader("etag");
xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/basicBasic/basicView", {
@@ -220,10 +224,13 @@ couchTests.list_views = function(debug) {
T(etag1 != etag2, "POST to map _list generates key-depdendent ETags");
// test the richness of the arguments
- xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/basicJSON/basicView");
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/basicJSON/basicView?update_seq=true");
T(xhr.status == 200, "standard get should be 200");
var resp = JSON.parse(xhr.responseText);
- TEquals(resp.head, {total_rows:10, offset:0});
+ TEquals(10, resp.head.total_rows);
+ TEquals(0, resp.head.offset);
+ TEquals(11, resp.head.update_seq);
+
T(resp.rows.length == 10);
TEquals(resp.rows[0], {"id": "0","key": 0,"value": "0"});
@@ -320,6 +327,16 @@ couchTests.list_views = function(debug) {
T(/FirstKey: 2/.test(xhr.responseText));
T(/LastKey: 7/.test(xhr.responseText));
+ // multi-key fetch with GET
+ var xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/simpleForm/basicView" +
+ "?keys=[2,4,5,7]");
+
+ T(xhr.status == 200, "multi key");
+ T(!(/Key: 1 /.test(xhr.responseText)));
+ T(/Key: 2/.test(xhr.responseText));
+ T(/FirstKey: 2/.test(xhr.responseText));
+ T(/LastKey: 7/.test(xhr.responseText));
+
// no multi-key fetch allowed when group=false
xhr = CouchDB.request("POST", "/test_suite_db/_design/lists/_list/simpleForm/withReduce?group=false", {
body: '{"keys":[2,4,5,7]}'
@@ -343,13 +360,21 @@ couchTests.list_views = function(debug) {
// T(xhr.getResponseHeader("Content-Type") == "text/plain");
T(xhr.responseText.match(/^head 0 1 2 tail$/) && "basic stop");
- xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/stopIter2/basicView");
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/stopIter2/basicView", {
+ headers : {
+ "Accept" : "text/html"
+ }
+ });
T(xhr.responseText.match(/^head 0 1 2 tail$/) && "stop 2");
// aborting iteration with reduce
var xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/stopIter/withReduce?group=true");
T(xhr.responseText.match(/^head 0 1 2 tail$/) && "reduce stop");
- xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/stopIter2/withReduce?group=true");
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/stopIter2/withReduce?group=true", {
+ headers : {
+ "Accept" : "text/html"
+ }
+ });
T(xhr.responseText.match(/^head 0 1 2 tail$/) && "reduce stop 2");
// with accept headers for HTML
@@ -383,7 +408,7 @@ couchTests.list_views = function(debug) {
T(/LastKey: 0/.test(xhr.responseText));
// Test we do multi-key requests on lists and views in separate docs.
- var url = "/test_suite_db/_design/lists/_list/simpleForm/views/basicView"
+ var url = "/test_suite_db/_design/lists/_list/simpleForm/views/basicView";
xhr = CouchDB.request("POST", url, {
body: '{"keys":[-2,-4,-5,-7]}'
});
@@ -394,6 +419,12 @@ couchTests.list_views = function(debug) {
T(/FirstKey: -2/.test(xhr.responseText));
T(/LastKey: -7/.test(xhr.responseText));
+ // Test if secObj is available
+ var xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/secObj/basicView");
+ T(xhr.status == 200, "standard get should be 200");
+ var resp = JSON.parse(xhr.responseText);
+ T(typeof(resp) == "object");
+
var erlViewTest = function() {
T(db.save(erlListDoc).ok);
var url = "/test_suite_db/_design/erlang/_list/simple/views/basicView" +
@@ -408,6 +439,8 @@ couchTests.list_views = function(debug) {
}
};
+
+
run_on_modified_server([{
section: "native_query_servers",
key: "erlang",
diff --git a/share/www/script/test/method_override.js b/share/www/script/test/method_override.js
new file mode 100644
index 00000000..0bb4c61f
--- /dev/null
+++ b/share/www/script/test/method_override.js
@@ -0,0 +1,40 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+// Allow broken HTTP clients to fake a full method vocabulary with an X-HTTP-METHOD-OVERRIDE header
+couchTests.method_override = function(debug) {
+ var result = JSON.parse(CouchDB.request("GET", "/").responseText);
+ T(result.couchdb == "Welcome");
+
+ var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
+ db.deleteDb();
+
+ db.createDb();
+
+ var doc = {bob : "connie"};
+ xhr = CouchDB.request("POST", "/test_suite_db/fnord", {body: JSON.stringify(doc), headers:{"X-HTTP-Method-Override" : "PUT"}});
+ T(xhr.status == 201);
+
+ doc = db.open("fnord");
+ T(doc.bob == "connie");
+
+ xhr = CouchDB.request("POST", "/test_suite_db/fnord?rev=" + doc._rev, {headers:{"X-HTTP-Method-Override" : "DELETE"}});
+ T(xhr.status == 200);
+
+ xhr = CouchDB.request("GET", "/test_suite_db/fnord2", {body: JSON.stringify(doc), headers:{"X-HTTP-Method-Override" : "PUT"}});
+ // Method Override is ignored when original Method isn't POST
+ T(xhr.status == 404);
+
+ doc = db.open("fnord");
+ T(doc == null);
+
+};
diff --git a/share/www/script/test/oauth.js b/share/www/script/test/oauth.js
index d55d13e8..82ebe8a4 100644
--- a/share/www/script/test/oauth.js
+++ b/share/www/script/test/oauth.js
@@ -71,7 +71,7 @@ couchTests.oauth = function(debug) {
var host = CouchDB.host;
var dbPair = {
source: {
- url: "http://" + host + "/test_suite_db_a",
+ url: CouchDB.protocol + host + "/test_suite_db_a",
auth: {
oauth: {
consumer_key: "key",
@@ -82,7 +82,7 @@ couchTests.oauth = function(debug) {
}
},
target: {
- url: "http://" + host + "/test_suite_db_b",
+ url: CouchDB.protocol + host + "/test_suite_db_b",
headers: {"Authorization": adminBasicAuthHeaderValue()}
}
};
@@ -90,16 +90,26 @@ couchTests.oauth = function(debug) {
// this function will be called on the modified server
var testFun = function () {
try {
- CouchDB.request("PUT", "http://" + host + "/_config/admins/testadmin", {
+ CouchDB.request("PUT", CouchDB.protocol + host + "/_config/admins/testadmin", {
headers: {"X-Couch-Persist": "false"},
body: JSON.stringify(testadminPassword)
});
-
- CouchDB.request("GET", "/_sleep?time=50");
+ var i = 0;
+ waitForSuccess(function() {
+ //loop until the couch server has processed the password
+ i += 1;
+ var xhr = CouchDB.request("GET", CouchDB.protocol + host + "/_config/admins/testadmin?foo="+i,{
+ headers: {
+ "Authorization": adminBasicAuthHeaderValue()
+ }});
+ if (xhr.responseText.indexOf("\"-hashed-") != 0) {
+ throw("still waiting");
+ }
+ }, "wait-for-admin");
CouchDB.newUuids(2); // so we have one to make the salt
- CouchDB.request("PUT", "http://" + host + "/_config/couch_httpd_auth/require_valid_user", {
+ CouchDB.request("PUT", CouchDB.protocol + host + "/_config/couch_httpd_auth/require_valid_user", {
headers: {
"X-Couch-Persist": "false",
"Authorization": adminBasicAuthHeaderValue()
@@ -116,7 +126,7 @@ couchTests.oauth = function(debug) {
// Create a user
var jasonUserDoc = CouchDB.prepareUserDoc({
- username: "jason",
+ name: "jason",
roles: ["test"]
}, "testpassword");
T(usersDb.save(jasonUserDoc).ok);
@@ -147,11 +157,11 @@ couchTests.oauth = function(debug) {
};
// Get request token via Authorization header
- xhr = oauthRequest("GET", "http://" + host + "/_oauth/request_token", message, accessor);
+ xhr = oauthRequest("GET", CouchDB.protocol + host + "/_oauth/request_token", message, accessor);
T(xhr.status == expectedCode);
// GET request token via query parameters
- xhr = oauthRequest("GET", "http://" + host + "/_oauth/request_token", message, accessor);
+ xhr = oauthRequest("GET", CouchDB.protocol + host + "/_oauth/request_token", message, accessor);
T(xhr.status == expectedCode);
responseMessage = OAuth.decodeForm(xhr.responseText);
@@ -161,7 +171,7 @@ couchTests.oauth = function(debug) {
//xhr = CouchDB.request("GET", authorization_url + '?oauth_token=' + responseMessage.oauth_token);
//T(xhr.status == expectedCode);
- xhr = oauthRequest("GET", "http://" + host + "/_session", message, accessor);
+ xhr = oauthRequest("GET", CouchDB.protocol + host + "/_session", message, accessor);
T(xhr.status == expectedCode);
if (xhr.status == expectedCode == 200) {
data = JSON.parse(xhr.responseText);
@@ -169,11 +179,11 @@ couchTests.oauth = function(debug) {
T(data.roles[0] == "test");
}
- xhr = oauthRequest("GET", "http://" + host + "/_session?foo=bar", message, accessor);
+ xhr = oauthRequest("GET", CouchDB.protocol + host + "/_session?foo=bar", message, accessor);
T(xhr.status == expectedCode);
// Test HEAD method
- xhr = oauthRequest("HEAD", "http://" + host + "/_session?foo=bar", message, accessor);
+ xhr = oauthRequest("HEAD", CouchDB.protocol + host + "/_session?foo=bar", message, accessor);
T(xhr.status == expectedCode);
// Replication
@@ -197,7 +207,7 @@ couchTests.oauth = function(debug) {
oauth_version: "1.0"
}
};
- xhr = oauthRequest("GET", "http://" + host + "/_session?foo=bar", message, adminAccessor);
+ xhr = oauthRequest("GET", CouchDB.protocol + host + "/_session?foo=bar", message, adminAccessor);
if (xhr.status == expectedCode == 200) {
data = JSON.parse(xhr.responseText);
T(data.name == "testadmin");
@@ -206,13 +216,13 @@ couchTests.oauth = function(debug) {
// Test when the user's token doesn't exist.
message.parameters.oauth_token = "not a token!";
- xhr = oauthRequest("GET", "http://" + host + "/_session?foo=bar",
+ xhr = oauthRequest("GET", CouchDB.protocol + host + "/_session?foo=bar",
message, adminAccessor);
T(xhr.status == 400, "Request should be invalid.");
}
}
} finally {
- var xhr = CouchDB.request("PUT", "http://" + host + "/_config/couch_httpd_auth/require_valid_user", {
+ var xhr = CouchDB.request("PUT", CouchDB.protocol + host + "/_config/couch_httpd_auth/require_valid_user", {
headers: {
"Authorization": adminBasicAuthHeaderValue(),
"X-Couch-Persist": "false"
@@ -221,7 +231,7 @@ couchTests.oauth = function(debug) {
});
T(xhr.status == 200);
- var xhr = CouchDB.request("DELETE", "http://" + host + "/_config/admins/testadmin", {
+ var xhr = CouchDB.request("DELETE", CouchDB.protocol + host + "/_config/admins/testadmin", {
headers: {
"Authorization": adminBasicAuthHeaderValue(),
"X-Couch-Persist": "false"
diff --git a/share/www/script/test/proxyauth.js b/share/www/script/test/proxyauth.js
new file mode 100644
index 00000000..40af0089
--- /dev/null
+++ b/share/www/script/test/proxyauth.js
@@ -0,0 +1,130 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+
+
+couchTests.proxyauth = function(debug) {
+ // this test proxy authentification handler
+
+ var usersDb = new CouchDB("test_suite_users", {"X-Couch-Full-Commit":"false"});
+ var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
+
+ if (debug) debugger;
+
+ // Simple secret key generator
+ function generateSecret(length) {
+ var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+ var secret = '';
+ for (var i=0; i<length; i++) {
+ secret += tab.charAt(Math.floor(Math.random() * 64));
+ }
+ return secret;
+ }
+
+ var secret = generateSecret(64);
+
+ function TestFun() {
+ usersDb.deleteDb();
+ usersDb.createDb();
+ db.deleteDb();
+ db.createDb();
+
+ var benoitcUserDoc = CouchDB.prepareUserDoc({
+ name: "benoitc@apache.org"
+ }, "test");
+ T(usersDb.save(benoitcUserDoc).ok);
+
+ T(CouchDB.session().userCtx.name == null);
+
+ // test that you can use basic auth aginst the users db
+ var s = CouchDB.session({
+ headers : {
+ "Authorization" : "Basic YmVub2l0Y0BhcGFjaGUub3JnOnRlc3Q="
+ }
+ });
+ T(s.userCtx.name == "benoitc@apache.org");
+ T(s.info.authenticated == "default");
+
+ CouchDB.logout();
+
+ var headers = {
+ "X-Auth-CouchDB-UserName": "benoitc@apache.org",
+ "X-Auth-CouchDB-Roles": "test",
+ "X-Auth-CouchDB-Token": hex_hmac_sha1(secret, "benoitc@apache.org")
+ };
+
+ var designDoc = {
+ _id:"_design/test",
+ language: "javascript",
+
+ shows: {
+ "welcome": stringFun(function(doc,req) {
+ return "Welcome " + req.userCtx["name"];
+ }),
+ "role": stringFun(function(doc, req) {
+ return req.userCtx['roles'][0];
+ })
+ }
+ };
+
+ db.save(designDoc);
+
+ var req = CouchDB.request("GET", "/test_suite_db/_design/test/_show/welcome",
+ {headers: headers});
+ T(req.responseText == "Welcome benoitc@apache.org");
+
+ req = CouchDB.request("GET", "/test_suite_db/_design/test/_show/role",
+ {headers: headers});
+ T(req.responseText == "test");
+
+ var xhr = CouchDB.request("PUT", "/_config/couch_httpd_auth/proxy_use_secret",{
+ body : JSON.stringify("true"),
+ headers: {"X-Couch-Persist": "false"}
+ });
+ T(xhr.status == 200);
+
+ req = CouchDB.request("GET", "/test_suite_db/_design/test/_show/welcome",
+ {headers: headers});
+ T(req.responseText == "Welcome benoitc@apache.org");
+
+ req = CouchDB.request("GET", "/test_suite_db/_design/test/_show/role",
+ {headers: headers});
+ T(req.responseText == "test");
+
+ }
+
+ run_on_modified_server(
+ [{section: "httpd",
+ key: "authentication_handlers",
+ value:"{couch_httpd_auth, proxy_authentification_handler}, {couch_httpd_auth, default_authentication_handler}"},
+ {section: "couch_httpd_auth",
+ key: "authentication_db",
+ value: "test_suite_users"},
+ {section: "couch_httpd_auth",
+ key: "secret",
+ value: secret},
+ {section: "couch_httpd_auth",
+ key: "x_auth_username",
+ value: "X-Auth-CouchDB-UserName"},
+ {section: "couch_httpd_auth",
+ key: "x_auth_roles",
+ value: "X-Auth-CouchDB-Roles"},
+ {section: "couch_httpd_auth",
+ key: "x_auth_token",
+ value: "X-Auth-CouchDB-Token"},
+ {section: "couch_httpd_auth",
+ key: "proxy_use_secret",
+ value: "false"}],
+ TestFun
+ );
+
+}; \ No newline at end of file
diff --git a/share/www/script/test/purge.js b/share/www/script/test/purge.js
index ca68b363..af72ea4f 100644
--- a/share/www/script/test/purge.js
+++ b/share/www/script/test/purge.js
@@ -30,7 +30,7 @@ couchTests.purge = function(debug) {
all_docs_twice: {map: "function(doc) { emit(doc.integer, null); emit(doc.integer, null) }"},
single_doc: {map: "function(doc) { if (doc._id == \"1\") { emit(1, null) }}"}
}
- }
+ };
T(db.save(designDoc).ok);
@@ -50,17 +50,19 @@ couchTests.purge = function(debug) {
// purge the documents
var xhr = CouchDB.request("POST", "/test_suite_db/_purge", {
- body: JSON.stringify({"1":[doc1._rev], "2":[doc2._rev]}),
+ body: JSON.stringify({"1":[doc1._rev], "2":[doc2._rev]})
});
T(xhr.status == 200);
+ var result = JSON.parse(xhr.responseText);
var newInfo = db.info();
+
// purging increments the update sequence
T(info.update_seq+1 == newInfo.update_seq);
// and it increments the purge_seq
T(info.purge_seq+1 == newInfo.purge_seq);
+ T(result.purge_seq == newInfo.purge_seq);
- var result = JSON.parse(xhr.responseText);
T(result.purged["1"][0] == doc1._rev);
T(result.purged["2"][0] == doc2._rev);
@@ -81,16 +83,18 @@ couchTests.purge = function(debug) {
var doc4 = db.open("4");
xhr = CouchDB.request("POST", "/test_suite_db/_purge", {
- body: JSON.stringify({"3":[doc3._rev]}),
+ body: JSON.stringify({"3":[doc3._rev]})
});
T(xhr.status == 200);
xhr = CouchDB.request("POST", "/test_suite_db/_purge", {
- body: JSON.stringify({"4":[doc4._rev]}),
+ body: JSON.stringify({"4":[doc4._rev]})
});
T(xhr.status == 200);
+ result = JSON.parse(xhr.responseText);
+ T(result.purge_seq == db.info().purge_seq);
var rows = db.view("test/all_docs_twice").rows;
for (var i = 4; i < numDocs; i++) {
diff --git a/share/www/script/test/reader_acl.js b/share/www/script/test/reader_acl.js
new file mode 100644
index 00000000..cc249ea4
--- /dev/null
+++ b/share/www/script/test/reader_acl.js
@@ -0,0 +1,198 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+couchTests.reader_acl = function(debug) {
+ // this tests read access control
+
+ var usersDb = new CouchDB("test_suite_users", {"X-Couch-Full-Commit":"false"});
+ var secretDb = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
+ function testFun() {
+ try {
+ usersDb.deleteDb();
+ usersDb.createDb();
+ secretDb.deleteDb();
+ secretDb.createDb();
+
+ // create a user with top-secret-clearance
+ var jchrisUserDoc = CouchDB.prepareUserDoc({
+ name: "jchris@apache.org",
+ roles : ["top-secret"]
+ }, "funnybone");
+ T(usersDb.save(jchrisUserDoc).ok);
+ usersDb.ensureFullCommit();
+
+ T(CouchDB.session().userCtx.name == null);
+
+ // set secret db to be read controlled
+ T(secretDb.save({_id:"baz",foo:"bar"}).ok);
+ T(secretDb.open("baz").foo == "bar");
+
+ T(secretDb.setSecObj({
+ "readers" : {
+ roles : ["super-secret-club"],
+ names : ["joe","barb"]
+ }
+ }).ok);
+ } finally {
+ CouchDB.logout();
+ }
+ }
+
+ // split into 2 funs so we can test restart behavior
+ function testFun2() {
+ try {
+ // can't read it as jchris b/c he's missing the needed role
+ T(CouchDB.login("jchris@apache.org", "funnybone").ok);
+ T(CouchDB.session().userCtx.name == "jchris@apache.org");
+
+ try {
+ secretDb.open("baz");
+ T(false && "can't open a doc from a secret db") ;
+ } catch(e) {
+ T(true)
+ }
+
+ CouchDB.logout();
+
+ // make anyone with the top-secret role an admin
+ // db admins are automatically readers
+ T(secretDb.setSecObj({
+ "admins" : {
+ roles : ["top-secret"],
+ names : []
+ },
+ "readers" : {
+ roles : ["super-secret-club"],
+ names : ["joe","barb"]
+ }
+ }).ok);
+
+
+ T(CouchDB.login("jchris@apache.org", "funnybone").ok);
+
+ // db admin can read
+ T(secretDb.open("baz").foo == "bar");
+
+ // and run temp views
+ TEquals(secretDb.query(function(doc) {
+ emit(null, null)
+ }).total_rows, 1);
+
+ CouchDB.logout();
+ T(CouchDB.session().userCtx.roles.indexOf("_admin") != -1);
+
+ // admin now adds the top-secret role to the db's readers
+ // and removes db-admins
+ T(secretDb.setSecObj({
+ "admins" : {
+ roles : [],
+ names : []
+ },
+ "readers" : {
+ roles : ["super-secret-club", "top-secret"],
+ names : ["joe","barb"]
+ }
+ }).ok);
+
+ // server _admin can always read
+ T(secretDb.open("baz").foo == "bar");
+
+ // and run temp views
+ TEquals(secretDb.query(function(doc) {
+ emit(null, null)
+ }).total_rows, 1);
+
+ T(secretDb.save({
+ "_id" : "_design/foo",
+ views : {
+ bar : {
+ map : "function(doc){emit(null, null)}"
+ }
+ }
+ }).ok)
+
+ // now top-secret users can read too
+ T(CouchDB.login("jchris@apache.org", "funnybone").ok);
+ T(CouchDB.session().userCtx.roles.indexOf("_admin") == -1);
+ T(secretDb.open("baz").foo == "bar");
+ // readers can query stored views
+ T(secretDb.view("foo/bar").total_rows == 1);
+
+ // readers can't do temp views
+ try {
+ var results = secretDb.query(function(doc) {
+ emit(null, null);
+ });
+ T(false && "temp view should be admin only");
+ } catch (e) {
+ T(true && "temp view is admin only");
+ }
+
+
+ CouchDB.logout();
+
+ // can't set non string reader names or roles
+ try {
+ secretDb.setSecObj({
+ "readers" : {
+ roles : ["super-secret-club", {"top-secret":"awesome"}],
+ names : ["joe","barb"]
+ }
+ })
+ T(false && "only string roles");
+ } catch (e) {}
+
+ try {
+ secretDb.setSecObj({
+ "readers" : {
+ roles : ["super-secret-club", {"top-secret":"awesome"}],
+ names : ["joe",22]
+ }
+ });
+ T(false && "only string names");
+ } catch (e) {}
+
+ try {
+ secretDb.setSecObj({
+ "readers" : {
+ roles : ["super-secret-club", {"top-secret":"awesome"}],
+ names : "joe"
+ }
+ });
+ T(false && "only lists of names");
+ } catch (e) {}
+ } finally {
+ CouchDB.logout();
+ }
+ };
+
+ run_on_modified_server(
+ [{section: "httpd",
+ key: "authentication_handlers",
+ value: "{couch_httpd_auth, cookie_authentication_handler}, {couch_httpd_auth, default_authentication_handler}"},
+ {section: "couch_httpd_auth",
+ key: "authentication_db", value: "test_suite_users"}],
+ testFun
+ );
+
+ // security changes will always commit synchronously
+ restartServer();
+
+ run_on_modified_server(
+ [{section: "httpd",
+ key: "authentication_handlers",
+ value: "{couch_httpd_auth, cookie_authentication_handler}, {couch_httpd_auth, default_authentication_handler}"},
+ {section: "couch_httpd_auth",
+ key: "authentication_db", value: "test_suite_users"}],
+ testFun2
+ );
+}
diff --git a/share/www/script/test/recreate_doc.js b/share/www/script/test/recreate_doc.js
index a6a64ac0..05843558 100644
--- a/share/www/script/test/recreate_doc.js
+++ b/share/www/script/test/recreate_doc.js
@@ -51,7 +51,7 @@ couchTests.recreate_doc = function(debug) {
data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
}
}
- }
+ };
try {
// same as before, but with binary
db.save(binAttDoc);
diff --git a/share/www/script/test/reduce.js b/share/www/script/test/reduce.js
index 9c80fa7f..979a0292 100644
--- a/share/www/script/test/reduce.js
+++ b/share/www/script/test/reduce.js
@@ -15,14 +15,15 @@ couchTests.reduce = function(debug) {
db.deleteDb();
db.createDb();
if (debug) debugger;
- var numDocs = 500
+ var numDocs = 500;
var docs = makeDocs(1,numDocs + 1);
db.bulkSave(docs);
var summate = function(N) {return (N+1)*N/2;};
var map = function (doc) {
emit(doc.integer, doc.integer);
- emit(doc.integer, doc.integer)};
+ emit(doc.integer, doc.integer);
+ };
var reduce = function (keys, values) { return sum(values); };
var result = db.query(map, reduce);
T(result.rows[0].value == 2*summate(numDocs));
@@ -69,7 +70,7 @@ couchTests.reduce = function(debug) {
T(db.info().doc_count == ((i - 1) * 10 * 11) + ((j + 1) * 11));
}
- map = function (doc) {emit(doc.keys, 1)};
+ map = function (doc) { emit(doc.keys, 1); };
reduce = function (keys, values) { return sum(values); };
var results = db.query(map, reduce, {group:true});
@@ -107,7 +108,7 @@ couchTests.reduce = function(debug) {
db.createDb();
- var map = function (doc) {emit(doc.val, doc.val)};
+ var map = function (doc) { emit(doc.val, doc.val); };
var reduceCombine = function (keys, values, rereduce) {
// This computes the standard deviation of the mapped results
var stdDeviation=0.0;
diff --git a/share/www/script/test/reduce_builtin.js b/share/www/script/test/reduce_builtin.js
index 7938a0cf..b3cc3cc7 100644
--- a/share/www/script/test/reduce_builtin.js
+++ b/share/www/script/test/reduce_builtin.js
@@ -16,22 +16,37 @@ couchTests.reduce_builtin = function(debug) {
db.createDb();
if (debug) debugger;
- var numDocs = 500
+ var numDocs = 500;
var docs = makeDocs(1,numDocs + 1);
db.bulkSave(docs);
var summate = function(N) {return (N+1)*N/2;};
+ var sumsqr = function(N) {
+ var acc = 0;
+ for (var i=1; i<=N; ++i) {
+ acc += i*i;
+ }
+ return acc;
+ };
+
// this is the same test as the reduce.js test
// only we'll let CouchDB run reduce in Erlang
var map = function (doc) {
emit(doc.integer, doc.integer);
- emit(doc.integer, doc.integer)};
+ emit(doc.integer, doc.integer);
+ };
var result = db.query(map, "_sum");
T(result.rows[0].value == 2*summate(numDocs));
result = db.query(map, "_count");
T(result.rows[0].value == 1000);
+ result = db.query(map, "_stats");
+ T(result.rows[0].value.sum == 2*summate(numDocs));
+ T(result.rows[0].value.count == 1000);
+ T(result.rows[0].value.min == 1);
+ T(result.rows[0].value.max == 500);
+ T(result.rows[0].value.sumsqr == 2*sumsqr(numDocs));
result = db.query(map, "_sum", {startkey: 4, endkey: 4});
T(result.rows[0].value == 8);
@@ -58,6 +73,26 @@ couchTests.reduce_builtin = function(debug) {
T(result.rows[0].value == 2*(summate(numDocs-i) - summate(i-1)));
}
+ // test for trailing characters after builtin functions, desired behaviour
+ // is to disregard any trailing characters
+ // I think the behavior should be a prefix test, so that even "_statsorama"
+ // or "_stats\nare\awesome" should work just as "_stats" does. - JChris
+
+ var trailing = ["\u000a", "orama", "\nare\nawesome", " ", " \n "];
+
+ for(var i=0; i < trailing.length; i++) {
+ result = db.query(map, "_sum" + trailing[i]);
+ T(result.rows[0].value == 2*summate(numDocs));
+ result = db.query(map, "_count" + trailing[i]);
+ T(result.rows[0].value == 1000);
+ result = db.query(map, "_stats" + trailing[i]);
+ T(result.rows[0].value.sum == 2*summate(numDocs));
+ T(result.rows[0].value.count == 1000);
+ T(result.rows[0].value.min == 1);
+ T(result.rows[0].value.max == 500);
+ T(result.rows[0].value.sumsqr == 2*sumsqr(numDocs));
+ }
+
db.deleteDb();
db.createDb();
@@ -81,7 +116,7 @@ couchTests.reduce_builtin = function(debug) {
T(db.info().doc_count == ((i - 1) * 10 * 11) + ((j + 1) * 11));
}
- map = function (doc) {emit(doc.keys, 1)};
+ map = function (doc) { emit(doc.keys, 1); };
// with emitted values being 1, count should be the same as sum
var builtins = ["_sum", "_count"];
@@ -115,5 +150,30 @@ couchTests.reduce_builtin = function(debug) {
T(equals(results.rows[5], {key:["d","b"],value:10*i}));
T(equals(results.rows[6], {key:["d","c"],value:10*i}));
};
+
+ map = function (doc) { emit(doc.keys, [1, 1]); };
+
+ var results = db.query(map, "_sum", {group:true});
+ T(equals(results.rows[0], {key:["a"],value:[20*i,20*i]}));
+ T(equals(results.rows[1], {key:["a","b"],value:[20*i,20*i]}));
+ T(equals(results.rows[2], {key:["a", "b", "c"],value:[10*i,10*i]}));
+ T(equals(results.rows[3], {key:["a", "b", "d"],value:[10*i,10*i]}));
+
+ var results = db.query(map, "_sum", {group: true, limit: 2});
+ T(equals(results.rows[0], {key: ["a"], value: [20*i,20*i]}));
+ T(equals(results.rows.length, 2));
+
+ var results = db.query(map, "_sum", {group_level:1});
+ T(equals(results.rows[0], {key:["a"],value:[70*i,70*i]}));
+ T(equals(results.rows[1], {key:["d"],value:[40*i,40*i]}));
+
+ var results = db.query(map, "_sum", {group_level:2});
+ T(equals(results.rows[0], {key:["a"],value:[20*i,20*i]}));
+ T(equals(results.rows[1], {key:["a","b"],value:[40*i,40*i]}));
+ T(equals(results.rows[2], {key:["a","c"],value:[10*i,10*i]}));
+ T(equals(results.rows[3], {key:["d"],value:[10*i,10*i]}));
+ T(equals(results.rows[4], {key:["d","a"],value:[10*i,10*i]}));
+ T(equals(results.rows[5], {key:["d","b"],value:[10*i,10*i]}));
+ T(equals(results.rows[6], {key:["d","c"],value:[10*i,10*i]}));
}
}
diff --git a/share/www/script/test/replication.js b/share/www/script/test/replication.js
index 78678937..5acff4ab 100644
--- a/share/www/script/test/replication.js
+++ b/share/www/script/test/replication.js
@@ -17,19 +17,19 @@ couchTests.replication = function(debug) {
{source:"test_suite_db_a",
target:"test_suite_db_b"},
{source:"test_suite_db_a",
- target:"http://" + host + "/test_suite_db_b"},
- {source:"http://" + host + "/test_suite_db_a",
+ target:CouchDB.protocol + host + "/test_suite_db_b"},
+ {source:CouchDB.protocol + host + "/test_suite_db_a",
target:"test_suite_db_b"},
- {source:"http://" + host + "/test_suite_db_a",
- target:"http://" + host + "/test_suite_db_b"}
- ]
+ {source:CouchDB.protocol + host + "/test_suite_db_a",
+ target:CouchDB.protocol + host + "/test_suite_db_b"}
+ ];
var dbA = new CouchDB("test_suite_db_a", {"X-Couch-Full-Commit":"false"});
var dbB = new CouchDB("test_suite_db_b", {"X-Couch-Full-Commit":"false"});
var numDocs = 10;
var xhr;
for (var testPair = 0; testPair < dbPairs.length; testPair++) {
- var A = dbPairs[testPair].source
- var B = dbPairs[testPair].target
+ var A = dbPairs[testPair].source;
+ var B = dbPairs[testPair].target;
dbA.deleteDb();
dbA.createDb();
@@ -41,7 +41,7 @@ couchTests.replication = function(debug) {
test_template: new function () {
this.init = function(dbA, dbB) {
// before anything has happened
- }
+ };
this.afterAB1 = function(dbA, dbB) {
// called after replicating src=A tgt=B first time.
};
@@ -165,20 +165,20 @@ couchTests.replication = function(debug) {
this.afterAB1 = function(dbA, dbB) {
var xhr = CouchDB.request("GET",
"/test_suite_db_a/bin_doc/foo%2Bbar.txt");
- T(xhr.responseText == "This is a base64 encoded text")
+ T(xhr.responseText == "This is a base64 encoded text");
xhr = CouchDB.request("GET",
"/test_suite_db_b/bin_doc/foo%2Bbar.txt");
- T(xhr.responseText == "This is a base64 encoded text")
+ T(xhr.responseText == "This is a base64 encoded text");
// and the design-doc
xhr = CouchDB.request("GET",
"/test_suite_db_a/_design/with_bin/foo%2Bbar.txt");
- T(xhr.responseText == "This is a base64 encoded text")
+ T(xhr.responseText == "This is a base64 encoded text");
xhr = CouchDB.request("GET",
"/test_suite_db_b/_design/with_bin/foo%2Bbar.txt");
- T(xhr.responseText == "This is a base64 encoded text")
+ T(xhr.responseText == "This is a base64 encoded text");
};
},
@@ -209,8 +209,8 @@ couchTests.replication = function(debug) {
var docB = dbB.open("foo", {conflicts: true, deleted_conflicts: true});
// We should have no conflicts this time
- T(docA._conflicts === undefined)
- T(docB._conflicts === undefined);
+ T(typeof docA._conflicts === "undefined");
+ T(typeof docB._conflicts === "undefined");
// They show up as deleted conflicts instead
T(docA._deleted_conflicts[0] == docB._deleted_conflicts[0]);
@@ -229,7 +229,7 @@ couchTests.replication = function(debug) {
var seqA = result.source_last_seq;
T(0 == result.history[0].start_last_seq);
- T(result.history[1] === undefined)
+ T(typeof result.history[1] === "undefined");
for(test in repTests) {
if(repTests[test].afterAB1) repTests[test].afterAB1(dbA, dbB);
@@ -239,7 +239,7 @@ couchTests.replication = function(debug) {
var seqB = result.source_last_seq;
T(0 == result.history[0].start_last_seq);
- T(result.history[1] === undefined)
+ T(typeof result.history[1] === "undefined");
for(test in repTests) {
if(repTests[test].afterBA1) repTests[test].afterBA1(dbA, dbB);
@@ -252,7 +252,7 @@ couchTests.replication = function(debug) {
T(seqA < result2.source_last_seq);
T(seqA == result2.history[0].start_last_seq);
- T(result2.history[1].end_last_seq == seqA)
+ T(result2.history[1].end_last_seq == seqA);
seqA = result2.source_last_seq;
@@ -260,11 +260,11 @@ couchTests.replication = function(debug) {
if(repTests[test].afterAB2) repTests[test].afterAB2(dbA, dbB);
}
- result = CouchDB.replicate(B, A)
+ result = CouchDB.replicate(B, A);
T(seqB < result.source_last_seq);
T(seqB == result.history[0].start_last_seq);
- T(result.history[1].end_last_seq == seqB)
+ T(result.history[1].end_last_seq == seqB);
seqB = result.source_last_seq;
@@ -296,9 +296,436 @@ couchTests.replication = function(debug) {
// remote
dbB.deleteDb();
- CouchDB.replicate(dbA.name, "http://" + CouchDB.host + "/test_suite_db_b", {
+ CouchDB.replicate(dbA.name, CouchDB.protocol + CouchDB.host + "/test_suite_db_b", {
body: {"create_target": true}
});
TEquals("test_suite_db_b", dbB.info().db_name,
"Target database should exist");
+
+ // continuous
+ var continuousResult = CouchDB.replicate(dbA.name, "test_suite_db_b", {
+ body: {"continuous": true}
+ });
+ T(continuousResult.ok);
+ T(continuousResult._local_id);
+
+ var cancelResult = CouchDB.replicate(dbA.name, "test_suite_db_b", {
+ body: {"cancel": true}
+ });
+ T(cancelResult.ok);
+ T(continuousResult._local_id == cancelResult._local_id);
+
+ try {
+ var cancelResult2 = CouchDB.replicate(dbA.name, "test_suite_db_b", {
+ body: {"cancel": true}
+ });
+ } catch (e) {
+ T(e.error == "not_found");
+ }
+ // test replication object option doc_ids
+
+ var dbA = new CouchDB("test_suite_rep_docs_db_a", {"X-Couch-Full-Commit":"false"});
+ var dbB = new CouchDB("test_suite_rep_docs_db_b", {"X-Couch-Full-Commit":"false"});
+
+ dbA.deleteDb();
+ dbA.createDb();
+
+ var all_docs = [
+ {
+ _id: "foo1",
+ value: "a"
+ },
+ {
+ _id: "foo2",
+ value: "b"
+ },
+ {
+ _id: "foo3",
+ value: "c"
+ },
+ {
+ _id: "slashed/foo",
+ value: "s"
+ },
+ {
+ _id: "_design/foobar",
+ language: "javascript",
+ value: "I am a design doc",
+ filters: {
+ idfilter: (function(doc, req) {
+ return doc.value == Number(req.filter_value);
+ }).toString()
+ },
+ views: {
+ countview: (function(doc) {
+ emit(doc.value, 1);
+ }).toString()
+ }
+ }
+ ];
+
+ for (var i = 0; i < all_docs.length; i++) {
+ T(dbA.save(all_docs[i]).ok);
+ }
+
+ var dbPairs = [
+ {source:"test_suite_rep_docs_db_a",
+ target:"test_suite_rep_docs_db_b"},
+ {source:"test_suite_rep_docs_db_a",
+ target:CouchDB.protocol + host + "/test_suite_rep_docs_db_b"},
+ {source:CouchDB.protocol + host + "/test_suite_rep_docs_db_a",
+ target:"test_suite_rep_docs_db_b"},
+ {source:CouchDB.protocol + host + "/test_suite_rep_docs_db_a",
+ target:CouchDB.protocol + host + "/test_suite_rep_docs_db_b"}
+ ];
+
+ var target_doc_ids = [
+ ["foo1", "foo3", "foo666"],
+ ["foo1", "foo666"],
+ ["foo666", "foo2"],
+ ["foo2", "foo9999", "foo1"],
+ ["foo3", "slashed/foo"],
+ ["foo3", "slashed%2Ffoo"],
+ ["foo1", "_design/foobar"],
+ ["foo1", "foo1001", "_design%2Ffoobar"]
+ ];
+
+ for (var i = 0; i < dbPairs.length; i++) {
+ var src_db = dbPairs[i].source;
+ var tgt_db = dbPairs[i].target;
+
+ for (var j = 0; j < target_doc_ids.length; j++) {
+ var doc_ids = target_doc_ids[j];
+ var valid_doc_ids = [];
+ var invalid_doc_ids = [];
+
+ for (var p = 0; p < doc_ids.length; p++) {
+ var id = doc_ids[p];
+ var found = false;
+
+ for (var k = 0; k < all_docs.length; k++) {
+ var doc = all_docs[k];
+
+ if (id === doc._id) {
+ found = true;
+ break;
+ }
+ }
+
+ if (found) {
+ valid_doc_ids.push(id);
+ } else {
+ invalid_doc_ids.push(id);
+ }
+ };
+
+ dbB.deleteDb();
+ dbB.createDb();
+
+ var repResult = CouchDB.replicate(src_db, tgt_db, {
+ body: {"doc_ids": doc_ids}
+ });
+
+ T(repResult.ok);
+ T(repResult.docs_written === valid_doc_ids.length);
+ T(repResult.docs_read === valid_doc_ids.length);
+ T(repResult.doc_write_failures === 0);
+
+ for (var k = 0; k < all_docs.length; k++) {
+ var doc = all_docs[k];
+ var tgt_doc = dbB.open(doc._id);
+
+ if (doc_ids.indexOf(doc._id) >= 0) {
+ T(tgt_doc !== null);
+ T(tgt_doc.value === doc.value);
+ } else {
+ T(tgt_doc === null);
+ }
+ }
+
+ for (var k = 0; k < invalid_doc_ids.length; k++) {
+ var tgt_doc = dbB.open(invalid_doc_ids[k]);
+
+ T(tgt_doc === null);
+ }
+ }
+ }
+
+ // test filtered replication
+ var filterFun1 = (function(doc, req) {
+ if (doc.value < Number(req.query.maxvalue)) {
+ return true;
+ } else {
+ return false;
+ }
+ }).toString();
+
+ var filterFun2 = (function(doc, req) {
+ return true;
+ }).toString();
+
+ var dbPairs = [
+ {source:"test_suite_filtered_rep_db_a",
+ target:"test_suite_filtered_rep_db_b"},
+ {source:"test_suite_filtered_rep_db_a",
+ target:CouchDB.protocol + host + "/test_suite_filtered_rep_db_b"},
+ {source:CouchDB.protocol + host + "/test_suite_filtered_rep_db_a",
+ target:"test_suite_filtered_rep_db_b"},
+ {source:CouchDB.protocol + host + "/test_suite_filtered_rep_db_a",
+ target:CouchDB.protocol + host + "/test_suite_filtered_rep_db_b"}
+ ];
+ var sourceDb = new CouchDB("test_suite_filtered_rep_db_a");
+ var targetDb = new CouchDB("test_suite_filtered_rep_db_b");
+
+ for (var i = 0; i < dbPairs.length; i++) {
+ sourceDb.deleteDb();
+ sourceDb.createDb();
+
+ T(sourceDb.save({_id: "foo1", value: 1}).ok);
+ T(sourceDb.save({_id: "foo2", value: 2}).ok);
+ T(sourceDb.save({_id: "foo3", value: 3}).ok);
+ T(sourceDb.save({_id: "foo4", value: 4}).ok);
+
+ var ddoc = {
+ "_id": "_design/mydesign",
+ "language": "javascript",
+ "filters": {
+ "myfilter": filterFun1
+ }
+ };
+
+ T(sourceDb.save(ddoc).ok);
+
+ targetDb.deleteDb();
+ targetDb.createDb();
+
+ var dbA = dbPairs[i].source;
+ var dbB = dbPairs[i].target;
+
+ var repResult = CouchDB.replicate(dbA, dbB, {
+ body: {
+ "filter" : "mydesign/myfilter",
+ "query_params" : {
+ "maxvalue": "3"
+ }
+ }
+ });
+
+ T(repResult.ok);
+ T(repResult.history instanceof Array);
+ T(repResult.history.length === 1);
+ T(repResult.history[0].docs_written === 2);
+ T(repResult.history[0].docs_read === 2);
+ T(repResult.history[0].doc_write_failures === 0);
+
+ var docFoo1 = targetDb.open("foo1");
+ T(docFoo1 !== null);
+ T(docFoo1.value === 1);
+
+ var docFoo2 = targetDb.open("foo2");
+ T(docFoo2 !== null);
+ T(docFoo2.value === 2);
+
+ var docFoo3 = targetDb.open("foo3");
+ T(docFoo3 === null);
+
+ var docFoo4 = targetDb.open("foo4");
+ T(docFoo4 === null);
+
+ // replication should start from scratch after the filter's code changed
+
+ ddoc.filters.myfilter = filterFun2;
+ T(sourceDb.save(ddoc).ok);
+
+ repResult = CouchDB.replicate(dbA, dbB, {
+ body: {
+ "filter" : "mydesign/myfilter",
+ "query_params" : {
+ "maxvalue": "3"
+ }
+ }
+ });
+
+ T(repResult.ok);
+ T(repResult.history instanceof Array);
+ T(repResult.history.length === 1);
+ T(repResult.history[0].docs_written === 3);
+ T(repResult.history[0].docs_read === 3);
+ T(repResult.history[0].doc_write_failures === 0);
+
+ docFoo1 = targetDb.open("foo1");
+ T(docFoo1 !== null);
+ T(docFoo1.value === 1);
+
+ docFoo2 = targetDb.open("foo2");
+ T(docFoo2 !== null);
+ T(docFoo2.value === 2);
+
+ docFoo3 = targetDb.open("foo3");
+ T(docFoo3 !== null);
+ T(docFoo3.value === 3);
+
+ docFoo4 = targetDb.open("foo4");
+ T(docFoo4 !== null);
+ T(docFoo4.value === 4);
+
+ T(targetDb.open("_design/mydesign") !== null);
+ }
+
+ // test for COUCHDB-868 - design docs' attachments not getting replicated
+ // when doing a pull replication with HTTP basic auth
+ dbA = new CouchDB("test_suite_db_a");
+ dbB = new CouchDB("test_suite_db_b");
+ var usersDb = new CouchDB("test_suite_auth");
+ var lorem = CouchDB.request(
+ "GET", "/_utils/script/test/lorem.txt").responseText;
+ var lorem_b64 = CouchDB.request(
+ "GET", "/_utils/script/test/lorem_b64.txt").responseText;
+
+ usersDb.deleteDb();
+ usersDb.createDb();
+ dbA.deleteDb();
+ dbA.createDb();
+ dbB.deleteDb();
+ dbB.createDb();
+
+ var atts_ddoc = {
+ _id: "_design/i_have_atts",
+ language: "javascript"
+ };
+ T(dbA.save(atts_ddoc).ok);
+
+ var rev = atts_ddoc._rev;
+ var att_1_name = "lorem.txt";
+ var att_2_name = "lorem.dat";
+ var xhr = CouchDB.request(
+ "PUT", "/" + dbA.name + "/" + atts_ddoc._id + "/" + att_1_name + "?rev=" + rev, {
+ headers: {"Content-Type": "text/plain;charset=utf-8"},
+ body: lorem
+ });
+ rev = JSON.parse(xhr.responseText).rev;
+ T(xhr.status === 201);
+ xhr = CouchDB.request(
+ "PUT", "/" + dbA.name + "/" + atts_ddoc._id + "/" + att_2_name + "?rev=" + rev, {
+ headers: {"Content-Type": "application/data"},
+ body: lorem_b64
+ });
+ T(xhr.status === 201);
+
+ var fdmananaUserDoc = CouchDB.prepareUserDoc({
+ name: "fdmanana",
+ roles: ["reader"]
+ }, "qwerty");
+ T(usersDb.save(fdmananaUserDoc).ok);
+
+ T(dbA.setSecObj({
+ admins: {
+ names: [],
+ roles: ["admin"]
+ },
+ readers: {
+ names: [],
+ roles: ["reader"]
+ }
+ }).ok);
+ T(dbB.setSecObj({
+ admins: {
+ names: ["fdmanana"],
+ roles: []
+ }
+ }).ok);
+
+ var server_config = [
+ {
+ section: "couch_httpd_auth",
+ key: "authentication_db",
+ value: usersDb.name
+ },
+ // to prevent admin party mode
+ {
+ section: "admins",
+ key: "joe",
+ value: "erlang"
+ }
+ ];
+
+ var test_fun = function() {
+ T(CouchDB.login("fdmanana", "qwerty").ok);
+ T(CouchDB.session().userCtx.name === "fdmanana");
+ T(CouchDB.session().userCtx.roles.indexOf("_admin") === -1);
+
+ var repResult = CouchDB.replicate(
+ CouchDB.protocol + "fdmanana:qwerty@" + host + "/" + dbA.name,
+ dbB.name
+ );
+ T(repResult.ok === true);
+ T(repResult.history instanceof Array);
+ T(repResult.history.length === 1);
+ T(repResult.history[0].docs_written === 1);
+ T(repResult.history[0].docs_read === 1);
+ T(repResult.history[0].doc_write_failures === 0);
+
+ var atts_ddoc_copy = dbB.open(atts_ddoc._id);
+ T(atts_ddoc_copy !== null);
+ T(typeof atts_ddoc_copy._attachments === "object");
+ T(atts_ddoc_copy._attachments !== null);
+ T(att_1_name in atts_ddoc_copy._attachments);
+ T(att_2_name in atts_ddoc_copy._attachments);
+
+ var xhr = CouchDB.request("GET", "/" + dbB.name + "/" + atts_ddoc._id + "/" + att_1_name);
+ T(xhr.status === 200);
+ T(xhr.responseText === lorem);
+
+ xhr = CouchDB.request("GET", "/" + dbB.name + "/" + atts_ddoc._id + "/" + att_2_name);
+ T(xhr.status === 200);
+ T(xhr.responseText === lorem_b64);
+
+ CouchDB.logout();
+ T(CouchDB.login("joe", "erlang").ok);
+ T(dbA.setSecObj({
+ admins: {
+ names: [],
+ roles: ["bar"]
+ },
+ readers: {
+ names: [],
+ roles: ["foo"]
+ }
+ }).ok);
+ T(dbB.deleteDb().ok === true);
+ T(dbB.createDb().ok === true);
+ T(dbB.setSecObj({
+ admins: {
+ names: ["fdmanana"],
+ roles: []
+ }
+ }).ok);
+ CouchDB.logout();
+
+ T(CouchDB.login("fdmanana", "qwerty").ok);
+ T(CouchDB.session().userCtx.name === "fdmanana");
+ T(CouchDB.session().userCtx.roles.indexOf("_admin") === -1);
+ try {
+ repResult = CouchDB.replicate(
+ CouchDB.protocol + "fdmanana:qwerty@" + host + "/" + dbA.name,
+ dbB.name
+ );
+ T(false, "replication should have failed");
+ } catch(x) {
+ T(x.error === "unauthorized");
+ }
+
+ atts_ddoc_copy = dbB.open(atts_ddoc._id);
+ T(atts_ddoc_copy === null);
+
+ CouchDB.logout();
+ T(CouchDB.login("joe", "erlang").ok);
+ };
+
+ run_on_modified_server(server_config, test_fun);
+
+ // cleanup
+ dbA.deleteDb();
+ dbB.deleteDb();
+ usersDb.deleteDb();
};
diff --git a/share/www/script/test/replicator_db.js b/share/www/script/test/replicator_db.js
new file mode 100644
index 00000000..3c6a5d8e
--- /dev/null
+++ b/share/www/script/test/replicator_db.js
@@ -0,0 +1,818 @@
+// 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.replicator_db = function(debug) {
+
+ if (debug) debugger;
+
+ var wait_rep_doc = 500; // number of millisecs to wait after saving a Rep Doc
+ var host = CouchDB.host;
+ var dbA = new CouchDB("test_suite_rep_db_a", {"X-Couch-Full-Commit":"false"});
+ var dbB = new CouchDB("test_suite_rep_db_b", {"X-Couch-Full-Commit":"false"});
+ var repDb = new CouchDB("test_suite_rep_db", {"X-Couch-Full-Commit":"false"});
+ var usersDb = new CouchDB("test_suite_auth", {"X-Couch-Full-Commit":"false"});
+
+ var docs1 = [
+ {
+ _id: "foo1",
+ value: 11
+ },
+ {
+ _id: "foo2",
+ value: 22
+ },
+ {
+ _id: "foo3",
+ value: 33
+ }
+ ];
+
+ function waitForRep(repDb, repDoc, state) {
+ var newRep,
+ t0 = new Date(),
+ t1,
+ ms = 1000;
+
+ do {
+ newRep = repDb.open(repDoc._id);
+ t1 = new Date();
+ } while (((t1 - t0) <= ms) && newRep._replication_state !== state);
+ }
+
+ function waitForSeq(sourceDb, targetDb) {
+ var targetSeq,
+ sourceSeq = sourceDb.info().update_seq,
+ t0 = new Date(),
+ t1,
+ ms = 1000;
+
+ do {
+ targetSeq = targetDb.info().update_seq;
+ t1 = new Date();
+ } while (((t1 - t0) <= ms) && targetSeq < sourceSeq);
+ }
+
+ function wait(ms) {
+ var t0 = new Date(), t1;
+ do {
+ CouchDB.request("GET", "/");
+ t1 = new Date();
+ } while ((t1 - t0) <= ms);
+ }
+
+
+ function populate_db(db, docs) {
+ db.deleteDb();
+ db.createDb();
+ for (var i = 0; i < docs.length; i++) {
+ var d = docs[i];
+ delete d._rev;
+ T(db.save(d).ok);
+ }
+ }
+
+ function simple_replication() {
+ populate_db(dbA, docs1);
+ populate_db(dbB, []);
+
+ var repDoc = {
+ _id: "foo_simple_rep",
+ source: dbA.name,
+ target: dbB.name
+ };
+ T(repDb.save(repDoc).ok);
+
+ waitForRep(repDb, repDoc, "completed");
+ for (var i = 0; i < docs1.length; i++) {
+ var doc = docs1[i];
+ var copy = dbB.open(doc._id);
+ T(copy !== null);
+ T(copy.value === doc.value);
+ }
+
+ var repDoc1 = repDb.open(repDoc._id);
+ T(repDoc1 !== null);
+ T(repDoc1.source === repDoc.source);
+ T(repDoc1.target === repDoc.target);
+ T(repDoc1._replication_state === "completed", "simple");
+ T(typeof repDoc1._replication_state_time === "number");
+ T(typeof repDoc1._replication_id === "string");
+ }
+
+
+ function filtered_replication() {
+ var docs2 = docs1.concat([
+ {
+ _id: "_design/mydesign",
+ language : "javascript",
+ filters : {
+ myfilter : (function(doc, req) {
+ return (doc.value % 2) !== Number(req.query.myparam);
+ }).toString()
+ }
+ }
+ ]);
+
+ populate_db(dbA, docs2);
+ populate_db(dbB, []);
+
+ var repDoc = {
+ _id: "foo_filt_rep_doc",
+ source: "http://" + host + "/" + dbA.name,
+ target: dbB.name,
+ filter: "mydesign/myfilter",
+ query_params: {
+ myparam: 1
+ }
+ };
+ T(repDb.save(repDoc).ok);
+
+ waitForRep(repDb, repDoc, "completed");
+ for (var i = 0; i < docs2.length; i++) {
+ var doc = docs2[i];
+ var copy = dbB.open(doc._id);
+
+ if (typeof doc.value === "number") {
+ if ((doc.value % 2) !== 1) {
+ T(copy !== null);
+ T(copy.value === doc.value);
+ } else {
+ T(copy === null);
+ }
+ }
+ }
+
+ var repDoc1 = repDb.open(repDoc._id);
+ T(repDoc1 !== null);
+ T(repDoc1.source === repDoc.source);
+ T(repDoc1.target === repDoc.target);
+ T(repDoc1._replication_state === "completed", "filtered");
+ T(typeof repDoc1._replication_state_time === "number");
+ T(typeof repDoc1._replication_id === "string");
+ }
+
+
+ function continuous_replication() {
+ populate_db(dbA, docs1);
+ populate_db(dbB, []);
+
+ var repDoc = {
+ _id: "foo_cont_rep_doc",
+ source: "http://" + host + "/" + dbA.name,
+ target: dbB.name,
+ continuous: true
+ };
+
+ T(repDb.save(repDoc).ok);
+
+ waitForSeq(dbA, dbB);
+ for (var i = 0; i < docs1.length; i++) {
+ var doc = docs1[i];
+ var copy = dbB.open(doc._id);
+ T(copy !== null);
+ T(copy.value === doc.value);
+ }
+
+ // add another doc to source, it will be replicated to target
+ var docX = {
+ _id: "foo1000",
+ value: 1001
+ };
+
+ T(dbA.save(docX).ok);
+
+ waitForSeq(dbA, dbB);
+ var copy = dbB.open("foo1000");
+ T(copy !== null);
+ T(copy.value === 1001);
+
+ var repDoc1 = repDb.open(repDoc._id);
+ T(repDoc1 !== null);
+ T(repDoc1.source === repDoc.source);
+ T(repDoc1.target === repDoc.target);
+ T(repDoc1._replication_state === "triggered");
+ T(typeof repDoc1._replication_state_time === "number");
+ T(typeof repDoc1._replication_id === "string");
+
+ // add a design doc to source, it will be replicated to target
+ // when the "user_ctx" property is not defined in the replication doc,
+ // the replication will be done under an _admin context, therefore
+ // design docs will be replicated
+ var ddoc = {
+ _id: "_design/foobar",
+ language: "javascript"
+ };
+
+ T(dbA.save(ddoc).ok);
+
+ waitForSeq(dbA, dbB);
+ var ddoc_copy = dbB.open("_design/foobar");
+ T(ddoc_copy !== null);
+ T(ddoc.language === "javascript");
+
+ // update the design doc on source, test that the new revision is replicated
+ ddoc.language = "erlang";
+ T(dbA.save(ddoc).ok);
+ T(ddoc._rev.indexOf("2-") === 0);
+
+ waitForSeq(dbA, dbB);
+ ddoc_copy = dbB.open("_design/foobar");
+ T(ddoc_copy !== null);
+ T(ddoc_copy._rev === ddoc._rev);
+ T(ddoc.language === "erlang");
+
+ // stop replication by deleting the replication document
+ T(repDb.deleteDoc(repDoc1).ok);
+
+ // add another doc to source, it will NOT be replicated to target
+ var docY = {
+ _id: "foo666",
+ value: 999
+ };
+
+ T(dbA.save(docY).ok);
+
+ wait(200); // is there a way to avoid wait here?
+ var copy = dbB.open("foo666");
+ T(copy === null);
+ }
+
+
+ function by_doc_ids_replication() {
+ // to test that we can replicate docs with slashes in their IDs
+ var docs2 = docs1.concat([
+ {
+ _id: "_design/mydesign",
+ language : "javascript"
+ }
+ ]);
+
+ populate_db(dbA, docs2);
+ populate_db(dbB, []);
+
+ var repDoc = {
+ _id: "foo_cont_rep_doc",
+ source: "http://" + host + "/" + dbA.name,
+ target: dbB.name,
+ doc_ids: ["foo666", "foo3", "_design/mydesign", "foo999", "foo1"]
+ };
+ T(repDb.save(repDoc).ok);
+
+ waitForRep(repDb, repDoc, "completed");
+ var copy = dbB.open("foo1");
+ T(copy !== null);
+ T(copy.value === 11);
+
+ copy = dbB.open("foo2");
+ T(copy === null);
+
+ copy = dbB.open("foo3");
+ T(copy !== null);
+ T(copy.value === 33);
+
+ copy = dbB.open("foo666");
+ T(copy === null);
+
+ copy = dbB.open("foo999");
+ T(copy === null);
+
+ copy = dbB.open("_design/mydesign");
+ T(copy !== null);
+ T(copy.language === "javascript");
+ }
+
+
+ function successive_identical_replications() {
+ populate_db(dbA, docs1);
+ populate_db(dbB, []);
+
+ var repDoc1 = {
+ _id: "foo_ident_rep_1",
+ source: dbA.name,
+ target: dbB.name
+ };
+ T(repDb.save(repDoc1).ok);
+
+ waitForRep(repDb, repDoc1, "completed");
+ for (var i = 0; i < docs1.length; i++) {
+ var doc = docs1[i];
+ var copy = dbB.open(doc._id);
+ T(copy !== null);
+ T(copy.value === doc.value);
+ }
+
+ var repDoc1_copy = repDb.open(repDoc1._id);
+ T(repDoc1_copy !== null);
+ T(repDoc1_copy.source === repDoc1.source);
+ T(repDoc1_copy.target === repDoc1.target);
+ T(repDoc1_copy._replication_state === "completed");
+ T(typeof repDoc1_copy._replication_state_time === "number");
+ T(typeof repDoc1_copy._replication_id === "string");
+
+ var newDoc = {
+ _id: "doc666",
+ value: 666
+ };
+ T(dbA.save(newDoc).ok);
+
+ wait(200);
+ var newDoc_copy = dbB.open(newDoc._id);
+ // not replicated because first replication is complete (not continuous)
+ T(newDoc_copy === null);
+
+ var repDoc2 = {
+ _id: "foo_ident_rep_2",
+ source: dbA.name,
+ target: dbB.name
+ };
+ T(repDb.save(repDoc2).ok);
+
+ waitForRep(repDb, repDoc2, "completed");
+ var newDoc_copy = dbB.open(newDoc._id);
+ T(newDoc_copy !== null);
+ T(newDoc_copy.value === newDoc.value);
+
+ var repDoc2_copy = repDb.open(repDoc2._id);
+ T(repDoc2_copy !== null);
+ T(repDoc2_copy.source === repDoc1.source);
+ T(repDoc2_copy.target === repDoc1.target);
+ T(repDoc2_copy._replication_state === "completed");
+ T(typeof repDoc2_copy._replication_state_time === "number");
+ T(typeof repDoc2_copy._replication_id === "string");
+ T(repDoc2_copy._replication_id === repDoc1_copy._replication_id);
+ }
+
+
+ // test the case where multiple replication docs (different IDs)
+ // describe in fact the same replication (source, target, etc)
+ function identical_rep_docs() {
+ populate_db(dbA, docs1);
+ populate_db(dbB, []);
+
+ var repDoc1 = {
+ _id: "foo_dup_rep_doc_1",
+ source: "http://" + host + "/" + dbA.name,
+ target: dbB.name
+ };
+ var repDoc2 = {
+ _id: "foo_dup_rep_doc_2",
+ source: "http://" + host + "/" + dbA.name,
+ target: dbB.name
+ };
+
+ T(repDb.save(repDoc1).ok);
+ T(repDb.save(repDoc2).ok);
+
+ waitForRep(repDb, repDoc1, "completed");
+ for (var i = 0; i < docs1.length; i++) {
+ var doc = docs1[i];
+ var copy = dbB.open(doc._id);
+ T(copy !== null);
+ T(copy.value === doc.value);
+ }
+
+ repDoc1 = repDb.open("foo_dup_rep_doc_1");
+ T(repDoc1 !== null);
+ T(repDoc1._replication_state === "completed", "identical");
+ T(typeof repDoc1._replication_state_time === "number");
+ T(typeof repDoc1._replication_id === "string");
+
+ repDoc2 = repDb.open("foo_dup_rep_doc_2");
+ T(repDoc2 !== null);
+ T(typeof repDoc2._replication_state === "undefined");
+ T(typeof repDoc2._replication_state_time === "undefined");
+ T(repDoc2._replication_id === repDoc1._replication_id);
+ }
+
+
+ // test the case where multiple replication docs (different IDs)
+ // describe in fact the same continuous replication (source, target, etc)
+ function identical_continuous_rep_docs() {
+ populate_db(dbA, docs1);
+ populate_db(dbB, []);
+
+ var repDoc1 = {
+ _id: "foo_dup_cont_rep_doc_1",
+ source: "http://" + host + "/" + dbA.name,
+ target: dbB.name,
+ continuous: true
+ };
+ var repDoc2 = {
+ _id: "foo_dup_cont_rep_doc_2",
+ source: "http://" + host + "/" + dbA.name,
+ target: dbB.name,
+ continuous: true
+ };
+
+ T(repDb.save(repDoc1).ok);
+ T(repDb.save(repDoc2).ok);
+
+ waitForSeq(dbA, dbB);
+ for (var i = 0; i < docs1.length; i++) {
+ var doc = docs1[i];
+ var copy = dbB.open(doc._id);
+ T(copy !== null);
+ T(copy.value === doc.value);
+ }
+
+ repDoc1 = repDb.open("foo_dup_cont_rep_doc_1");
+ T(repDoc1 !== null);
+ T(repDoc1._replication_state === "triggered");
+ T(typeof repDoc1._replication_state_time === "number");
+ T(typeof repDoc1._replication_id === "string");
+
+ repDoc2 = repDb.open("foo_dup_cont_rep_doc_2");
+ T(repDoc2 !== null);
+ T(typeof repDoc2._replication_state === "undefined");
+ T(typeof repDoc2._replication_state_time === "undefined");
+ T(repDoc2._replication_id === repDoc1._replication_id);
+
+ var newDoc = {
+ _id: "foo666",
+ value: 999
+ };
+ T(dbA.save(newDoc).ok);
+
+ waitForSeq(dbA, dbB);
+ var copy = dbB.open("foo666");
+ T(copy !== null);
+ T(copy.value === 999);
+
+ // deleting second replication doc, doesn't affect the 1st one and
+ // neither it stops the replication
+ T(repDb.deleteDoc(repDoc2).ok);
+ repDoc1 = repDb.open("foo_dup_cont_rep_doc_1");
+ T(repDoc1 !== null);
+ T(repDoc1._replication_state === "triggered");
+ T(typeof repDoc1._replication_state_time === "number");
+
+ var newDoc2 = {
+ _id: "foo5000",
+ value: 5000
+ };
+ T(dbA.save(newDoc2).ok);
+
+ waitForSeq(dbA, dbB);
+ var copy = dbB.open("foo5000");
+ T(copy !== null);
+ T(copy.value === 5000);
+
+ // deleting the 1st replication document stops the replication
+ T(repDb.deleteDoc(repDoc1).ok);
+ var newDoc3 = {
+ _id: "foo1983",
+ value: 1983
+ };
+ T(dbA.save(newDoc3).ok);
+
+ wait(wait_rep_doc); //how to remove wait?
+ var copy = dbB.open("foo1983");
+ T(copy === null);
+ }
+
+
+ function test_replication_credentials_delegation() {
+ populate_db(usersDb, []);
+
+ var joeUserDoc = CouchDB.prepareUserDoc({
+ name: "joe",
+ roles: ["god", "erlanger"]
+ }, "erly");
+ T(usersDb.save(joeUserDoc).ok);
+
+ var ddoc = {
+ _id: "_design/beer",
+ language: "javascript"
+ };
+ populate_db(dbA, docs1.concat([ddoc]));
+ populate_db(dbB, []);
+
+ T(dbB.setSecObj({
+ admins: {
+ names: [],
+ roles: ["god"]
+ }
+ }).ok);
+
+ var server_admins_config = [
+ {
+ section: "admins",
+ key: "fdmanana",
+ value: "qwerty"
+ }
+ ];
+
+ run_on_modified_server(server_admins_config, function() {
+
+ T(CouchDB.login("fdmanana", "qwerty").ok);
+ T(CouchDB.session().userCtx.name === "fdmanana");
+ T(CouchDB.session().userCtx.roles.indexOf("_admin") !== -1);
+
+ var repDoc = {
+ _id: "foo_rep_del_doc_1",
+ source: dbA.name,
+ target: dbB.name,
+ user_ctx: {
+ name: "joe",
+ roles: ["erlanger"]
+ }
+ };
+
+ T(repDb.save(repDoc).ok);
+
+ waitForRep(repDb, repDoc, "completed");
+ for (var i = 0; i < docs1.length; i++) {
+ var doc = docs1[i];
+ var copy = dbB.open(doc._id);
+ T(copy !== null);
+ T(copy.value === doc.value);
+ }
+
+ // design doc was not replicated, because joe is not an admin of db B
+ var doc = dbB.open(ddoc._id);
+ T(doc === null);
+
+ // now test the same replication but putting the role "god" in the
+ // delegation user context property
+ var repDoc2 = {
+ _id: "foo_rep_del_doc_2",
+ source: dbA.name,
+ target: dbB.name,
+ user_ctx: {
+ name: "joe",
+ roles: ["erlanger", "god"]
+ }
+ };
+ T(repDb.save(repDoc2).ok);
+
+ waitForRep(repDb, repDoc2, "completed");
+ for (var i = 0; i < docs1.length; i++) {
+ var doc = docs1[i];
+ var copy = dbB.open(doc._id);
+ T(copy !== null);
+ T(copy.value === doc.value);
+ }
+
+ // because anyone with a 'god' role is an admin of db B, a replication
+ // that is delegated to a 'god' role can write design docs to db B
+ doc = dbB.open(ddoc._id);
+ T(doc !== null);
+ T(doc.language === ddoc.language);
+ });
+ }
+
+
+ function continuous_replication_survives_restart() {
+ var origRepDbName = CouchDB.request(
+ "GET", "/_config/replicator/db").responseText;
+
+ repDb.deleteDb();
+
+ var xhr = CouchDB.request("PUT", "/_config/replicator/db", {
+ body : JSON.stringify(repDb.name),
+ headers: {"X-Couch-Persist": "false"}
+ });
+ T(xhr.status === 200);
+
+ populate_db(dbA, docs1);
+ populate_db(dbB, []);
+
+ var repDoc = {
+ _id: "foo_cont_rep_survives_doc",
+ source: "http://" + host + "/" + dbA.name,
+ target: dbB.name,
+ continuous: true
+ };
+
+ T(repDb.save(repDoc).ok);
+
+ waitForSeq(dbA, dbB);
+ for (var i = 0; i < docs1.length; i++) {
+ var doc = docs1[i];
+ var copy = dbB.open(doc._id);
+ T(copy !== null);
+ T(copy.value === doc.value);
+ }
+
+ repDb.ensureFullCommit();
+ dbA.ensureFullCommit();
+
+ restartServer();
+
+ xhr = CouchDB.request("PUT", "/_config/replicator/db", {
+ body : JSON.stringify(repDb.name),
+ headers: {"X-Couch-Persist": "false"}
+ });
+
+ T(xhr.status === 200);
+
+ // add another doc to source, it will be replicated to target
+ var docX = {
+ _id: "foo1000",
+ value: 1001
+ };
+
+ T(dbA.save(docX).ok);
+
+ waitForSeq(dbA, dbB);
+ var copy = dbB.open("foo1000");
+ T(copy !== null);
+ T(copy.value === 1001);
+
+ repDoc = repDb.open("foo_cont_rep_survives_doc");
+ T(repDoc !== null);
+ T(repDoc.continuous === true);
+
+ // stop replication
+ T(repDb.deleteDoc(repDoc).ok);
+
+ xhr = CouchDB.request("PUT", "/_config/replicator/db", {
+ body : origRepDbName,
+ headers: {"X-Couch-Persist": "false"}
+ });
+ T(xhr.status === 200);
+ }
+
+
+ function rep_db_write_authorization() {
+ populate_db(dbA, docs1);
+ populate_db(dbB, []);
+
+ var server_admins_config = [
+ {
+ section: "admins",
+ key: "fdmanana",
+ value: "qwerty"
+ }
+ ];
+
+ run_on_modified_server(server_admins_config, function() {
+ var repDoc = {
+ _id: "foo_rep_doc",
+ source: dbA.name,
+ target: dbB.name
+ };
+
+ T(CouchDB.login("fdmanana", "qwerty").ok);
+ T(CouchDB.session().userCtx.name === "fdmanana");
+ T(CouchDB.session().userCtx.roles.indexOf("_admin") !== -1);
+
+ T(repDb.save(repDoc).ok);
+
+ waitForRep(repDb, repDoc, "completed");
+
+ for (var i = 0; i < docs1.length; i++) {
+ var doc = docs1[i];
+ var copy = dbB.open(doc._id);
+
+ T(copy !== null);
+ T(copy.value === doc.value);
+ }
+
+ repDoc = repDb.open("foo_rep_doc");
+ T(repDoc !== null);
+ repDoc.target = "test_suite_foo_db";
+ repDoc.create_target = true;
+
+ // Only the replicator can update replication documents.
+ // Admins can only add and delete replication documents.
+ try {
+ repDb.save(repDoc);
+ T(false && "Should have thrown an exception");
+ } catch (x) {
+ T(x["error"] === "forbidden");
+ }
+ });
+ }
+
+
+ function rep_doc_with_bad_rep_id() {
+ populate_db(dbA, docs1);
+ populate_db(dbB, []);
+
+ var repDoc = {
+ _id: "foo_rep",
+ source: dbA.name,
+ target: dbB.name,
+ replication_id: "1234abc"
+ };
+ T(repDb.save(repDoc).ok);
+
+ waitForRep(repDb, repDoc, "completed");
+ for (var i = 0; i < docs1.length; i++) {
+ var doc = docs1[i];
+ var copy = dbB.open(doc._id);
+ T(copy !== null);
+ T(copy.value === doc.value);
+ }
+
+ var repDoc1 = repDb.open(repDoc._id);
+ T(repDoc1 !== null);
+ T(repDoc1.source === repDoc.source);
+ T(repDoc1.target === repDoc.target);
+ T(repDoc1._replication_state === "completed",
+ "replication document with bad replication id failed");
+ T(typeof repDoc1._replication_state_time === "number");
+ T(typeof repDoc1._replication_id === "string");
+ T(repDoc1._replication_id !== "1234abc");
+ }
+
+
+ function error_state_replication() {
+ populate_db(dbA, docs1);
+
+ var repDoc = {
+ _id: "foo_error_rep",
+ source: dbA.name,
+ target: "nonexistent_test_db"
+ };
+ T(repDb.save(repDoc).ok);
+
+ waitForRep(repDb, repDoc, "error");
+ var repDoc1 = repDb.open(repDoc._id);
+ T(repDoc1 !== null);
+ T(repDoc1._replication_state === "error");
+ T(typeof repDoc1._replication_state_time === "number");
+ T(typeof repDoc1._replication_id === "string");
+ }
+
+
+ // run all the tests
+ var server_config = [
+ {
+ section: "replicator",
+ key: "db",
+ value: repDb.name
+ }
+ ];
+
+ repDb.deleteDb();
+ run_on_modified_server(server_config, simple_replication);
+
+ repDb.deleteDb();
+ restartServer();
+ run_on_modified_server(server_config, filtered_replication);
+
+ repDb.deleteDb();
+ restartServer();
+ run_on_modified_server(server_config, continuous_replication);
+
+ repDb.deleteDb();
+ restartServer();
+ run_on_modified_server(server_config, by_doc_ids_replication);
+
+ repDb.deleteDb();
+ restartServer();
+ run_on_modified_server(server_config, successive_identical_replications);
+
+ repDb.deleteDb();
+ restartServer();
+ run_on_modified_server(server_config, identical_rep_docs);
+
+ repDb.deleteDb();
+ restartServer();
+ run_on_modified_server(server_config, identical_continuous_rep_docs);
+
+ repDb.deleteDb();
+ restartServer();
+ run_on_modified_server(server_config, rep_db_write_authorization);
+
+ repDb.deleteDb();
+ restartServer();
+ run_on_modified_server(server_config, rep_doc_with_bad_rep_id);
+
+ var server_config_2 = server_config.concat([
+ {
+ section: "couch_httpd_auth",
+ key: "authentication_db",
+ value: usersDb.name
+ }
+ ]);
+ repDb.deleteDb();
+ restartServer();
+ run_on_modified_server(server_config_2, test_replication_credentials_delegation);
+
+ repDb.deleteDb();
+ restartServer();
+ continuous_replication_survives_restart();
+
+ repDb.deleteDb();
+ restartServer();
+ run_on_modified_server(server_config, error_state_replication);
+
+
+ // cleanup
+ repDb.deleteDb();
+ usersDb.deleteDb();
+ dbA.deleteDb();
+ dbB.deleteDb();
+};
diff --git a/share/www/script/test/rewrite.js b/share/www/script/test/rewrite.js
new file mode 100644
index 00000000..474abb5e
--- /dev/null
+++ b/share/www/script/test/rewrite.js
@@ -0,0 +1,443 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+
+
+couchTests.rewrite = function(debug) {
+ // this test _rewrite handler
+
+
+ var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
+ db.deleteDb();
+ db.createDb();
+
+
+ if (debug) debugger;
+ run_on_modified_server(
+ [{section: "httpd",
+ key: "authentication_handlers",
+ value: "{couch_httpd_auth, special_test_authentication_handler}"},
+ {section:"httpd",
+ key: "WWW-Authenticate",
+ value: "X-Couch-Test-Auth"}],
+
+ function(){
+ var designDoc = {
+ _id:"_design/test",
+ language: "javascript",
+ _attachments:{
+ "foo.txt": {
+ content_type:"text/plain",
+ data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
+ }
+ },
+ rewrites: [
+ {
+ "from": "foo",
+ "to": "foo.txt"
+ },
+ {
+ "from": "foo2",
+ "to": "foo.txt",
+ "method": "GET"
+ },
+ {
+ "from": "hello/:id",
+ "to": "_update/hello/:id",
+ "method": "PUT"
+ },
+ {
+ "from": "/welcome",
+ "to": "_show/welcome"
+ },
+ {
+ "from": "/welcome/:name",
+ "to": "_show/welcome",
+ "query": {
+ "name": ":name"
+ }
+ },
+ {
+ "from": "/welcome2",
+ "to": "_show/welcome",
+ "query": {
+ "name": "user"
+ }
+ },
+ {
+ "from": "/welcome3/:name",
+ "to": "_update/welcome2/:name",
+ "method": "PUT"
+ },
+ {
+ "from": "/welcome3/:name",
+ "to": "_show/welcome2/:name",
+ "method": "GET"
+ },
+ {
+ "from": "/welcome4/*",
+ "to" : "_show/welcome3",
+ "query": {
+ "name": "*"
+ }
+ },
+ {
+ "from": "/type/<type>.json",
+ "to": "_show/type/:type",
+ "query": {
+ "format": "json"
+ }
+ },
+ {
+ "from": "/type/<type>.xml",
+ "to": "_show/type/:type",
+ "query": {
+ "format": "xml"
+ }
+ },
+ {
+ "from": "/type/<type>",
+ "to": "_show/type/:type",
+ "query": {
+ "format": "html"
+ }
+ },
+ {
+ "from": "/welcome5/*",
+ "to" : "_show/*",
+ "query": {
+ "name": "*"
+ }
+ },
+ {
+ "from": "basicView",
+ "to": "_view/basicView",
+ },
+ {
+ "from": "simpleForm/basicView",
+ "to": "_list/simpleForm/basicView",
+ },
+ {
+ "from": "simpleForm/basicViewFixed",
+ "to": "_list/simpleForm/basicView",
+ "query": {
+ "startkey": 3,
+ "endkey": 8
+ }
+ },
+ {
+ "from": "simpleForm/basicViewPath/:start/:end",
+ "to": "_list/simpleForm/basicView",
+ "query": {
+ "startkey": ":start",
+ "endkey": ":end"
+ }
+ },
+ {
+ "from": "simpleForm/complexView",
+ "to": "_list/simpleForm/complexView",
+ "query": {
+ "key": [1, 2]
+ }
+ },
+ {
+ "from": "simpleForm/complexView2",
+ "to": "_list/simpleForm/complexView",
+ "query": {
+ "key": ["test", {}]
+ }
+ },
+ {
+ "from": "simpleForm/complexView3",
+ "to": "_list/simpleForm/complexView",
+ "query": {
+ "key": ["test", ["test", "essai"]]
+ }
+ },
+ {
+ "from": "simpleForm/complexView4",
+ "to": "_list/simpleForm/complexView2",
+ "query": {
+ "key": {"c": 1}
+ }
+ },
+ {
+ "from": "simpleForm/complexView5/:a/:b",
+ "to": "_list/simpleForm/complexView3",
+ "query": {
+ "key": [":a", ":b"]
+ }
+ },
+ {
+ "from": "simpleForm/complexView6",
+ "to": "_list/simpleForm/complexView3",
+ "query": {
+ "key": [":a", ":b"]
+ }
+ },
+ {
+ "from": "/",
+ "to": "_view/basicView",
+ }
+ ],
+ lists: {
+ simpleForm: stringFun(function(head, req) {
+ log("simpleForm");
+ send('<ul>');
+ var row, row_number = 0, prevKey, firstKey = null;
+ while (row = getRow()) {
+ row_number += 1;
+ if (!firstKey) firstKey = row.key;
+ prevKey = row.key;
+ send('\n<li>Key: '+row.key
+ +' Value: '+row.value
+ +' LineNo: '+row_number+'</li>');
+ }
+ return '</ul><p>FirstKey: '+ firstKey + ' LastKey: '+ prevKey+'</p>';
+ }),
+ },
+ shows: {
+ "welcome": stringFun(function(doc,req) {
+ return "Welcome " + req.query["name"];
+ }),
+ "welcome2": stringFun(function(doc, req) {
+ return "Welcome " + doc.name;
+ }),
+ "welcome3": stringFun(function(doc,req) {
+ return "Welcome " + req.query["name"];
+ }),
+ "type": stringFun(function(doc, req) {
+ return req.id + " as " + req.query.format;
+ })
+ },
+ updates: {
+ "hello" : stringFun(function(doc, req) {
+ if (!doc) {
+ if (req.id) {
+ return [{
+ _id : req.id
+ }, "New World"]
+ }
+ return [null, "Empty World"];
+ }
+ doc.world = "hello";
+ doc.edited_by = req.userCtx;
+ return [doc, "hello doc"];
+ }),
+ "welcome2": stringFun(function(doc, req) {
+ if (!doc) {
+ if (req.id) {
+ return [{
+ _id: req.id,
+ name: req.id
+ }, "New World"]
+ }
+ return [null, "Empty World"];
+ }
+ return [doc, "hello doc"];
+ })
+ },
+ views : {
+ basicView : {
+ map : stringFun(function(doc) {
+ if (doc.integer) {
+ emit(doc.integer, doc.string);
+ }
+
+ })
+ },
+ complexView: {
+ map: stringFun(function(doc) {
+ if (doc.type == "complex") {
+ emit([doc.a, doc.b], doc.string);
+ }
+ })
+ },
+ complexView2: {
+ map: stringFun(function(doc) {
+ if (doc.type == "complex") {
+ emit(doc.a, doc.string);
+ }
+ })
+ },
+ complexView3: {
+ map: stringFun(function(doc) {
+ if (doc.type == "complex") {
+ emit(doc.b, doc.string);
+ }
+ })
+ }
+ }
+ }
+
+ db.save(designDoc);
+
+ var docs = makeDocs(0, 10);
+ db.bulkSave(docs);
+
+ var docs2 = [
+ {"a": 1, "b": 1, "string": "doc 1", "type": "complex"},
+ {"a": 1, "b": 2, "string": "doc 2", "type": "complex"},
+ {"a": "test", "b": {}, "string": "doc 3", "type": "complex"},
+ {"a": "test", "b": ["test", "essai"], "string": "doc 4", "type": "complex"},
+ {"a": {"c": 1}, "b": "", "string": "doc 5", "type": "complex"}
+ ];
+
+ db.bulkSave(docs2);
+
+ // test simple rewriting
+
+ req = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/foo");
+ T(req.responseText == "This is a base64 encoded text");
+ T(req.getResponseHeader("Content-Type") == "text/plain");
+
+ req = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/foo2");
+ T(req.responseText == "This is a base64 encoded text");
+ T(req.getResponseHeader("Content-Type") == "text/plain");
+
+
+ // test POST
+ // hello update world
+
+ var doc = {"word":"plankton", "name":"Rusty"}
+ var resp = db.save(doc);
+ T(resp.ok);
+ var docid = resp.id;
+
+ xhr = CouchDB.request("PUT", "/test_suite_db/_design/test/_rewrite/hello/"+docid);
+ T(xhr.status == 201);
+ T(xhr.responseText == "hello doc");
+ T(/charset=utf-8/.test(xhr.getResponseHeader("Content-Type")))
+
+ doc = db.open(docid);
+ T(doc.world == "hello");
+
+ req = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/welcome?name=user");
+ T(req.responseText == "Welcome user");
+
+ req = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/welcome/user");
+ T(req.responseText == "Welcome user");
+
+ req = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/welcome2");
+ T(req.responseText == "Welcome user");
+
+ xhr = CouchDB.request("PUT", "/test_suite_db/_design/test/_rewrite/welcome3/test");
+ T(xhr.status == 201);
+ T(xhr.responseText == "New World");
+ T(/charset=utf-8/.test(xhr.getResponseHeader("Content-Type")));
+
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/welcome3/test");
+ T(xhr.responseText == "Welcome test");
+
+ req = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/welcome4/user");
+ T(req.responseText == "Welcome user");
+
+ req = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/welcome5/welcome3");
+ T(req.responseText == "Welcome welcome3");
+
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/basicView");
+ T(xhr.status == 200, "view call");
+ T(/{"total_rows":9/.test(xhr.responseText));
+
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/");
+ T(xhr.status == 200, "view call");
+ T(/{"total_rows":9/.test(xhr.responseText));
+
+
+ // get with query params
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/simpleForm/basicView?startkey=3&endkey=8");
+ T(xhr.status == 200, "with query params");
+ T(!(/Key: 1/.test(xhr.responseText)));
+ T(/FirstKey: 3/.test(xhr.responseText));
+ T(/LastKey: 8/.test(xhr.responseText));
+
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/simpleForm/basicViewFixed");
+ T(xhr.status == 200, "with query params");
+ T(!(/Key: 1/.test(xhr.responseText)));
+ T(/FirstKey: 3/.test(xhr.responseText));
+ T(/LastKey: 8/.test(xhr.responseText));
+
+ // get with query params
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/simpleForm/basicViewFixed?startkey=4");
+ T(xhr.status == 200, "with query params");
+ T(!(/Key: 1/.test(xhr.responseText)));
+ T(/FirstKey: 3/.test(xhr.responseText));
+ T(/LastKey: 8/.test(xhr.responseText));
+
+ // get with query params
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/simpleForm/basicViewPath/3/8");
+ T(xhr.status == 200, "with query params");
+ T(!(/Key: 1/.test(xhr.responseText)));
+ T(/FirstKey: 3/.test(xhr.responseText));
+ T(/LastKey: 8/.test(xhr.responseText));
+
+ // get with query params
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/simpleForm/complexView");
+ T(xhr.status == 200, "with query params");
+ T(/FirstKey: [1, 2]/.test(xhr.responseText));
+
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/simpleForm/complexView2");
+ T(xhr.status == 200, "with query params");
+ T(/Value: doc 3/.test(xhr.responseText));
+
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/simpleForm/complexView3");
+ T(xhr.status == 200, "with query params");
+ T(/Value: doc 4/.test(xhr.responseText));
+
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/simpleForm/complexView4");
+ T(xhr.status == 200, "with query params");
+ T(/Value: doc 5/.test(xhr.responseText));
+
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/simpleForm/complexView5/test/essai");
+ T(xhr.status == 200, "with query params");
+ T(/Value: doc 4/.test(xhr.responseText));
+
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/simpleForm/complexView6?a=test&b=essai");
+ T(xhr.status == 200, "with query params");
+ T(/Value: doc 4/.test(xhr.responseText));
+
+ req = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/type/test.json");
+ T(req.responseText == "test as json");
+
+ req = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/type/test.xml");
+ T(req.responseText == "test as xml");
+
+ req = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/type/test");
+ T(req.responseText == "test as html");
+
+ // test path relative to server
+ designDoc.rewrites.push({
+ "from": "uuids",
+ "to": "../../../_uuids"
+ });
+ T(db.save(designDoc).ok);
+
+ var xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/uuids");
+ T(xhr.status == 500);
+ var result = JSON.parse(xhr.responseText);
+ T(result.error == "insecure_rewrite_rule");
+
+ run_on_modified_server(
+ [{section: "httpd",
+ key: "secure_rewrites",
+ value: "false"}],
+ function() {
+ var xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/uuids?cache=bust");
+ T(xhr.status == 200);
+ var result = JSON.parse(xhr.responseText);
+ T(result.uuids.length == 1);
+ var first = result.uuids[0];
+ });
+
+ });
+
+}
diff --git a/share/www/script/test/security_validation.js b/share/www/script/test/security_validation.js
index d07195e1..42aa11c9 100644
--- a/share/www/script/test/security_validation.js
+++ b/share/www/script/test/security_validation.js
@@ -13,7 +13,7 @@
couchTests.security_validation = function(debug) {
// This tests couchdb's security and validation features. This does
// not test authentication, except to use test authentication code made
- // specifically for this testing. It is a WWWW-Authenticate scheme named
+ // specifically for this testing. It is a WWW-Authenticate scheme named
// X-Couch-Test-Auth, and the user names and passwords are hard coded
// on the server-side.
//
@@ -21,7 +21,7 @@ couchTests.security_validation = function(debug) {
// implementation for Firefox and Safari, and probably other browsers are
// broken (Firefox always prompts the user on 401 failures, Safari gives
// odd security errors when using different name/passwords, perhaps due
- // to cross site scripting prevention). These problems essentially make Basic
+ // to cross site scripting prevention). These problems essentially make Basic
// authentication testing in the browser impossible. But while hard to
// test automated in the browser, Basic auth may still useful for real
// world use where these bugs/behaviors don't matter.
@@ -69,7 +69,13 @@ couchTests.security_validation = function(debug) {
var designDoc = {
_id:"_design/test",
language: "javascript",
- validate_doc_update: "(" + (function (newDoc, oldDoc, userCtx) {
+ validate_doc_update: "(" + (function (newDoc, oldDoc, userCtx, secObj) {
+ if (secObj.admin_override) {
+ if (userCtx.roles.indexOf('_admin') != -1) {
+ // user is admin, they can do anything
+ return true;
+ }
+ }
// docs should have an author field.
if (!newDoc._deleted && !newDoc.author) {
throw {forbidden:
@@ -99,13 +105,27 @@ couchTests.security_validation = function(debug) {
}
// set user as the admin
- T(db.setDbProperty("_admins", ["Damien Katz"]).ok);
+ T(db.setSecObj({
+ admins : {names : ["Damien Katz"]}
+ }).ok);
T(userDb.save(designDoc).ok);
- // test the _whoami endpoint
+ var user2Db = new CouchDB("test_suite_db",
+ {"WWW-Authenticate": "X-Couch-Test-Auth Jan Lehnardt:apple"}
+ );
+ // Attempt to save the design as a non-admin (in replication scenario)
+ try {
+ user2Db.save(designDoc, {new_edits : false});
+ T(false && "Can't get here. Should have thrown an error on design doc");
+ } catch (e) {
+ T(e.error == "unauthorized");
+ T(user2Db.last_req.status == 401);
+ }
+
+ // test the _session API
var resp = userDb.request("GET", "/_session");
- var user = JSON.parse(resp.responseText)
+ var user = JSON.parse(resp.responseText).userCtx;
T(user.name == "Damien Katz");
// test that the roles are listed properly
TEquals(user.roles, []);
@@ -116,20 +136,23 @@ couchTests.security_validation = function(debug) {
doc.foo=2;
T(userDb.save(doc).ok);
- // Save a document that's missing an author field.
- try {
- userDb.save({foo:1});
- T(false && "Can't get here. Should have thrown an error 2");
- } catch (e) {
- T(e.error == "forbidden");
- T(userDb.last_req.status == 403);
+ // Save a document that's missing an author field (before and after compaction)
+ for (var i=0; i<2; i++) {
+ try {
+ userDb.save({foo:1});
+ T(false && "Can't get here. Should have thrown an error 2");
+ } catch (e) {
+ T(e.error == "forbidden");
+ T(userDb.last_req.status == 403);
+ }
+ // compact.
+ T(db.compact().ok);
+ T(db.last_req.status == 202);
+ // compaction isn't instantaneous, loop until done
+ while (db.info().compact_running) {};
}
// Now attempt to update the document as a different user, Jan
- var user2Db = new CouchDB("test_suite_db",
- {"WWW-Authenticate": "X-Couch-Test-Auth Jan Lehnardt:apple"}
- );
-
var doc = user2Db.open("testdoc");
doc.foo=3;
try {
@@ -158,6 +181,37 @@ couchTests.security_validation = function(debug) {
T(e.error == "unauthorized");
T(userDb.last_req.status == 401);
}
+
+ // admin must save with author field unless admin override
+ var resp = db.request("GET", "/_session");
+ var user = JSON.parse(resp.responseText).userCtx;
+ T(user.name == null);
+ // test that we are admin
+ TEquals(user.roles, ["_admin"]);
+
+ // can't save the doc even though we are admin
+ var doc = db.open("testdoc");
+ doc.foo=3;
+ try {
+ db.save(doc);
+ T(false && "Can't get here. Should have thrown an error 3");
+ } catch (e) {
+ T(e.error == "unauthorized");
+ T(db.last_req.status == 401);
+ }
+
+ // now turn on admin override
+ T(db.setDbProperty("_security", {admin_override : true}).ok);
+ T(db.save(doc).ok);
+
+ // try to do something lame
+ try {
+ db.setDbProperty("_security", ["foo"]);
+ T(false && "can't do this");
+ } catch(e) {}
+
+ // go back to normal
+ T(db.setDbProperty("_security", {admin_override : false}).ok);
// Now delete document
T(user2Db.deleteDoc(doc).ok);
@@ -188,7 +242,6 @@ couchTests.security_validation = function(debug) {
T(db.open("booboo") == null);
T(db.open("foofoo") == null);
-
// Now test replication
var AuthHeaders = {"WWW-Authenticate": "X-Couch-Test-Auth Christopher Lenz:dog food"};
var host = CouchDB.host;
@@ -197,16 +250,16 @@ couchTests.security_validation = function(debug) {
target:"test_suite_db_b"},
{source:"test_suite_db_a",
- target:{url: "http://" + host + "/test_suite_db_b",
+ target:{url: CouchDB.protocol + host + "/test_suite_db_b",
headers: AuthHeaders}},
- {source:{url:"http://" + host + "/test_suite_db_a",
+ {source:{url:CouchDB.protocol + host + "/test_suite_db_a",
headers: AuthHeaders},
target:"test_suite_db_b"},
- {source:{url:"http://" + host + "/test_suite_db_a",
+ {source:{url:CouchDB.protocol + host + "/test_suite_db_a",
headers: AuthHeaders},
- target:{url:"http://" + host + "/test_suite_db_b",
+ target:{url:CouchDB.protocol + host + "/test_suite_db_b",
headers: AuthHeaders}},
]
var adminDbA = new CouchDB("test_suite_db_a", {"X-Couch-Full-Commit":"false"});
diff --git a/share/www/script/test/show_documents.js b/share/www/script/test/show_documents.js
index f484e029..55ed9698 100644
--- a/share/www/script/test/show_documents.js
+++ b/share/www/script/test/show_documents.js
@@ -25,7 +25,11 @@ couchTests.show_documents = function(debug) {
if (doc) {
return "Hello World";
} else {
- return "Empty World";
+ if(req.id) {
+ return "New World";
+ } else {
+ return "Empty World";
+ }
}
}),
"just-name" : stringFun(function(doc, req) {
@@ -50,18 +54,28 @@ couchTests.show_documents = function(debug) {
json : req
}
}),
+ "show-deleted" : stringFun(function(doc, req) {
+ if(doc) {
+ return doc._id;
+ } else {
+ return "No doc " + req.id;
+ }
+ }),
"render-error" : stringFun(function(doc, req) {
return noSuchVariable;
}),
"empty" : stringFun(function(doc, req) {
return "";
}),
+ "fail" : stringFun(function(doc, req) {
+ return doc._id;
+ }),
"xml-type" : stringFun(function(doc, req) {
return {
"headers" : {
"Content-Type" : "application/xml"
},
- "body" : new XML('<xml><node foo="bar"/></xml>')
+ "body" : new XML('<xml><node foo="bar"/></xml>').toXMLString()
}
}),
"no-set-etag" : stringFun(function(doc, req) {
@@ -72,6 +86,25 @@ couchTests.show_documents = function(debug) {
"body" : "something"
}
}),
+ "list-api" : stringFun(function(doc, req) {
+ start({"X-Couch-Test-Header": "Yeah"});
+ send("Hey");
+ }),
+ "list-api-mix" : stringFun(function(doc, req) {
+ start({"X-Couch-Test-Header": "Yeah"});
+ send("Hey ");
+ return "Dude";
+ }),
+ "list-api-mix-with-header" : stringFun(function(doc, req) {
+ start({"X-Couch-Test-Header": "Yeah"});
+ send("Hey ");
+ return {
+ headers: {
+ "X-Couch-Test-Header-Awesome": "Oh Yeah!"
+ },
+ body: "Dude"
+ };
+ }),
"accept-switch" : stringFun(function(doc, req) {
if (req.headers["Accept"].match(/image/)) {
return {
@@ -114,12 +147,19 @@ couchTests.show_documents = function(debug) {
// E4X outside of a string. Outside of tests you
// can just use E4X literals.
eval('xml.node.@foo = doc.word');
- return xml;
+ log('xml: '+xml.toSource());
+ return xml.toXMLString();
});
provides("foo", function() {
return "foofoo";
});
+ }),
+ "withSlash": stringFun(function(doc, req) {
+ return { json: doc }
+ }),
+ "secObj": stringFun(function(doc, req) {
+ return { json: req.secObj };
})
}
};
@@ -157,7 +197,7 @@ couchTests.show_documents = function(debug) {
T(xhr.responseText == "");
// // hello template world (non-existing docid)
- xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/hello/nonExistingDoc");
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/fail/nonExistingDoc");
T(xhr.status == 404);
var resp = JSON.parse(xhr.responseText);
T(resp.error == "not_found");
@@ -169,8 +209,7 @@ couchTests.show_documents = function(debug) {
// show with missing doc
xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/just-name/missingdoc");
T(xhr.status == 404);
- var resp = JSON.parse(xhr.responseText);
- T(resp.error == "not_found");
+ TEquals("No such doc", xhr.responseText);
// show with missing func
xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/missing/"+docid);
@@ -342,4 +381,56 @@ couchTests.show_documents = function(debug) {
xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/json/foo");
TEquals(1, JSON.parse(xhr.responseText)._conflicts.length);
+ var doc3 = {_id:"a/b/c", a:1};
+ db.save(doc3);
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/withSlash/a/b/c");
+ T(xhr.status == 200);
+
+ // hello template world (non-existing docid)
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/hello/nonExistingDoc");
+ T(xhr.responseText == "New World");
+
+ // test list() compatible API
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/list-api/foo");
+ T(xhr.responseText == "Hey");
+ TEquals("Yeah", xhr.getResponseHeader("X-Couch-Test-Header"), "header should be cool");
+
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/list-api-mix/foo");
+ T(xhr.responseText == "Hey Dude");
+ TEquals("Yeah", xhr.getResponseHeader("X-Couch-Test-Header"), "header should be cool");
+
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/list-api-mix-with-header/foo");
+ T(xhr.responseText == "Hey Dude");
+ TEquals("Yeah", xhr.getResponseHeader("X-Couch-Test-Header"), "header should be cool");
+ TEquals("Oh Yeah!", xhr.getResponseHeader("X-Couch-Test-Header-Awesome"), "header should be cool");
+
+ // test deleted docs
+ var doc = {_id:"testdoc",foo:1};
+ db.save(doc);
+ var xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/show-deleted/testdoc");
+ TEquals("testdoc", xhr.responseText, "should return 'testdoc'");
+
+ db.deleteDoc(doc);
+ var xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/show-deleted/testdoc");
+ TEquals("No doc testdoc", xhr.responseText, "should return 'no doc testdoc'");
+
+
+ run_on_modified_server(
+ [{section: "httpd",
+ key: "authentication_handlers",
+ value: "{couch_httpd_auth, special_test_authentication_handler}"},
+ {section:"httpd",
+ key: "WWW-Authenticate",
+ value: "X-Couch-Test-Auth"}],
+
+ function() {
+ T(db.setDbProperty("_security", {foo: true}).ok);
+ T(db.save(doc).ok);
+
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/secObj");
+ var resp = JSON.parse(xhr.responseText);
+ T(resp.foo == true);
+ }
+ );
+
};
diff --git a/share/www/script/test/stats.js b/share/www/script/test/stats.js
index 793b390d..6fb0fbba 100644
--- a/share/www/script/test/stats.js
+++ b/share/www/script/test/stats.js
@@ -30,7 +30,7 @@ couchTests.stats = function(debug) {
_id:"_design/test", // turn off couch.js id escaping?
language: "javascript",
views: {
- all_docs: {map: "function(doc) {emit(doc.integer, null);}"},
+ all_docs: {map: "function(doc) {emit(doc.integer, null);}"}
}
};
db.save(designDoc);
@@ -81,19 +81,17 @@ couchTests.stats = function(debug) {
var pre_dbs = getStat("couchdb", "open_databases").current || 0;
var pre_files = getStat("couchdb", "open_os_files").current || 0;
- // We have to make sure that as we open the max'th database
- // that we've waited for more than 1 second since opening
- // the first database so that any delayed commits will be
- // flushed.
var triggered = false;
var db = null;
for(var i = 0; i < max*2; i++) {
- try {
- db = newDb("test_suite_db_" + i, true);
- } catch(e) {
- triggered = true;
- CouchDB.request("GET", "/_sleep?time=1500");
- db = newDb("test_suite_db_" + i, true);
+ while (true) {
+ try {
+ db = newDb("test_suite_db_" + i, true);
+ break;
+ } catch(e) {
+ // all_dbs_active error!
+ triggered = true;
+ }
}
// Trigger a delayed commit
@@ -162,12 +160,15 @@ couchTests.stats = function(debug) {
runTest("couchdb", "database_writes", {
run: function(db) {
- CouchDB.request("POST", "/test_suite_db", {body: '{"a": "1"}'})
+ CouchDB.request("POST", "/test_suite_db", {
+ headers: {"Content-Type": "application/json"},
+ body: '{"a": "1"}'
+ });
},
test: function(before, after) {
TEquals(before+1, after, "POST'ing new docs increments doc writes.");
}
- })
+ });
runTest("couchdb", "database_writes", {
setup: function(db) {db.save({"_id": "test"});},
@@ -246,7 +247,7 @@ couchTests.stats = function(debug) {
});
runTest("httpd", "temporary_view_reads", {
- run: function(db) {db.query(function(doc) {emit(doc._id)})},
+ run: function(db) { db.query(function(doc) { emit(doc._id); }); },
test: function(before, after) {
TEquals(before+1, after, "Temporary views have their own counter.");
}
@@ -260,7 +261,7 @@ couchTests.stats = function(debug) {
});
runTest("httpd", "view_reads", {
- run: function(db) {db.query(function(doc) {emit(doc._id)});},
+ run: function(db) { db.query(function(doc) { emit(doc._id); }); },
test: function(before, after) {
TEquals(before, after, "Temporary views don't affect permanent views.");
}
diff --git a/share/www/script/test/update_documents.js b/share/www/script/test/update_documents.js
index 87fc7352..49d3b68a 100644
--- a/share/www/script/test/update_documents.js
+++ b/share/www/script/test/update_documents.js
@@ -68,10 +68,13 @@ couchTests.update_documents = function(debug) {
"headers" : {
"Content-Type" : "application/xml"
},
- "body" : xml
+ "body" : xml.toXMLString()
};
return [doc, resp];
+ }),
+ "get-uuid" : stringFun(function(doc, req) {
+ return [null, req.uuid];
})
}
};
@@ -110,9 +113,13 @@ couchTests.update_documents = function(debug) {
T(JSON.parse(xhr.responseText).error == "method_not_allowed");
// // hello update world (non-existing docid)
+ xhr = CouchDB.request("GET", "/test_suite_db/nonExistingDoc");
+ T(xhr.status == 404);
xhr = CouchDB.request("PUT", "/test_suite_db/_design/update/_update/hello/nonExistingDoc");
T(xhr.status == 201);
T(xhr.responseText == "<p>New World</p>");
+ xhr = CouchDB.request("GET", "/test_suite_db/nonExistingDoc");
+ T(xhr.status == 200);
// in place update
xhr = CouchDB.request("PUT", "/test_suite_db/_design/update/_update/in-place/"+docid+'?field=title&value=test');
@@ -123,7 +130,7 @@ couchTests.update_documents = function(debug) {
// bump counter
xhr = CouchDB.request("PUT", "/test_suite_db/_design/update/_update/bump-counter/"+docid, {
- headers : {"X-Couch-Full-Commit":"false"}
+ headers : {"X-Couch-Full-Commit":"true"}
});
T(xhr.status == 201);
T(xhr.responseText == "<h1>bumped it!</h1>");
@@ -135,12 +142,16 @@ couchTests.update_documents = function(debug) {
headers : {"X-Couch-Full-Commit":"true"}
});
+ var NewRev = xhr.getResponseHeader("X-Couch-Update-NewRev");
doc = db.open(docid);
+ T(doc['_rev'] == NewRev);
+
+
T(doc.counter == 2);
// parse xml
xhr = CouchDB.request("PUT", "/test_suite_db/_design/update/_update/xml/"+docid, {
- headers : {"X-Couch-Full-Commit":"false"},
+ headers : {"X-Couch-Full-Commit":"true"},
"body" : '<xml><foo>bar</foo></xml>'
});
T(xhr.status == 201);
@@ -148,5 +159,10 @@ couchTests.update_documents = function(debug) {
doc = db.open(docid);
T(doc.via_xml == "bar");
+
+ // Server provides UUID when POSTing without an ID in the URL
+ xhr = CouchDB.request("POST", "/test_suite_db/_design/update/_update/get-uuid/");
+ T(xhr.status == 200);
+ T(xhr.responseText.length == 32);
};
diff --git a/share/www/script/test/users_db.js b/share/www/script/test/users_db.js
index 2cf63fcf..1e13e5d7 100644
--- a/share/www/script/test/users_db.js
+++ b/share/www/script/test/users_db.js
@@ -24,44 +24,101 @@ couchTests.users_db = function(debug) {
// to determine the actual users db name.
function testFun() {
- usersDb.deleteDb();
-
// test that the validation function is installed
var ddoc = usersDb.open("_design/_auth");
T(ddoc.validate_doc_update);
// test that you can login as a user using basic auth
var jchrisUserDoc = CouchDB.prepareUserDoc({
- username: "jchris@apache.org"
+ name: "jchris@apache.org"
}, "funnybone");
T(usersDb.save(jchrisUserDoc).ok);
- T(CouchDB.session().name == null);
+ T(CouchDB.session().userCtx.name == null);
+
+ // test that you can use basic auth aginst the users db
var s = CouchDB.session({
headers : {
+ // base64_encode("jchris@apache.org:funnybone")
"Authorization" : "Basic amNocmlzQGFwYWNoZS5vcmc6ZnVubnlib25l"
}
});
- T(s.name == "jchris@apache.org");
- T(s.user_doc._id == "org.couchdb.user:jchris@apache.org")
- T(s.info.authenticated == "{couch_httpd_auth, default_authentication_handler}");
- T(s.info.user_db == "test_suite_users");
- TEquals(["{couch_httpd_oauth, oauth_authentication_handler}",
- "{couch_httpd_auth, cookie_authentication_handler}",
- "{couch_httpd_auth, default_authentication_handler}"], s.info.handlers);
+ T(s.userCtx.name == "jchris@apache.org");
+ T(s.info.authenticated == "default");
+ T(s.info.authentication_db == "test_suite_users");
+ TEquals(["oauth", "cookie", "default"], s.info.authentication_handlers);
var s = CouchDB.session({
headers : {
- "Authorization" : "Basic Xzpf" // username and pass of _:_
+ "Authorization" : "Basic Xzpf" // name and pass of _:_
}
});
T(s.name == null);
- T(s.info.authenticated == "{couch_httpd_auth, default_authentication_handler}");
+ T(s.info.authenticated == "default");
+
+
+ // ok, now create a conflicting edit on the jchris doc, and make sure there's no login.
+ var jchrisUser2 = JSON.parse(JSON.stringify(jchrisUserDoc));
+ jchrisUser2.foo = "bar";
+ T(usersDb.save(jchrisUser2).ok);
+ try {
+ usersDb.save(jchrisUserDoc);
+ T(false && "should be an update conflict")
+ } catch(e) {
+ T(true);
+ }
+ // save as bulk with new_edits=false to force conflict save
+ var resp = usersDb.bulkSave([jchrisUserDoc],{all_or_nothing : true});
+
+ var jchrisWithConflict = usersDb.open(jchrisUserDoc._id, {conflicts : true});
+ T(jchrisWithConflict._conflicts.length == 1)
+
+ // no login with conflicted user doc
+ try {
+ var s = CouchDB.session({
+ headers : {
+ "Authorization" : "Basic amNocmlzQGFwYWNoZS5vcmc6ZnVubnlib25l"
+ }
+ });
+ T(false && "this will throw")
+ } catch(e) {
+ T(e.error == "unauthorized")
+ T(/conflict/.test(e.reason))
+ }
+
+ // you can delete a user doc
+ s = CouchDB.session().userCtx;
+ T(s.name == null);
+ T(s.roles.indexOf("_admin") !== -1);
+ T(usersDb.deleteDoc(jchrisWithConflict).ok);
+
+ // you can't change doc from type "user"
+ jchrisUserDoc = usersDb.open(jchrisUserDoc._id);
+ jchrisUserDoc.type = "not user";
+ try {
+ usersDb.save(jchrisUserDoc);
+ T(false && "should only allow us to save doc when type == 'user'");
+ } catch(e) {
+ T(e.reason == "doc.type must be user");
+ }
+ jchrisUserDoc.type = "user";
+
+ // "roles" must be an array
+ jchrisUserDoc.roles = "not an array";
+ try {
+ usersDb.save(jchrisUserDoc);
+ T(false && "should only allow us to save doc when roles is an array");
+ } catch(e) {
+ T(e.reason == "doc.roles must be an array");
+ }
+ jchrisUserDoc.roles = [];
};
-
+
+ usersDb.deleteDb();
run_on_modified_server(
[{section: "couch_httpd_auth",
- key: "authentication_db", value: "test_suite_users"}],
+ key: "authentication_db", value: usersDb.name}],
testFun
);
+ usersDb.deleteDb(); // cleanup
-} \ No newline at end of file
+}
diff --git a/share/www/script/test/uuids.js b/share/www/script/test/uuids.js
index 4de7ce90..fc33a105 100644
--- a/share/www/script/test/uuids.js
+++ b/share/www/script/test/uuids.js
@@ -93,12 +93,20 @@ couchTests.uuids = function(debug) {
xhr = CouchDB.request("GET", "/_uuids?count=1000");
T(xhr.status == 200);
result = JSON.parse(xhr.responseText);
- for(var i = 1; i < result.uuids.length; i++) {
- T(result.uuids[i].length == 32);
- var u1 = result.uuids[i-1].substr(0, 13);
- var u2 = result.uuids[i].substr(0, 13);
- T(u1 < u2, "UTC uuids are roughly ordered.");
+ T(result.uuids[1].length == 32);
+
+ // no collisions
+ var seen = {};
+ for(var i in result.uuids) {
+ var id = result.uuids[i];
+ T(seen[id] === undefined);
+ seen[id] = 1;
}
+
+ // roughly ordered
+ var u1 = result.uuids[1].substr(0, 13);
+ var u2 = result.uuids[result.uuids.length-1].substr(0, 13);
+ T(u1 < u2, "UTC uuids are only roughly ordered, so this assertion may fail occasionally. Don't sweat it.");
};
run_on_modified_server([{
diff --git a/share/www/script/test/view_collation.js b/share/www/script/test/view_collation.js
index f4ae4a15..b01a5c50 100644
--- a/share/www/script/test/view_collation.js
+++ b/share/www/script/test/view_collation.js
@@ -90,27 +90,27 @@ couchTests.view_collation = function(debug) {
// the inclusive_end=true functionality is limited to endkey currently
// if you need inclusive_start=false for startkey, please do implement. ;)
var rows = db.query(queryFun, null, {endkey : "b", inclusive_end:true}).rows;
- T(rows[rows.length-1].key == "b")
+ T(rows[rows.length-1].key == "b");
// descending=true
var rows = db.query(queryFun, null, {endkey : "b",
descending:true, inclusive_end:true}).rows;
- T(rows[rows.length-1].key == "b")
+ T(rows[rows.length-1].key == "b");
// test inclusive_end=false
var rows = db.query(queryFun, null, {endkey : "b", inclusive_end:false}).rows;
- T(rows[rows.length-1].key == "aa")
+ T(rows[rows.length-1].key == "aa");
// descending=true
var rows = db.query(queryFun, null, {endkey : "b",
descending:true, inclusive_end:false}).rows;
- T(rows[rows.length-1].key == "B")
+ T(rows[rows.length-1].key == "B");
var rows = db.query(queryFun, null, {
endkey : "b", endkey_docid: "10",
inclusive_end:false}).rows;
- T(rows[rows.length-1].key == "aa")
+ T(rows[rows.length-1].key == "aa");
var rows = db.query(queryFun, null, {
endkey : "b", endkey_docid: "11",
inclusive_end:false}).rows;
- T(rows[rows.length-1].key == "b")
+ T(rows[rows.length-1].key == "b");
};
diff --git a/share/www/script/test/view_collation_raw.js b/share/www/script/test/view_collation_raw.js
index 08f37fae..31624cdb 100644
--- a/share/www/script/test/view_collation_raw.js
+++ b/share/www/script/test/view_collation_raw.js
@@ -97,27 +97,27 @@ couchTests.view_collation_raw = function(debug) {
// the inclusive_end=true functionality is limited to endkey currently
// if you need inclusive_start=false for startkey, please do implement. ;)
var rows = db.view("test/test", {endkey : "b", inclusive_end:true}).rows;
- T(rows[rows.length-1].key == "b")
+ T(rows[rows.length-1].key == "b");
// descending=true
var rows = db.view("test/test", {endkey : "b",
descending:true, inclusive_end:true}).rows;
- T(rows[rows.length-1].key == "b")
+ T(rows[rows.length-1].key == "b");
// test inclusive_end=false
var rows = db.view("test/test", {endkey : "b", inclusive_end:false}).rows;
- T(rows[rows.length-1].key == "aa")
+ T(rows[rows.length-1].key == "aa");
// descending=true
var rows = db.view("test/test", {endkey : "b",
descending:true, inclusive_end:false}).rows;
- T(rows[rows.length-1].key == "ba")
+ T(rows[rows.length-1].key == "ba");
var rows = db.view("test/test", {
endkey : "b", endkey_docid: "10",
inclusive_end:false}).rows;
- T(rows[rows.length-1].key == "aa")
+ T(rows[rows.length-1].key == "aa");
var rows = db.view("test/test", {
endkey : "b", endkey_docid: "11",
inclusive_end:false}).rows;
- T(rows[rows.length-1].key == "aa")
+ T(rows[rows.length-1].key == "aa");
};
diff --git a/share/www/script/test/view_compaction.js b/share/www/script/test/view_compaction.js
new file mode 100644
index 00000000..a11fb7bd
--- /dev/null
+++ b/share/www/script/test/view_compaction.js
@@ -0,0 +1,104 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+couchTests.view_compaction = function(debug) {
+
+ if (debug) debugger;
+
+ var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit": "true"});
+
+ db.deleteDb();
+ db.createDb();
+
+ var ddoc = {
+ _id: "_design/foo",
+ language: "javascript",
+ views: {
+ view1: {
+ map: "function(doc) { emit(doc._id, doc.value) }"
+ },
+ view2: {
+ map: "function(doc) { emit(doc._id, doc.value); }",
+ reduce: "function(keys, values, rereduce) { return sum(values); }"
+ }
+ }
+ };
+ T(db.save(ddoc).ok);
+
+ var docs = makeDocs(0, 1000);
+ db.bulkSave(docs);
+
+ var resp = db.view('foo/view1', {});
+ T(resp.rows.length === 1000);
+
+ resp = db.view('foo/view2', {});
+ T(resp.rows.length === 1);
+
+ resp = db.designInfo("_design/foo");
+ T(resp.view_index.update_seq === 1001);
+
+
+ // update docs
+ for (var i = 0; i < docs.length; i++) {
+ docs[i].integer = docs[i].integer + 1;
+ }
+ db.bulkSave(docs);
+
+
+ resp = db.view('foo/view1', {});
+ T(resp.rows.length === 1000);
+
+ resp = db.view('foo/view2', {});
+ T(resp.rows.length === 1);
+
+ resp = db.designInfo("_design/foo");
+ T(resp.view_index.update_seq === 2001);
+
+
+ // update docs again...
+ for (var i = 0; i < docs.length; i++) {
+ docs[i].integer = docs[i].integer + 2;
+ }
+ db.bulkSave(docs);
+
+
+ resp = db.view('foo/view1', {});
+ T(resp.rows.length === 1000);
+
+ resp = db.view('foo/view2', {});
+ T(resp.rows.length === 1);
+
+ resp = db.designInfo("_design/foo");
+ T(resp.view_index.update_seq === 3001);
+
+ var disk_size_before_compact = resp.view_index.disk_size;
+
+ // compact view group
+ var xhr = CouchDB.request("POST", "/" + db.name + "/_compact" + "/foo");
+ T(JSON.parse(xhr.responseText).ok === true);
+
+ resp = db.designInfo("_design/foo");
+ while (resp.view_index.compact_running === true) {
+ resp = db.designInfo("_design/foo");
+ }
+
+
+ resp = db.view('foo/view1', {});
+ T(resp.rows.length === 1000);
+
+ resp = db.view('foo/view2', {});
+ T(resp.rows.length === 1);
+
+ resp = db.designInfo("_design/foo");
+ T(resp.view_index.update_seq === 3001);
+ T(resp.view_index.disk_size < disk_size_before_compact);
+}; \ No newline at end of file
diff --git a/share/www/script/test/view_errors.js b/share/www/script/test/view_errors.js
index 0f90c46f..e8bd08e4 100644
--- a/share/www/script/test/view_errors.js
+++ b/share/www/script/test/view_errors.js
@@ -16,8 +16,6 @@ couchTests.view_errors = function(debug) {
db.createDb();
if (debug) debugger;
-
-
run_on_modified_server(
[{section: "couchdb",
key: "os_process_timeout",
@@ -26,12 +24,13 @@ couchTests.view_errors = function(debug) {
var doc = {integer: 1, string: "1", array: [1, 2, 3]};
T(db.save(doc).ok);
- // emitting a key value that is undefined should result in that row not
- // being included in the view results
+ // emitting a key value that is undefined should result in that row
+ // being included in the view results as null
var results = db.query(function(doc) {
emit(doc.undef, null);
});
- T(results.total_rows == 0);
+ T(results.total_rows == 1);
+ T(results.rows[0].key == null);
// if a view function throws an exception, its results are not included in
// the view index, but the view does not itself raise an error
@@ -41,13 +40,13 @@ couchTests.view_errors = function(debug) {
T(results.total_rows == 0);
// if a view function includes an undefined value in the emitted key or
- // value, an error is logged and the result is not included in the view
- // index, and the view itself does not raise an error
+ // value, it is treated as null
var results = db.query(function(doc) {
emit([doc._id, doc.undef], null);
});
- T(results.total_rows == 0);
-
+ T(results.total_rows == 1);
+ T(results.rows[0].key[1] == null);
+
// querying a view with invalid params should give a resonable error message
var xhr = CouchDB.request("POST", "/test_suite_db/_temp_view?startkey=foo", {
headers: {"Content-Type": "application/json"},
@@ -57,14 +56,14 @@ couchTests.view_errors = function(debug) {
});
T(JSON.parse(xhr.responseText).error == "bad_request");
- // views should ignore Content-Type, like the rest of CouchDB
+ // content type must be json
var xhr = CouchDB.request("POST", "/test_suite_db/_temp_view", {
headers: {"Content-Type": "application/x-www-form-urlencoded"},
body: JSON.stringify({language: "javascript",
map : "function(doc){}"
})
});
- T(xhr.status == 200);
+ T(xhr.status == 415);
var map = function (doc) {emit(doc.integer, doc.integer);};
@@ -75,9 +74,6 @@ couchTests.view_errors = function(debug) {
T(e.error == "query_parse_error");
}
- // reduce=false on map views doesn't work, so group=true will
- // never throw for temp reduce views.
-
var designDoc = {
_id:"_design/test",
language: "javascript",
@@ -105,6 +101,15 @@ couchTests.view_errors = function(debug) {
db.view("test/no_reduce", {group: true});
T(0 == 1);
} catch(e) {
+ T(db.last_req.status == 400);
+ T(e.error == "query_parse_error");
+ }
+
+ try {
+ db.view("test/no_reduce", {group_level: 1});
+ T(0 == 1);
+ } catch(e) {
+ T(db.last_req.status == 400);
T(e.error == "query_parse_error");
}
@@ -116,10 +121,23 @@ couchTests.view_errors = function(debug) {
T(e.error == "query_parse_error");
}
+ db.view("test/no_reduce", {reduce: false});
+ TEquals(200, db.last_req.status, "reduce=false for map views (without"
+ + " group or group_level) is allowed");
+
try {
db.view("test/with_reduce", {group: true, reduce: false});
T(0 == 1);
} catch(e) {
+ T(db.last_req.status == 400);
+ T(e.error == "query_parse_error");
+ }
+
+ try {
+ db.view("test/with_reduce", {group_level: 1, reduce: false});
+ T(0 == 1);
+ } catch(e) {
+ T(db.last_req.status == 400);
T(e.error == "query_parse_error");
}
@@ -159,5 +177,13 @@ couchTests.view_errors = function(debug) {
T(xhr.status == 500);
result = JSON.parse(xhr.responseText);
T(result.error == "reduce_overflow_error");
+
+ try {
+ db.query(function() {emit(null, null)}, null, {startkey: 2, endkey:1});
+ T(0 == 1);
+ } catch(e) {
+ T(e.error == "query_parse_error");
+ T(e.reason.match(/no rows can match/i));
+ }
});
};
diff --git a/share/www/script/test/view_multi_key_all_docs.js b/share/www/script/test/view_multi_key_all_docs.js
index 62e49665..1113be4d 100644
--- a/share/www/script/test/view_multi_key_all_docs.js
+++ b/share/www/script/test/view_multi_key_all_docs.js
@@ -25,24 +25,52 @@ couchTests.view_multi_key_all_docs = function(debug) {
for(var i=0; i<rows.length; i++)
T(rows[i].id == keys[i]);
+ // keys in GET parameters
+ rows = db.allDocs({keys:keys}, null).rows;
+ T(rows.length == keys.length);
+ for(var i=0; i<rows.length; i++)
+ T(rows[i].id == keys[i]);
+
rows = db.allDocs({limit: 1}, keys).rows;
T(rows.length == 1);
T(rows[0].id == keys[0]);
+ // keys in GET parameters
+ rows = db.allDocs({limit: 1, keys: keys}, null).rows;
+ T(rows.length == 1);
+ T(rows[0].id == keys[0]);
+
rows = db.allDocs({skip: 2}, keys).rows;
T(rows.length == 3);
for(var i=0; i<rows.length; i++)
T(rows[i].id == keys[i+2]);
+ // keys in GET parameters
+ rows = db.allDocs({skip: 2, keys: keys}, null).rows;
+ T(rows.length == 3);
+ for(var i=0; i<rows.length; i++)
+ T(rows[i].id == keys[i+2]);
+
rows = db.allDocs({descending: "true"}, keys).rows;
T(rows.length == keys.length);
for(var i=0; i<rows.length; i++)
T(rows[i].id == keys[keys.length-i-1]);
+ // keys in GET parameters
+ rows = db.allDocs({descending: "true", keys: keys}, null).rows;
+ T(rows.length == keys.length);
+ for(var i=0; i<rows.length; i++)
+ T(rows[i].id == keys[keys.length-i-1]);
+
rows = db.allDocs({descending: "true", skip: 3, limit:1}, keys).rows;
T(rows.length == 1);
T(rows[0].id == keys[1]);
+ // keys in GET parameters
+ rows = db.allDocs({descending: "true", skip: 3, limit:1, keys: keys}, null).rows;
+ T(rows.length == 1);
+ T(rows[0].id == keys[1]);
+
// Check we get invalid rows when the key doesn't exist
rows = db.allDocs({}, [1, "i_dont_exist", "0"]).rows;
T(rows.length == 3);
@@ -51,4 +79,13 @@ couchTests.view_multi_key_all_docs = function(debug) {
T(rows[1].error == "not_found");
T(!rows[1].id);
T(rows[2].id == rows[2].key && rows[2].key == "0");
+
+ // keys in GET parameters
+ rows = db.allDocs({keys: [1, "i_dont_exist", "0"]}, null).rows;
+ T(rows.length == 3);
+ T(rows[0].error == "not_found");
+ T(!rows[0].id);
+ T(rows[1].error == "not_found");
+ T(!rows[1].id);
+ T(rows[2].id == rows[2].key && rows[2].key == "0");
};
diff --git a/share/www/script/test/view_multi_key_design.js b/share/www/script/test/view_multi_key_design.js
index 5a2f645d..38396955 100644
--- a/share/www/script/test/view_multi_key_design.js
+++ b/share/www/script/test/view_multi_key_design.js
@@ -34,11 +34,11 @@ couchTests.view_multi_key_design = function(debug) {
reduce:"function (keys, values) { return sum(values); };"
}
}
- }
+ };
T(db.save(designDoc).ok);
// Test that missing keys work too
- var keys = [101,30,15,37,50]
+ var keys = [101,30,15,37,50];
var reduce = db.view("test/summate",{group:true},keys).rows;
T(reduce.length == keys.length-1); // 101 is missing
for(var i=0; i<reduce.length; i++) {
@@ -54,6 +54,13 @@ couchTests.view_multi_key_design = function(debug) {
T(rows[i].key == rows[i].value);
}
+ // with GET keys
+ rows = db.view("test/all_docs",{keys:keys},null).rows;
+ for(var i=0;i<rows.length; i++) {
+ T(keys.indexOf(rows[i].key) != -1);
+ T(rows[i].key == rows[i].value);
+ }
+
var reduce = db.view("test/summate",{group:true},keys).rows;
T(reduce.length == keys.length);
for(var i=0; i<reduce.length; i++) {
@@ -61,8 +68,18 @@ couchTests.view_multi_key_design = function(debug) {
T(reduce[i].key == reduce[i].value);
}
+ // with GET keys
+ reduce = db.view("test/summate",{group:true,keys:keys},null).rows;
+ T(reduce.length == keys.length);
+ for(var i=0; i<reduce.length; i++) {
+ T(keys.indexOf(reduce[i].key) != -1);
+ T(reduce[i].key == reduce[i].value);
+ }
+
// Test that invalid parameter combinations get rejected
var badargs = [{startkey:0}, {endkey:0}, {key: 0}, {group_level: 2}];
+ var getbadargs = [{startkey:0, keys:keys}, {endkey:0, keys:keys},
+ {key:0, keys:keys}, {group_level: 2, keys:keys}];
for(var i in badargs)
{
try {
@@ -71,6 +88,13 @@ couchTests.view_multi_key_design = function(debug) {
} catch (e) {
T(e.error == "query_parse_error");
}
+
+ try {
+ db.view("test/all_docs",getbadargs[i],null);
+ T(0==1);
+ } catch (e) {
+ T(e.error = "query_parse_error");
+ }
}
try {
@@ -80,8 +104,18 @@ couchTests.view_multi_key_design = function(debug) {
T(e.error == "query_parse_error");
}
+ try {
+ db.view("test/summate",{keys:keys},null);
+ T(0==1);
+ } catch (e) {
+ T(e.error == "query_parse_error");
+ }
+
// Test that a map & reduce containing func support keys when reduce=false
- resp = db.view("test/summate", {reduce: false}, keys);
+ var resp = db.view("test/summate", {reduce: false}, keys);
+ T(resp.rows.length == 5);
+
+ resp = db.view("test/summate", {reduce: false, keys: keys}, null);
T(resp.rows.length == 5);
// Check that limiting by startkey_docid and endkey_docid get applied
@@ -96,34 +130,66 @@ couchTests.view_multi_key_design = function(debug) {
T(curr[i].value == exp_val[i]);
}
+ curr = db.view("test/multi_emit", {startkey_docid: 21, endkey_docid: 23, keys: [0, 2]}, null).rows;
+ T(curr.length == 6);
+ for( var i = 0 ; i < 6 ; i++)
+ {
+ T(curr[i].key == exp_key[i]);
+ T(curr[i].value == exp_val[i]);
+ }
+
// Check limit works
curr = db.view("test/all_docs", {limit: 1}, keys).rows;
T(curr.length == 1);
T(curr[0].key == 10);
+ curr = db.view("test/all_docs", {limit: 1, keys: keys}, null).rows;
+ T(curr.length == 1);
+ T(curr[0].key == 10);
+
// Check offset works
curr = db.view("test/multi_emit", {skip: 1}, [0]).rows;
T(curr.length == 99);
T(curr[0].value == 1);
+ curr = db.view("test/multi_emit", {skip: 1, keys: [0]}, null).rows;
+ T(curr.length == 99);
+ T(curr[0].value == 1);
+
// Check that dir works
curr = db.view("test/multi_emit", {descending: "true"}, [1]).rows;
T(curr.length == 100);
T(curr[0].value == 99);
T(curr[99].value == 0);
+ curr = db.view("test/multi_emit", {descending: "true", keys: [1]}, null).rows;
+ T(curr.length == 100);
+ T(curr[0].value == 99);
+ T(curr[99].value == 0);
+
// Check a couple combinations
curr = db.view("test/multi_emit", {descending: "true", skip: 3, limit: 2}, [2]).rows;
T(curr.length, 2);
T(curr[0].value == 96);
T(curr[1].value == 95);
+ curr = db.view("test/multi_emit", {descending: "true", skip: 3, limit: 2, keys: [2]}, null).rows;
+ T(curr.length, 2);
+ T(curr[0].value == 96);
+ T(curr[1].value == 95);
+
curr = db.view("test/multi_emit", {skip: 2, limit: 3, startkey_docid: "13"}, [0]).rows;
T(curr.length == 3);
T(curr[0].value == 15);
T(curr[1].value == 16);
T(curr[2].value == 17);
+ curr = db.view("test/multi_emit", {skip: 2, limit: 3, startkey_docid: "13", keys: [0]}, null).rows;
+ T(curr.length == 3);
+ T(curr[0].value == 15);
+ T(curr[1].value == 16);
+ T(curr[2].value == 17);
+
curr = db.view("test/multi_emit",
{skip: 1, limit: 5, startkey_docid: "25", endkey_docid: "27"}, [1]).rows;
T(curr.length == 2);
@@ -131,8 +197,20 @@ couchTests.view_multi_key_design = function(debug) {
T(curr[1].value == 27);
curr = db.view("test/multi_emit",
+ {skip: 1, limit: 5, startkey_docid: "25", endkey_docid: "27", keys: [1]}, null).rows;
+ T(curr.length == 2);
+ T(curr[0].value == 26);
+ T(curr[1].value == 27);
+
+ curr = db.view("test/multi_emit",
{skip: 1, limit: 5, startkey_docid: "28", endkey_docid: "26", descending: "true"}, [1]).rows;
T(curr.length == 2);
T(curr[0].value == 27);
T(curr[1].value == 26);
+
+ curr = db.view("test/multi_emit",
+ {skip: 1, limit: 5, startkey_docid: "28", endkey_docid: "26", descending: "true", keys: [1]}, null).rows;
+ T(curr.length == 2);
+ T(curr[0].value == 27);
+ T(curr[1].value == 26);
};
diff --git a/share/www/script/test/view_pagination.js b/share/www/script/test/view_pagination.js
index 1af2df35..ed3a7ee1 100644
--- a/share/www/script/test/view_pagination.js
+++ b/share/www/script/test/view_pagination.js
@@ -19,7 +19,7 @@ couchTests.view_pagination = function(debug) {
var docs = makeDocs(0, 100);
db.bulkSave(docs);
- var queryFun = function(doc) { emit(doc.integer, null) };
+ var queryFun = function(doc) { emit(doc.integer, null); };
var i;
// page through the view ascending
@@ -29,13 +29,26 @@ couchTests.view_pagination = function(debug) {
startkey_docid: i,
limit: 10
});
- T(queryResults.rows.length == 10)
- T(queryResults.total_rows == docs.length)
- T(queryResults.offset == i)
+ T(queryResults.rows.length == 10);
+ T(queryResults.total_rows == docs.length);
+ T(queryResults.offset == i);
var j;
for (j = 0; j < 10;j++) {
T(queryResults.rows[j].key == i + j);
}
+
+ // test aliases start_key and start_key_doc_id
+ queryResults = db.query(queryFun, null, {
+ start_key: i,
+ start_key_doc_id: i,
+ limit: 10
+ });
+ T(queryResults.rows.length == 10);
+ T(queryResults.total_rows == docs.length);
+ T(queryResults.offset == i);
+ for (j = 0; j < 10;j++) {
+ T(queryResults.rows[j].key == i + j);
+ }
}
// page through the view descending
@@ -46,9 +59,9 @@ couchTests.view_pagination = function(debug) {
descending: true,
limit: 10
});
- T(queryResults.rows.length == 10)
- T(queryResults.total_rows == docs.length)
- T(queryResults.offset == docs.length - i - 1)
+ T(queryResults.rows.length == 10);
+ T(queryResults.total_rows == docs.length);
+ T(queryResults.offset == docs.length - i - 1);
var j;
for (j = 0; j < 10; j++) {
T(queryResults.rows[j].key == i - j);
@@ -63,60 +76,72 @@ couchTests.view_pagination = function(debug) {
descending: false,
limit: 10
});
- T(queryResults.rows.length == 10)
- T(queryResults.total_rows == docs.length)
- T(queryResults.offset == i)
+ T(queryResults.rows.length == 10);
+ T(queryResults.total_rows == docs.length);
+ T(queryResults.offset == i);
var j;
for (j = 0; j < 10;j++) {
T(queryResults.rows[j].key == i + j);
}
}
+ function testEndkeyDocId(queryResults) {
+ T(queryResults.rows.length == 35);
+ T(queryResults.total_rows == docs.length);
+ T(queryResults.offset == 1);
+ T(queryResults.rows[0].id == "1");
+ T(queryResults.rows[1].id == "10");
+ T(queryResults.rows[2].id == "11");
+ T(queryResults.rows[3].id == "12");
+ T(queryResults.rows[4].id == "13");
+ T(queryResults.rows[5].id == "14");
+ T(queryResults.rows[6].id == "15");
+ T(queryResults.rows[7].id == "16");
+ T(queryResults.rows[8].id == "17");
+ T(queryResults.rows[9].id == "18");
+ T(queryResults.rows[10].id == "19");
+ T(queryResults.rows[11].id == "2");
+ T(queryResults.rows[12].id == "20");
+ T(queryResults.rows[13].id == "21");
+ T(queryResults.rows[14].id == "22");
+ T(queryResults.rows[15].id == "23");
+ T(queryResults.rows[16].id == "24");
+ T(queryResults.rows[17].id == "25");
+ T(queryResults.rows[18].id == "26");
+ T(queryResults.rows[19].id == "27");
+ T(queryResults.rows[20].id == "28");
+ T(queryResults.rows[21].id == "29");
+ T(queryResults.rows[22].id == "3");
+ T(queryResults.rows[23].id == "30");
+ T(queryResults.rows[24].id == "31");
+ T(queryResults.rows[25].id == "32");
+ T(queryResults.rows[26].id == "33");
+ T(queryResults.rows[27].id == "34");
+ T(queryResults.rows[28].id == "35");
+ T(queryResults.rows[29].id == "36");
+ T(queryResults.rows[30].id == "37");
+ T(queryResults.rows[31].id == "38");
+ T(queryResults.rows[32].id == "39");
+ T(queryResults.rows[33].id == "4");
+ T(queryResults.rows[34].id == "40");
+ }
+
// test endkey_docid
- var queryResults = db.query(function(doc) { emit(null, null);}, null, {
+ var queryResults = db.query(function(doc) { emit(null, null); }, null, {
startkey: null,
startkey_docid: 1,
endkey: null,
endkey_docid: 40
});
+ testEndkeyDocId(queryResults);
- T(queryResults.rows.length == 35)
- T(queryResults.total_rows == docs.length)
- T(queryResults.offset == 1)
- T(queryResults.rows[0].id == "1");
- T(queryResults.rows[1].id == "10");
- T(queryResults.rows[2].id == "11");
- T(queryResults.rows[3].id == "12");
- T(queryResults.rows[4].id == "13");
- T(queryResults.rows[5].id == "14");
- T(queryResults.rows[6].id == "15");
- T(queryResults.rows[7].id == "16");
- T(queryResults.rows[8].id == "17");
- T(queryResults.rows[9].id == "18");
- T(queryResults.rows[10].id == "19");
- T(queryResults.rows[11].id == "2");
- T(queryResults.rows[12].id == "20");
- T(queryResults.rows[13].id == "21");
- T(queryResults.rows[14].id == "22");
- T(queryResults.rows[15].id == "23");
- T(queryResults.rows[16].id == "24");
- T(queryResults.rows[17].id == "25");
- T(queryResults.rows[18].id == "26");
- T(queryResults.rows[19].id == "27");
- T(queryResults.rows[20].id == "28");
- T(queryResults.rows[21].id == "29");
- T(queryResults.rows[22].id == "3");
- T(queryResults.rows[23].id == "30");
- T(queryResults.rows[24].id == "31");
- T(queryResults.rows[25].id == "32");
- T(queryResults.rows[26].id == "33");
- T(queryResults.rows[27].id == "34");
- T(queryResults.rows[28].id == "35");
- T(queryResults.rows[29].id == "36");
- T(queryResults.rows[30].id == "37");
- T(queryResults.rows[31].id == "38");
- T(queryResults.rows[32].id == "39");
- T(queryResults.rows[33].id == "4");
- T(queryResults.rows[34].id == "40");
+ // test aliases end_key_doc_id and end_key
+ queryResults = db.query(function(doc) { emit(null, null); }, null, {
+ start_key: null,
+ start_key_doc_id: 1,
+ end_key: null,
+ end_key_doc_id: 40
+ });
+ testEndkeyDocId(queryResults);
};
diff --git a/share/www/script/test/view_sandboxing.js b/share/www/script/test/view_sandboxing.js
index 9f893b28..02951d9f 100644
--- a/share/www/script/test/view_sandboxing.js
+++ b/share/www/script/test/view_sandboxing.js
@@ -42,11 +42,99 @@ couchTests.view_sandboxing = function(debug) {
// make sure that a view cannot access the map_funs array defined used by
// the view server
- var results = db.query(function(doc) { map_funs.push(1); emit(null, doc) });
+ var results = db.query(function(doc) { map_funs.push(1); emit(null, doc); });
T(results.total_rows == 0);
// make sure that a view cannot access the map_results array defined used by
// the view server
- var results = db.query(function(doc) { map_results.push(1); emit(null, doc) });
+ var results = db.query(function(doc) { map_results.push(1); emit(null, doc); });
T(results.total_rows == 0);
+
+ // test for COUCHDB-925
+ // altering 'doc' variable in map function affects other map functions
+ var ddoc = {
+ _id: "_design/foobar",
+ language: "javascript",
+ views: {
+ view1: {
+ map:
+ (function(doc) {
+ if (doc.values) {
+ doc.values = [666];
+ }
+ if (doc.tags) {
+ doc.tags.push("qwerty");
+ }
+ if (doc.tokens) {
+ doc.tokens["c"] = 3;
+ }
+ }).toString()
+ },
+ view2: {
+ map:
+ (function(doc) {
+ if (doc.values) {
+ emit(doc._id, doc.values);
+ }
+ if (doc.tags) {
+ emit(doc._id, doc.tags);
+ }
+ if (doc.tokens) {
+ emit(doc._id, doc.tokens);
+ }
+ }).toString()
+ }
+ }
+ };
+ var doc1 = {
+ _id: "doc1",
+ values: [1, 2, 3]
+ };
+ var doc2 = {
+ _id: "doc2",
+ tags: ["foo", "bar"],
+ tokens: {a: 1, b: 2}
+ };
+
+ db.deleteDb();
+ db.createDb();
+ T(db.save(ddoc).ok);
+ T(db.save(doc1).ok);
+ T(db.save(doc2).ok);
+
+ var view1Results = db.view(
+ "foobar/view1", {bypass_cache: Math.round(Math.random() * 1000)});
+ var view2Results = db.view(
+ "foobar/view2", {bypass_cache: Math.round(Math.random() * 1000)});
+
+ TEquals(0, view1Results.rows.length, "view1 has 0 rows");
+ TEquals(3, view2Results.rows.length, "view2 has 3 rows");
+
+ TEquals(doc1._id, view2Results.rows[0].key);
+ TEquals(doc2._id, view2Results.rows[1].key);
+ TEquals(doc2._id, view2Results.rows[2].key);
+
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=449657
+ TEquals(3, view2Results.rows[0].value.length,
+ "Warning: installed SpiderMonkey version doesn't allow sealing of arrays");
+ if (view2Results.rows[0].value.length === 3) {
+ TEquals(1, view2Results.rows[0].value[0]);
+ TEquals(2, view2Results.rows[0].value[1]);
+ TEquals(3, view2Results.rows[0].value[2]);
+ }
+
+ TEquals(1, view2Results.rows[1].value["a"]);
+ TEquals(2, view2Results.rows[1].value["b"]);
+ TEquals('undefined', typeof view2Results.rows[1].value["c"],
+ "doc2.tokens object was not sealed");
+
+ TEquals(2, view2Results.rows[2].value.length,
+ "Warning: installed SpiderMonkey version doesn't allow sealing of arrays");
+ if (view2Results.rows[2].value.length === 2) {
+ TEquals("foo", view2Results.rows[2].value[0]);
+ TEquals("bar", view2Results.rows[2].value[1]);
+ }
+
+ // cleanup
+ db.deleteDb();
};
diff --git a/share/www/script/test/view_update_seq.js b/share/www/script/test/view_update_seq.js
new file mode 100644
index 00000000..69b8c42d
--- /dev/null
+++ b/share/www/script/test/view_update_seq.js
@@ -0,0 +1,106 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+couchTests.view_update_seq = function(debug) {
+ var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"true"});
+ db.deleteDb();
+ db.createDb();
+ if (debug) debugger;
+
+ T(db.info().update_seq == 0);
+
+ var resp = db.allDocs({update_seq:true});
+
+ T(resp.rows.length == 0);
+ T(resp.update_seq == 0);
+
+ var designDoc = {
+ _id:"_design/test",
+ language: "javascript",
+ views: {
+ all_docs: {
+ map: "function(doc) { emit(doc.integer, doc.string) }"
+ },
+ summate: {
+ map:"function (doc) {emit(doc.integer, doc.integer)};",
+ reduce:"function (keys, values) { return sum(values); };"
+ }
+ }
+ };
+ T(db.save(designDoc).ok);
+
+ T(db.info().update_seq == 1);
+
+ resp = db.allDocs({update_seq:true});
+
+ T(resp.rows.length == 1);
+ T(resp.update_seq == 1);
+
+ var docs = makeDocs(0, 100);
+ db.bulkSave(docs);
+
+ resp = db.allDocs({limit: 1});
+ T(resp.rows.length == 1);
+ T(!resp.update_seq, "all docs");
+
+ resp = db.allDocs({limit: 1, update_seq:true});
+ T(resp.rows.length == 1);
+ T(resp.update_seq == 101);
+
+ resp = db.view('test/all_docs', {limit: 1, update_seq:true});
+ T(resp.rows.length == 1);
+ T(resp.update_seq == 101);
+
+ resp = db.view('test/all_docs', {limit: 1, update_seq:false});
+ T(resp.rows.length == 1);
+ T(!resp.update_seq, "view");
+
+ resp = db.view('test/summate', {update_seq:true});
+ T(resp.rows.length == 1);
+ T(resp.update_seq == 101);
+
+ db.save({"id":"0"});
+ resp = db.view('test/all_docs', {limit: 1,stale: "ok", update_seq:true});
+ T(resp.rows.length == 1);
+ T(resp.update_seq == 101);
+
+ db.save({"id":"00"});
+ resp = db.view('test/all_docs',
+ {limit: 1, stale: "update_after", update_seq: true});
+ T(resp.rows.length == 1);
+ T(resp.update_seq == 101);
+
+ // wait 5 seconds for the next assertions to pass in very slow machines
+ var t0 = new Date(), t1;
+ do {
+ CouchDB.request("GET", "/");
+ t1 = new Date();
+ } while ((t1 - t0) < 5000);
+
+ resp = db.view('test/all_docs', {limit: 1, stale: "ok", update_seq: true});
+ T(resp.rows.length == 1);
+ T(resp.update_seq == 103);
+
+ resp = db.view('test/all_docs', {limit: 1, update_seq:true});
+ T(resp.rows.length == 1);
+ T(resp.update_seq == 103);
+
+ resp = db.view('test/all_docs',{update_seq:true},["0","1"]);
+ T(resp.update_seq == 103);
+
+ resp = db.view('test/all_docs',{update_seq:true},["0","1"]);
+ T(resp.update_seq == 103);
+
+ resp = db.view('test/summate',{group:true, update_seq:true},["0","1"]);
+ T(resp.update_seq == 103);
+
+};
diff --git a/share/www/script/test/view_xml.js b/share/www/script/test/view_xml.js
index 451fb6a8..3403b47c 100644
--- a/share/www/script/test/view_xml.js
+++ b/share/www/script/test/view_xml.js
@@ -22,7 +22,7 @@ couchTests.view_xml = function(debug) {
var results = db.query(
"function(doc) {\n" +
" var xml = new XML(doc.content);\n" +
- " emit(xml.title.text(), null);\n" +
+ " emit(xml.title.text().toXMLString(), null);\n" +
"}");
T(results.total_rows == 2);
T(results.rows[0].key == "Testing E4X");
@@ -31,7 +31,7 @@ couchTests.view_xml = function(debug) {
var results = db.query(
"function(doc) {\n" +
" var xml = new XML(doc.content);\n" +
- " emit(xml.title.@id, null);\n" +
+ " emit(xml.title.@id.toXMLString(), null);\n" +
"}");
T(results.total_rows == 2);
T(results.rows[0].key == "e4x");