You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by kx...@apache.org on 2015/02/09 21:00:28 UTC

[27/57] [abbrv] couchdb commit: updated refs/heads/developer-preview-2.0 to 849b334

http://git-wip-us.apache.org/repos/asf/couchdb/blob/6a4893aa/test/javascript/json2.js
----------------------------------------------------------------------
diff --git a/test/javascript/json2.js b/test/javascript/json2.js
new file mode 100644
index 0000000..a1a3b17
--- /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');
+        };
+    }
+}());

http://git-wip-us.apache.org/repos/asf/couchdb/blob/6a4893aa/test/javascript/oauth.js
----------------------------------------------------------------------
diff --git a/test/javascript/oauth.js b/test/javascript/oauth.js
new file mode 100644
index 0000000..ada00a2
--- /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;
+        }
+    ));

http://git-wip-us.apache.org/repos/asf/couchdb/blob/6a4893aa/test/javascript/replicator_db_inc.js
----------------------------------------------------------------------
diff --git a/test/javascript/replicator_db_inc.js b/test/javascript/replicator_db_inc.js
new file mode 100644
index 0000000..23f8587
--- /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

http://git-wip-us.apache.org/repos/asf/couchdb/blob/6a4893aa/test/javascript/run
----------------------------------------------------------------------
diff --git a/test/javascript/run b/test/javascript/run
index ab145b1..4c71204 100755
--- a/test/javascript/run
+++ b/test/javascript/run
@@ -27,12 +27,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()
@@ -106,14 +106,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"):

http://git-wip-us.apache.org/repos/asf/couchdb/blob/6a4893aa/test/javascript/sha1.js
----------------------------------------------------------------------
diff --git a/test/javascript/sha1.js b/test/javascript/sha1.js
new file mode 100644
index 0000000..ee73a63
--- /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;
+}

http://git-wip-us.apache.org/repos/asf/couchdb/blob/6a4893aa/test/javascript/tests/all_docs.js
----------------------------------------------------------------------
diff --git a/test/javascript/tests/all_docs.js b/test/javascript/tests/all_docs.js
new file mode 100644
index 0000000..66ad880
--- /dev/null
+++ b/test/javascript/tests/all_docs.js
@@ -0,0 +1,142 @@
+// 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.all_docs = function(debug) {
+  var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
+  db.deleteDb();
+  db.createDb();
+  if (debug) debugger;
+
+  // Create some more documents.
+  // Notice the use of the ok member on the return result.
+  T(db.save({_id:"0",a:1,b:1}).ok);
+  T(db.save({_id:"3",a:4,b:16}).ok);
+  T(db.save({_id:"1",a:2,b:4}).ok);
+  T(db.save({_id:"2",a:3,b:9}).ok);
+
+  // Check the all docs
+  var results = db.allDocs();
+  var rows = results.rows;
+
+  T(results.total_rows == results.rows.length);
+
+  for(var i=0; i < rows.length; i++) {
+    T(rows[i].id >= "0" && rows[i].id <= "4");
+  }
+
+  // Check _all_docs with descending=true
+  var desc = db.allDocs({descending:true});
+  T(desc.total_rows == desc.rows.length);
+
+  // Check _all_docs offset
+  var all = db.allDocs({startkey:"2"});
+  T(all.offset == 2);
+
+  // Confirm that queries may assume raw collation.
+  var raw = db.allDocs({ startkey: "org.couchdb.user:",
+                         endkey  : "org.couchdb.user;"
+                       });
+  TEquals(0, raw.rows.length);
+
+  // check that the docs show up in the seq view in the order they were created
+  var changes = db.changes();
+  var ids = ["0","3","1","2"];
+  for (var i=0; i < changes.results.length; i++) {
+    var row = changes.results[i];
+    T(row.id == ids[i], "seq order");
+  };
+
+  // it should work in reverse as well
+  changes = db.changes({descending:true});
+  ids = ["2","1","3","0"];
+  for (var i=0; i < changes.results.length; i++) {
+    var row = changes.results[i];
+    T(row.id == ids[i], "descending=true");
+  };
+
+  // check that deletions also show up right
+  var doc1 = db.open("1");
+  var deleted = db.deleteDoc(doc1);
+  T(deleted.ok);
+  changes = db.changes();
+  // the deletion should make doc id 1 have the last seq num
+  T(changes.results.length == 4);
+  T(changes.results[3].id == "1");
+  T(changes.results[3].deleted);
+
+  // do an update
+  var doc2 = db.open("3");
+  doc2.updated = "totally";
+  db.save(doc2);
+  changes = db.changes();
+
+  // the update should make doc id 3 have the last seq num
+  T(changes.results.length == 4);
+  T(changes.results[3].id == "3");
+
+  // ok now lets see what happens with include docs
+  changes = db.changes({include_docs: true});
+  T(changes.results.length == 4);
+  T(changes.results[3].id == "3");
+  T(changes.results[3].doc.updated == "totally");
+
+  T(changes.results[2].doc);
+  T(changes.results[2].doc._deleted);
+
+  rows = db.allDocs({include_docs: true}, ["1"]).rows;
+  TEquals(1, rows.length);
+  TEquals("1", rows[0].key);
+  TEquals("1", rows[0].id);
+  TEquals(true, rows[0].value.deleted);
+  TEquals(null, rows[0].doc);
+
+  // add conflicts
+  var conflictDoc1 = {
+    _id: "3", _rev: "2-aa01552213fafa022e6167113ed01087", value: "X"
+  };
+  var conflictDoc2 = {
+    _id: "3", _rev: "2-ff01552213fafa022e6167113ed01087", value: "Z"
+  };
+  T(db.save(conflictDoc1, {new_edits: false}));
+  T(db.save(conflictDoc2, {new_edits: false}));
+
+  var winRev = db.open("3");
+
+  changes = db.changes({include_docs: true, conflicts: true, style: "all_docs"});
+  TEquals("3", changes.results[3].id);
+  TEquals(3, changes.results[3].changes.length);
+  TEquals(winRev._rev, changes.results[3].changes[0].rev);
+  TEquals("3", changes.results[3].doc._id);
+  TEquals(winRev._rev, changes.results[3].doc._rev);
+  TEquals(true, changes.results[3].doc._conflicts instanceof Array);
+  TEquals(2, changes.results[3].doc._conflicts.length);
+
+  rows = db.allDocs({include_docs: true, conflicts: true}).rows;
+  TEquals(3, rows.length);
+  TEquals("3", rows[2].key);
+  TEquals("3", rows[2].id);
+  TEquals(winRev._rev, rows[2].value.rev);
+  TEquals(winRev._rev, rows[2].doc._rev);
+  TEquals("3", rows[2].doc._id);
+  TEquals(true, rows[2].doc._conflicts instanceof Array);
+  TEquals(2, rows[2].doc._conflicts.length);
+
+  // test the all docs collates sanely
+  db.save({_id: "Z", foo: "Z"});
+  db.save({_id: "a", foo: "a"});
+
+  var rows = db.allDocs({startkey: "Z", endkey: "Z"}).rows;
+  T(rows.length == 1);
+
+  // cleanup
+  db.deleteDb();
+};

