couchdb-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From j..@apache.org
Subject [4/4] couchdb commit: updated refs/heads/goodbye-futon to 4bf97fc
Date Fri, 10 Oct 2014 19:24:24 GMT
Re-add JS test files


Project: http://git-wip-us.apache.org/repos/asf/couchdb/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb/commit/4bf97fc9
Tree: http://git-wip-us.apache.org/repos/asf/couchdb/tree/4bf97fc9
Diff: http://git-wip-us.apache.org/repos/asf/couchdb/diff/4bf97fc9

Branch: refs/heads/goodbye-futon
Commit: 4bf97fc993bf9686ec2d031b65c9d21418bc8424
Parents: e72de10
Author: Jan Lehnardt <jan@apache.org>
Authored: Fri Oct 10 21:23:30 2014 +0200
Committer: Jan Lehnardt <jan@apache.org>
Committed: Fri Oct 10 21:23:37 2014 +0200

----------------------------------------------------------------------
 share/test/couch.js             | 520 ++++++++++++++++++++++++
 share/test/couch_http.js        |  73 ++++
 share/test/couch_test_runner.js | 465 +++++++++++++++++++++
 share/test/json2.js             | 482 ++++++++++++++++++++++
 share/test/oauth.js             | 763 ++++++++++++++++++++++-------------
 share/test/replicator_db_inc.js |  96 +++++
 share/test/sha1.js              | 202 ++++++++++
 share/test/test_setup.js        |  88 ++++
 8 files changed, 2416 insertions(+), 273 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb/blob/4bf97fc9/share/test/couch.js
----------------------------------------------------------------------
diff --git a/share/test/couch.js b/share/test/couch.js
new file mode 100644
index 0000000..7e4eeed
--- /dev/null
+++ b/share/test/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;

http://git-wip-us.apache.org/repos/asf/couchdb/blob/4bf97fc9/share/test/couch_http.js
----------------------------------------------------------------------
diff --git a/share/test/couch_http.js b/share/test/couch_http.js
new file mode 100644
index 0000000..c44ce28
--- /dev/null
+++ b/share/test/couch_http.js
@@ -0,0 +1,73 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+(function() {
+  if(typeof(CouchHTTP) != "undefined") {
+    CouchHTTP.prototype.open = function(method, url, async) {
+      if(!/^\s*http:\/\//.test(url)) {
+        if(/^\//.test(url)) {
+          // The couch.uri file (base_url) has a trailing slash
+          url = this.base_url + url.slice(1);
+        } else {
+          url = this.base_url + url;
+        }
+      }
+      
+      return this._open(method, url, async);
+    };
+    
+    CouchHTTP.prototype.setRequestHeader = function(name, value) {
+      // Drop content-length headers because cURL will set it for us
+      // based on body length
+      if(name.toLowerCase().replace(/^\s+|\s+$/g, '') != "content-length") {
+        this._setRequestHeader(name, value);
+      }
+    }
+    
+    CouchHTTP.prototype.send = function(body) {
+      this._send(body || "");
+      var headers = {};
+      this._headers.forEach(function(hdr) {
+          var pair = hdr.split(":");
+          var name = pair.shift();
+          headers[name] = pair.join(":").replace(/^\s+|\s+$/g, "");
+      });
+      this.headers = headers;
+    };
+
+    CouchHTTP.prototype.getResponseHeader = function(name) {
+      for(var hdr in this.headers) {
+        if(hdr.toLowerCase() == name.toLowerCase()) {
+          return this.headers[hdr];
+        }
+      }
+      return null;
+    };
+  }
+})();
+
+CouchDB.urlPrefix = "";
+CouchDB.newXhr = function() {
+  return new CouchHTTP();
+};
+
+CouchDB.xhrheader = function(xhr, header) {
+  if(typeof(xhr) == "CouchHTTP") {
+    return xhr.getResponseHeader(header);
+  } else {
+    return xhr.headers[header];
+  }
+}
+
+CouchDB.xhrbody = function(xhr) {
+  return xhr.responseText || xhr.body;
+}

http://git-wip-us.apache.org/repos/asf/couchdb/blob/4bf97fc9/share/test/couch_test_runner.js
----------------------------------------------------------------------
diff --git a/share/test/couch_test_runner.js b/share/test/couch_test_runner.js
new file mode 100644
index 0000000..efc4dc2
--- /dev/null
+++ b/share/test/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;
+};

