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 2014/12/10 12:08:18 UTC
[11/39] couchdb commit: updated refs/heads/master to 9950caa
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']);
+
+};