http://git-wip-us.apache.org/repos/asf/couchdb/blob/6a4893aa/test/javascript/tests/attachment_names.js
----------------------------------------------------------------------
diff --git a/test/javascript/tests/attachment_names.js b/test/javascript/tests/attachment_names.js
new file mode 100644
index 0000000..c9a5fcc
--- /dev/null
+++ b/test/javascript/tests/attachment_names.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.
+
+couchTests.attachment_names = function(debug) {
+  var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
+  db.deleteDb();
+  db.createDb();
+  if (debug) debugger;
+
+  var goodDoc = {
+    _id: "good_doc",
+    _attachments: {
+      "Колян.txt": {
+       content_type:"application/octet-stream",
+       data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
+      }
+    }
+  };
+
+  var save_response = db.save(goodDoc);
+  T(save_response.ok);
+
+  var xhr = CouchDB.request("GET", "/test_suite_db/good_doc/Колян.txt");
+  T(xhr.responseText == "This is a base64 encoded text");
+  T(xhr.getResponseHeader("Content-Type") == "application/octet-stream");
+  TEquals("\"aEI7pOYCRBLTRQvvqYrrJQ==\"", xhr.getResponseHeader("Etag"));
+
+  var binAttDoc = {
+    _id: "bin_doc",
+    _attachments:{
+      "foo\x80txt": {
+        content_type:"text/plain",
+        data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
+      }
+    }
+  };
+
+  // inline attachments
+  resp = db.save(binAttDoc);
+  TEquals(true, resp.ok, "attachment_name: inline attachment");
+
+
+  // standalone docs
+  var bin_data = "JHAPDO*AU£PN ){(3u[d 93DQ9¡€])}    ææøo'∂ƒæ≤çæππ•¥∫¶®#†π¶®¥π€ª®˙π8np";
+
+
+  var xhr = (CouchDB.request("PUT", "/test_suite_db/bin_doc3/attachment\x80txt", {
+    headers:{"Content-Type":"text/plain;charset=utf-8"},
+    body:bin_data
+  }));
+
+  var resp = JSON.parse(xhr.responseText);
+  TEquals(201, xhr.status, "attachment_name: standalone API");
+  TEquals(true, resp.ok, "attachment_name: standalone API");
+
+  // bulk docs
+  var docs = { docs: [binAttDoc] };
+
+  var xhr = CouchDB.request("POST", "/test_suite_db/_bulk_docs", {
+    body: JSON.stringify(docs)
+  });
+
+  TEquals(201, xhr.status, "attachment_name: bulk docs");
+
+
+  // leading underscores
+  var binAttDoc = {
+    _id: "bin_doc2",
+    _attachments:{
+      "_foo.txt": {
+        content_type:"text/plain",
+        data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
+      }
+    }
+  };
+
+  try {
+    db.save(binAttDoc);
+    TEquals(1, 2, "Attachment name with leading underscore saved. Should never show!");
+  } catch (e) {
+    TEquals("bad_request", e.error, "attachment_name: leading underscore");
+    TEquals("Attachment name can't start with '_'", e.reason, "attachment_name: leading underscore");
+  }
+
+  // todo: form uploads, waiting for cmlenz' test case for form uploads
+
+};

