summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJan Lehnardt <jan@apache.org>2014-10-10 20:46:08 +0200
committerJan Lehnardt <jan@apache.org>2014-11-14 18:22:30 +0100
commite2d9c9b1987831089c50a657963c483adb6ee900 (patch)
treef116bf71cddb2d96c14de72bd5a380a72b9f6b7c
parent6de6ca673c082f8c2c093e76f2834407b1ab0bed (diff)
downloadcouchdb-e2d9c9b1987831089c50a657963c483adb6ee900.tar.gz
Move JS tests to test/javascript/tests
-rw-r--r--share/www/script/test/oauth.js294
-rw-r--r--test/javascript/couch.js520
-rw-r--r--test/javascript/couch_test_runner.js465
-rw-r--r--test/javascript/json2.js482
-rw-r--r--test/javascript/oauth.js511
-rw-r--r--test/javascript/replicator_db_inc.js96
-rwxr-xr-xtest/javascript/run16
-rw-r--r--test/javascript/sha1.js202
-rw-r--r--test/javascript/tests/all_docs.js (renamed from share/www/script/test/all_docs.js)0
-rw-r--r--test/javascript/tests/attachment_names.js (renamed from share/www/script/test/attachment_names.js)0
-rw-r--r--test/javascript/tests/attachment_paths.js (renamed from share/www/script/test/attachment_paths.js)0
-rw-r--r--test/javascript/tests/attachment_ranges.js (renamed from share/www/script/test/attachment_ranges.js)0
-rw-r--r--test/javascript/tests/attachment_views.js (renamed from share/www/script/test/attachment_views.js)0
-rw-r--r--test/javascript/tests/attachments.js (renamed from share/www/script/test/attachments.js)0
-rw-r--r--test/javascript/tests/attachments_multipart.js (renamed from share/www/script/test/attachments_multipart.js)0
-rw-r--r--test/javascript/tests/auth_cache.js (renamed from share/www/script/test/auth_cache.js)0
-rw-r--r--test/javascript/tests/basics.js (renamed from share/www/script/test/basics.js)0
-rw-r--r--test/javascript/tests/batch_save.js (renamed from share/www/script/test/batch_save.js)0
-rw-r--r--test/javascript/tests/bulk_docs.js (renamed from share/www/script/test/bulk_docs.js)0
-rw-r--r--test/javascript/tests/changes.js (renamed from share/www/script/test/changes.js)0
-rw-r--r--test/javascript/tests/coffee.js (renamed from share/www/script/test/coffee.js)0
-rw-r--r--test/javascript/tests/compact.js (renamed from share/www/script/test/compact.js)0
-rw-r--r--test/javascript/tests/config.js (renamed from share/www/script/test/config.js)0
-rw-r--r--test/javascript/tests/conflicts.js (renamed from share/www/script/test/conflicts.js)0
-rw-r--r--test/javascript/tests/content_negotiation.js (renamed from share/www/script/test/content_negotiation.js)0
-rw-r--r--test/javascript/tests/cookie_auth.js (renamed from share/www/script/test/cookie_auth.js)0
-rw-r--r--test/javascript/tests/copy_doc.js (renamed from share/www/script/test/copy_doc.js)0
-rw-r--r--test/javascript/tests/delayed_commits.js (renamed from share/www/script/test/delayed_commits.js)0
-rw-r--r--test/javascript/tests/design_docs.js (renamed from share/www/script/test/design_docs.js)0
-rw-r--r--test/javascript/tests/design_options.js (renamed from share/www/script/test/design_options.js)0
-rw-r--r--test/javascript/tests/design_paths.js (renamed from share/www/script/test/design_paths.js)0
-rw-r--r--test/javascript/tests/erlang_views.js (renamed from share/www/script/test/erlang_views.js)0
-rw-r--r--test/javascript/tests/etags_head.js (renamed from share/www/script/test/etags_head.js)0
-rw-r--r--test/javascript/tests/etags_views.js (renamed from share/www/script/test/etags_views.js)0
-rw-r--r--test/javascript/tests/form_submit.js (renamed from share/www/script/test/form_submit.js)0
-rw-r--r--test/javascript/tests/http.js (renamed from share/www/script/test/http.js)0
-rw-r--r--test/javascript/tests/invalid_docids.js (renamed from share/www/script/test/invalid_docids.js)0
-rw-r--r--test/javascript/tests/jsonp.js (renamed from share/www/script/test/jsonp.js)0
-rw-r--r--test/javascript/tests/large_docs.js (renamed from share/www/script/test/large_docs.js)0
-rw-r--r--test/javascript/tests/list_views.js (renamed from share/www/script/test/list_views.js)0
-rw-r--r--test/javascript/tests/lorem.txt (renamed from share/www/script/test/lorem.txt)0
-rw-r--r--test/javascript/tests/lorem_b64.txt (renamed from share/www/script/test/lorem_b64.txt)0
-rw-r--r--test/javascript/tests/lots_of_docs.js (renamed from share/www/script/test/lots_of_docs.js)0
-rw-r--r--test/javascript/tests/method_override.js (renamed from share/www/script/test/method_override.js)0
-rw-r--r--test/javascript/tests/multiple_rows.js (renamed from share/www/script/test/multiple_rows.js)0
-rw-r--r--test/javascript/tests/oauth_users_db.js (renamed from share/www/script/test/oauth_users_db.js)0
-rw-r--r--test/javascript/tests/proxyauth.js (renamed from share/www/script/test/proxyauth.js)0
-rw-r--r--test/javascript/tests/purge.js (renamed from share/www/script/test/purge.js)0
-rw-r--r--test/javascript/tests/reader_acl.js (renamed from share/www/script/test/reader_acl.js)0
-rw-r--r--test/javascript/tests/recreate_doc.js (renamed from share/www/script/test/recreate_doc.js)0
-rw-r--r--test/javascript/tests/reduce.js (renamed from share/www/script/test/reduce.js)0
-rw-r--r--test/javascript/tests/reduce_builtin.js (renamed from share/www/script/test/reduce_builtin.js)0
-rw-r--r--test/javascript/tests/reduce_false.js (renamed from share/www/script/test/reduce_false.js)0
-rw-r--r--test/javascript/tests/reduce_false_temp.js (renamed from share/www/script/test/reduce_false_temp.js)0
-rw-r--r--test/javascript/tests/replication.js (renamed from share/www/script/test/replication.js)0
-rw-r--r--test/javascript/tests/replicator_db_bad_rep_id.js (renamed from share/www/script/test/replicator_db_bad_rep_id.js)0
-rw-r--r--test/javascript/tests/replicator_db_by_doc_id.js (renamed from share/www/script/test/replicator_db_by_doc_id.js)0
-rw-r--r--test/javascript/tests/replicator_db_compact_rep_db.js (renamed from share/www/script/test/replicator_db_compact_rep_db.js)0
-rw-r--r--test/javascript/tests/replicator_db_continuous.js (renamed from share/www/script/test/replicator_db_continuous.js)0
-rw-r--r--test/javascript/tests/replicator_db_credential_delegation.js (renamed from share/www/script/test/replicator_db_credential_delegation.js)0
-rw-r--r--test/javascript/tests/replicator_db_field_validation.js (renamed from share/www/script/test/replicator_db_field_validation.js)0
-rw-r--r--test/javascript/tests/replicator_db_filtered.js (renamed from share/www/script/test/replicator_db_filtered.js)0
-rw-r--r--test/javascript/tests/replicator_db_identical.js (renamed from share/www/script/test/replicator_db_identical.js)0
-rw-r--r--test/javascript/tests/replicator_db_identical_continuous.js (renamed from share/www/script/test/replicator_db_identical_continuous.js)0
-rw-r--r--test/javascript/tests/replicator_db_invalid_filter.js (renamed from share/www/script/test/replicator_db_invalid_filter.js)0
-rw-r--r--test/javascript/tests/replicator_db_security.js (renamed from share/www/script/test/replicator_db_security.js)0
-rw-r--r--test/javascript/tests/replicator_db_simple.js (renamed from share/www/script/test/replicator_db_simple.js)0
-rw-r--r--test/javascript/tests/replicator_db_successive.js (renamed from share/www/script/test/replicator_db_successive.js)0
-rw-r--r--test/javascript/tests/replicator_db_survives.js (renamed from share/www/script/test/replicator_db_survives.js)0
-rw-r--r--test/javascript/tests/replicator_db_swap_rep_db.js (renamed from share/www/script/test/replicator_db_swap_rep_db.js)0
-rw-r--r--test/javascript/tests/replicator_db_update_security.js (renamed from share/www/script/test/replicator_db_update_security.js)0
-rw-r--r--test/javascript/tests/replicator_db_user_ctx.js (renamed from share/www/script/test/replicator_db_user_ctx.js)0
-rw-r--r--test/javascript/tests/replicator_db_write_auth.js (renamed from share/www/script/test/replicator_db_write_auth.js)0
-rw-r--r--test/javascript/tests/rev_stemming.js (renamed from share/www/script/test/rev_stemming.js)0
-rw-r--r--test/javascript/tests/rewrite.js (renamed from share/www/script/test/rewrite.js)0
-rw-r--r--test/javascript/tests/security_validation.js (renamed from share/www/script/test/security_validation.js)0
-rw-r--r--test/javascript/tests/show_documents.js (renamed from share/www/script/test/show_documents.js)0
-rw-r--r--test/javascript/tests/stats.js (renamed from share/www/script/test/stats.js)0
-rw-r--r--test/javascript/tests/update_documents.js (renamed from share/www/script/test/update_documents.js)0
-rw-r--r--test/javascript/tests/users_db.js (renamed from share/www/script/test/users_db.js)0
-rw-r--r--test/javascript/tests/users_db_security.js (renamed from share/www/script/test/users_db_security.js)0
-rw-r--r--test/javascript/tests/utf8.js (renamed from share/www/script/test/utf8.js)0
-rw-r--r--test/javascript/tests/uuids.js (renamed from share/www/script/test/uuids.js)0
-rw-r--r--test/javascript/tests/view_collation.js (renamed from share/www/script/test/view_collation.js)0
-rw-r--r--test/javascript/tests/view_collation_raw.js (renamed from share/www/script/test/view_collation_raw.js)0
-rw-r--r--test/javascript/tests/view_compaction.js (renamed from share/www/script/test/view_compaction.js)0
-rw-r--r--test/javascript/tests/view_conflicts.js (renamed from share/www/script/test/view_conflicts.js)0
-rw-r--r--test/javascript/tests/view_errors.js (renamed from share/www/script/test/view_errors.js)0
-rw-r--r--test/javascript/tests/view_include_docs.js (renamed from share/www/script/test/view_include_docs.js)0
-rw-r--r--test/javascript/tests/view_multi_key_all_docs.js (renamed from share/www/script/test/view_multi_key_all_docs.js)0
-rw-r--r--test/javascript/tests/view_multi_key_design.js (renamed from share/www/script/test/view_multi_key_design.js)0
-rw-r--r--test/javascript/tests/view_multi_key_temp.js (renamed from share/www/script/test/view_multi_key_temp.js)0
-rw-r--r--test/javascript/tests/view_offsets.js (renamed from share/www/script/test/view_offsets.js)0
-rw-r--r--test/javascript/tests/view_pagination.js (renamed from share/www/script/test/view_pagination.js)0
-rw-r--r--test/javascript/tests/view_sandboxing.js (renamed from share/www/script/test/view_sandboxing.js)0
-rw-r--r--test/javascript/tests/view_update_seq.js (renamed from share/www/script/test/view_update_seq.js)0
96 files changed, 2284 insertions, 302 deletions
diff --git a/share/www/script/test/oauth.js b/share/www/script/test/oauth.js
deleted file mode 100644
index 8b4e694d6..000000000
--- a/share/www/script/test/oauth.js
+++ /dev/null
@@ -1,294 +0,0 @@
-// 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.oauth = function(debug) {
- // This tests OAuth authentication.
-
- var authorization_url = "/_oauth/authorize";
-
- var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
- db.deleteDb();
- db.createDb();
- if (debug) debugger;
-
- 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 dbC = new CouchDB("test_suite_db_c", {"X-Couch-Full-Commit":"false"});
- var dbD = new CouchDB("test_suite_users", {"X-Couch-Full-Commit":"false"});
- dbA.deleteDb();
- dbA.createDb();
- dbB.deleteDb();
- dbB.createDb();
- dbC.deleteDb();
- dbC.createDb();
- dbD.deleteDb();
-
- // 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;
- }
-
- function oauthRequest(method, path, message, accessor) {
- message.action = path;
- message.method = method || 'GET';
- OAuth.SignatureMethod.sign(message, accessor);
- var parameters = message.parameters;
- if (method == "POST" || method == "GET") {
- if (method == "GET") {
- return CouchDB.request("GET", OAuth.addToURL(path, parameters));
- } else {
- return CouchDB.request("POST", path, {
- headers: {"Content-Type": "application/x-www-form-urlencoded"},
- body: OAuth.formEncode(parameters)
- });
- }
- } else {
- return CouchDB.request(method, path, {
- headers: {Authorization: OAuth.getAuthorizationHeader('', parameters)}
- });
- }
- }
-
- var consumerSecret = generateSecret(64);
- var tokenSecret = generateSecret(64);
- var admintokenSecret = generateSecret(64);
- var testadminPassword = "ohsosecret";
-
- var adminBasicAuthHeaderValue = function() {
- var retval = 'Basic ' + binb2b64(str2binb("testadmin:" + testadminPassword));
- return retval;
- }
-
- var host = CouchDB.host;
- var dbPair = {
- source: {
- url: CouchDB.protocol + host + "/test_suite_db_a",
- auth: {
- oauth: {
- consumer_key: "key",
- consumer_secret: consumerSecret,
- token_secret: tokenSecret,
- token: "foo"
- }
- }
- },
- target: {
- url: CouchDB.protocol + host + "/test_suite_db_b",
- headers: {"Authorization": adminBasicAuthHeaderValue()}
- }
- };
-
- // this function will be called on the modified server
- var testFun = function () {
- try {
- CouchDB.request("PUT", CouchDB.protocol + host + "/_config/admins/testadmin", {
- headers: {"X-Couch-Persist": "false"},
- body: JSON.stringify(testadminPassword)
- });
- 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("\"-pbkdf2-") != 0) {
- throw("still waiting");
- }
- return true;
- }, "wait-for-admin");
-
- CouchDB.newUuids(2); // so we have one to make the salt
-
- CouchDB.request("PUT", CouchDB.protocol + host + "/_config/couch_httpd_auth/require_valid_user", {
- headers: {
- "X-Couch-Persist": "false",
- "Authorization": adminBasicAuthHeaderValue()
- },
- body: JSON.stringify("true")
- });
-
- var usersDb = new CouchDB("test_suite_users", {
- "X-Couch-Full-Commit":"false",
- "Authorization": adminBasicAuthHeaderValue()
- });
-
- // Create a user
- var jasonUserDoc = CouchDB.prepareUserDoc({
- name: "jason",
- roles: ["test"]
- }, "testpassword");
- T(usersDb.save(jasonUserDoc).ok);
-
-
- var accessor = {
- consumerSecret: consumerSecret,
- tokenSecret: tokenSecret
- };
- var adminAccessor = {
- consumerSecret: consumerSecret,
- tokenSecret: admintokenSecret
- };
-
- var signatureMethods = ["PLAINTEXT", "HMAC-SHA1"];
- var consumerKeys = {key: 200, nonexistent_key: 400};
- for (var i=0; i<signatureMethods.length; i++) {
- for (var consumerKey in consumerKeys) {
- var expectedCode = consumerKeys[consumerKey];
- var message = {
- parameters: {
- oauth_signature_method: signatureMethods[i],
- oauth_consumer_key: consumerKey,
- oauth_token: "foo",
- oauth_token_secret: tokenSecret,
- oauth_version: "1.0"
- }
- };
-
- // Get request token via Authorization header
- xhr = oauthRequest("GET", CouchDB.protocol + host + "/_oauth/request_token", message, accessor);
- T(xhr.status == expectedCode);
-
- // GET request token via query parameters
- xhr = oauthRequest("GET", CouchDB.protocol + host + "/_oauth/request_token", message, accessor);
- T(xhr.status == expectedCode);
-
- responseMessage = OAuth.decodeForm(xhr.responseText);
-
- // Obtaining User Authorization
- //Only needed for 3-legged OAuth
- //xhr = CouchDB.request("GET", authorization_url + '?oauth_token=' + responseMessage.oauth_token);
- //T(xhr.status == expectedCode);
-
- xhr = oauthRequest("GET", CouchDB.protocol + host + "/_session", message, accessor);
- T(xhr.status == expectedCode);
- if (xhr.status == expectedCode == 200) {
- data = JSON.parse(xhr.responseText);
- T(data.name == "jason");
- T(data.roles[0] == "test");
- }
-
- xhr = oauthRequest("GET", CouchDB.protocol + host + "/_session?foo=bar", message, accessor);
- T(xhr.status == expectedCode);
-
- // Test HEAD method
- xhr = oauthRequest("HEAD", CouchDB.protocol + host + "/_session?foo=bar", message, accessor);
- T(xhr.status == expectedCode);
-
- // Replication
- var dbA = new CouchDB("test_suite_db_a", {
- "X-Couch-Full-Commit":"false",
- "Authorization": adminBasicAuthHeaderValue()
- });
- T(dbA.save({_id:"_design/"+i+consumerKey}).ok);
- var result = CouchDB.replicate(dbPair.source, dbPair.target, {
- headers: {"Authorization": adminBasicAuthHeaderValue()}
- });
- T(result.ok);
-
- // Test if rewriting doesn't break OAuth (c.f. COUCHDB-1321)
- var dbC = new CouchDB("test_suite_db_c", {
- "X-Couch-Full-Commit":"false",
- "Authorization": adminBasicAuthHeaderValue()
- });
- var ddocId = "_design/"+ i + consumerKey;
- var ddoc = {
- _id: ddocId,
- language: "javascript",
- _attachments:{
- "bar": {
- content_type:"text/plain",
- data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
- }
- },
- rewrites: [{"from": "foo/:a", "to": ":a"}]
- };
- T(dbC.save(ddoc).ok);
- xhr = oauthRequest("GET", CouchDB.protocol + host + "/test_suite_db_c/" + ddocId + "/_rewrite/foo/bar", message, accessor);
- T(xhr.status == expectedCode);
-
- // Test auth via admin user defined in .ini
- var message = {
- parameters: {
- oauth_signature_method: signatureMethods[i],
- oauth_consumer_key: consumerKey,
- oauth_token: "bar",
- oauth_token_secret: admintokenSecret,
- oauth_version: "1.0"
- }
- };
- 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");
- T(data.roles[0] == "_admin");
- }
-
- // Test when the user's token doesn't exist.
- message.parameters.oauth_token = "not a token!";
- 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", CouchDB.protocol + host + "/_config/couch_httpd_auth/require_valid_user", {
- headers: {
- "Authorization": adminBasicAuthHeaderValue(),
- "X-Couch-Persist": "false"
- },
- body: JSON.stringify("false")
- });
- T(xhr.status == 200);
-
- var xhr = CouchDB.request("DELETE", CouchDB.protocol + host + "/_config/admins/testadmin", {
- headers: {
- "Authorization": adminBasicAuthHeaderValue(),
- "X-Couch-Persist": "false"
- }
- });
- T(xhr.status == 200);
- }
- };
-
- run_on_modified_server(
- [
- {section: "httpd",
- key: "WWW-Authenticate", value: 'OAuth'},
- {section: "couch_httpd_auth",
- key: "secret", value: generateSecret(64)},
- {section: "couch_httpd_auth",
- key: "authentication_db", value: "test_suite_users"},
- {section: "oauth_consumer_secrets",
- key: "key", value: consumerSecret},
- {section: "oauth_token_users",
- key: "foo", value: "jason"},
- {section: "oauth_token_users",
- key: "bar", value: "testadmin"},
- {section: "oauth_token_secrets",
- key: "foo", value: tokenSecret},
- {section: "oauth_token_secrets",
- key: "bar", value: admintokenSecret},
- {section: "couch_httpd_oauth",
- key: "authorization_url", value: authorization_url},
- {section: "couch_httpd_oauth",
- key: "use_users_db", value: "false"}
- ],
- testFun
- );
-};
diff --git a/test/javascript/couch.js b/test/javascript/couch.js
new file mode 100644
index 000000000..7e4eeed73
--- /dev/null
+++ b/test/javascript/couch.js
@@ -0,0 +1,520 @@
+// 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.
+
+// A simple class to represent a database. Uses XMLHttpRequest to interface with
+// the CouchDB server.
+
+function CouchDB(name, httpHeaders) {
+ this.name = name;
+ this.uri = "/" + encodeURIComponent(name) + "/";
+
+ // The XMLHttpRequest object from the most recent request. Callers can
+ // use this to check result http status and headers.
+ this.last_req = null;
+
+ this.request = function(method, uri, requestOptions) {
+ requestOptions = requestOptions || {};
+ requestOptions.headers = combine(requestOptions.headers, httpHeaders);
+ return CouchDB.request(method, uri, requestOptions);
+ };
+
+ // Creates the database on the server
+ this.createDb = function() {
+ this.last_req = this.request("PUT", this.uri);
+ CouchDB.maybeThrowError(this.last_req);
+ return JSON.parse(this.last_req.responseText);
+ };
+
+ // Deletes the database on the server
+ this.deleteDb = function() {
+ this.last_req = this.request("DELETE", this.uri + "?sync=true");
+ if (this.last_req.status == 404) {
+ return false;
+ }
+ CouchDB.maybeThrowError(this.last_req);
+ return JSON.parse(this.last_req.responseText);
+ };
+
+ // Save a document to the database
+ this.save = function(doc, options, http_headers) {
+ if (doc._id == undefined) {
+ doc._id = CouchDB.newUuids(1)[0];
+ }
+ http_headers = http_headers || {};
+ this.last_req = this.request("PUT", this.uri +
+ encodeURIComponent(doc._id) + encodeOptions(options),
+ {body: JSON.stringify(doc), headers: http_headers});
+ CouchDB.maybeThrowError(this.last_req);
+ var result = JSON.parse(this.last_req.responseText);
+ doc._rev = result.rev;
+ return result;
+ };
+
+ // Open a document from the database
+ this.open = function(docId, url_params, http_headers) {
+ this.last_req = this.request("GET", this.uri + encodeURIComponent(docId)
+ + encodeOptions(url_params), {headers:http_headers});
+ if (this.last_req.status == 404) {
+ return null;
+ }
+ CouchDB.maybeThrowError(this.last_req);
+ return JSON.parse(this.last_req.responseText);
+ };
+
+ // Deletes a document from the database
+ this.deleteDoc = function(doc) {
+ this.last_req = this.request("DELETE", this.uri + encodeURIComponent(doc._id)
+ + "?rev=" + doc._rev);
+ CouchDB.maybeThrowError(this.last_req);
+ var result = JSON.parse(this.last_req.responseText);
+ doc._rev = result.rev; //record rev in input document
+ doc._deleted = true;
+ return result;
+ };
+
+ // Deletes an attachment from a document
+ this.deleteDocAttachment = function(doc, attachment_name) {
+ this.last_req = this.request("DELETE", this.uri + encodeURIComponent(doc._id)
+ + "/" + attachment_name + "?rev=" + doc._rev);
+ CouchDB.maybeThrowError(this.last_req);
+ var result = JSON.parse(this.last_req.responseText);
+ doc._rev = result.rev; //record rev in input document
+ return result;
+ };
+
+ this.bulkSave = function(docs, options) {
+ // first prepoulate the UUIDs for new documents
+ var newCount = 0;
+ for (var i=0; i<docs.length; i++) {
+ if (docs[i]._id == undefined) {
+ newCount++;
+ }
+ }
+ var newUuids = CouchDB.newUuids(newCount);
+ var newCount = 0;
+ for (var i=0; i<docs.length; i++) {
+ if (docs[i]._id == undefined) {
+ docs[i]._id = newUuids.pop();
+ }
+ }
+ var json = {"docs": docs};
+ // put any options in the json
+ for (var option in options) {
+ json[option] = options[option];
+ }
+ this.last_req = this.request("POST", this.uri + "_bulk_docs", {
+ body: JSON.stringify(json)
+ });
+ if (this.last_req.status == 417) {
+ return {errors: JSON.parse(this.last_req.responseText)};
+ }
+ else {
+ CouchDB.maybeThrowError(this.last_req);
+ var results = JSON.parse(this.last_req.responseText);
+ for (var i = 0; i < docs.length; i++) {
+ if(results[i] && results[i].rev && results[i].ok) {
+ docs[i]._rev = results[i].rev;
+ }
+ }
+ return results;
+ }
+ };
+
+ this.ensureFullCommit = function() {
+ this.last_req = this.request("POST", this.uri + "_ensure_full_commit");
+ CouchDB.maybeThrowError(this.last_req);
+ return JSON.parse(this.last_req.responseText);
+ };
+
+ // Applies the map function to the contents of database and returns the results.
+ this.query = function(mapFun, reduceFun, options, keys, language) {
+ var body = {language: language || "javascript"};
+ if(keys) {
+ body.keys = keys ;
+ }
+ if (typeof(mapFun) != "string") {
+ mapFun = mapFun.toSource ? mapFun.toSource() : "(" + mapFun.toString() + ")";
+ }
+ body.map = mapFun;
+ if (reduceFun != null) {
+ if (typeof(reduceFun) != "string") {
+ reduceFun = reduceFun.toSource ?
+ reduceFun.toSource() : "(" + reduceFun.toString() + ")";
+ }
+ body.reduce = reduceFun;
+ }
+ if (options && options.options != undefined) {
+ body.options = options.options;
+ delete options.options;
+ }
+ this.last_req = this.request("POST", this.uri + "_temp_view"
+ + encodeOptions(options), {
+ headers: {"Content-Type": "application/json"},
+ body: JSON.stringify(body)
+ });
+ CouchDB.maybeThrowError(this.last_req);
+ return JSON.parse(this.last_req.responseText);
+ };
+
+ this.view = function(viewname, options, keys) {
+ var viewParts = viewname.split('/');
+ var viewPath = this.uri + "_design/" + viewParts[0] + "/_view/"
+ + viewParts[1] + encodeOptions(options);
+ if(!keys) {
+ this.last_req = this.request("GET", viewPath);
+ } else {
+ this.last_req = this.request("POST", viewPath, {
+ headers: {"Content-Type": "application/json"},
+ body: JSON.stringify({keys:keys})
+ });
+ }
+ if (this.last_req.status == 404) {
+ return null;
+ }
+ CouchDB.maybeThrowError(this.last_req);
+ return JSON.parse(this.last_req.responseText);
+ };
+
+ // gets information about the database
+ this.info = function() {
+ this.last_req = this.request("GET", this.uri);
+ CouchDB.maybeThrowError(this.last_req);
+ return JSON.parse(this.last_req.responseText);
+ };
+
+ // gets information about a design doc
+ this.designInfo = function(docid) {
+ this.last_req = this.request("GET", this.uri + docid + "/_info");
+ CouchDB.maybeThrowError(this.last_req);
+ return JSON.parse(this.last_req.responseText);
+ };
+
+ this.allDocs = function(options,keys) {
+ if(!keys) {
+ this.last_req = this.request("GET", this.uri + "_all_docs"
+ + encodeOptions(options));
+ } else {
+ this.last_req = this.request("POST", this.uri + "_all_docs"
+ + encodeOptions(options), {
+ headers: {"Content-Type": "application/json"},
+ body: JSON.stringify({keys:keys})
+ });
+ }
+ CouchDB.maybeThrowError(this.last_req);
+ return JSON.parse(this.last_req.responseText);
+ };
+
+ this.designDocs = function() {
+ return this.allDocs({startkey:"_design", endkey:"_design0"});
+ };
+
+ this.changes = function(options) {
+ this.last_req = this.request("GET", this.uri + "_changes"
+ + encodeOptions(options));
+ CouchDB.maybeThrowError(this.last_req);
+ return JSON.parse(this.last_req.responseText);
+ };
+
+ this.compact = function() {
+ this.last_req = this.request("POST", this.uri + "_compact");
+ CouchDB.maybeThrowError(this.last_req);
+ return JSON.parse(this.last_req.responseText);
+ };
+
+ this.viewCleanup = function() {
+ this.last_req = this.request("POST", this.uri + "_view_cleanup");
+ CouchDB.maybeThrowError(this.last_req);
+ return JSON.parse(this.last_req.responseText);
+ };
+
+ this.setDbProperty = function(propId, propValue) {
+ this.last_req = this.request("PUT", this.uri + propId,{
+ body:JSON.stringify(propValue)
+ });
+ CouchDB.maybeThrowError(this.last_req);
+ return JSON.parse(this.last_req.responseText);
+ };
+
+ this.getDbProperty = function(propId) {
+ this.last_req = this.request("GET", this.uri + propId);
+ CouchDB.maybeThrowError(this.last_req);
+ return JSON.parse(this.last_req.responseText);
+ };
+
+ this.setSecObj = function(secObj) {
+ this.last_req = this.request("PUT", this.uri + "_security",{
+ body:JSON.stringify(secObj)
+ });
+ CouchDB.maybeThrowError(this.last_req);
+ return JSON.parse(this.last_req.responseText);
+ };
+
+ this.getSecObj = function() {
+ this.last_req = this.request("GET", this.uri + "_security");
+ CouchDB.maybeThrowError(this.last_req);
+ return JSON.parse(this.last_req.responseText);
+ };
+
+ // Convert a options object to an url query string.
+ // ex: {key:'value',key2:'value2'} becomes '?key="value"&key2="value2"'
+ function encodeOptions(options) {
+ var buf = [];
+ if (typeof(options) == "object" && options !== null) {
+ for (var name in options) {
+ if (!options.hasOwnProperty(name)) { continue; };
+ var value = options[name];
+ if (name == "key" || name == "keys" || name == "startkey" || name == "endkey" || (name == "open_revs" && value !== "all")) {
+ value = toJSON(value);
+ }
+ buf.push(encodeURIComponent(name) + "=" + encodeURIComponent(value));
+ }
+ }
+ if (!buf.length) {
+ return "";
+ }
+ return "?" + buf.join("&");
+ }
+
+ function toJSON(obj) {
+ return obj !== null ? JSON.stringify(obj) : null;
+ }
+
+ function combine(object1, object2) {
+ if (!object2) {
+ return object1;
+ }
+ if (!object1) {
+ return object2;
+ }
+
+ for (var name in object2) {
+ object1[name] = object2[name];
+ }
+ return object1;
+ }
+
+}
+
+// this is the XMLHttpRequest object from last request made by the following
+// CouchDB.* functions (except for calls to request itself).
+// Use this from callers to check HTTP status or header values of requests.
+CouchDB.last_req = null;
+CouchDB.urlPrefix = '';
+
+CouchDB.login = function(name, password) {
+ CouchDB.last_req = CouchDB.request("POST", "/_session", {
+ headers: {"Content-Type": "application/x-www-form-urlencoded",
+ "X-CouchDB-WWW-Authenticate": "Cookie"},
+ body: "name=" + encodeURIComponent(name) + "&password="
+ + encodeURIComponent(password)
+ });
+ return JSON.parse(CouchDB.last_req.responseText);
+}
+
+CouchDB.logout = function() {
+ CouchDB.last_req = CouchDB.request("DELETE", "/_session", {
+ headers: {"Content-Type": "application/x-www-form-urlencoded",
+ "X-CouchDB-WWW-Authenticate": "Cookie"}
+ });
+ return JSON.parse(CouchDB.last_req.responseText);
+};
+
+CouchDB.session = function(options) {
+ options = options || {};
+ CouchDB.last_req = CouchDB.request("GET", "/_session", options);
+ CouchDB.maybeThrowError(CouchDB.last_req);
+ return JSON.parse(CouchDB.last_req.responseText);
+};
+
+CouchDB.allDbs = function() {
+ CouchDB.last_req = CouchDB.request("GET", "/_all_dbs");
+ CouchDB.maybeThrowError(CouchDB.last_req);
+ return JSON.parse(CouchDB.last_req.responseText);
+};
+
+CouchDB.allDesignDocs = function() {
+ var ddocs = {}, dbs = CouchDB.allDbs();
+ for (var i=0; i < dbs.length; i++) {
+ var db = new CouchDB(dbs[i]);
+ ddocs[dbs[i]] = db.designDocs();
+ };
+ return ddocs;
+};
+
+CouchDB.getVersion = function() {
+ CouchDB.last_req = CouchDB.request("GET", "/");
+ CouchDB.maybeThrowError(CouchDB.last_req);
+ return JSON.parse(CouchDB.last_req.responseText).version;
+};
+
+CouchDB.reloadConfig = function() {
+ CouchDB.last_req = CouchDB.request("POST", "/_config/_reload");
+ CouchDB.maybeThrowError(CouchDB.last_req);
+ return JSON.parse(CouchDB.last_req.responseText);
+};
+
+CouchDB.replicate = function(source, target, rep_options) {
+ rep_options = rep_options || {};
+ var headers = rep_options.headers || {};
+ var body = rep_options.body || {};
+ body.source = source;
+ body.target = target;
+ CouchDB.last_req = CouchDB.request("POST", "/_replicate", {
+ headers: headers,
+ body: JSON.stringify(body)
+ });
+ CouchDB.maybeThrowError(CouchDB.last_req);
+ return JSON.parse(CouchDB.last_req.responseText);
+};
+
+CouchDB.newXhr = function() {
+ if (typeof(XMLHttpRequest) != "undefined") {
+ return new XMLHttpRequest();
+ } else if (typeof(ActiveXObject) != "undefined") {
+ return new ActiveXObject("Microsoft.XMLHTTP");
+ } else {
+ throw new Error("No XMLHTTPRequest support detected");
+ }
+};
+
+CouchDB.xhrbody = function(xhr) {
+ if (xhr.responseText) {
+ return xhr.responseText;
+ } else if (xhr.body) {
+ return xhr.body
+ } else {
+ throw new Error("No XMLHTTPRequest support detected");
+ }
+}
+
+CouchDB.xhrheader = function(xhr, header) {
+ if(xhr.getResponseHeader) {
+ return xhr.getResponseHeader(header);
+ } else if(xhr.headers) {
+ return xhr.headers[header] || null;
+ } else {
+ throw new Error("No XMLHTTPRequest support detected");
+ }
+}
+
+CouchDB.proxyUrl = function(uri) {
+ if(uri.substr(0, CouchDB.protocol.length) != CouchDB.protocol) {
+ uri = CouchDB.urlPrefix + uri;
+ }
+ return uri;
+}
+
+CouchDB.request = function(method, uri, options) {
+ options = typeof(options) == 'object' ? options : {};
+ options.headers = typeof(options.headers) == 'object' ? options.headers : {};
+ options.headers["Content-Type"] = options.headers["Content-Type"] || options.headers["content-type"] || "application/json";
+ options.headers["Accept"] = options.headers["Accept"] || options.headers["accept"] || "application/json";
+ var req = CouchDB.newXhr();
+ uri = CouchDB.proxyUrl(uri);
+ req.open(method, uri, false);
+ if (options.headers) {
+ var headers = options.headers;
+ for (var headerName in headers) {
+ if (!headers.hasOwnProperty(headerName)) { continue; }
+ req.setRequestHeader(headerName, headers[headerName]);
+ }
+ }
+ req.send(options.body || "");
+ return req;
+};
+
+CouchDB.requestStats = function(path, test) {
+ var query_arg = "";
+ if(test !== null) {
+ query_arg = "?flush=true";
+ }
+
+ var url = "/_stats/" + path.join("/") + query_arg;
+ var stat = CouchDB.request("GET", url).responseText;
+ return JSON.parse(stat);
+};
+
+CouchDB.uuids_cache = [];
+
+CouchDB.newUuids = function(n, buf) {
+ buf = buf || 100;
+ if (CouchDB.uuids_cache.length >= n) {
+ var uuids = CouchDB.uuids_cache.slice(CouchDB.uuids_cache.length - n);
+ if(CouchDB.uuids_cache.length - n == 0) {
+ CouchDB.uuids_cache = [];
+ } else {
+ CouchDB.uuids_cache =
+ CouchDB.uuids_cache.slice(0, CouchDB.uuids_cache.length - n);
+ }
+ return uuids;
+ } else {
+ CouchDB.last_req = CouchDB.request("GET", "/_uuids?count=" + (buf + n));
+ CouchDB.maybeThrowError(CouchDB.last_req);
+ var result = JSON.parse(CouchDB.last_req.responseText);
+ CouchDB.uuids_cache =
+ CouchDB.uuids_cache.concat(result.uuids.slice(0, buf));
+ return result.uuids.slice(buf);
+ }
+};
+
+CouchDB.maybeThrowError = function(req) {
+ if (req.status >= 400) {
+ try {
+ var result = JSON.parse(req.responseText);
+ } catch (ParseError) {
+ var result = {error:"unknown", reason:req.responseText};
+ }
+
+ throw (new CouchError(result));
+ }
+}
+
+CouchDB.params = function(options) {
+ options = options || {};
+ var returnArray = [];
+ for(var key in options) {
+ var value = options[key];
+ returnArray.push(key + "=" + value);
+ }
+ return returnArray.join("&");
+};
+// Used by replication test
+if (typeof window == 'undefined' || !window) {
+ var hostRE = RegExp("https?://([^\/]+)");
+ var getter = function () {
+ return (new CouchHTTP).base_url.match(hostRE)[1];
+ };
+ if(Object.defineProperty) {
+ Object.defineProperty(CouchDB, "host", {
+ get : getter,
+ enumerable : true
+ });
+ } else {
+ CouchDB.__defineGetter__("host", getter);
+ }
+ CouchDB.protocol = "http://";
+ CouchDB.inBrowser = false;
+} else {
+ CouchDB.host = window.location.host;
+ CouchDB.inBrowser = true;
+ CouchDB.protocol = window.location.protocol + "//";
+}
+
+// Turns an {error: ..., reason: ...} response into an Error instance
+function CouchError(error) {
+ var inst = new Error(error.reason);
+ inst.name = 'CouchError';
+ inst.error = error.error;
+ inst.reason = error.reason;
+ return inst;
+}
+CouchError.prototype.constructor = CouchError;
diff --git a/test/javascript/couch_test_runner.js b/test/javascript/couch_test_runner.js
new file mode 100644
index 000000000..efc4dc242
--- /dev/null
+++ b/test/javascript/couch_test_runner.js
@@ -0,0 +1,465 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+// *********************** Test Framework of Sorts ************************* //
+
+
+function loadScript(url) {
+ // disallow loading remote URLs
+ var re = /^[a-z0-9_]+(\/[a-z0-9_]+)*\.js#?$/;
+ if (!re.test(url)) {
+ throw "Not loading remote test scripts";
+ }
+ if (typeof document != "undefined") document.write('<script src="'+url+'"></script>');
+};
+
+function patchTest(fun) {
+ var source = fun.toString();
+ var output = "";
+ var i = 0;
+ var testMarker = "T(";
+ while (i < source.length) {
+ var testStart = source.indexOf(testMarker, i);
+ if (testStart == -1) {
+ output = output + source.substring(i, source.length);
+ break;
+ }
+ var testEnd = source.indexOf(");", testStart);
+ var testCode = source.substring(testStart + testMarker.length, testEnd);
+ output += source.substring(i, testStart) + "T(" + testCode + "," + JSON.stringify(testCode);
+ i = testEnd;
+ }
+ try {
+ return eval("(" + output + ")");
+ } catch (e) {
+ return null;
+ }
+}
+
+function runAllTests() {
+ var rows = $("#tests tbody.content tr");
+ $("td", rows).text("");
+ $("td.status", rows).removeClass("error").removeClass("failure").removeClass("success").text("not run");
+ var offset = 0;
+ function runNext() {
+ if (offset < rows.length) {
+ var row = rows.get(offset);
+ runTest($("th button", row).get(0), function() {
+ offset += 1;
+ setTimeout(runNext, 100);
+ }, false, true);
+ } else {
+ saveTestReport();
+ }
+ }
+ runNext();
+}
+
+var numFailures = 0;
+var currentRow = null;
+
+function runTest(button, callback, debug, noSave) {
+
+ // offer to save admins
+ if (currentRow != null) {
+ alert("Can not run multiple tests simultaneously.");
+ return;
+ }
+ var row = currentRow = $(button).parents("tr").get(0);
+ $("td.status", row).removeClass("error").removeClass("failure").removeClass("success");
+ $("td", row).text("");
+ $("#toolbar li.current").text("Running: "+row.id);
+ var testFun = couchTests[row.id];
+ function run() {
+ numFailures = 0;
+ var start = new Date().getTime();
+ try {
+ if (debug == undefined || !debug) {
+ testFun = patchTest(testFun) || testFun;
+ }
+ testFun(debug);
+ var status = numFailures > 0 ? "failure" : "success";
+ } catch (e) {
+ var status = "error";
+ if ($("td.details ol", row).length == 0) {
+ $("<ol></ol>").appendTo($("td.details", row));
+ }
+ $("<li><b>Exception raised:</b> <code class='error'></code></li>")
+ .find("code").text(JSON.stringify(e)).end()
+ .appendTo($("td.details ol", row));
+ if (debug) {
+ currentRow = null;
+ throw e;
+ }
+ }
+ if ($("td.details ol", row).length) {
+ $("<a href='#'>Run with debugger</a>").click(function() {
+ runTest(this, undefined, true);
+ }).prependTo($("td.details ol", row));
+ }
+ var duration = new Date().getTime() - start;
+ $("td.status", row).removeClass("running").addClass(status).text(status);
+ $("td.duration", row).text(duration + "ms");
+ $("#toolbar li.current").text("Finished: "+row.id);
+ updateTestsFooter();
+ currentRow = null;
+ if (callback) callback();
+ if (!noSave) saveTestReport();
+ }
+ $("td.status", row).addClass("running").text("running…");
+ setTimeout(run, 100);
+}
+
+function showSource(cell) {
+ var name = $(cell).text();
+ var win = window.open("", name, "width=700,height=500,resizable=yes,scrollbars=yes");
+ win.document.location = "script/test/" + name + ".js";
+}
+
+var readyToRun;
+function setupAdminParty(fun) {
+ if (readyToRun) {
+ fun();
+ } else {
+ function removeAdmins(confs, doneFun) {
+ // iterate through the config and remove current user last
+ // current user is at front of list
+ var remove = confs.pop();
+ if (remove) {
+ $.couch.config({
+ success : function() {
+ removeAdmins(confs, doneFun);
+ }
+ }, "admins", remove[0], null);
+ } else {
+ doneFun();
+ }
+ };
+ $.couch.session({
+ success : function(resp) {
+ var userCtx = resp.userCtx;
+ if (userCtx.name && userCtx.roles.indexOf("_admin") != -1) {
+ // admin but not admin party. dialog offering to make admin party
+ $.showDialog("dialog/_admin_party.html", {
+ submit: function(data, callback) {
+ $.couch.config({
+ success : function(conf) {
+ var meAdmin, adminConfs = [];
+ for (var name in conf) {
+ if (name == userCtx.name) {
+ meAdmin = [name, conf[name]];
+ } else {
+ adminConfs.push([name, conf[name]]);
+ }
+ }
+ adminConfs.unshift(meAdmin);
+ removeAdmins(adminConfs, function() {
+ callback();
+ $.futon.session.sidebar();
+ readyToRun = true;
+ setTimeout(fun, 500);
+ });
+ }
+ }, "admins");
+ }
+ });
+ } else if (userCtx.roles.indexOf("_admin") != -1) {
+ // admin party!
+ readyToRun = true;
+ fun();
+ } else {
+ // not an admin
+ alert("Error: You need to be an admin to run the tests.");
+ };
+ }
+ });
+ }
+};
+
+function updateTestsListing() {
+ for (var name in couchTests) {
+ var testFunction = couchTests[name];
+ var row = $("<tr><th></th><td></td><td></td><td></td></tr>")
+ .find("th").text(name).attr("title", "Show source").click(function() {
+ showSource(this);
+ }).end()
+ .find("td:nth(0)").addClass("status").text("not run").end()
+ .find("td:nth(1)").addClass("duration").end()
+ .find("td:nth(2)").addClass("details").end();
+ $("<button type='button' class='run' title='Run test'></button>").click(function() {
+ this.blur();
+ var self = this;
+ // check for admin party
+ setupAdminParty(function() {
+ runTest(self);
+ });
+ return false;
+ }).prependTo(row.find("th"));
+ row.attr("id", name).appendTo("#tests tbody.content");
+ }
+ $("#tests tr").removeClass("odd").filter(":odd").addClass("odd");
+ updateTestsFooter();
+}
+
+function updateTestsFooter() {
+ var tests = $("#tests tbody.content tr td.status");
+ var testsRun = tests.filter(".success, .error, .failure");
+ var testsFailed = testsRun.not(".success");
+ var totalDuration = 0;
+ $("#tests tbody.content tr td.duration:contains('ms')").each(function() {
+ var text = $(this).text();
+ totalDuration += parseInt(text.substr(0, text.length - 2), 10);
+ });
+ $("#tests tbody.footer td").html("<span>"+testsRun.length + " of " + tests.length +
+ " test(s) run, " + testsFailed.length + " failures (" +
+ totalDuration + " ms)</span> ");
+}
+
+// make report and save to local db
+// display how many reports need replicating to the mothership
+// have button to replicate them
+
+function saveTestReport(report) {
+ var report = makeTestReport();
+ if (report) {
+ var db = $.couch.db("test_suite_reports");
+ var saveReport = function(db_info) {
+ report.db = db_info;
+ $.couch.info({success : function(node_info) {
+ report.node = node_info;
+ db.saveDoc(report);
+ }});
+ };
+ var createDb = function() {
+ db.create({success: function() {
+ db.info({success:saveReport});
+ }});
+ };
+ db.info({error: createDb, success:saveReport});
+ }
+};
+
+function makeTestReport() {
+ var report = {};
+ report.summary = $("#tests tbody.footer td").text();
+ report.platform = testPlatform();
+ var date = new Date();
+ report.timestamp = date.getTime();
+ report.timezone = date.getTimezoneOffset();
+ report.tests = [];
+ $("#tests tbody.content tr").each(function() {
+ var status = $("td.status", this).text();
+ if (status != "not run") {
+ var test = {};
+ test.name = this.id;
+ test.status = status;
+ test.duration = parseInt($("td.duration", this).text());
+ test.details = [];
+ $("td.details li", this).each(function() {
+ test.details.push($(this).text());
+ });
+ if (test.details.length == 0) {
+ delete test.details;
+ }
+ report.tests.push(test);
+ }
+ });
+ if (report.tests.length > 0) return report;
+};
+
+function testPlatform() {
+ var b = $.browser;
+ var bs = ["mozilla", "msie", "opera", "safari"];
+ for (var i=0; i < bs.length; i++) {
+ if (b[bs[i]]) {
+ return {"browser" : bs[i], "version" : b.version};
+ }
+ };
+ return {"browser" : "undetected"};
+}
+
+
+function reportTests() {
+ // replicate the database to couchdb.couchdb.org
+}
+
+// Use T to perform a test that returns false on failure and if the test fails,
+// display the line that failed.
+// Example:
+// T(MyValue==1);
+function T(arg1, arg2, testName) {
+ if (!arg1) {
+ if (currentRow) {
+ if ($("td.details ol", currentRow).length == 0) {
+ $("<ol></ol>").appendTo($("td.details", currentRow));
+ }
+ var message = (arg2 != null ? arg2 : arg1).toString();
+ $("<li><b>Assertion " + (testName ? "'" + testName + "'" : "") + " failed:</b> <code class='failure'></code></li>")
+ .find("code").text(message).end()
+ .appendTo($("td.details ol", currentRow));
+ }
+ numFailures += 1;
+ }
+}
+
+function TIsnull(actual, testName) {
+ T(actual === null, "expected 'null', got '"
+ + repr(actual) + "'", testName);
+}
+
+function TEquals(expected, actual, testName) {
+ T(equals(expected, actual), "expected '" + repr(expected) +
+ "', got '" + repr(actual) + "'", testName);
+}
+
+function TEqualsIgnoreCase(expected, actual, testName) {
+ T(equals(expected.toUpperCase(), actual.toUpperCase()), "expected '" + repr(expected) +
+ "', got '" + repr(actual) + "'", testName);
+}
+
+function equals(a,b) {
+ if (a === b) return true;
+ try {
+ return repr(a) === repr(b);
+ } catch (e) {
+ return false;
+ }
+}
+
+function repr(val) {
+ if (val === undefined) {
+ return null;
+ } else if (val === null) {
+ return "null";
+ } else {
+ return JSON.stringify(val);
+ }
+}
+
+function makeDocs(start, end, templateDoc) {
+ var templateDocSrc = templateDoc ? JSON.stringify(templateDoc) : "{}";
+ if (end === undefined) {
+ end = start;
+ start = 0;
+ }
+ var docs = [];
+ for (var i = start; i < end; i++) {
+ var newDoc = eval("(" + templateDocSrc + ")");
+ newDoc._id = (i).toString();
+ newDoc.integer = i;
+ newDoc.string = (i).toString();
+ docs.push(newDoc);
+ }
+ return docs;
+}
+
+function run_on_modified_server(settings, fun) {
+ try {
+ // set the settings
+ for(var i=0; i < settings.length; i++) {
+ var s = settings[i];
+ var xhr = CouchDB.request("PUT", "/_config/" + s.section + "/" + s.key, {
+ body: JSON.stringify(s.value),
+ headers: {"X-Couch-Persist": "false"}
+ });
+ CouchDB.maybeThrowError(xhr);
+ s.oldValue = xhr.responseText;
+ }
+ // run the thing
+ fun();
+ } finally {
+ // unset the settings
+ for(var j=0; j < i; j++) {
+ var s = settings[j];
+ if(s.oldValue == "\"\"\n") { // unset value
+ CouchDB.request("DELETE", "/_config/" + s.section + "/" + s.key, {
+ headers: {"X-Couch-Persist": "false"}
+ });
+ } else {
+ CouchDB.request("PUT", "/_config/" + s.section + "/" + s.key, {
+ body: s.oldValue,
+ headers: {"X-Couch-Persist": "false"}
+ });
+ }
+ }
+ }
+}
+
+function stringFun(fun) {
+ var string = fun.toSource ? fun.toSource() : "(" + fun.toString() + ")";
+ return string;
+}
+
+function waitForSuccess(fun, tag) {
+ var start = new Date();
+ while(true) {
+ if (new Date() - start > 5000) {
+ throw("timeout: "+tag);
+ } else {
+ try {
+ fun();
+ break;
+ } catch (e) {}
+ // sync http req allow async req to happen
+ try {
+ CouchDB.request("GET", "/test_suite_db/?tag="+encodeURIComponent(tag));
+ } catch (e) {}
+ }
+ }
+}
+
+function getCurrentToken() {
+ var xhr = CouchDB.request("GET", "/_restart/token");
+ return JSON.parse(xhr.responseText).token;
+};
+
+
+function restartServer() {
+ var token = getCurrentToken();
+ var token_changed = false;
+ var tDelta = 5000;
+ var t0 = new Date();
+ var t1;
+
+ CouchDB.request("POST", "/_restart");
+
+ do {
+ try {
+ if(token != getCurrentToken()) {
+ token_changed = true;
+ }
+ } catch (e) {
+ // Ignore errors while the server restarts
+ }
+ t1 = new Date();
+ } while(((t1 - t0) <= tDelta) && !token_changed);
+
+ if(!token_changed) {
+ throw("Server restart failed");
+ }
+}
+
+// legacy functions for CouchDB < 1.2.0
+// we keep them to make sure we keep BC
+CouchDB.user_prefix = "org.couchdb.user:";
+
+CouchDB.prepareUserDoc = function(user_doc, new_password) {
+ user_doc._id = user_doc._id || CouchDB.user_prefix + user_doc.name;
+ if (new_password) {
+ user_doc.password = new_password;
+ }
+ user_doc.type = "user";
+ if (!user_doc.roles) {
+ user_doc.roles = [];
+ }
+ return user_doc;
+};
diff --git a/test/javascript/json2.js b/test/javascript/json2.js
new file mode 100644
index 000000000..a1a3b170c
--- /dev/null
+++ b/test/javascript/json2.js
@@ -0,0 +1,482 @@
+/*
+ http://www.JSON.org/json2.js
+ 2010-03-20
+
+ Public Domain.
+
+ NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
+
+ See http://www.JSON.org/js.html
+
+
+ This code should be minified before deployment.
+ See http://javascript.crockford.com/jsmin.html
+
+ USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
+ NOT CONTROL.
+
+
+ This file creates a global JSON object containing two methods: stringify
+ and parse.
+
+ JSON.stringify(value, replacer, space)
+ value any JavaScript value, usually an object or array.
+
+ replacer an optional parameter that determines how object
+ values are stringified for objects. It can be a
+ function or an array of strings.
+
+ space an optional parameter that specifies the indentation
+ of nested structures. If it is omitted, the text will
+ be packed without extra whitespace. If it is a number,
+ it will specify the number of spaces to indent at each
+ level. If it is a string (such as '\t' or '&nbsp;'),
+ it contains the characters used to indent at each level.
+
+ This method produces a JSON text from a JavaScript value.
+
+ When an object value is found, if the object contains a toJSON
+ method, its toJSON method will be called and the result will be
+ stringified. A toJSON method does not serialize: it returns the
+ value represented by the name/value pair that should be serialized,
+ or undefined if nothing should be serialized. The toJSON method
+ will be passed the key associated with the value, and this will be
+ bound to the value
+
+ For example, this would serialize Dates as ISO strings.
+
+ Date.prototype.toJSON = function (key) {
+ function f(n) {
+ // Format integers to have at least two digits.
+ return n < 10 ? '0' + n : n;
+ }
+
+ return this.getUTCFullYear() + '-' +
+ f(this.getUTCMonth() + 1) + '-' +
+ f(this.getUTCDate()) + 'T' +
+ f(this.getUTCHours()) + ':' +
+ f(this.getUTCMinutes()) + ':' +
+ f(this.getUTCSeconds()) + 'Z';
+ };
+
+ You can provide an optional replacer method. It will be passed the
+ key and value of each member, with this bound to the containing
+ object. The value that is returned from your method will be
+ serialized. If your method returns undefined, then the member will
+ be excluded from the serialization.
+
+ If the replacer parameter is an array of strings, then it will be
+ used to select the members to be serialized. It filters the results
+ such that only members with keys listed in the replacer array are
+ stringified.
+
+ Values that do not have JSON representations, such as undefined or
+ functions, will not be serialized. Such values in objects will be
+ dropped; in arrays they will be replaced with null. You can use
+ a replacer function to replace those with JSON values.
+ JSON.stringify(undefined) returns undefined.
+
+ The optional space parameter produces a stringification of the
+ value that is filled with line breaks and indentation to make it
+ easier to read.
+
+ If the space parameter is a non-empty string, then that string will
+ be used for indentation. If the space parameter is a number, then
+ the indentation will be that many spaces.
+
+ Example:
+
+ text = JSON.stringify(['e', {pluribus: 'unum'}]);
+ // text is '["e",{"pluribus":"unum"}]'
+
+
+ text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
+ // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
+
+ text = JSON.stringify([new Date()], function (key, value) {
+ return this[key] instanceof Date ?
+ 'Date(' + this[key] + ')' : value;
+ });
+ // text is '["Date(---current time---)"]'
+
+
+ JSON.parse(text, reviver)
+ This method parses a JSON text to produce an object or array.
+ It can throw a SyntaxError exception.
+
+ The optional reviver parameter is a function that can filter and
+ transform the results. It receives each of the keys and values,
+ and its return value is used instead of the original value.
+ If it returns what it received, then the structure is not modified.
+ If it returns undefined then the member is deleted.
+
+ Example:
+
+ // Parse the text. Values that look like ISO date strings will
+ // be converted to Date objects.
+
+ myData = JSON.parse(text, function (key, value) {
+ var a;
+ if (typeof value === 'string') {
+ a =
+/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
+ if (a) {
+ return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
+ +a[5], +a[6]));
+ }
+ }
+ return value;
+ });
+
+ myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
+ var d;
+ if (typeof value === 'string' &&
+ value.slice(0, 5) === 'Date(' &&
+ value.slice(-1) === ')') {
+ d = new Date(value.slice(5, -1));
+ if (d) {
+ return d;
+ }
+ }
+ return value;
+ });
+
+
+ This is a reference implementation. You are free to copy, modify, or
+ redistribute.
+*/
+
+/*jslint evil: true, strict: false */
+
+/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
+ call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
+ getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
+ lastIndex, length, parse, prototype, push, replace, slice, stringify,
+ test, toJSON, toString, valueOf
+*/
+
+
+// Create a JSON object only if one does not already exist. We create the
+// methods in a closure to avoid creating global variables.
+
+if (!this.JSON) {
+ this.JSON = {};
+}
+
+(function () {
+
+ function f(n) {
+ // Format integers to have at least two digits.
+ return n < 10 ? '0' + n : n;
+ }
+
+ if (typeof Date.prototype.toJSON !== 'function') {
+
+ Date.prototype.toJSON = function (key) {
+
+ return isFinite(this.valueOf()) ?
+ this.getUTCFullYear() + '-' +
+ f(this.getUTCMonth() + 1) + '-' +
+ f(this.getUTCDate()) + 'T' +
+ f(this.getUTCHours()) + ':' +
+ f(this.getUTCMinutes()) + ':' +
+ f(this.getUTCSeconds()) + 'Z' : null;
+ };
+
+ String.prototype.toJSON =
+ Number.prototype.toJSON =
+ Boolean.prototype.toJSON = function (key) {
+ return this.valueOf();
+ };
+ }
+
+ var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+ escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+ gap,
+ indent,
+ meta = { // table of character substitutions
+ '\b': '\\b',
+ '\t': '\\t',
+ '\n': '\\n',
+ '\f': '\\f',
+ '\r': '\\r',
+ '"' : '\\"',
+ '\\': '\\\\'
+ },
+ rep;
+
+
+ function quote(string) {
+
+// If the string contains no control characters, no quote characters, and no
+// backslash characters, then we can safely slap some quotes around it.
+// Otherwise we must also replace the offending characters with safe escape
+// sequences.
+
+ escapable.lastIndex = 0;
+ return escapable.test(string) ?
+ '"' + string.replace(escapable, function (a) {
+ var c = meta[a];
+ return typeof c === 'string' ? c :
+ '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+ }) + '"' :
+ '"' + string + '"';
+ }
+
+
+ function str(key, holder) {
+
+// Produce a string from holder[key].
+
+ var i, // The loop counter.
+ k, // The member key.
+ v, // The member value.
+ length,
+ mind = gap,
+ partial,
+ value = holder[key];
+
+// If the value has a toJSON method, call it to obtain a replacement value.
+
+ if (value && typeof value === 'object' &&
+ typeof value.toJSON === 'function') {
+ value = value.toJSON(key);
+ }
+
+// If we were called with a replacer function, then call the replacer to
+// obtain a replacement value.
+
+ if (typeof rep === 'function') {
+ value = rep.call(holder, key, value);
+ }
+
+// What happens next depends on the value's type.
+
+ switch (typeof value) {
+ case 'string':
+ return quote(value);
+
+ case 'number':
+
+// JSON numbers must be finite. Encode non-finite numbers as null.
+
+ return isFinite(value) ? String(value) : 'null';
+
+ case 'boolean':
+ case 'null':
+
+// If the value is a boolean or null, convert it to a string. Note:
+// typeof null does not produce 'null'. The case is included here in
+// the remote chance that this gets fixed someday.
+
+ return String(value);
+
+// If the type is 'object', we might be dealing with an object or an array or
+// null.
+
+ case 'object':
+
+// Due to a specification blunder in ECMAScript, typeof null is 'object',
+// so watch out for that case.
+
+ if (!value) {
+ return 'null';
+ }
+
+// Make an array to hold the partial results of stringifying this object value.
+
+ gap += indent;
+ partial = [];
+
+// Is the value an array?
+
+ if (Object.prototype.toString.apply(value) === '[object Array]') {
+
+// The value is an array. Stringify every element. Use null as a placeholder
+// for non-JSON values.
+
+ length = value.length;
+ for (i = 0; i < length; i += 1) {
+ partial[i] = str(i, value) || 'null';
+ }
+
+// Join all of the elements together, separated with commas, and wrap them in
+// brackets.
+
+ v = partial.length === 0 ? '[]' :
+ gap ? '[\n' + gap +
+ partial.join(',\n' + gap) + '\n' +
+ mind + ']' :
+ '[' + partial.join(',') + ']';
+ gap = mind;
+ return v;
+ }
+
+// If the replacer is an array, use it to select the members to be stringified.
+
+ if (rep && typeof rep === 'object') {
+ length = rep.length;
+ for (i = 0; i < length; i += 1) {
+ k = rep[i];
+ if (typeof k === 'string') {
+ v = str(k, value);
+ if (v) {
+ partial.push(quote(k) + (gap ? ': ' : ':') + v);
+ }
+ }
+ }
+ } else {
+
+// Otherwise, iterate through all of the keys in the object.
+
+ for (k in value) {
+ if (Object.hasOwnProperty.call(value, k)) {
+ v = str(k, value);
+ if (v) {
+ partial.push(quote(k) + (gap ? ': ' : ':') + v);
+ }
+ }
+ }
+ }
+
+// Join all of the member texts together, separated with commas,
+// and wrap them in braces.
+
+ v = partial.length === 0 ? '{}' :
+ gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
+ mind + '}' : '{' + partial.join(',') + '}';
+ gap = mind;
+ return v;
+ }
+ }
+
+// If the JSON object does not yet have a stringify method, give it one.
+
+ if (typeof JSON.stringify !== 'function') {
+ JSON.stringify = function (value, replacer, space) {
+
+// The stringify method takes a value and an optional replacer, and an optional
+// space parameter, and returns a JSON text. The replacer can be a function
+// that can replace values, or an array of strings that will select the keys.
+// A default replacer method can be provided. Use of the space parameter can
+// produce text that is more easily readable.
+
+ var i;
+ gap = '';
+ indent = '';
+
+// If the space parameter is a number, make an indent string containing that
+// many spaces.
+
+ if (typeof space === 'number') {
+ for (i = 0; i < space; i += 1) {
+ indent += ' ';
+ }
+
+// If the space parameter is a string, it will be used as the indent string.
+
+ } else if (typeof space === 'string') {
+ indent = space;
+ }
+
+// If there is a replacer, it must be a function or an array.
+// Otherwise, throw an error.
+
+ rep = replacer;
+ if (replacer && typeof replacer !== 'function' &&
+ (typeof replacer !== 'object' ||
+ typeof replacer.length !== 'number')) {
+ throw new Error('JSON.stringify');
+ }
+
+// Make a fake root object containing our value under the key of ''.
+// Return the result of stringifying the value.
+
+ return str('', {'': value});
+ };
+ }
+
+
+// If the JSON object does not yet have a parse method, give it one.
+
+ if (typeof JSON.parse !== 'function') {
+ JSON.parse = function (text, reviver) {
+
+// The parse method takes a text and an optional reviver function, and returns
+// a JavaScript value if the text is a valid JSON text.
+
+ var j;
+
+ function walk(holder, key) {
+
+// The walk method is used to recursively walk the resulting structure so
+// that modifications can be made.
+
+ var k, v, value = holder[key];
+ if (value && typeof value === 'object') {
+ for (k in value) {
+ if (Object.hasOwnProperty.call(value, k)) {
+ v = walk(value, k);
+ if (v !== undefined) {
+ value[k] = v;
+ } else {
+ delete value[k];
+ }
+ }
+ }
+ }
+ return reviver.call(holder, key, value);
+ }
+
+
+// Parsing happens in four stages. In the first stage, we replace certain
+// Unicode characters with escape sequences. JavaScript handles many characters
+// incorrectly, either silently deleting them, or treating them as line endings.
+
+ text = String(text);
+ cx.lastIndex = 0;
+ if (cx.test(text)) {
+ text = text.replace(cx, function (a) {
+ return '\\u' +
+ ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+ });
+ }
+
+// In the second stage, we run the text against regular expressions that look
+// for non-JSON patterns. We are especially concerned with '()' and 'new'
+// because they can cause invocation, and '=' because it can cause mutation.
+// But just to be safe, we want to reject all unexpected forms.
+
+// We split the second stage into 4 regexp operations in order to work around
+// crippling inefficiencies in IE's and Safari's regexp engines. First we
+// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
+// replace all simple value tokens with ']' characters. Third, we delete all
+// open brackets that follow a colon or comma or that begin the text. Finally,
+// we look to see that the remaining characters are only whitespace or ']' or
+// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
+
+ if (/^[\],:{}\s]*$/.
+test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
+replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
+replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
+
+// In the third stage we use the eval function to compile the text into a
+// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
+// in JavaScript: it can begin a block or an object literal. We wrap the text
+// in parens to eliminate the ambiguity.
+
+ j = eval('(' + text + ')');
+
+// In the optional fourth stage, we recursively walk the new structure, passing
+// each name/value pair to a reviver function for possible transformation.
+
+ return typeof reviver === 'function' ?
+ walk({'': j}, '') : j;
+ }
+
+// If the text is not JSON parseable, then a SyntaxError is thrown.
+
+ throw new SyntaxError('JSON.parse');
+ };
+ }
+}());
diff --git a/test/javascript/oauth.js b/test/javascript/oauth.js
new file mode 100644
index 000000000..ada00a275
--- /dev/null
+++ b/test/javascript/oauth.js
@@ -0,0 +1,511 @@
+/*
+ * Copyright 2008 Netflix, Inc.
+ *
+ * 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.
+ */
+
+/* Here's some JavaScript software for implementing OAuth.
+
+ This isn't as useful as you might hope. OAuth is based around
+ allowing tools and websites to talk to each other. However,
+ JavaScript running in web browsers is hampered by security
+ restrictions that prevent code running on one website from
+ accessing data stored or served on another.
+
+ Before you start hacking, make sure you understand the limitations
+ posed by cross-domain XMLHttpRequest.
+
+ On the bright side, some platforms use JavaScript as their
+ language, but enable the programmer to access other web sites.
+ Examples include Google Gadgets, and Microsoft Vista Sidebar.
+ For those platforms, this library should come in handy.
+*/
+
+// The HMAC-SHA1 signature method calls b64_hmac_sha1, defined by
+// http://pajhome.org.uk/crypt/md5/sha1.js
+
+/* An OAuth message is represented as an object like this:
+ {method: "GET", action: "http://server.com/path", parameters: ...}
+
+ The parameters may be either a map {name: value, name2: value2}
+ or an Array of name-value pairs [[name, value], [name2, value2]].
+ The latter representation is more powerful: it supports parameters
+ in a specific sequence, or several parameters with the same name;
+ for example [["a", 1], ["b", 2], ["a", 3]].
+
+ Parameter names and values are NOT percent-encoded in an object.
+ They must be encoded before transmission and decoded after reception.
+ For example, this message object:
+ {method: "GET", action: "http://server/path", parameters: {p: "x y"}}
+ ... can be transmitted as an HTTP request that begins:
+ GET /path?p=x%20y HTTP/1.0
+ (This isn't a valid OAuth request, since it lacks a signature etc.)
+ Note that the object "x y" is transmitted as x%20y. To encode
+ parameters, you can call OAuth.addToURL, OAuth.formEncode or
+ OAuth.getAuthorization.
+
+ This message object model harmonizes with the browser object model for
+ input elements of an form, whose value property isn't percent encoded.
+ The browser encodes each value before transmitting it. For example,
+ see consumer.setInputs in example/consumer.js.
+ */
+var OAuth; if (OAuth == null) OAuth = {};
+
+OAuth.setProperties = function setProperties(into, from) {
+ if (into != null && from != null) {
+ for (var key in from) {
+ into[key] = from[key];
+ }
+ }
+ return into;
+}
+
+OAuth.setProperties(OAuth, // utility functions
+{
+ percentEncode: function percentEncode(s) {
+ if (s == null) {
+ return "";
+ }
+ if (s instanceof Array) {
+ var e = "";
+ for (var i = 0; i < s.length; ++i) {
+ if (e != "") e += '&';
+ e += percentEncode(s[i]);
+ }
+ return e;
+ }
+ s = encodeURIComponent(s);
+ // Now replace the values which encodeURIComponent doesn't do
+ // encodeURIComponent ignores: - _ . ! ~ * ' ( )
+ // OAuth dictates the only ones you can ignore are: - _ . ~
+ // Source: http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Functions:encodeURIComponent
+ s = s.replace(/\!/g, "%21");
+ s = s.replace(/\*/g, "%2A");
+ s = s.replace(/\'/g, "%27");
+ s = s.replace(/\(/g, "%28");
+ s = s.replace(/\)/g, "%29");
+ return s;
+ }
+,
+ decodePercent: function decodePercent(s) {
+ if (s != null) {
+ // Handle application/x-www-form-urlencoded, which is defined by
+ // http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.1
+ s = s.replace(/\+/g, " ");
+ }
+ return decodeURIComponent(s);
+ }
+,
+ /** Convert the given parameters to an Array of name-value pairs. */
+ getParameterList: function getParameterList(parameters) {
+ if (parameters == null) {
+ return [];
+ }
+ if (typeof parameters != "object") {
+ return decodeForm(parameters + "");
+ }
+ if (parameters instanceof Array) {
+ return parameters;
+ }
+ var list = [];
+ for (var p in parameters) {
+ list.push([p, parameters[p]]);
+ }
+ return list;
+ }
+,
+ /** Convert the given parameters to a map from name to value. */
+ getParameterMap: function getParameterMap(parameters) {
+ if (parameters == null) {
+ return {};
+ }
+ if (typeof parameters != "object") {
+ return getParameterMap(decodeForm(parameters + ""));
+ }
+ if (parameters instanceof Array) {
+ var map = {};
+ for (var p = 0; p < parameters.length; ++p) {
+ var key = parameters[p][0];
+ if (map[key] === undefined) { // first value wins
+ map[key] = parameters[p][1];
+ }
+ }
+ return map;
+ }
+ return parameters;
+ }
+,
+ getParameter: function getParameter(parameters, name) {
+ if (parameters instanceof Array) {
+ for (var p = 0; p < parameters.length; ++p) {
+ if (parameters[p][0] == name) {
+ return parameters[p][1]; // first value wins
+ }
+ }
+ } else {
+ return OAuth.getParameterMap(parameters)[name];
+ }
+ return null;
+ }
+,
+ formEncode: function formEncode(parameters) {
+ var form = "";
+ var list = OAuth.getParameterList(parameters);
+ for (var p = 0; p < list.length; ++p) {
+ var value = list[p][1];
+ if (value == null) value = "";
+ if (form != "") form += '&';
+ form += OAuth.percentEncode(list[p][0])
+ +'='+ OAuth.percentEncode(value);
+ }
+ return form;
+ }
+,
+ decodeForm: function decodeForm(form) {
+ var list = [];
+ var nvps = form.split('&');
+ for (var n = 0; n < nvps.length; ++n) {
+ var nvp = nvps[n];
+ if (nvp == "") {
+ continue;
+ }
+ var equals = nvp.indexOf('=');
+ var name;
+ var value;
+ if (equals < 0) {
+ name = OAuth.decodePercent(nvp);
+ value = null;
+ } else {
+ name = OAuth.decodePercent(nvp.substring(0, equals));
+ value = OAuth.decodePercent(nvp.substring(equals + 1));
+ }
+ list.push([name, value]);
+ }
+ return list;
+ }
+,
+ setParameter: function setParameter(message, name, value) {
+ var parameters = message.parameters;
+ if (parameters instanceof Array) {
+ for (var p = 0; p < parameters.length; ++p) {
+ if (parameters[p][0] == name) {
+ if (value === undefined) {
+ parameters.splice(p, 1);
+ } else {
+ parameters[p][1] = value;
+ value = undefined;
+ }
+ }
+ }
+ if (value !== undefined) {
+ parameters.push([name, value]);
+ }
+ } else {
+ parameters = OAuth.getParameterMap(parameters);
+ parameters[name] = value;
+ message.parameters = parameters;
+ }
+ }
+,
+ setParameters: function setParameters(message, parameters) {
+ var list = OAuth.getParameterList(parameters);
+ for (var i = 0; i < list.length; ++i) {
+ OAuth.setParameter(message, list[i][0], list[i][1]);
+ }
+ }
+,
+ /** Fill in parameters to help construct a request message.
+ This function doesn't fill in every parameter.
+ The accessor object should be like:
+ {consumerKey:'foo', consumerSecret:'bar', accessorSecret:'nurn', token:'krelm', tokenSecret:'blah'}
+ The accessorSecret property is optional.
+ */
+ completeRequest: function completeRequest(message, accessor) {
+ if (message.method == null) {
+ message.method = "GET";
+ }
+ var map = OAuth.getParameterMap(message.parameters);
+ if (map.oauth_consumer_key == null) {
+ OAuth.setParameter(message, "oauth_consumer_key", accessor.consumerKey || "");
+ }
+ if (map.oauth_token == null && accessor.token != null) {
+ OAuth.setParameter(message, "oauth_token", accessor.token);
+ }
+ if (map.oauth_version == null) {
+ OAuth.setParameter(message, "oauth_version", "1.0");
+ }
+ if (map.oauth_timestamp == null) {
+ OAuth.setParameter(message, "oauth_timestamp", OAuth.timestamp());
+ }
+ if (map.oauth_nonce == null) {
+ OAuth.setParameter(message, "oauth_nonce", OAuth.nonce(6));
+ }
+ OAuth.SignatureMethod.sign(message, accessor);
+ }
+,
+ setTimestampAndNonce: function setTimestampAndNonce(message) {
+ OAuth.setParameter(message, "oauth_timestamp", OAuth.timestamp());
+ OAuth.setParameter(message, "oauth_nonce", OAuth.nonce(6));
+ }
+,
+ addToURL: function addToURL(url, parameters) {
+ newURL = url;
+ if (parameters != null) {
+ var toAdd = OAuth.formEncode(parameters);
+ if (toAdd.length > 0) {
+ var q = url.indexOf('?');
+ if (q < 0) newURL += '?';
+ else newURL += '&';
+ newURL += toAdd;
+ }
+ }
+ return newURL;
+ }
+,
+ /** Construct the value of the Authorization header for an HTTP request. */
+ getAuthorizationHeader: function getAuthorizationHeader(realm, parameters) {
+ var header = 'OAuth realm="' + OAuth.percentEncode(realm) + '"';
+ var list = OAuth.getParameterList(parameters);
+ for (var p = 0; p < list.length; ++p) {
+ var parameter = list[p];
+ var name = parameter[0];
+ if (name.indexOf("oauth_") == 0) {
+ header += ',' + OAuth.percentEncode(name) + '="' + OAuth.percentEncode(parameter[1]) + '"';
+ }
+ }
+ return header;
+ }
+,
+ timestamp: function timestamp() {
+ var d = new Date();
+ return Math.floor(d.getTime()/1000);
+ }
+,
+ nonce: function nonce(length) {
+ var chars = OAuth.nonce.CHARS;
+ var result = "";
+ for (var i = 0; i < length; ++i) {
+ var rnum = Math.floor(Math.random() * chars.length);
+ result += chars.substring(rnum, rnum+1);
+ }
+ return result;
+ }
+});
+
+OAuth.nonce.CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz";
+
+/** Define a constructor function,
+ without causing trouble to anyone who was using it as a namespace.
+ That is, if parent[name] already existed and had properties,
+ copy those properties into the new constructor.
+ */
+OAuth.declareClass = function declareClass(parent, name, newConstructor) {
+ var previous = parent[name];
+ parent[name] = newConstructor;
+ if (newConstructor != null && previous != null) {
+ for (var key in previous) {
+ if (key != "prototype") {
+ newConstructor[key] = previous[key];
+ }
+ }
+ }
+ return newConstructor;
+}
+
+/** An abstract algorithm for signing messages. */
+OAuth.declareClass(OAuth, "SignatureMethod", function OAuthSignatureMethod(){});
+
+OAuth.setProperties(OAuth.SignatureMethod.prototype, // instance members
+{
+ /** Add a signature to the message. */
+ sign: function sign(message) {
+ var baseString = OAuth.SignatureMethod.getBaseString(message);
+ var signature = this.getSignature(baseString);
+ OAuth.setParameter(message, "oauth_signature", signature);
+ return signature; // just in case someone's interested
+ }
+,
+ /** Set the key string for signing. */
+ initialize: function initialize(name, accessor) {
+ var consumerSecret;
+ if (accessor.accessorSecret != null
+ && name.length > 9
+ && name.substring(name.length-9) == "-Accessor")
+ {
+ consumerSecret = accessor.accessorSecret;
+ } else {
+ consumerSecret = accessor.consumerSecret;
+ }
+ this.key = OAuth.percentEncode(consumerSecret)
+ +"&"+ OAuth.percentEncode(accessor.tokenSecret);
+ }
+});
+
+/* SignatureMethod expects an accessor object to be like this:
+ {tokenSecret: "lakjsdflkj...", consumerSecret: "QOUEWRI..", accessorSecret: "xcmvzc..."}
+ The accessorSecret property is optional.
+ */
+// Class members:
+OAuth.setProperties(OAuth.SignatureMethod, // class members
+{
+ sign: function sign(message, accessor) {
+ var name = OAuth.getParameterMap(message.parameters).oauth_signature_method;
+ if (name == null || name == "") {
+ name = "HMAC-SHA1";
+ OAuth.setParameter(message, "oauth_signature_method", name);
+ }
+ OAuth.SignatureMethod.newMethod(name, accessor).sign(message);
+ }
+,
+ /** Instantiate a SignatureMethod for the given method name. */
+ newMethod: function newMethod(name, accessor) {
+ var impl = OAuth.SignatureMethod.REGISTERED[name];
+ if (impl != null) {
+ var method = new impl();
+ method.initialize(name, accessor);
+ return method;
+ }
+ var err = new Error("signature_method_rejected");
+ var acceptable = "";
+ for (var r in OAuth.SignatureMethod.REGISTERED) {
+ if (acceptable != "") acceptable += '&';
+ acceptable += OAuth.percentEncode(r);
+ }
+ err.oauth_acceptable_signature_methods = acceptable;
+ throw err;
+ }
+,
+ /** A map from signature method name to constructor. */
+ REGISTERED : {}
+,
+ /** Subsequently, the given constructor will be used for the named methods.
+ The constructor will be called with no parameters.
+ The resulting object should usually implement getSignature(baseString).
+ You can easily define such a constructor by calling makeSubclass, below.
+ */
+ registerMethodClass: function registerMethodClass(names, classConstructor) {
+ for (var n = 0; n < names.length; ++n) {
+ OAuth.SignatureMethod.REGISTERED[names[n]] = classConstructor;
+ }
+ }
+,
+ /** Create a subclass of OAuth.SignatureMethod, with the given getSignature function. */
+ makeSubclass: function makeSubclass(getSignatureFunction) {
+ var superClass = OAuth.SignatureMethod;
+ var subClass = function() {
+ superClass.call(this);
+ };
+ subClass.prototype = new superClass();
+ // Delete instance variables from prototype:
+ // delete subclass.prototype... There aren't any.
+ subClass.prototype.getSignature = getSignatureFunction;
+ subClass.prototype.constructor = subClass;
+ return subClass;
+ }
+,
+ getBaseString: function getBaseString(message) {
+ var URL = message.action;
+ var q = URL.indexOf('?');
+ var parameters;
+ if (q < 0) {
+ parameters = message.parameters;
+ } else {
+ // Combine the URL query string with the other parameters:
+ parameters = OAuth.decodeForm(URL.substring(q + 1));
+ var toAdd = OAuth.getParameterList(message.parameters);
+ for (var a = 0; a < toAdd.length; ++a) {
+ parameters.push(toAdd[a]);
+ }
+ }
+ return OAuth.percentEncode(message.method.toUpperCase())
+ +'&'+ OAuth.percentEncode(OAuth.SignatureMethod.normalizeUrl(URL))
+ +'&'+ OAuth.percentEncode(OAuth.SignatureMethod.normalizeParameters(parameters));
+ }
+,
+ normalizeUrl: function normalizeUrl(url) {
+ var uri = OAuth.SignatureMethod.parseUri(url);
+ var scheme = uri.protocol.toLowerCase();
+ var authority = uri.authority.toLowerCase();
+ var dropPort = (scheme == "http" && uri.port == 80)
+ || (scheme == "https" && uri.port == 443);
+ if (dropPort) {
+ // find the last : in the authority
+ var index = authority.lastIndexOf(":");
+ if (index >= 0) {
+ authority = authority.substring(0, index);
+ }
+ }
+ var path = uri.path;
+ if (!path) {
+ path = "/"; // conforms to RFC 2616 section 3.2.2
+ }
+ // we know that there is no query and no fragment here.
+ return scheme + "://" + authority + path;
+ }
+,
+ parseUri: function parseUri (str) {
+ /* This function was adapted from parseUri 1.2.1
+ http://stevenlevithan.com/demo/parseuri/js/assets/parseuri.js
+ */
+ var o = {key: ["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"],
+ parser: {strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/ }};
+ var m = o.parser.strict.exec(str);
+ var uri = {};
+ var i = 14;
+ while (i--) uri[o.key[i]] = m[i] || "";
+ return uri;
+ }
+,
+ normalizeParameters: function normalizeParameters(parameters) {
+ if (parameters == null) {
+ return "";
+ }
+ var list = OAuth.getParameterList(parameters);
+ var sortable = [];
+ for (var p = 0; p < list.length; ++p) {
+ var nvp = list[p];
+ if (nvp[0] != "oauth_signature") {
+ sortable.push([ OAuth.percentEncode(nvp[0])
+ + " " // because it comes before any character that can appear in a percentEncoded string.
+ + OAuth.percentEncode(nvp[1])
+ , nvp]);
+ }
+ }
+ sortable.sort(function(a,b) {
+ if (a[0] < b[0]) return -1;
+ if (a[0] > b[0]) return 1;
+ return 0;
+ });
+ var sorted = [];
+ for (var s = 0; s < sortable.length; ++s) {
+ sorted.push(sortable[s][1]);
+ }
+ return OAuth.formEncode(sorted);
+ }
+});
+
+OAuth.SignatureMethod.registerMethodClass(["PLAINTEXT", "PLAINTEXT-Accessor"],
+ OAuth.SignatureMethod.makeSubclass(
+ function getSignature(baseString) {
+ return this.key;
+ }
+ ));
+
+OAuth.SignatureMethod.registerMethodClass(["HMAC-SHA1", "HMAC-SHA1-Accessor"],
+ OAuth.SignatureMethod.makeSubclass(
+ function getSignature(baseString) {
+ b64pad = '=';
+ var signature = b64_hmac_sha1(this.key, baseString);
+ return signature;
+ }
+ ));
diff --git a/test/javascript/replicator_db_inc.js b/test/javascript/replicator_db_inc.js
new file mode 100644
index 000000000..23f858768
--- /dev/null
+++ b/test/javascript/replicator_db_inc.js
@@ -0,0 +1,96 @@
+// 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.
+
+var replicator_db = {};
+replicator_db.wait_rep_doc = 500; // number of millisecs to wait after saving a Rep Doc
+replicator_db.dbA = new CouchDB("test_suite_rep_db_a", {"X-Couch-Full-Commit":"false"});
+replicator_db.dbB = new CouchDB("test_suite_rep_db_b", {"X-Couch-Full-Commit":"false"});
+replicator_db.repDb = new CouchDB("test_suite_rep_db", {"X-Couch-Full-Commit":"false"});
+replicator_db.usersDb = new CouchDB("test_suite_auth", {"X-Couch-Full-Commit":"false"});
+
+replicator_db.docs1 = [
+ {
+ _id: "foo1",
+ value: 11
+ },
+ {
+ _id: "foo2",
+ value: 22
+ },
+ {
+ _id: "foo3",
+ value: 33
+ }
+];
+
+replicator_db.waitForRep = function waitForSeq(repDb, repDoc, state) {
+ var newRep,
+ t0 = new Date(),
+ t1,
+ ms = 3000;
+
+ do {
+ newRep = repDb.open(repDoc._id);
+ t1 = new Date();
+ } while (((t1 - t0) <= ms) && newRep._replication_state !== state);
+}
+
+replicator_db.waitForSeq = function waitForSeq(sourceDb, targetDb) {
+ var targetSeq,
+ sourceSeq = sourceDb.info().update_seq,
+ t0 = new Date(),
+ t1,
+ ms = 3000;
+
+ do {
+ targetSeq = targetDb.info().update_seq;
+ t1 = new Date();
+ } while (((t1 - t0) <= ms) && targetSeq < sourceSeq);
+}
+
+replicator_db.waitForDocPos = function waitForDocPos(db, docId, pos) {
+ var doc, curPos, t0, t1,
+ maxWait = 3000;
+
+ doc = db.open(docId);
+ curPos = Number(doc._rev.split("-", 1));
+ t0 = t1 = new Date();
+
+ while ((curPos < pos) && ((t1 - t0) <= maxWait)) {
+ doc = db.open(docId);
+ curPos = Number(doc._rev.split("-", 1));
+ t1 = new Date();
+ }
+
+ return doc;
+}
+
+replicator_db.wait = function wait(ms) {
+ var t0 = new Date(), t1;
+ do {
+ CouchDB.request("GET", "/");
+ t1 = new Date();
+ } while ((t1 - t0) <= ms);
+}
+
+
+replicator_db.populate_db = function populate_db(db, docs) {
+ if (db.name !== replicator_db.usersDb.name) {
+ db.deleteDb();
+ db.createDb();
+ }
+ for (var i = 0; i < docs.length; i++) {
+ var d = docs[i];
+ delete d._rev;
+ T(db.save(d).ok);
+ }
+} \ No newline at end of file
diff --git a/test/javascript/run b/test/javascript/run
index 883fd37c1..3ba83b728 100755
--- a/test/javascript/run
+++ b/test/javascript/run
@@ -33,12 +33,12 @@ N = 3
COUCHJS = "src/couch/priv/couchjs"
SCRIPTS = """
- share/www/script/json2.js
- share/www/script/sha1.js
- share/www/script/oauth.js
- share/www/script/couch.js
- share/www/script/replicator_db_inc.js
- share/www/script/couch_test_runner.js
+ test/javascript/json2.js
+ test/javascript/sha1.js
+ test/javascript/oauth.js
+ test/javascript/couch.js
+ test/javascript/replicator_db_inc.js
+ test/javascript/couch_test_runner.js
test/javascript/couch_http.js
test/javascript/test_setup.js
""".split()
@@ -107,14 +107,14 @@ def main():
tests = []
if not len(args):
- args = ["share/www/script/test"]
+ args = ["test/javascript/tests"]
for name in args:
if os.path.isdir(name):
tests.extend(glob.glob(os.path.join(name, "*.js")))
elif os.path.isfile(name):
tests.append(name)
else:
- pname = os.path.join("share/www/script/test", name)
+ pname = os.path.join("test/javascript/tests", name)
if os.path.isfile(pname):
tests.append(pname)
elif os.path.isfile(pname + ".js"):
diff --git a/test/javascript/sha1.js b/test/javascript/sha1.js
new file mode 100644
index 000000000..ee73a6341
--- /dev/null
+++ b/test/javascript/sha1.js
@@ -0,0 +1,202 @@
+/*
+ * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined
+ * in FIPS PUB 180-1
+ * Version 2.1a Copyright Paul Johnston 2000 - 2002.
+ * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
+ * Distributed under the BSD License
+ * See http://pajhome.org.uk/crypt/md5 for details.
+ */
+
+/*
+ * Configurable variables. You may need to tweak these to be compatible with
+ * the server-side, but the defaults work in most cases.
+ */
+var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */
+var b64pad = "="; /* base-64 pad character. "=" for strict RFC compliance */
+var chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode */
+
+/*
+ * These are the functions you'll usually want to call
+ * They take string arguments and return either hex or base-64 encoded strings
+ */
+function hex_sha1(s){return binb2hex(core_sha1(str2binb(s),s.length * chrsz));}
+function b64_sha1(s){return binb2b64(core_sha1(str2binb(s),s.length * chrsz));}
+function str_sha1(s){return binb2str(core_sha1(str2binb(s),s.length * chrsz));}
+function hex_hmac_sha1(key, data){ return binb2hex(core_hmac_sha1(key, data));}
+function b64_hmac_sha1(key, data){ return binb2b64(core_hmac_sha1(key, data));}
+function str_hmac_sha1(key, data){ return binb2str(core_hmac_sha1(key, data));}
+
+/*
+ * Perform a simple self-test to see if the VM is working
+ */
+function sha1_vm_test()
+{
+ return hex_sha1("abc") == "a9993e364706816aba3e25717850c26c9cd0d89d";
+}
+
+/*
+ * Calculate the SHA-1 of an array of big-endian words, and a bit length
+ */
+function core_sha1(x, len)
+{
+ /* append padding */
+ x[len >> 5] |= 0x80 << (24 - len % 32);
+ x[((len + 64 >> 9) << 4) + 15] = len;
+
+ var w = Array(80);
+ var a = 1732584193;
+ var b = -271733879;
+ var c = -1732584194;
+ var d = 271733878;
+ var e = -1009589776;
+
+ for(var i = 0; i < x.length; i += 16)
+ {
+ var olda = a;
+ var oldb = b;
+ var oldc = c;
+ var oldd = d;
+ var olde = e;
+
+ for(var j = 0; j < 80; j++)
+ {
+ if(j < 16) w[j] = x[i + j];
+ else w[j] = rol(w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16], 1);
+ var t = safe_add(safe_add(rol(a, 5), sha1_ft(j, b, c, d)),
+ safe_add(safe_add(e, w[j]), sha1_kt(j)));
+ e = d;
+ d = c;
+ c = rol(b, 30);
+ b = a;
+ a = t;
+ }
+
+ a = safe_add(a, olda);
+ b = safe_add(b, oldb);
+ c = safe_add(c, oldc);
+ d = safe_add(d, oldd);
+ e = safe_add(e, olde);
+ }
+ return Array(a, b, c, d, e);
+
+}
+
+/*
+ * Perform the appropriate triplet combination function for the current
+ * iteration
+ */
+function sha1_ft(t, b, c, d)
+{
+ if(t < 20) return (b & c) | ((~b) & d);
+ if(t < 40) return b ^ c ^ d;
+ if(t < 60) return (b & c) | (b & d) | (c & d);
+ return b ^ c ^ d;
+}
+
+/*
+ * Determine the appropriate additive constant for the current iteration
+ */
+function sha1_kt(t)
+{
+ return (t < 20) ? 1518500249 : (t < 40) ? 1859775393 :
+ (t < 60) ? -1894007588 : -899497514;
+}
+
+/*
+ * Calculate the HMAC-SHA1 of a key and some data
+ */
+function core_hmac_sha1(key, data)
+{
+ var bkey = str2binb(key);
+ if(bkey.length > 16) bkey = core_sha1(bkey, key.length * chrsz);
+
+ var ipad = Array(16), opad = Array(16);
+ for(var i = 0; i < 16; i++)
+ {
+ ipad[i] = bkey[i] ^ 0x36363636;
+ opad[i] = bkey[i] ^ 0x5C5C5C5C;
+ }
+
+ var hash = core_sha1(ipad.concat(str2binb(data)), 512 + data.length * chrsz);
+ return core_sha1(opad.concat(hash), 512 + 160);
+}
+
+/*
+ * Add integers, wrapping at 2^32. This uses 16-bit operations internally
+ * to work around bugs in some JS interpreters.
+ */
+function safe_add(x, y)
+{
+ var lsw = (x & 0xFFFF) + (y & 0xFFFF);
+ var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
+ return (msw << 16) | (lsw & 0xFFFF);
+}
+
+/*
+ * Bitwise rotate a 32-bit number to the left.
+ */
+function rol(num, cnt)
+{
+ return (num << cnt) | (num >>> (32 - cnt));
+}
+
+/*
+ * Convert an 8-bit or 16-bit string to an array of big-endian words
+ * In 8-bit function, characters >255 have their hi-byte silently ignored.
+ */
+function str2binb(str)
+{
+ var bin = Array();
+ var mask = (1 << chrsz) - 1;
+ for(var i = 0; i < str.length * chrsz; i += chrsz)
+ bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (32 - chrsz - i%32);
+ return bin;
+}
+
+/*
+ * Convert an array of big-endian words to a string
+ */
+function binb2str(bin)
+{
+ var str = "";
+ var mask = (1 << chrsz) - 1;
+ for(var i = 0; i < bin.length * 32; i += chrsz)
+ str += String.fromCharCode((bin[i>>5] >>> (32 - chrsz - i%32)) & mask);
+ return str;
+}
+
+/*
+ * Convert an array of big-endian words to a hex string.
+ */
+function binb2hex(binarray)
+{
+ var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
+ var str = "";
+ for(var i = 0; i < binarray.length * 4; i++)
+ {
+ str += hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8+4)) & 0xF) +
+ hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8 )) & 0xF);
+ }
+ return str;
+}
+
+/*
+ * Convert an array of big-endian words to a base-64 string
+ */
+function binb2b64(binarray)
+{
+ var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+ var str = "";
+ for(var i = 0; i < binarray.length * 4; i += 3)
+ {
+ var triplet = (((binarray[i >> 2] >> 8 * (3 - i %4)) & 0xFF) << 16)
+ | (((binarray[i+1 >> 2] >> 8 * (3 - (i+1)%4)) & 0xFF) << 8 )
+ | ((binarray[i+2 >> 2] >> 8 * (3 - (i+2)%4)) & 0xFF);
+ for(var j = 0; j < 4; j++)
+ {
+ if(i * 8 + j * 6 > binarray.length * 32) str += b64pad;
+ else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F);
+ }
+ }
+ return str;
+}
diff --git a/share/www/script/test/all_docs.js b/test/javascript/tests/all_docs.js
index 66ad880f5..66ad880f5 100644
--- a/share/www/script/test/all_docs.js
+++ b/test/javascript/tests/all_docs.js
diff --git a/share/www/script/test/attachment_names.js b/test/javascript/tests/attachment_names.js
index c9a5fcce3..c9a5fcce3 100644
--- a/share/www/script/test/attachment_names.js
+++ b/test/javascript/tests/attachment_names.js
diff --git a/share/www/script/test/attachment_paths.js b/test/javascript/tests/attachment_paths.js
index 3f6ffb7cd..3f6ffb7cd 100644
--- a/share/www/script/test/attachment_paths.js
+++ b/test/javascript/tests/attachment_paths.js
diff --git a/share/www/script/test/attachment_ranges.js b/test/javascript/tests/attachment_ranges.js
index 7d9afb567..7d9afb567 100644
--- a/share/www/script/test/attachment_ranges.js
+++ b/test/javascript/tests/attachment_ranges.js
diff --git a/share/www/script/test/attachment_views.js b/test/javascript/tests/attachment_views.js
index b55aabea1..b55aabea1 100644
--- a/share/www/script/test/attachment_views.js
+++ b/test/javascript/tests/attachment_views.js
diff --git a/share/www/script/test/attachments.js b/test/javascript/tests/attachments.js
index 2fa08ee25..2fa08ee25 100644
--- a/share/www/script/test/attachments.js
+++ b/test/javascript/tests/attachments.js
diff --git a/share/www/script/test/attachments_multipart.js b/test/javascript/tests/attachments_multipart.js
index 6f924a7fb..6f924a7fb 100644
--- a/share/www/script/test/attachments_multipart.js
+++ b/test/javascript/tests/attachments_multipart.js
diff --git a/share/www/script/test/auth_cache.js b/test/javascript/tests/auth_cache.js
index 39b9887ea..39b9887ea 100644
--- a/share/www/script/test/auth_cache.js
+++ b/test/javascript/tests/auth_cache.js
diff --git a/share/www/script/test/basics.js b/test/javascript/tests/basics.js
index 993456c72..993456c72 100644
--- a/share/www/script/test/basics.js
+++ b/test/javascript/tests/basics.js
diff --git a/share/www/script/test/batch_save.js b/test/javascript/tests/batch_save.js
index a1b00192b..a1b00192b 100644
--- a/share/www/script/test/batch_save.js
+++ b/test/javascript/tests/batch_save.js
diff --git a/share/www/script/test/bulk_docs.js b/test/javascript/tests/bulk_docs.js
index 27a97c8a4..27a97c8a4 100644
--- a/share/www/script/test/bulk_docs.js
+++ b/test/javascript/tests/bulk_docs.js
diff --git a/share/www/script/test/changes.js b/test/javascript/tests/changes.js
index d5a42362a..d5a42362a 100644
--- a/share/www/script/test/changes.js
+++ b/test/javascript/tests/changes.js
diff --git a/share/www/script/test/coffee.js b/test/javascript/tests/coffee.js
index 93061248c..93061248c 100644
--- a/share/www/script/test/coffee.js
+++ b/test/javascript/tests/coffee.js
diff --git a/share/www/script/test/compact.js b/test/javascript/tests/compact.js
index 68c83b31b..68c83b31b 100644
--- a/share/www/script/test/compact.js
+++ b/test/javascript/tests/compact.js
diff --git a/share/www/script/test/config.js b/test/javascript/tests/config.js
index 37b339b99..37b339b99 100644
--- a/share/www/script/test/config.js
+++ b/test/javascript/tests/config.js
diff --git a/share/www/script/test/conflicts.js b/test/javascript/tests/conflicts.js
index 79266abb0..79266abb0 100644
--- a/share/www/script/test/conflicts.js
+++ b/test/javascript/tests/conflicts.js
diff --git a/share/www/script/test/content_negotiation.js b/test/javascript/tests/content_negotiation.js
index 36e7dfbd3..36e7dfbd3 100644
--- a/share/www/script/test/content_negotiation.js
+++ b/test/javascript/tests/content_negotiation.js
diff --git a/share/www/script/test/cookie_auth.js b/test/javascript/tests/cookie_auth.js
index 9b4bd6414..9b4bd6414 100644
--- a/share/www/script/test/cookie_auth.js
+++ b/test/javascript/tests/cookie_auth.js
diff --git a/share/www/script/test/copy_doc.js b/test/javascript/tests/copy_doc.js
index d59576145..d59576145 100644
--- a/share/www/script/test/copy_doc.js
+++ b/test/javascript/tests/copy_doc.js
diff --git a/share/www/script/test/delayed_commits.js b/test/javascript/tests/delayed_commits.js
index dbb072fbf..dbb072fbf 100644
--- a/share/www/script/test/delayed_commits.js
+++ b/test/javascript/tests/delayed_commits.js
diff --git a/share/www/script/test/design_docs.js b/test/javascript/tests/design_docs.js
index dd38858a6..dd38858a6 100644
--- a/share/www/script/test/design_docs.js
+++ b/test/javascript/tests/design_docs.js
diff --git a/share/www/script/test/design_options.js b/test/javascript/tests/design_options.js
index 05764e243..05764e243 100644
--- a/share/www/script/test/design_options.js
+++ b/test/javascript/tests/design_options.js
diff --git a/share/www/script/test/design_paths.js b/test/javascript/tests/design_paths.js
index 426a252cc..426a252cc 100644
--- a/share/www/script/test/design_paths.js
+++ b/test/javascript/tests/design_paths.js
diff --git a/share/www/script/test/erlang_views.js b/test/javascript/tests/erlang_views.js
index c6bc5d710..c6bc5d710 100644
--- a/share/www/script/test/erlang_views.js
+++ b/test/javascript/tests/erlang_views.js
diff --git a/share/www/script/test/etags_head.js b/test/javascript/tests/etags_head.js
index 63e299949..63e299949 100644
--- a/share/www/script/test/etags_head.js
+++ b/test/javascript/tests/etags_head.js
diff --git a/share/www/script/test/etags_views.js b/test/javascript/tests/etags_views.js
index 6d8e97b88..6d8e97b88 100644
--- a/share/www/script/test/etags_views.js
+++ b/test/javascript/tests/etags_views.js
diff --git a/share/www/script/test/form_submit.js b/test/javascript/tests/form_submit.js
index 710bf4746..710bf4746 100644
--- a/share/www/script/test/form_submit.js
+++ b/test/javascript/tests/form_submit.js
diff --git a/share/www/script/test/http.js b/test/javascript/tests/http.js
index 21c42a072..21c42a072 100644
--- a/share/www/script/test/http.js
+++ b/test/javascript/tests/http.js
diff --git a/share/www/script/test/invalid_docids.js b/test/javascript/tests/invalid_docids.js
index d0195b021..d0195b021 100644
--- a/share/www/script/test/invalid_docids.js
+++ b/test/javascript/tests/invalid_docids.js
diff --git a/share/www/script/test/jsonp.js b/test/javascript/tests/jsonp.js
index e4f4490bf..e4f4490bf 100644
--- a/share/www/script/test/jsonp.js
+++ b/test/javascript/tests/jsonp.js
diff --git a/share/www/script/test/large_docs.js b/test/javascript/tests/large_docs.js
index b84648b74..b84648b74 100644
--- a/share/www/script/test/large_docs.js
+++ b/test/javascript/tests/large_docs.js
diff --git a/share/www/script/test/list_views.js b/test/javascript/tests/list_views.js
index ece81ea00..ece81ea00 100644
--- a/share/www/script/test/list_views.js
+++ b/test/javascript/tests/list_views.js
diff --git a/share/www/script/test/lorem.txt b/test/javascript/tests/lorem.txt
index 0ef85bab8..0ef85bab8 100644
--- a/share/www/script/test/lorem.txt
+++ b/test/javascript/tests/lorem.txt
diff --git a/share/www/script/test/lorem_b64.txt b/test/javascript/tests/lorem_b64.txt
index 8a21d79e6..8a21d79e6 100644
--- a/share/www/script/test/lorem_b64.txt
+++ b/test/javascript/tests/lorem_b64.txt
diff --git a/share/www/script/test/lots_of_docs.js b/test/javascript/tests/lots_of_docs.js
index 2fe702b10..2fe702b10 100644
--- a/share/www/script/test/lots_of_docs.js
+++ b/test/javascript/tests/lots_of_docs.js
diff --git a/share/www/script/test/method_override.js b/test/javascript/tests/method_override.js
index 0bb4c61fb..0bb4c61fb 100644
--- a/share/www/script/test/method_override.js
+++ b/test/javascript/tests/method_override.js
diff --git a/share/www/script/test/multiple_rows.js b/test/javascript/tests/multiple_rows.js
index 4f6fcd3bf..4f6fcd3bf 100644
--- a/share/www/script/test/multiple_rows.js
+++ b/test/javascript/tests/multiple_rows.js
diff --git a/share/www/script/test/oauth_users_db.js b/test/javascript/tests/oauth_users_db.js
index b98069ed0..b98069ed0 100644
--- a/share/www/script/test/oauth_users_db.js
+++ b/test/javascript/tests/oauth_users_db.js
diff --git a/share/www/script/test/proxyauth.js b/test/javascript/tests/proxyauth.js
index 1677a6636..1677a6636 100644
--- a/share/www/script/test/proxyauth.js
+++ b/test/javascript/tests/proxyauth.js
diff --git a/share/www/script/test/purge.js b/test/javascript/tests/purge.js
index 29689137b..29689137b 100644
--- a/share/www/script/test/purge.js
+++ b/test/javascript/tests/purge.js
diff --git a/share/www/script/test/reader_acl.js b/test/javascript/tests/reader_acl.js
index ff770c728..ff770c728 100644
--- a/share/www/script/test/reader_acl.js
+++ b/test/javascript/tests/reader_acl.js
diff --git a/share/www/script/test/recreate_doc.js b/test/javascript/tests/recreate_doc.js
index f9723793b..f9723793b 100644
--- a/share/www/script/test/recreate_doc.js
+++ b/test/javascript/tests/recreate_doc.js
diff --git a/share/www/script/test/reduce.js b/test/javascript/tests/reduce.js
index 36e5cb7db..36e5cb7db 100644
--- a/share/www/script/test/reduce.js
+++ b/test/javascript/tests/reduce.js
diff --git a/share/www/script/test/reduce_builtin.js b/test/javascript/tests/reduce_builtin.js
index b3cc3cc70..b3cc3cc70 100644
--- a/share/www/script/test/reduce_builtin.js
+++ b/test/javascript/tests/reduce_builtin.js
diff --git a/share/www/script/test/reduce_false.js b/test/javascript/tests/reduce_false.js
index 699b258f9..699b258f9 100644
--- a/share/www/script/test/reduce_false.js
+++ b/test/javascript/tests/reduce_false.js
diff --git a/share/www/script/test/reduce_false_temp.js b/test/javascript/tests/reduce_false_temp.js
index d45f05b22..d45f05b22 100644
--- a/share/www/script/test/reduce_false_temp.js
+++ b/test/javascript/tests/reduce_false_temp.js
diff --git a/share/www/script/test/replication.js b/test/javascript/tests/replication.js
index 21a430452..21a430452 100644
--- a/share/www/script/test/replication.js
+++ b/test/javascript/tests/replication.js
diff --git a/share/www/script/test/replicator_db_bad_rep_id.js b/test/javascript/tests/replicator_db_bad_rep_id.js
index 285b863e7..285b863e7 100644
--- a/share/www/script/test/replicator_db_bad_rep_id.js
+++ b/test/javascript/tests/replicator_db_bad_rep_id.js
diff --git a/share/www/script/test/replicator_db_by_doc_id.js b/test/javascript/tests/replicator_db_by_doc_id.js
index 1e1a38521..1e1a38521 100644
--- a/share/www/script/test/replicator_db_by_doc_id.js
+++ b/test/javascript/tests/replicator_db_by_doc_id.js
diff --git a/share/www/script/test/replicator_db_compact_rep_db.js b/test/javascript/tests/replicator_db_compact_rep_db.js
index 0dea0ed66..0dea0ed66 100644
--- a/share/www/script/test/replicator_db_compact_rep_db.js
+++ b/test/javascript/tests/replicator_db_compact_rep_db.js
diff --git a/share/www/script/test/replicator_db_continuous.js b/test/javascript/tests/replicator_db_continuous.js
index a2b17d00a..a2b17d00a 100644
--- a/share/www/script/test/replicator_db_continuous.js
+++ b/test/javascript/tests/replicator_db_continuous.js
diff --git a/share/www/script/test/replicator_db_credential_delegation.js b/test/javascript/tests/replicator_db_credential_delegation.js
index 7dd9d1812..7dd9d1812 100644
--- a/share/www/script/test/replicator_db_credential_delegation.js
+++ b/test/javascript/tests/replicator_db_credential_delegation.js
diff --git a/share/www/script/test/replicator_db_field_validation.js b/test/javascript/tests/replicator_db_field_validation.js
index 167bdccb0..167bdccb0 100644
--- a/share/www/script/test/replicator_db_field_validation.js
+++ b/test/javascript/tests/replicator_db_field_validation.js
diff --git a/share/www/script/test/replicator_db_filtered.js b/test/javascript/tests/replicator_db_filtered.js
index 31c78a7c5..31c78a7c5 100644
--- a/share/www/script/test/replicator_db_filtered.js
+++ b/test/javascript/tests/replicator_db_filtered.js
diff --git a/share/www/script/test/replicator_db_identical.js b/test/javascript/tests/replicator_db_identical.js
index cdf4592be..cdf4592be 100644
--- a/share/www/script/test/replicator_db_identical.js
+++ b/test/javascript/tests/replicator_db_identical.js
diff --git a/share/www/script/test/replicator_db_identical_continuous.js b/test/javascript/tests/replicator_db_identical_continuous.js
index 240c531f5..240c531f5 100644
--- a/share/www/script/test/replicator_db_identical_continuous.js
+++ b/test/javascript/tests/replicator_db_identical_continuous.js
diff --git a/share/www/script/test/replicator_db_invalid_filter.js b/test/javascript/tests/replicator_db_invalid_filter.js
index 7b6df8296..7b6df8296 100644
--- a/share/www/script/test/replicator_db_invalid_filter.js
+++ b/test/javascript/tests/replicator_db_invalid_filter.js
diff --git a/share/www/script/test/replicator_db_security.js b/test/javascript/tests/replicator_db_security.js
index 7a2bfd18a..7a2bfd18a 100644
--- a/share/www/script/test/replicator_db_security.js
+++ b/test/javascript/tests/replicator_db_security.js
diff --git a/share/www/script/test/replicator_db_simple.js b/test/javascript/tests/replicator_db_simple.js
index f7acedb37..f7acedb37 100644
--- a/share/www/script/test/replicator_db_simple.js
+++ b/test/javascript/tests/replicator_db_simple.js
diff --git a/share/www/script/test/replicator_db_successive.js b/test/javascript/tests/replicator_db_successive.js
index 4898c3336..4898c3336 100644
--- a/share/www/script/test/replicator_db_successive.js
+++ b/test/javascript/tests/replicator_db_successive.js
diff --git a/share/www/script/test/replicator_db_survives.js b/test/javascript/tests/replicator_db_survives.js
index 38273ca93..38273ca93 100644
--- a/share/www/script/test/replicator_db_survives.js
+++ b/test/javascript/tests/replicator_db_survives.js
diff --git a/share/www/script/test/replicator_db_swap_rep_db.js b/test/javascript/tests/replicator_db_swap_rep_db.js
index 04f4e9fa2..04f4e9fa2 100644
--- a/share/www/script/test/replicator_db_swap_rep_db.js
+++ b/test/javascript/tests/replicator_db_swap_rep_db.js
diff --git a/share/www/script/test/replicator_db_update_security.js b/test/javascript/tests/replicator_db_update_security.js
index 465151468..465151468 100644
--- a/share/www/script/test/replicator_db_update_security.js
+++ b/test/javascript/tests/replicator_db_update_security.js
diff --git a/share/www/script/test/replicator_db_user_ctx.js b/test/javascript/tests/replicator_db_user_ctx.js
index 570fc7d55..570fc7d55 100644
--- a/share/www/script/test/replicator_db_user_ctx.js
+++ b/test/javascript/tests/replicator_db_user_ctx.js
diff --git a/share/www/script/test/replicator_db_write_auth.js b/test/javascript/tests/replicator_db_write_auth.js
index 697abf340..697abf340 100644
--- a/share/www/script/test/replicator_db_write_auth.js
+++ b/test/javascript/tests/replicator_db_write_auth.js
diff --git a/share/www/script/test/rev_stemming.js b/test/javascript/tests/rev_stemming.js
index 954da79e0..954da79e0 100644
--- a/share/www/script/test/rev_stemming.js
+++ b/test/javascript/tests/rev_stemming.js
diff --git a/share/www/script/test/rewrite.js b/test/javascript/tests/rewrite.js
index 5c56fa503..5c56fa503 100644
--- a/share/www/script/test/rewrite.js
+++ b/test/javascript/tests/rewrite.js
diff --git a/share/www/script/test/security_validation.js b/test/javascript/tests/security_validation.js
index 14e5d040c..14e5d040c 100644
--- a/share/www/script/test/security_validation.js
+++ b/test/javascript/tests/security_validation.js
diff --git a/share/www/script/test/show_documents.js b/test/javascript/tests/show_documents.js
index 618925f7d..618925f7d 100644
--- a/share/www/script/test/show_documents.js
+++ b/test/javascript/tests/show_documents.js
diff --git a/share/www/script/test/stats.js b/test/javascript/tests/stats.js
index 87440b36e..87440b36e 100644
--- a/share/www/script/test/stats.js
+++ b/test/javascript/tests/stats.js
diff --git a/share/www/script/test/update_documents.js b/test/javascript/tests/update_documents.js
index bdb7a9981..bdb7a9981 100644
--- a/share/www/script/test/update_documents.js
+++ b/test/javascript/tests/update_documents.js
diff --git a/share/www/script/test/users_db.js b/test/javascript/tests/users_db.js
index 56dae6bb8..56dae6bb8 100644
--- a/share/www/script/test/users_db.js
+++ b/test/javascript/tests/users_db.js
diff --git a/share/www/script/test/users_db_security.js b/test/javascript/tests/users_db_security.js
index f2ca8bc7e..f2ca8bc7e 100644
--- a/share/www/script/test/users_db_security.js
+++ b/test/javascript/tests/users_db_security.js
diff --git a/share/www/script/test/utf8.js b/test/javascript/tests/utf8.js
index 04f6313ab..04f6313ab 100644
--- a/share/www/script/test/utf8.js
+++ b/test/javascript/tests/utf8.js
diff --git a/share/www/script/test/uuids.js b/test/javascript/tests/uuids.js
index d304c4e95..d304c4e95 100644
--- a/share/www/script/test/uuids.js
+++ b/test/javascript/tests/uuids.js
diff --git a/share/www/script/test/view_collation.js b/test/javascript/tests/view_collation.js
index b01a5c506..b01a5c506 100644
--- a/share/www/script/test/view_collation.js
+++ b/test/javascript/tests/view_collation.js
diff --git a/share/www/script/test/view_collation_raw.js b/test/javascript/tests/view_collation_raw.js
index 779f7eb82..779f7eb82 100644
--- a/share/www/script/test/view_collation_raw.js
+++ b/test/javascript/tests/view_collation_raw.js
diff --git a/share/www/script/test/view_compaction.js b/test/javascript/tests/view_compaction.js
index 35d627696..35d627696 100644
--- a/share/www/script/test/view_compaction.js
+++ b/test/javascript/tests/view_compaction.js
diff --git a/share/www/script/test/view_conflicts.js b/test/javascript/tests/view_conflicts.js
index 96f97d569..96f97d569 100644
--- a/share/www/script/test/view_conflicts.js
+++ b/test/javascript/tests/view_conflicts.js
diff --git a/share/www/script/test/view_errors.js b/test/javascript/tests/view_errors.js
index e8bd08e48..e8bd08e48 100644
--- a/share/www/script/test/view_errors.js
+++ b/test/javascript/tests/view_errors.js
diff --git a/share/www/script/test/view_include_docs.js b/test/javascript/tests/view_include_docs.js
index dab79b824..dab79b824 100644
--- a/share/www/script/test/view_include_docs.js
+++ b/test/javascript/tests/view_include_docs.js
diff --git a/share/www/script/test/view_multi_key_all_docs.js b/test/javascript/tests/view_multi_key_all_docs.js
index 7c7f6f894..7c7f6f894 100644
--- a/share/www/script/test/view_multi_key_all_docs.js
+++ b/test/javascript/tests/view_multi_key_all_docs.js
diff --git a/share/www/script/test/view_multi_key_design.js b/test/javascript/tests/view_multi_key_design.js
index a84d07a49..a84d07a49 100644
--- a/share/www/script/test/view_multi_key_design.js
+++ b/test/javascript/tests/view_multi_key_design.js
diff --git a/share/www/script/test/view_multi_key_temp.js b/test/javascript/tests/view_multi_key_temp.js
index 3c0540958..3c0540958 100644
--- a/share/www/script/test/view_multi_key_temp.js
+++ b/test/javascript/tests/view_multi_key_temp.js
diff --git a/share/www/script/test/view_offsets.js b/test/javascript/tests/view_offsets.js
index 464a1ae29..464a1ae29 100644
--- a/share/www/script/test/view_offsets.js
+++ b/test/javascript/tests/view_offsets.js
diff --git a/share/www/script/test/view_pagination.js b/test/javascript/tests/view_pagination.js
index ed3a7ee19..ed3a7ee19 100644
--- a/share/www/script/test/view_pagination.js
+++ b/test/javascript/tests/view_pagination.js
diff --git a/share/www/script/test/view_sandboxing.js b/test/javascript/tests/view_sandboxing.js
index 5c73c5ae0..5c73c5ae0 100644
--- a/share/www/script/test/view_sandboxing.js
+++ b/test/javascript/tests/view_sandboxing.js
diff --git a/share/www/script/test/view_update_seq.js b/test/javascript/tests/view_update_seq.js
index df92b11d6..df92b11d6 100644
--- a/share/www/script/test/view_update_seq.js
+++ b/test/javascript/tests/view_update_seq.js