diff options
author | Jan Lehnardt <jan@apache.org> | 2014-10-10 20:46:08 +0200 |
---|---|---|
committer | Jan Lehnardt <jan@apache.org> | 2014-11-14 18:22:30 +0100 |
commit | e2d9c9b1987831089c50a657963c483adb6ee900 (patch) | |
tree | f116bf71cddb2d96c14de72bd5a380a72b9f6b7c | |
parent | 6de6ca673c082f8c2c093e76f2834407b1ab0bed (diff) | |
download | couchdb-e2d9c9b1987831089c50a657963c483adb6ee900.tar.gz |
Move JS tests to test/javascript/tests
-rw-r--r-- | share/www/script/test/oauth.js | 294 | ||||
-rw-r--r-- | test/javascript/couch.js | 520 | ||||
-rw-r--r-- | test/javascript/couch_test_runner.js | 465 | ||||
-rw-r--r-- | test/javascript/json2.js | 482 | ||||
-rw-r--r-- | test/javascript/oauth.js | 511 | ||||
-rw-r--r-- | test/javascript/replicator_db_inc.js | 96 | ||||
-rwxr-xr-x | test/javascript/run | 16 | ||||
-rw-r--r-- | test/javascript/sha1.js | 202 | ||||
-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 ' '), + 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 |