http://git-wip-us.apache.org/repos/asf/couchdb/blob/6a4893aa/test/javascript/tests/attachment_paths.js
----------------------------------------------------------------------
diff --git a/test/javascript/tests/attachment_paths.js b/test/javascript/tests/attachment_paths.js
new file mode 100644
index 0000000..3f6ffb7
--- /dev/null
+++ b/test/javascript/tests/attachment_paths.js
@@ -0,0 +1,153 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+couchTests.attachment_paths = function(debug) {
+  if (debug) debugger;
+  var dbNames = ["test_suite_db", "test_suite_db/with_slashes"];
+  for (var i=0; i < dbNames.length; i++) {
+    var db = new CouchDB(dbNames[i]);
+    var dbName = encodeURIComponent(dbNames[i]);
+    db.deleteDb();
+    db.createDb();
+
+    // first just save a regular doc with an attachment that has a slash in the url.
+    // (also gonna run an encoding check case)
+    var binAttDoc = {
+      _id: "bin_doc",
+      _attachments:{
+        "foo/bar.txt": {
+          content_type:"text/plain",
+          data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
+        },
+        "foo%2Fbaz.txt": {
+          content_type:"text/plain",
+          data: "V2UgbGlrZSBwZXJjZW50IHR3byBGLg=="
+        }
+      }
+    };
+
+    T(db.save(binAttDoc).ok);
+
+    var xhr = CouchDB.request("GET", "/"+dbName+"/bin_doc/foo/bar.txt");
+    T(xhr.responseText == "This is a base64 encoded text");
+    T(xhr.getResponseHeader("Content-Type") == "text/plain");
+
+    // lets try it with an escaped attachment id...
+    // weird that it's at two urls
+    var xhr = CouchDB.request("GET", "/"+dbName+"/bin_doc/foo%2Fbar.txt");
+    T(xhr.status == 200);
+    // xhr.responseText == "This is a base64 encoded text"
+
+    var xhr = CouchDB.request("GET", "/"+dbName+"/bin_doc/foo/baz.txt");
+    T(xhr.status == 404);
+
+    var xhr = CouchDB.request("GET", "/"+dbName+"/bin_doc/foo%252Fbaz.txt");
+    T(xhr.status == 200);
+    T(xhr.responseText == "We like percent two F.");
+
+    // require a _rev to PUT
+    var xhr = CouchDB.request("PUT", "/"+dbName+"/bin_doc/foo/attachment.txt", {
+      headers:{"Content-Type":"text/plain;charset=utf-8"},
+      body:"Just some text"
+    });
+    T(xhr.status == 409);
+
+    var xhr = CouchDB.request("PUT", "/"+dbName+"/bin_doc/foo/bar2.txt?rev=" + binAttDoc._rev, {
+      body:"This is no base64 encoded text",
+      headers:{"Content-Type": "text/plain;charset=utf-8"}
+    });
+    T(xhr.status == 201);
+    var rev = JSON.parse(xhr.responseText).rev;
+
+    binAttDoc = db.open("bin_doc");
+
+    T(binAttDoc._attachments["foo/bar.txt"] !== undefined);
+    T(binAttDoc._attachments["foo%2Fbaz.txt"] !== undefined);
+    T(binAttDoc._attachments["foo/bar2.txt"] !== undefined);
+    TEquals("text/plain;charset=utf-8",                   // thank you Safari
+      binAttDoc._attachments["foo/bar2.txt"].content_type.toLowerCase(),
+      "correct content-type"
+    );
+    T(binAttDoc._attachments["foo/bar2.txt"].length == 30);
+
+    //// now repeat the while thing with a design doc
+
+    // first just save a regular doc with an attachment that has a slash in the url.
+    // (also gonna run an encoding check case)
+    var binAttDoc = {
+      _id: "_design/bin_doc",
+      _attachments:{
+        "foo/bar.txt": {
+          content_type:"text/plain",
+          data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
+        },
+        "foo%2Fbaz.txt": {
+          content_type:"text/plain",
+          data: "V2UgbGlrZSBwZXJjZW50IHR3byBGLg=="
+        }
+      }
+    };
+
+    T(db.save(binAttDoc).ok);
+
+    var xhr = CouchDB.request("GET", "/"+dbName+"/_design%2Fbin_doc/foo/bar.txt");
+    T(xhr.responseText == "This is a base64 encoded text");
+    T(xhr.getResponseHeader("Content-Type") == "text/plain");
+
+    // lets try it with an escaped attachment id...
+    // weird that it's at two urls
+    var xhr = CouchDB.request("GET", "/"+dbName+"/_design%2Fbin_doc/foo%2Fbar.txt");
+    T(xhr.responseText == "This is a base64 encoded text");
+    T(xhr.status == 200);
+
+    // err, 3 urls
+    var xhr = CouchDB.request("GET", "/"+dbName+"/_design/bin_doc/foo%2Fbar.txt");
+    T(xhr.responseText == "This is a base64 encoded text");
+    T(xhr.status == 200);
+
+    // I mean um, 4 urls
+    var xhr = CouchDB.request("GET", "/"+dbName+"/_design/bin_doc/foo/bar.txt");
+    T(xhr.responseText == "This is a base64 encoded text");
+    T(xhr.status == 200);
+
+    var xhr = CouchDB.request("GET", "/"+dbName+"/_design%2Fbin_doc/foo/baz.txt");
+    T(xhr.status == 404);
+
+    var xhr = CouchDB.request("GET", "/"+dbName+"/_design%2Fbin_doc/foo%252Fbaz.txt");
+    T(xhr.status == 200);
+    T(xhr.responseText == "We like percent two F.");
+
+    // require a _rev to PUT
+    var xhr = CouchDB.request("PUT", "/"+dbName+"/_design%2Fbin_doc/foo/attachment.txt", {
+      headers:{"Content-Type":"text/plain;charset=utf-8"},
+      body:"Just some text"
+    });
+    T(xhr.status == 409);
+
+    var xhr = CouchDB.request("PUT", "/"+dbName+"/_design%2Fbin_doc/foo/bar2.txt?rev=" + binAttDoc._rev, {
+      body:"This is no base64 encoded text",
+      headers:{"Content-Type": "text/plain;charset=utf-8"}
+    });
+    T(xhr.status == 201);
+    var rev = JSON.parse(xhr.responseText).rev;
+
+    binAttDoc = db.open("_design/bin_doc");
+
+    T(binAttDoc._attachments["foo/bar.txt"] !== undefined);
+    T(binAttDoc._attachments["foo/bar2.txt"] !== undefined);
+    TEquals("text/plain;charset=utf-8",                   // thank you Safari
+      binAttDoc._attachments["foo/bar2.txt"].content_type.toLowerCase(),
+      "correct content-type"
+    );
+    T(binAttDoc._attachments["foo/bar2.txt"].length == 30);
+  }
+};