http://git-wip-us.apache.org/repos/asf/couchdb/blob/4bf97fc9/share/test/json2.js
----------------------------------------------------------------------
diff --git a/share/test/json2.js b/share/test/json2.js
new file mode 100644
index 0000000..a1a3b17
--- /dev/null
+++ b/share/test/json2.js
@@ -0,0 +1,482 @@
+/*
+    http://www.JSON.org/json2.js
+    2010-03-20
+
+    Public Domain.
+
+    NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
+
+    See http://www.JSON.org/js.html
+
+
+    This code should be minified before deployment.
+    See http://javascript.crockford.com/jsmin.html
+
+    USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
+    NOT CONTROL.
+
+
+    This file creates a global JSON object containing two methods: stringify
+    and parse.
+
+        JSON.stringify(value, replacer, space)
+            value       any JavaScript value, usually an object or array.
+
+            replacer    an optional parameter that determines how object
+                        values are stringified for objects. It can be a
+                        function or an array of strings.
+
+            space       an optional parameter that specifies the indentation
+                        of nested structures. If it is omitted, the text will
+                        be packed without extra whitespace. If it is a number,
+                        it will specify the number of spaces to indent at each
+                        level. If it is a string (such as '\t' or '&nbsp;'),
+                        it contains the characters used to indent at each level.
+
+            This method produces a JSON text from a JavaScript value.
+
+            When an object value is found, if the object contains a toJSON
+            method, its toJSON method will be called and the result will be
+            stringified. A toJSON method does not serialize: it returns the
+            value represented by the name/value pair that should be serialized,
+            or undefined if nothing should be serialized. The toJSON method
+            will be passed the key associated with the value, and this will be
+            bound to the value
+
+            For example, this would serialize Dates as ISO strings.
+
+                Date.prototype.toJSON = function (key) {
+                    function f(n) {
+                        // Format integers to have at least two digits.
+                        return n < 10 ? '0' + n : n;
+                    }
+
+                    return this.getUTCFullYear()   + '-' +
+                         f(this.getUTCMonth() + 1) + '-' +
+                         f(this.getUTCDate())      + 'T' +
+                         f(this.getUTCHours())     + ':' +
+                         f(this.getUTCMinutes())   + ':' +
+                         f(this.getUTCSeconds())   + 'Z';
+                };
+
+            You can provide an optional replacer method. It will be passed the
+            key and value of each member, with this bound to the containing
+            object. The value that is returned from your method will be
+            serialized. If your method returns undefined, then the member will
+            be excluded from the serialization.
+
+            If the replacer parameter is an array of strings, then it will be
+            used to select the members to be serialized. It filters the results
+            such that only members with keys listed in the replacer array are
+            stringified.
+
+            Values that do not have JSON representations, such as undefined or
+            functions, will not be serialized. Such values in objects will be
+            dropped; in arrays they will be replaced with null. You can use
+            a replacer function to replace those with JSON values.
+            JSON.stringify(undefined) returns undefined.
+
+            The optional space parameter produces a stringification of the
+            value that is filled with line breaks and indentation to make it
+            easier to read.
+
+            If the space parameter is a non-empty string, then that string will
+            be used for indentation. If the space parameter is a number, then
+            the indentation will be that many spaces.
+
+            Example:
+
+            text = JSON.stringify(['e', {pluribus: 'unum'}]);
+            // text is '["e",{"pluribus":"unum"}]'
+
+
+            text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
+            // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
+
+            text = JSON.stringify([new Date()], function (key, value) {
+                return this[key] instanceof Date ?
+                    'Date(' + this[key] + ')' : value;
+            });
+            // text is '["Date(---current time---)"]'
+
+
+        JSON.parse(text, reviver)
+            This method parses a JSON text to produce an object or array.
+            It can throw a SyntaxError exception.
+
+            The optional reviver parameter is a function that can filter and
+            transform the results. It receives each of the keys and values,
+            and its return value is used instead of the original value.
+            If it returns what it received, then the structure is not modified.
+            If it returns undefined then the member is deleted.
+
+            Example:
+
+            // Parse the text. Values that look like ISO date strings will
+            // be converted to Date objects.
+
+            myData = JSON.parse(text, function (key, value) {
+                var a;
+                if (typeof value === 'string') {
+                    a =
+/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
+                    if (a) {
+                        return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
+                            +a[5], +a[6]));
+                    }
+                }
+                return value;
+            });
+
+            myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
+                var d;
+                if (typeof value === 'string' &&
+                        value.slice(0, 5) === 'Date(' &&
+                        value.slice(-1) === ')') {
+                    d = new Date(value.slice(5, -1));
+                    if (d) {
+                        return d;
+                    }
+                }
+                return value;
+            });
+
+
+    This is a reference implementation. You are free to copy, modify, or
+    redistribute.
+*/
+
+/*jslint evil: true, strict: false */
+
+/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
+    call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
+    getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
+    lastIndex, length, parse, prototype, push, replace, slice, stringify,
+    test, toJSON, toString, valueOf
+*/
+
+
+// Create a JSON object only if one does not already exist. We create the
+// methods in a closure to avoid creating global variables.
+
+if (!this.JSON) {
+    this.JSON = {};
+}
+
+(function () {
+
+    function f(n) {
+        // Format integers to have at least two digits.
+        return n < 10 ? '0' + n : n;
+    }
+
+    if (typeof Date.prototype.toJSON !== 'function') {
+
+        Date.prototype.toJSON = function (key) {
+
+            return isFinite(this.valueOf()) ?
+                   this.getUTCFullYear()   + '-' +
+                 f(this.getUTCMonth() + 1) + '-' +
+                 f(this.getUTCDate())      + 'T' +
+                 f(this.getUTCHours())     + ':' +
+                 f(this.getUTCMinutes())   + ':' +
+                 f(this.getUTCSeconds())   + 'Z' : null;
+        };
+
+        String.prototype.toJSON =
+        Number.prototype.toJSON =
+        Boolean.prototype.toJSON = function (key) {
+            return this.valueOf();
+        };
+    }
+
+    var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+        escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+        gap,
+        indent,
+        meta = {    // table of character substitutions
+            '\b': '\\b',
+            '\t': '\\t',
+            '\n': '\\n',
+            '\f': '\\f',
+            '\r': '\\r',
+            '"' : '\\"',
+            '\\': '\\\\'
+        },
+        rep;
+
+
+    function quote(string) {
+
+// If the string contains no control characters, no quote characters, and no
+// backslash characters, then we can safely slap some quotes around it.
+// Otherwise we must also replace the offending characters with safe escape
+// sequences.
+
+        escapable.lastIndex = 0;
+        return escapable.test(string) ?
+            '"' + string.replace(escapable, function (a) {
+                var c = meta[a];
+                return typeof c === 'string' ? c :
+                    '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+            }) + '"' :
+            '"' + string + '"';
+    }
+
+
+    function str(key, holder) {
+
+// Produce a string from holder[key].
+
+        var i,          // The loop counter.
+            k,          // The member key.
+            v,          // The member value.
+            length,
+            mind = gap,
+            partial,
+            value = holder[key];
+
+// If the value has a toJSON method, call it to obtain a replacement value.
+
+        if (value && typeof value === 'object' &&
+                typeof value.toJSON === 'function') {
+            value = value.toJSON(key);
+        }
+
+// If we were called with a replacer function, then call the replacer to
+// obtain a replacement value.
+
+        if (typeof rep === 'function') {
+            value = rep.call(holder, key, value);
+        }
+
+// What happens next depends on the value's type.
+
+        switch (typeof value) {
+        case 'string':
+            return quote(value);
+
+        case 'number':
+
+// JSON numbers must be finite. Encode non-finite numbers as null.
+
+            return isFinite(value) ? String(value) : 'null';
+
+        case 'boolean':
+        case 'null':
+
+// If the value is a boolean or null, convert it to a string. Note:
+// typeof null does not produce 'null'. The case is included here in
+// the remote chance that this gets fixed someday.
+
+            return String(value);
+
+// If the type is 'object', we might be dealing with an object or an array or
+// null.
+
+        case 'object':
+
+// Due to a specification blunder in ECMAScript, typeof null is 'object',
+// so watch out for that case.
+
+            if (!value) {
+                return 'null';
+            }
+
+// Make an array to hold the partial results of stringifying this object value.
+
+            gap += indent;
+            partial = [];
+
+// Is the value an array?
+
+            if (Object.prototype.toString.apply(value) === '[object Array]') {
+
+// The value is an array. Stringify every element. Use null as a placeholder
+// for non-JSON values.
+
+                length = value.length;
+                for (i = 0; i < length; i += 1) {
+                    partial[i] = str(i, value) || 'null';
+                }
+
+// Join all of the elements together, separated with commas, and wrap them in
+// brackets.
+
+                v = partial.length === 0 ? '[]' :
+                    gap ? '[\n' + gap +
+                            partial.join(',\n' + gap) + '\n' +
+                                mind + ']' :
+                          '[' + partial.join(',') + ']';
+                gap = mind;
+                return v;
+            }
+
+// If the replacer is an array, use it to select the members to be stringified.
+
+            if (rep && typeof rep === 'object') {
+                length = rep.length;
+                for (i = 0; i < length; i += 1) {
+                    k = rep[i];
+                    if (typeof k === 'string') {
+                        v = str(k, value);
+                        if (v) {
+                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
+                        }
+                    }
+                }
+            } else {
+
+// Otherwise, iterate through all of the keys in the object.
+
+                for (k in value) {
+                    if (Object.hasOwnProperty.call(value, k)) {
+                        v = str(k, value);
+                        if (v) {
+                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
+                        }
+                    }
+                }
+            }
+
+// Join all of the member texts together, separated with commas,
+// and wrap them in braces.
+
+            v = partial.length === 0 ? '{}' :
+                gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
+                        mind + '}' : '{' + partial.join(',') + '}';
+            gap = mind;
+            return v;
+        }
+    }
+
+// If the JSON object does not yet have a stringify method, give it one.
+
+    if (typeof JSON.stringify !== 'function') {
+        JSON.stringify = function (value, replacer, space) {
+
+// The stringify method takes a value and an optional replacer, and an optional
+// space parameter, and returns a JSON text. The replacer can be a function
+// that can replace values, or an array of strings that will select the keys.
+// A default replacer method can be provided. Use of the space parameter can
+// produce text that is more easily readable.
+
+            var i;
+            gap = '';
+            indent = '';
+
+// If the space parameter is a number, make an indent string containing that
+// many spaces.
+
+            if (typeof space === 'number') {
+                for (i = 0; i < space; i += 1) {
+                    indent += ' ';
+                }
+
+// If the space parameter is a string, it will be used as the indent string.
+
+            } else if (typeof space === 'string') {
+                indent = space;
+            }
+
+// If there is a replacer, it must be a function or an array.
+// Otherwise, throw an error.
+
+            rep = replacer;
+            if (replacer && typeof replacer !== 'function' &&
+                    (typeof replacer !== 'object' ||
+                     typeof replacer.length !== 'number')) {
+                throw new Error('JSON.stringify');
+            }
+
+// Make a fake root object containing our value under the key of ''.
+// Return the result of stringifying the value.
+
+            return str('', {'': value});
+        };
+    }
+
+
+// If the JSON object does not yet have a parse method, give it one.
+
+    if (typeof JSON.parse !== 'function') {
+        JSON.parse = function (text, reviver) {
+
+// The parse method takes a text and an optional reviver function, and returns
+// a JavaScript value if the text is a valid JSON text.
+
+            var j;
+
+            function walk(holder, key) {
+
+// The walk method is used to recursively walk the resulting structure so
+// that modifications can be made.
+
+                var k, v, value = holder[key];
+                if (value && typeof value === 'object') {
+                    for (k in value) {
+                        if (Object.hasOwnProperty.call(value, k)) {
+                            v = walk(value, k);
+                            if (v !== undefined) {
+                                value[k] = v;
+                            } else {
+                                delete value[k];
+                            }
+                        }
+                    }
+                }
+                return reviver.call(holder, key, value);
+            }
+
+
+// Parsing happens in four stages. In the first stage, we replace certain
+// Unicode characters with escape sequences. JavaScript handles many characters
+// incorrectly, either silently deleting them, or treating them as line endings.
+
+            text = String(text);
+            cx.lastIndex = 0;
+            if (cx.test(text)) {
+                text = text.replace(cx, function (a) {
+                    return '\\u' +
+                        ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+                });
+            }
+
+// In the second stage, we run the text against regular expressions that look
+// for non-JSON patterns. We are especially concerned with '()' and 'new'
+// because they can cause invocation, and '=' because it can cause mutation.
+// But just to be safe, we want to reject all unexpected forms.
+
+// We split the second stage into 4 regexp operations in order to work around
+// crippling inefficiencies in IE's and Safari's regexp engines. First we
+// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
+// replace all simple value tokens with ']' characters. Third, we delete all
+// open brackets that follow a colon or comma or that begin the text. Finally,
+// we look to see that the remaining characters are only whitespace or ']' or
+// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
+
+            if (/^[\],:{}\s]*$/.
+test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
+replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
+replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
+
+// In the third stage we use the eval function to compile the text into a
+// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
+// in JavaScript: it can begin a block or an object literal. We wrap the text
+// in parens to eliminate the ambiguity.
+
+                j = eval('(' + text + ')');
+
+// In the optional fourth stage, we recursively walk the new structure, passing
+// each name/value pair to a reviver function for possible transformation.
+
+                return typeof reviver === 'function' ?
+                    walk({'': j}, '') : j;
+            }
+
+// If the text is not JSON parseable, then a SyntaxError is thrown.
+
+            throw new SyntaxError('JSON.parse');
+        };
+    }
+}());

http://git-wip-us.apache.org/repos/asf/couchdb/blob/4bf97fc9/share/test/oauth.js
----------------------------------------------------------------------
diff --git a/share/test/oauth.js b/share/test/oauth.js
index 8b4e694..ada00a2 100644
--- a/share/test/oauth.js
+++ b/share/test/oauth.js
@@ -1,294 +1,511 @@
-// 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.
+/*
+ * 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.
+ */
 
-couchTests.oauth = function(debug) {
-  // This tests OAuth authentication.
+/* Here's some JavaScript software for implementing OAuth.
 
-  var authorization_url = "/_oauth/authorize";
+   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.
 
-  var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
-  db.deleteDb();
-  db.createDb();
-  if (debug) debugger;
+   Before you start hacking, make sure you understand the limitations
+   posed by cross-domain XMLHttpRequest.
 
-  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();
+   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.
+*/
 
-  // 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");
+// The HMAC-SHA1 signature method calls b64_hmac_sha1, defined by
+// http://pajhome.org.uk/crypt/md5/sha1.js
 
-      CouchDB.newUuids(2); // so we have one to make the salt
+/* An OAuth message is represented as an object like this:
+   {method: "GET", action: "http://server.com/path", parameters: ...}
 
-      CouchDB.request("PUT", CouchDB.protocol + host + "/_config/couch_httpd_auth/require_valid_user", {
-        headers: {
-          "X-Couch-Persist": "false",
-          "Authorization": adminBasicAuthHeaderValue()
-        },
-        body: JSON.stringify("true")
-      });
+   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]].
 
-      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);
+   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 = {};
 
-      var accessor = {
-        consumerSecret: consumerSecret,
-        tokenSecret: tokenSecret
-      };
-      var adminAccessor = {
-        consumerSecret: consumerSecret,
-        tokenSecret: admintokenSecret
-      };
+OAuth.setProperties = function setProperties(into, from) {
+    if (into != null && from != null) {
+        for (var key in from) {
+            into[key] = from[key];
+        }
+    }
+    return into;
+}
 
-      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"
+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]);
             }
-          };
-
-          // 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");
-          }
+            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;
+    }
+});
 
-          xhr = oauthRequest("GET", CouchDB.protocol + host + "/_session?foo=bar", message, accessor);
-          T(xhr.status == expectedCode);
+OAuth.nonce.CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz";
 
-          // Test HEAD method
-          xhr = oauthRequest("HEAD", CouchDB.protocol + host + "/_session?foo=bar", message, accessor);
-          T(xhr.status == expectedCode);
+/** 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;
+}
 
-          // 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);
+/** An abstract algorithm for signing messages. */
+OAuth.declareClass(OAuth, "SignatureMethod", function OAuthSignatureMethod(){});
 
-          // 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);
+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);
+    }
+});
 
-          // 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"
+/* 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]);
             }
-          };
-          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);
+        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);
+    }
+});
 
-  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
-  );
-};
+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;
+        }
+    ));

http://git-wip-us.apache.org/repos/asf/couchdb/blob/4bf97fc9/share/test/replicator_db_inc.js
----------------------------------------------------------------------
diff --git a/share/test/replicator_db_inc.js b/share/test/replicator_db_inc.js
new file mode 100644
index 0000000..23f8587
--- /dev/null
+++ b/share/test/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


Mime
View raw message