http://git-wip-us.apache.org/repos/asf/couchdb/blob/6a4893aa/test/javascript/tests/attachment_ranges.js
----------------------------------------------------------------------
diff --git a/test/javascript/tests/attachment_ranges.js b/test/javascript/tests/attachment_ranges.js
new file mode 100644
index 0000000..7d9afb5
--- /dev/null
+++ b/test/javascript/tests/attachment_ranges.js
@@ -0,0 +1,160 @@
+// 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 cacheBust() {
+    return "?anti-cache=" + String(Math.round(Math.random() * 1000000));
+};
+
+couchTests.attachment_ranges = function(debug) {
+    var db = new CouchDB("test_suite_db", {
+        "X-Couch-Full-Commit": "false"
+    });
+    db.deleteDb();
+    db.createDb();
+
+    if (debug) debugger;
+
+    if((typeof window != "undefined") && window.navigator.userAgent.match(/Chrome/)) {
+        // Chrome is broken.
+        return;
+    }
+
+    var binAttDoc = {
+        _id: "bin_doc",
+        _attachments: {
+            "foo.txt": {
+                content_type: "application/octet-stream",
+                data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
+            }
+        }
+    };
+
+    var save_response = db.save(binAttDoc);
+    T(save_response.ok);
+
+    // Fetching the whole entity is a 206.
+    var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc/foo.txt" + cacheBust(), {
+        headers: {
+            "Range": "bytes=0-28"
+        }
+    });
+    TEquals(206, xhr.status, "fetch 0-28");
+    TEquals("This is a base64 encoded text", xhr.responseText);
+    TEquals("bytes 0-28/29", xhr.getResponseHeader("Content-Range"));
+    TEquals("29", xhr.getResponseHeader("Content-Length"));
+
+    // Fetch the whole entity without an end offset is a 200.
+    var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc/foo.txt" + cacheBust(), {
+        headers: {
+            "Range": "bytes=0-"
+        }
+    });
+    TEquals(200, xhr.status, "fetch 0-");
+    TEquals("This is a base64 encoded text", xhr.responseText);
+    TEquals(null, xhr.getResponseHeader("Content-Range"));
+    TEquals("29", xhr.getResponseHeader("Content-Length"));
+
+    // Even if you ask multiple times.
+    var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc/foo.txt" + cacheBust(), {
+        headers: {
+            "Range": "bytes=0-,0-,0-"
+        }
+    });
+    TEquals(200, xhr.status, "multiple 0-'s");
+
+    // Badly formed range header is a 200.
+    var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc/foo.txt" + cacheBust(), {
+        headers: {
+            "Range": "bytes:0-"
+        }
+    });
+    TEquals(200, xhr.status, "fetch with bad range header");
+
+    // Fetch the end of an entity without an end offset is a 206.
+    var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc/foo.txt"  + cacheBust(), {
+        headers: {
+            "Range": "bytes=2-"
+        }
+    });
+    TEquals(206, xhr.status, "fetch 2-");
+    TEquals("is is a base64 encoded text", xhr.responseText);
+    TEquals("bytes 2-28/29", xhr.getResponseHeader("Content-Range"));
+    TEquals("27", xhr.getResponseHeader("Content-Length"));
+
+    // Fetch past the end of the entity is a 206
+    var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc/foo.txt"  + cacheBust(), {
+        headers: {
+            "Range": "bytes=0-29"
+        }
+    });
+    TEquals(206, xhr.status, "fetch 0-29");
+    TEquals("bytes 0-28/29", xhr.getResponseHeader("Content-Range"));
+    TEquals("29", xhr.getResponseHeader("Content-Length"));
+
+    // Fetch first part of entity is a 206
+    var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc/foo.txt" + cacheBust(), {
+        headers: {
+            "Range": "bytes=0-3"
+        }
+    });
+    TEquals(206, xhr.status, "fetch 0-3");
+    TEquals("This", xhr.responseText);
+    TEquals("4", xhr.getResponseHeader("Content-Length"));
+    TEquals("bytes 0-3/29", xhr.getResponseHeader("Content-Range"));
+
+    // Fetch middle of entity is also a 206
+    var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc/foo.txt" + cacheBust(), {
+        headers: {
+            "Range": "bytes=10-15"
+        }
+    });
+    TEquals(206, xhr.status, "fetch 10-15");
+    TEquals("base64", xhr.responseText);
+    TEquals("6", xhr.getResponseHeader("Content-Length"));
+    TEquals("bytes 10-15/29", xhr.getResponseHeader("Content-Range"));
+
+    // Fetch end of entity is also a 206
+    var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc/foo.txt" + cacheBust(), {
+        headers: {
+            "Range": "bytes=-3"
+        }
+    });
+    TEquals(206, xhr.status, "fetch -3");
+    TEquals("ext", xhr.responseText);
+    TEquals("3", xhr.getResponseHeader("Content-Length"));
+    TEquals("bytes 26-28/29", xhr.getResponseHeader("Content-Range"));
+    
+    // backward range is 416
+    var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc/foo.txt" + cacheBust(), {
+       headers: {
+           "Range": "bytes=5-3"
+       }
+    });
+    TEquals(416, xhr.status, "fetch 5-3");
+
+    // range completely outside of entity is 416
+    var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc/foo.txt" + cacheBust(), {
+        headers: {
+            "Range": "bytes=300-310"
+        }
+    });
+    TEquals(416, xhr.status, "fetch 300-310");
+
+    // We ignore a Range header with too many ranges
+    var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc/foo.txt" + cacheBust(), {
+        headers: {
+            "Range": "bytes=0-1,0-1,0-1,0-1,0-1,0-1,0-1,0-1,0-1,0-1"
+        }
+    });
+    TEquals(200, xhr.status, "too many ranges");
+
+};

http://git-wip-us.apache.org/repos/asf/couchdb/blob/6a4893aa/test/javascript/tests/attachment_views.js
----------------------------------------------------------------------
diff --git a/test/javascript/tests/attachment_views.js b/test/javascript/tests/attachment_views.js
new file mode 100644
index 0000000..b55aabe
--- /dev/null
+++ b/test/javascript/tests/attachment_views.js
@@ -0,0 +1,140 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+couchTests.attachment_views= function(debug) {
+
+  var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
+  db.deleteDb();
+  db.createDb();
+  if (debug) debugger;
+
+  // count attachments in a view
+
+  var attachmentData = "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ=";
+
+  db.bulkSave(makeDocs(0, 10));
+
+  db.bulkSave(makeDocs(10, 20, {
+    _attachments:{
+      "foo.txt": {
+        content_type:"text/plain",
+        data: attachmentData
+      }
+    }
+  }));
+
+  db.bulkSave(makeDocs(20, 30, {
+    _attachments:{
+      "foo.txt": {
+        content_type:"text/plain",
+        data: attachmentData
+      },
+      "bar.txt": {
+        content_type:"text/plain",
+        data: attachmentData
+      }
+    }
+  }));
+
+  db.bulkSave(makeDocs(30, 40, {
+    _attachments:{
+      "foo.txt": {
+        content_type:"text/plain",
+        data: attachmentData
+      },
+      "bar.txt": {
+        content_type:"text/plain",
+        data: attachmentData
+      },
+      "baz.txt": {
+        content_type:"text/plain",
+        data: attachmentData
+      }
+    }
+  }));
+
+  var mapFunction = function(doc) {
+    var count = 0;
+
+    for(var idx in doc._attachments) {
+      count = count + 1;
+    }
+
+    emit(parseInt(doc._id), count);
+  };
+
+  var reduceFunction = function(key, values) {
+    return sum(values);
+  };
+
+  var result = db.query(mapFunction, reduceFunction);
+
+  T(result.rows.length == 1);
+  T(result.rows[0].value == 60);
+
+  var result = db.query(mapFunction, reduceFunction, {
+    startkey:10,
+    endkey:19
+  });
+
+  T(result.rows.length == 1);
+  T(result.rows[0].value == 10);
+
+  var result = db.query(mapFunction, reduceFunction, {
+    startkey:20,
+    endkey:29
+  });
+
+  T(result.rows.length == 1);
+  T(result.rows[0].value == 20);
+
+  var result = db.query(mapFunction, null, {
+    startkey: 30,
+    endkey: 39,
+    include_docs: true
+  });
+
+  T(result.rows.length == 10);
+  T(result.rows[0].value == 3);
+  T(result.rows[0].doc._attachments['baz.txt'].stub === true);
+  T(result.rows[0].doc._attachments['baz.txt'].data === undefined);
+  T(result.rows[0].doc._attachments['baz.txt'].encoding === undefined);
+  T(result.rows[0].doc._attachments['baz.txt'].encoded_length === undefined);
+
+  var result = db.query(mapFunction, null, {
+    startkey: 30,
+    endkey: 39,
+    include_docs: true,
+    attachments: true
+  });
+
+  T(result.rows.length == 10);
+  T(result.rows[0].value == 3);
+  T(result.rows[0].doc._attachments['baz.txt'].data === attachmentData);
+  T(result.rows[0].doc._attachments['baz.txt'].stub === undefined);
+  T(result.rows[0].doc._attachments['baz.txt'].encoding === undefined);
+  T(result.rows[0].doc._attachments['baz.txt'].encoded_length === undefined);
+
+  var result = db.query(mapFunction, null, {
+    startkey: 30,
+    endkey: 39,
+    include_docs: true,
+    att_encoding_info: true
+  });
+
+  T(result.rows.length == 10);
+  T(result.rows[0].value == 3);
+  T(result.rows[0].doc._attachments['baz.txt'].data === undefined);
+  T(result.rows[0].doc._attachments['baz.txt'].stub === true);
+  T(result.rows[0].doc._attachments['baz.txt'].encoding === "gzip");
+  T(result.rows[0].doc._attachments['baz.txt'].encoded_length === 47);
+};

http://git-wip-us.apache.org/repos/asf/couchdb/blob/6a4893aa/test/javascript/tests/attachments.js
----------------------------------------------------------------------
diff --git a/test/javascript/tests/attachments.js b/test/javascript/tests/attachments.js
new file mode 100644
index 0000000..2fa08ee
--- /dev/null
+++ b/test/javascript/tests/attachments.js
@@ -0,0 +1,328 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+couchTests.attachments= function(debug) {
+  var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
+  db.deleteDb();
+  db.createDb();
+  if (debug) debugger;
+
+
+  // MD5 Digests of compressible attachments and therefore Etags
+  // will vary depending on platform gzip implementation.
+  // These MIME types are defined in [attachments] compressible_types
+  var binAttDoc = {
+    _id: "bin_doc",
+    _attachments:{
+      "foo.txt": {
+        content_type:"application/octet-stream",
+        data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
+      }
+    }
+  };
+
+  var save_response = db.save(binAttDoc);
+  T(save_response.ok);
+
+  var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc/foo.txt");
+  T(xhr.responseText == "This is a base64 encoded text");
+  T(xhr.getResponseHeader("Content-Type") == "application/octet-stream");
+  TEquals("\"aEI7pOYCRBLTRQvvqYrrJQ==\"", xhr.getResponseHeader("Etag"));
+
+  // empty attachment
+  var binAttDoc2 = {
+    _id: "bin_doc2",
+    _attachments:{
+      "foo.txt": {
+        content_type:"text/plain",
+        data: ""
+      }
+    }
+  }
+
+  T(db.save(binAttDoc2).ok);
+
+  var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc2/foo.txt");
+  T(xhr.responseText.length == 0);
+  T(xhr.getResponseHeader("Content-Type") == "text/plain");
+
+  // test RESTful doc API
+
+  var xhr = CouchDB.request("PUT", "/test_suite_db/bin_doc2/foo2.txt?rev=" + binAttDoc2._rev, {
+    body:"This is no base64 encoded text",
+    headers:{"Content-Type": "text/plain;charset=utf-8"}
+  });
+  T(xhr.status == 201);
+  TEquals("/bin_doc2/foo2.txt",
+    xhr.getResponseHeader("Location").substr(-18),
+    "should return Location header to newly created or updated attachment");
+
+  var rev = JSON.parse(xhr.responseText).rev;
+
+  binAttDoc2 = db.open("bin_doc2");
+
+  T(binAttDoc2._attachments["foo.txt"] !== undefined);
+  T(binAttDoc2._attachments["foo2.txt"] !== undefined);
+  TEqualsIgnoreCase("text/plain;charset=utf-8", binAttDoc2._attachments["foo2.txt"].content_type);
+  T(binAttDoc2._attachments["foo2.txt"].length == 30);
+
+  var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc2/foo2.txt");
+  T(xhr.responseText == "This is no base64 encoded text");
+  TEqualsIgnoreCase("text/plain;charset=utf-8", xhr.getResponseHeader("Content-Type"));
+
+  // test without rev, should fail
+  var xhr = CouchDB.request("DELETE", "/test_suite_db/bin_doc2/foo2.txt");
+  T(xhr.status == 409);
+
+  // test with rev, should not fail
+  var xhr = CouchDB.request("DELETE", "/test_suite_db/bin_doc2/foo2.txt?rev=" + rev);
+  T(xhr.status == 200);
+  TEquals(null, xhr.getResponseHeader("Location"),
+    "should not return Location header on DELETE request");
+
+  // test binary data
+  var bin_data = "JHAPDO*AU£PN ){(3u[d 93DQ9¡€])}    ææøo'∂ƒæ≤çæππ•¥∫¶®#†π¶®¥π€ª®˙π8np";
+  var xhr = CouchDB.request("PUT", "/test_suite_db/bin_doc3/attachment.txt", {
+    headers:{"Content-Type":"text/plain;charset=utf-8"},
+    body:bin_data
+  });
+  T(xhr.status == 201);
+  var rev = JSON.parse(xhr.responseText).rev;
+  TEquals('"' + rev + '"', xhr.getResponseHeader("Etag"));
+
+  var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc3/attachment.txt");
+  T(xhr.responseText == bin_data);
+  TEqualsIgnoreCase("text/plain;charset=utf-8", xhr.getResponseHeader("Content-Type"));
+
+  // without rev
+  var xhr = CouchDB.request("PUT", "/test_suite_db/bin_doc3/attachment.txt", {
+    headers:{"Content-Type":"text/plain;charset=utf-8"},
+    body:bin_data
+  });
+  T(xhr.status == 409);
+
+  // with nonexistent rev
+  var xhr = CouchDB.request("PUT", "/test_suite_db/bin_doc3/attachment.txt"  + "?rev=1-adae8575ecea588919bd08eb020c708e", {
+    headers:{"Content-Type":"text/plain;charset=utf-8"},
+    body:bin_data
+  });
+  T(xhr.status == 409);
+
+  // with current rev
+  var xhr = CouchDB.request("PUT", "/test_suite_db/bin_doc3/attachment.txt?rev=" + rev, {
+    headers:{"Content-Type":"text/plain;charset=utf-8"},
+    body:bin_data
+  });
+  T(xhr.status == 201);
+  var rev = JSON.parse(xhr.responseText).rev;
+  TEquals('"' + rev + '"', xhr.getResponseHeader("Etag"));
+
+  var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc3/attachment.txt");
+  T(xhr.responseText == bin_data);
+  TEqualsIgnoreCase("text/plain;charset=utf-8", xhr.getResponseHeader("Content-Type"));
+
+  var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc3/attachment.txt?rev=" + rev);
+  T(xhr.responseText == bin_data);
+  TEqualsIgnoreCase("text/plain;charset=utf-8", xhr.getResponseHeader("Content-Type"));
+
+  var xhr = CouchDB.request("DELETE", "/test_suite_db/bin_doc3/attachment.txt?rev=" + rev);
+  T(xhr.status == 200);
+
+  var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc3/attachment.txt");
+  T(xhr.status == 404);
+
+  // deleted attachment is still accessible with revision
+  var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc3/attachment.txt?rev=" + rev);
+  T(xhr.status == 200);
+  T(xhr.responseText == bin_data);
+  TEqualsIgnoreCase("text/plain;charset=utf-8", xhr.getResponseHeader("Content-Type"));
+
+  // empty attachments
+  var xhr = CouchDB.request("PUT", "/test_suite_db/bin_doc4/attachment.txt", {
+    headers:{"Content-Type":"text/plain;charset=utf-8"},
+    body:""
+  });
+  T(xhr.status == 201);
+  var rev = JSON.parse(xhr.responseText).rev;
+
+  var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc4/attachment.txt");
+  T(xhr.status == 200);
+  T(xhr.responseText.length == 0);
+
+  // overwrite previsously empty attachment
+  var xhr = CouchDB.request("PUT", "/test_suite_db/bin_doc4/attachment.txt?rev=" + rev, {
+    headers:{"Content-Type":"text/plain;charset=utf-8"},
+    body:"This is a string"
+  });
+  T(xhr.status == 201);
+
+  var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc4/attachment.txt");
+  T(xhr.status == 200);
+  T(xhr.responseText == "This is a string");
+
+  // Attachment sparseness COUCHDB-220
+
+  var docs = [];
+  for (var i = 0; i < 5; i++) {
+    var doc = {
+      _id: (i).toString(),
+      _attachments:{
+        "foo.txt": {
+          content_type:"text/plain",
+          data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
+        }
+      }
+    };
+    docs.push(doc);
+  }
+
+  var saved = db.bulkSave(docs);
+  // now delete the docs, and while we are looping over them, remove the
+  // '_rev' field so we can re-create after deletion.
+  var to_up = [];
+  for (i=0;i<saved.length;i++) {
+    to_up.push({'_id': saved[i]['id'], '_rev': saved[i]['rev'], '_deleted': true});
+    delete docs[i]._rev;
+  }
+  // delete them.
+  var saved2 = db.bulkSave(to_up);
+  // re-create them
+  var saved3 = db.bulkSave(docs);
+
+  var before = db.info().disk_size;
+
+  // Compact it.
+  T(db.compact().ok);
+  T(db.last_req.status == 202);
+  // compaction isn't instantaneous, loop until done
+  while (db.info().compact_running) {};
+
+  var after = db.info().disk_size;
+
+  // Compaction should reduce the database slightly, but not
+  // orders of magnitude (unless attachments introduce sparseness)
+  T(after > before * 0.1, "before: " + before + " after: " + after);
+
+
+  // test large attachments - COUCHDB-366
+  var lorem = CouchDB.request("GET", "/_utils/script/test/lorem.txt").responseText;
+
+  var xhr = CouchDB.request("PUT", "/test_suite_db/bin_doc5/lorem.txt", {
+    headers:{"Content-Type":"text/plain;charset=utf-8"},
+    body:lorem
+  });
+  T(xhr.status == 201);
+  var rev = JSON.parse(xhr.responseText).rev;
+
+  var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc5/lorem.txt");
+  T(xhr.responseText == lorem);
+  TEqualsIgnoreCase("text/plain;charset=utf-8", xhr.getResponseHeader("Content-Type"));
+
+  // test large inline attachment too
+  var lorem_b64 = CouchDB.request("GET", "/_utils/script/test/lorem_b64.txt").responseText;
+  var doc = db.open("bin_doc5", {attachments:true});
+  T(doc._attachments["lorem.txt"].data == lorem_b64);
+
+  // test etags for attachments.
+  var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc5/lorem.txt");
+  T(xhr.status == 200);
+  var etag = xhr.getResponseHeader("etag");
+  xhr = CouchDB.request("GET", "/test_suite_db/bin_doc5/lorem.txt", {
+    headers: {"if-none-match": etag}
+  });
+  T(xhr.status == 304);
+
+  // test COUCHDB-497 - empty attachments
+  var xhr = CouchDB.request("PUT", "/test_suite_db/bin_doc5/empty.txt?rev="+rev, {
+    headers:{"Content-Type":"text/plain;charset=utf-8", "Content-Length": "0"},
+    body:""
+  });
+  TEquals(201, xhr.status, "should send 201 Accepted");
+  var rev = JSON.parse(xhr.responseText).rev;
+  var xhr = CouchDB.request("PUT", "/test_suite_db/bin_doc5/empty.txt?rev="+rev, {
+    headers:{"Content-Type":"text/plain;charset=utf-8"}
+  });
+  TEquals(201, xhr.status, "should send 201 Accepted");
+
+  // implicit doc creation allows creating docs with a reserved id. COUCHDB-565
+  var xhr = CouchDB.request("PUT", "/test_suite_db/_nonexistant/attachment.txt", {
+    headers: {"Content-Type":"text/plain;charset=utf-8"},
+    body: "THIS IS AN ATTACHMENT. BOOYA!"
+  });
+  TEquals(400, xhr.status, "should return error code 400 Bad Request");
+
+  // test COUCHDB-809 - stubs should only require the 'stub' field
+  var bin_doc6 = {
+    _id: "bin_doc6",
+    _attachments:{
+      "foo.txt": {
+        content_type:"text/plain",
+        data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
+      }
+    }
+  };
+  T(db.save(bin_doc6).ok);
+  // stub out the attachment
+  bin_doc6._attachments["foo.txt"] = { stub: true };
+  T(db.save(bin_doc6).ok == true);
+
+  // wrong rev pos specified
+  
+  // stub out the attachment with the wrong revpos
+  bin_doc6._attachments["foo.txt"] = { stub: true, revpos: 10};
+  try {
+      T(db.save(bin_doc6).ok == true);
+      T(false && "Shouldn't get here!");
+  } catch (e) {
+      T(e.error == "missing_stub");
+  }
+
+  // test MD5 header
+  var bin_data = "foo bar"
+  var xhr = CouchDB.request("PUT", "/test_suite_db/bin_doc7/attachment.txt", {
+    headers:{"Content-Type":"application/octet-stream",
+             "Content-MD5":"MntvB0NYESObxH4VRDUycw=="},
+    body:bin_data
+  });
+  TEquals(201, xhr.status);
+
+  var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc7/attachment.txt");
+  TEquals('MntvB0NYESObxH4VRDUycw==', xhr.getResponseHeader("Content-MD5"));
+
+  // test attachment via multipart/form-data
+  var bin_doc8 = {
+    _id: "bin_doc8"
+  };
+  T(db.save(bin_doc8).ok);
+  var doc = db.open("bin_doc8");
+  var body = "------TF\r\n" +
+    "Content-Disposition: form-data; name=\"_rev\"\r\n\r\n" +
+    doc._rev + "\r\n" +
+    "------TF\r\n" +
+    "Content-Disposition: form-data; name=\"_attachments\"; filename=\"file.txt\"\r\n" +
+    "Content-Type: text/plain\r\n\r\n" +
+    "contents of file.txt\r\n\r\n" +
+    "------TF--"
+  xhr = CouchDB.request("POST", "/test_suite_db/bin_doc8", {
+    headers: {
+      "Content-Type": "multipart/form-data; boundary=----TF",
+      "Content-Length": body.length
+    },
+    body: body
+  });
+  TEquals(201, xhr.status);
+  TEquals(true, JSON.parse(xhr.responseText).ok);
+  var doc = db.open("bin_doc8");
+  T(doc._attachments);
+  T(doc._attachments['file.txt']);
+
+};