summaryrefslogtreecommitdiff
path: root/rel/overlay/share/www/script
diff options
context:
space:
mode:
authorAdam Kocoloski <adam@cloudant.com>2011-08-15 15:45:15 -0400
committerAdam Kocoloski <adam@cloudant.com>2011-08-15 15:45:15 -0400
commit6ffe1675dd7b004e48891956a6bdbe32899ce80c (patch)
tree57326d9a498481e65bb0db38c66daf10896801f1 /rel/overlay/share/www/script
parent52ff89ff7996e839b9e2f91fd76184d362a8aeb0 (diff)
parentfdd1a5d0bc48b49b0df5c9217beff9574011283c (diff)
Merge branch '11554-merge-couchdb-1.1'
Diffstat (limited to 'rel/overlay/share/www/script')
-rw-r--r--rel/overlay/share/www/script/couch.js4
-rw-r--r--rel/overlay/share/www/script/couch_tests.js4
-rw-r--r--rel/overlay/share/www/script/futon.browse.js10
-rw-r--r--rel/overlay/share/www/script/futon.format.js4
-rw-r--r--rel/overlay/share/www/script/futon.js55
-rw-r--r--rel/overlay/share/www/script/jquery-ui-1.8.11.custom.min.js81
-rw-r--r--rel/overlay/share/www/script/jquery.couch.js95
-rw-r--r--rel/overlay/share/www/script/json2.js3
-rw-r--r--rel/overlay/share/www/script/jspec/jspec.js2
-rw-r--r--rel/overlay/share/www/script/test/all_docs.js41
-rw-r--r--rel/overlay/share/www/script/test/attachment_names.js41
-rw-r--r--rel/overlay/share/www/script/test/attachment_ranges.js134
-rw-r--r--rel/overlay/share/www/script/test/attachments.js2
-rw-r--r--rel/overlay/share/www/script/test/attachments_multipart.js169
-rw-r--r--rel/overlay/share/www/script/test/basics.js14
-rw-r--r--rel/overlay/share/www/script/test/changes.js157
-rw-r--r--rel/overlay/share/www/script/test/config.js106
-rw-r--r--rel/overlay/share/www/script/test/conflicts.js25
-rw-r--r--rel/overlay/share/www/script/test/cookie_auth.js12
-rw-r--r--rel/overlay/share/www/script/test/copy_doc.js3
-rw-r--r--rel/overlay/share/www/script/test/design_docs.js535
-rw-r--r--rel/overlay/share/www/script/test/etags_views.js98
-rw-r--r--rel/overlay/share/www/script/test/http.js6
-rw-r--r--rel/overlay/share/www/script/test/jsonp.js2
-rw-r--r--rel/overlay/share/www/script/test/list_views.js47
-rw-r--r--rel/overlay/share/www/script/test/oauth.js28
-rw-r--r--rel/overlay/share/www/script/test/proxyauth.js2
-rw-r--r--rel/overlay/share/www/script/test/purge.js32
-rw-r--r--rel/overlay/share/www/script/test/reduce_builtin.js25
-rw-r--r--rel/overlay/share/www/script/test/replication.js303
-rw-r--r--rel/overlay/share/www/script/test/replicator_db.js1447
-rw-r--r--rel/overlay/share/www/script/test/rewrite.js60
-rw-r--r--rel/overlay/share/www/script/test/security_validation.js8
-rw-r--r--rel/overlay/share/www/script/test/show_documents.js22
-rw-r--r--rel/overlay/share/www/script/test/update_documents.js4
-rw-r--r--rel/overlay/share/www/script/test/view_errors.js8
-rw-r--r--rel/overlay/share/www/script/test/view_include_docs.js54
-rw-r--r--rel/overlay/share/www/script/test/view_multi_key_all_docs.js37
-rw-r--r--rel/overlay/share/www/script/test/view_multi_key_design.js78
-rw-r--r--rel/overlay/share/www/script/test/view_pagination.js123
-rw-r--r--rel/overlay/share/www/script/test/view_update_seq.js25
41 files changed, 3523 insertions, 383 deletions
diff --git a/rel/overlay/share/www/script/couch.js b/rel/overlay/share/www/script/couch.js
index ca860bd5..6a997cff 100644
--- a/rel/overlay/share/www/script/couch.js
+++ b/rel/overlay/share/www/script/couch.js
@@ -272,7 +272,7 @@ function CouchDB(name, httpHeaders) {
for (var name in options) {
if (!options.hasOwnProperty(name)) { continue; };
var value = options[name];
- if (name == "key" || name == "startkey" || name == "endkey") {
+ if (name == "key" || name == "keys" || name == "startkey" || name == "endkey") {
value = toJSON(value);
}
buf.push(encodeURIComponent(name) + "=" + encodeURIComponent(value));
@@ -402,7 +402,7 @@ CouchDB.request = function(method, uri, options) {
options.headers["Content-Type"] = options.headers["Content-Type"] || options.headers["content-type"] || "application/json";
options.headers["Accept"] = options.headers["Accept"] || options.headers["accept"] || "application/json";
var req = CouchDB.newXhr();
- if(uri.substr(0, "http://".length) != "http://") {
+ if(uri.substr(0, CouchDB.protocol.length) != CouchDB.protocol) {
uri = CouchDB.urlPrefix + uri;
}
req.open(method, uri, false);
diff --git a/rel/overlay/share/www/script/couch_tests.js b/rel/overlay/share/www/script/couch_tests.js
index 896b3538..eb573526 100644
--- a/rel/overlay/share/www/script/couch_tests.js
+++ b/rel/overlay/share/www/script/couch_tests.js
@@ -13,10 +13,12 @@
// Used by replication test
if (typeof window == 'undefined' || !window) {
CouchDB.host = "127.0.0.1:5984";
+ CouchDB.protocol = "http://";
CouchDB.inBrowser = false;
} else {
CouchDB.host = window.location.host;
CouchDB.inBrowser = true;
+ CouchDB.protocol = window.location.protocol + "//";
}
CouchDB.urlPrefix = "..";
@@ -35,6 +37,7 @@ loadTest("attachments_multipart.js");
loadTest("attachment_conflicts.js");
loadTest("attachment_names.js");
loadTest("attachment_paths.js");
+loadTest("attachment_ranges.js");
loadTest("attachment_views.js");
loadTest("auth_cache.js");
loadTest("batch_save.js");
@@ -74,6 +77,7 @@ loadTest("reduce_builtin.js");
loadTest("reduce_false.js");
loadTest("reduce_false_temp.js");
loadTest("replication.js");
+loadTest("replicator_db.js");
loadTest("rev_stemming.js");
loadTest("rewrite.js");
loadTest("security_validation.js");
diff --git a/rel/overlay/share/www/script/futon.browse.js b/rel/overlay/share/www/script/futon.browse.js
index a3f6e8cb..b981feec 100644
--- a/rel/overlay/share/www/script/futon.browse.js
+++ b/rel/overlay/share/www/script/futon.browse.js
@@ -682,10 +682,12 @@
key = $.futon.formatJSON(row.key, {indent: 0, linesep: ""});
}
if (row.id) {
- $("<td class='key'><a href='document.html?" + encodeURIComponent(db.name) +
- "/" + $.couch.encodeDocId(row.id) + "'><strong></strong><br>" +
- "<span class='docid'>ID:&nbsp;" + $.futon.escape(row.id) + "</span></a></td>")
- .find("strong").text(key).end()
+ key = key.replace(/\\"/, '"');
+ var rowlink = encodeURIComponent(db.name) +
+ "/" + $.couch.encodeDocId(row.id);
+ $("<td class='key'><a href=\"document.html?" + rowlink + "\"><strong>"
+ + $.futon.escape(key) + "</strong><br>"
+ + "<span class='docid'>ID:&nbsp;" + $.futon.escape(row.id) + "</span></a></td>")
.appendTo(tr);
} else {
$("<td class='key'><strong></strong></td>")
diff --git a/rel/overlay/share/www/script/futon.format.js b/rel/overlay/share/www/script/futon.format.js
index 8d9b7f5c..0eb9b104 100644
--- a/rel/overlay/share/www/script/futon.format.js
+++ b/rel/overlay/share/www/script/futon.format.js
@@ -17,8 +17,8 @@
return string.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
- .replace(/"/, "&quot;")
- .replace(/'/, "&#39;")
+ .replace(/"/g, "&quot;")
+ .replace(/'/g, "&#39;")
;
},
diff --git a/rel/overlay/share/www/script/futon.js b/rel/overlay/share/www/script/futon.js
index c4647ed1..fb73e3c9 100644
--- a/rel/overlay/share/www/script/futon.js
+++ b/rel/overlay/share/www/script/futon.js
@@ -68,6 +68,10 @@ function $$(node) {
callback({name: "Please enter a name."});
return false;
};
+ return validatePassword(data, callback);
+ };
+
+ function validatePassword(data, callback) {
if (!data.password || data.password.length == 0) {
callback({password: "Please enter a password."});
return false;
@@ -129,11 +133,61 @@ function $$(node) {
return false;
};
+ function changePassword () {
+ $.showDialog("dialog/_change_password.html", {
+ submit: function(data, callback) {
+ if (validatePassword(data, callback)) {
+ if (data.password != data.verify_password) {
+ callback({verify_password: "Passwords don't match."});
+ return false;
+ }
+ } else {
+ return false;
+ }
+ $.couch.session({success: function (resp) {
+ if (resp.userCtx.roles.indexOf("_admin") > -1) {
+ $.couch.config({
+ success : function () {
+ doLogin(resp.userCtx.name, data.password, function(errors) {
+ if(!$.isEmptyObject(errors)) {
+ callback(errors);
+ return;
+ } else {
+ location.reload();
+ }
+ });
+ }
+ }, "admins", resp.userCtx.name, data.password);
+ } else {
+ $.couch.db(resp.info.authentication_db).openDoc("org.couchdb.user:"+resp.userCtx.name, {
+ success: function (user) {
+ $.couch.db(resp.info.authentication_db).saveDoc($.couch.prepareUserDoc(user, data.password), {
+ success: function() {
+ doLogin(user.name, data.password, function(errors) {
+ if(!$.isEmptyObject(errors)) {
+ callback(errors);
+ return;
+ } else {
+ location.reload();
+ }
+ });
+ }
+ });
+ }
+ });
+ }
+ }});
+ }
+ });
+ return false;
+ };
+
this.setupSidebar = function() {
$("#userCtx .login").click(login);
$("#userCtx .logout").click(logout);
$("#userCtx .signup").click(signup);
$("#userCtx .createadmin").click(createAdmin);
+ $("#userCtx .changepass").click(changePassword);
};
this.sidebar = function() {
@@ -146,6 +200,7 @@ function $$(node) {
if (userCtx.name) {
$("#userCtx .name").text(userCtx.name).attr({href : $.couch.urlPrefix + "/_utils/document.html?"+encodeURIComponent(r.info.authentication_db)+"/org.couchdb.user%3A"+encodeURIComponent(userCtx.name)});
if (userCtx.roles.indexOf("_admin") != -1) {
+ $("#userCtx .loggedin").show();
$("#userCtx .loggedinadmin").show();
} else {
$("#userCtx .loggedin").show();
diff --git a/rel/overlay/share/www/script/jquery-ui-1.8.11.custom.min.js b/rel/overlay/share/www/script/jquery-ui-1.8.11.custom.min.js
new file mode 100644
index 00000000..45b927e0
--- /dev/null
+++ b/rel/overlay/share/www/script/jquery-ui-1.8.11.custom.min.js
@@ -0,0 +1,81 @@
+/*!
+ * jQuery UI 1.8.11
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI
+ */
+(function(c,j){function k(a){return!c(a).parents().andSelf().filter(function(){return c.curCSS(this,"visibility")==="hidden"||c.expr.filters.hidden(this)}).length}c.ui=c.ui||{};if(!c.ui.version){c.extend(c.ui,{version:"1.8.11",keyCode:{ALT:18,BACKSPACE:8,CAPS_LOCK:20,COMMA:188,COMMAND:91,COMMAND_LEFT:91,COMMAND_RIGHT:93,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,MENU:93,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,
+NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38,WINDOWS:91}});c.fn.extend({_focus:c.fn.focus,focus:function(a,b){return typeof a==="number"?this.each(function(){var d=this;setTimeout(function(){c(d).focus();b&&b.call(d)},a)}):this._focus.apply(this,arguments)},scrollParent:function(){var a;a=c.browser.msie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?this.parents().filter(function(){return/(relative|absolute|fixed)/.test(c.curCSS(this,
+"position",1))&&/(auto|scroll)/.test(c.curCSS(this,"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0):this.parents().filter(function(){return/(auto|scroll)/.test(c.curCSS(this,"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0);return/fixed/.test(this.css("position"))||!a.length?c(document):a},zIndex:function(a){if(a!==j)return this.css("zIndex",a);if(this.length){a=c(this[0]);for(var b;a.length&&a[0]!==document;){b=a.css("position");
+if(b==="absolute"||b==="relative"||b==="fixed"){b=parseInt(a.css("zIndex"),10);if(!isNaN(b)&&b!==0)return b}a=a.parent()}}return 0},disableSelection:function(){return this.bind((c.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection",function(a){a.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}});c.each(["Width","Height"],function(a,b){function d(f,g,l,m){c.each(e,function(){g-=parseFloat(c.curCSS(f,"padding"+this,true))||0;if(l)g-=parseFloat(c.curCSS(f,
+"border"+this+"Width",true))||0;if(m)g-=parseFloat(c.curCSS(f,"margin"+this,true))||0});return g}var e=b==="Width"?["Left","Right"]:["Top","Bottom"],h=b.toLowerCase(),i={innerWidth:c.fn.innerWidth,innerHeight:c.fn.innerHeight,outerWidth:c.fn.outerWidth,outerHeight:c.fn.outerHeight};c.fn["inner"+b]=function(f){if(f===j)return i["inner"+b].call(this);return this.each(function(){c(this).css(h,d(this,f)+"px")})};c.fn["outer"+b]=function(f,g){if(typeof f!=="number")return i["outer"+b].call(this,f);return this.each(function(){c(this).css(h,
+d(this,f,true,g)+"px")})}});c.extend(c.expr[":"],{data:function(a,b,d){return!!c.data(a,d[3])},focusable:function(a){var b=a.nodeName.toLowerCase(),d=c.attr(a,"tabindex");if("area"===b){b=a.parentNode;d=b.name;if(!a.href||!d||b.nodeName.toLowerCase()!=="map")return false;a=c("img[usemap=#"+d+"]")[0];return!!a&&k(a)}return(/input|select|textarea|button|object/.test(b)?!a.disabled:"a"==b?a.href||!isNaN(d):!isNaN(d))&&k(a)},tabbable:function(a){var b=c.attr(a,"tabindex");return(isNaN(b)||b>=0)&&c(a).is(":focusable")}});
+c(function(){var a=document.body,b=a.appendChild(b=document.createElement("div"));c.extend(b.style,{minHeight:"100px",height:"auto",padding:0,borderWidth:0});c.support.minHeight=b.offsetHeight===100;c.support.selectstart="onselectstart"in b;a.removeChild(b).style.display="none"});c.extend(c.ui,{plugin:{add:function(a,b,d){a=c.ui[a].prototype;for(var e in d){a.plugins[e]=a.plugins[e]||[];a.plugins[e].push([b,d[e]])}},call:function(a,b,d){if((b=a.plugins[b])&&a.element[0].parentNode)for(var e=0;e<b.length;e++)a.options[b[e][0]]&&
+b[e][1].apply(a.element,d)}},contains:function(a,b){return document.compareDocumentPosition?a.compareDocumentPosition(b)&16:a!==b&&a.contains(b)},hasScroll:function(a,b){if(c(a).css("overflow")==="hidden")return false;b=b&&b==="left"?"scrollLeft":"scrollTop";var d=false;if(a[b]>0)return true;a[b]=1;d=a[b]>0;a[b]=0;return d},isOverAxis:function(a,b,d){return a>b&&a<b+d},isOver:function(a,b,d,e,h,i){return c.ui.isOverAxis(a,d,h)&&c.ui.isOverAxis(b,e,i)}})}})(jQuery);
+;/*!
+ * jQuery UI Widget 1.8.11
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Widget
+ */
+(function(b,j){if(b.cleanData){var k=b.cleanData;b.cleanData=function(a){for(var c=0,d;(d=a[c])!=null;c++)b(d).triggerHandler("remove");k(a)}}else{var l=b.fn.remove;b.fn.remove=function(a,c){return this.each(function(){if(!c)if(!a||b.filter(a,[this]).length)b("*",this).add([this]).each(function(){b(this).triggerHandler("remove")});return l.call(b(this),a,c)})}}b.widget=function(a,c,d){var e=a.split(".")[0],f;a=a.split(".")[1];f=e+"-"+a;if(!d){d=c;c=b.Widget}b.expr[":"][f]=function(h){return!!b.data(h,
+a)};b[e]=b[e]||{};b[e][a]=function(h,g){arguments.length&&this._createWidget(h,g)};c=new c;c.options=b.extend(true,{},c.options);b[e][a].prototype=b.extend(true,c,{namespace:e,widgetName:a,widgetEventPrefix:b[e][a].prototype.widgetEventPrefix||a,widgetBaseClass:f},d);b.widget.bridge(a,b[e][a])};b.widget.bridge=function(a,c){b.fn[a]=function(d){var e=typeof d==="string",f=Array.prototype.slice.call(arguments,1),h=this;d=!e&&f.length?b.extend.apply(null,[true,d].concat(f)):d;if(e&&d.charAt(0)==="_")return h;
+e?this.each(function(){var g=b.data(this,a),i=g&&b.isFunction(g[d])?g[d].apply(g,f):g;if(i!==g&&i!==j){h=i;return false}}):this.each(function(){var g=b.data(this,a);g?g.option(d||{})._init():b.data(this,a,new c(d,this))});return h}};b.Widget=function(a,c){arguments.length&&this._createWidget(a,c)};b.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",options:{disabled:false},_createWidget:function(a,c){b.data(c,this.widgetName,this);this.element=b(c);this.options=b.extend(true,{},this.options,
+this._getCreateOptions(),a);var d=this;this.element.bind("remove."+this.widgetName,function(){d.destroy()});this._create();this._trigger("create");this._init()},_getCreateOptions:function(){return b.metadata&&b.metadata.get(this.element[0])[this.widgetName]},_create:function(){},_init:function(){},destroy:function(){this.element.unbind("."+this.widgetName).removeData(this.widgetName);this.widget().unbind("."+this.widgetName).removeAttr("aria-disabled").removeClass(this.widgetBaseClass+"-disabled ui-state-disabled")},
+widget:function(){return this.element},option:function(a,c){var d=a;if(arguments.length===0)return b.extend({},this.options);if(typeof a==="string"){if(c===j)return this.options[a];d={};d[a]=c}this._setOptions(d);return this},_setOptions:function(a){var c=this;b.each(a,function(d,e){c._setOption(d,e)});return this},_setOption:function(a,c){this.options[a]=c;if(a==="disabled")this.widget()[c?"addClass":"removeClass"](this.widgetBaseClass+"-disabled ui-state-disabled").attr("aria-disabled",c);return this},
+enable:function(){return this._setOption("disabled",false)},disable:function(){return this._setOption("disabled",true)},_trigger:function(a,c,d){var e=this.options[a];c=b.Event(c);c.type=(a===this.widgetEventPrefix?a:this.widgetEventPrefix+a).toLowerCase();d=d||{};if(c.originalEvent){a=b.event.props.length;for(var f;a;){f=b.event.props[--a];c[f]=c.originalEvent[f]}}this.element.trigger(c,d);return!(b.isFunction(e)&&e.call(this.element[0],c,d)===false||c.isDefaultPrevented())}}})(jQuery);
+;/*
+ * jQuery UI Position 1.8.11
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Position
+ */
+(function(c){c.ui=c.ui||{};var n=/left|center|right/,o=/top|center|bottom/,t=c.fn.position,u=c.fn.offset;c.fn.position=function(b){if(!b||!b.of)return t.apply(this,arguments);b=c.extend({},b);var a=c(b.of),d=a[0],g=(b.collision||"flip").split(" "),e=b.offset?b.offset.split(" "):[0,0],h,k,j;if(d.nodeType===9){h=a.width();k=a.height();j={top:0,left:0}}else if(d.setTimeout){h=a.width();k=a.height();j={top:a.scrollTop(),left:a.scrollLeft()}}else if(d.preventDefault){b.at="left top";h=k=0;j={top:b.of.pageY,
+left:b.of.pageX}}else{h=a.outerWidth();k=a.outerHeight();j=a.offset()}c.each(["my","at"],function(){var f=(b[this]||"").split(" ");if(f.length===1)f=n.test(f[0])?f.concat(["center"]):o.test(f[0])?["center"].concat(f):["center","center"];f[0]=n.test(f[0])?f[0]:"center";f[1]=o.test(f[1])?f[1]:"center";b[this]=f});if(g.length===1)g[1]=g[0];e[0]=parseInt(e[0],10)||0;if(e.length===1)e[1]=e[0];e[1]=parseInt(e[1],10)||0;if(b.at[0]==="right")j.left+=h;else if(b.at[0]==="center")j.left+=h/2;if(b.at[1]==="bottom")j.top+=
+k;else if(b.at[1]==="center")j.top+=k/2;j.left+=e[0];j.top+=e[1];return this.each(function(){var f=c(this),l=f.outerWidth(),m=f.outerHeight(),p=parseInt(c.curCSS(this,"marginLeft",true))||0,q=parseInt(c.curCSS(this,"marginTop",true))||0,v=l+p+(parseInt(c.curCSS(this,"marginRight",true))||0),w=m+q+(parseInt(c.curCSS(this,"marginBottom",true))||0),i=c.extend({},j),r;if(b.my[0]==="right")i.left-=l;else if(b.my[0]==="center")i.left-=l/2;if(b.my[1]==="bottom")i.top-=m;else if(b.my[1]==="center")i.top-=
+m/2;i.left=Math.round(i.left);i.top=Math.round(i.top);r={left:i.left-p,top:i.top-q};c.each(["left","top"],function(s,x){c.ui.position[g[s]]&&c.ui.position[g[s]][x](i,{targetWidth:h,targetHeight:k,elemWidth:l,elemHeight:m,collisionPosition:r,collisionWidth:v,collisionHeight:w,offset:e,my:b.my,at:b.at})});c.fn.bgiframe&&f.bgiframe();f.offset(c.extend(i,{using:b.using}))})};c.ui.position={fit:{left:function(b,a){var d=c(window);d=a.collisionPosition.left+a.collisionWidth-d.width()-d.scrollLeft();b.left=
+d>0?b.left-d:Math.max(b.left-a.collisionPosition.left,b.left)},top:function(b,a){var d=c(window);d=a.collisionPosition.top+a.collisionHeight-d.height()-d.scrollTop();b.top=d>0?b.top-d:Math.max(b.top-a.collisionPosition.top,b.top)}},flip:{left:function(b,a){if(a.at[0]!=="center"){var d=c(window);d=a.collisionPosition.left+a.collisionWidth-d.width()-d.scrollLeft();var g=a.my[0]==="left"?-a.elemWidth:a.my[0]==="right"?a.elemWidth:0,e=a.at[0]==="left"?a.targetWidth:-a.targetWidth,h=-2*a.offset[0];b.left+=
+a.collisionPosition.left<0?g+e+h:d>0?g+e+h:0}},top:function(b,a){if(a.at[1]!=="center"){var d=c(window);d=a.collisionPosition.top+a.collisionHeight-d.height()-d.scrollTop();var g=a.my[1]==="top"?-a.elemHeight:a.my[1]==="bottom"?a.elemHeight:0,e=a.at[1]==="top"?a.targetHeight:-a.targetHeight,h=-2*a.offset[1];b.top+=a.collisionPosition.top<0?g+e+h:d>0?g+e+h:0}}}};if(!c.offset.setOffset){c.offset.setOffset=function(b,a){if(/static/.test(c.curCSS(b,"position")))b.style.position="relative";var d=c(b),
+g=d.offset(),e=parseInt(c.curCSS(b,"top",true),10)||0,h=parseInt(c.curCSS(b,"left",true),10)||0;g={top:a.top-g.top+e,left:a.left-g.left+h};"using"in a?a.using.call(b,g):d.css(g)};c.fn.offset=function(b){var a=this[0];if(!a||!a.ownerDocument)return null;if(b)return this.each(function(){c.offset.setOffset(this,b)});return u.call(this)}}})(jQuery);
+;/*
+ * jQuery UI Autocomplete 1.8.11
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Autocomplete
+ *
+ * Depends:
+ * jquery.ui.core.js
+ * jquery.ui.widget.js
+ * jquery.ui.position.js
+ */
+(function(d){var e=0;d.widget("ui.autocomplete",{options:{appendTo:"body",autoFocus:false,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null},pending:0,_create:function(){var a=this,b=this.element[0].ownerDocument,g;this.element.addClass("ui-autocomplete-input").attr("autocomplete","off").attr({role:"textbox","aria-autocomplete":"list","aria-haspopup":"true"}).bind("keydown.autocomplete",function(c){if(!(a.options.disabled||a.element.attr("readonly"))){g=
+false;var f=d.ui.keyCode;switch(c.keyCode){case f.PAGE_UP:a._move("previousPage",c);break;case f.PAGE_DOWN:a._move("nextPage",c);break;case f.UP:a._move("previous",c);c.preventDefault();break;case f.DOWN:a._move("next",c);c.preventDefault();break;case f.ENTER:case f.NUMPAD_ENTER:if(a.menu.active){g=true;c.preventDefault()}case f.TAB:if(!a.menu.active)return;a.menu.select(c);break;case f.ESCAPE:a.element.val(a.term);a.close(c);break;default:clearTimeout(a.searching);a.searching=setTimeout(function(){if(a.term!=
+a.element.val()){a.selectedItem=null;a.search(null,c)}},a.options.delay);break}}}).bind("keypress.autocomplete",function(c){if(g){g=false;c.preventDefault()}}).bind("focus.autocomplete",function(){if(!a.options.disabled){a.selectedItem=null;a.previous=a.element.val()}}).bind("blur.autocomplete",function(c){if(!a.options.disabled){clearTimeout(a.searching);a.closing=setTimeout(function(){a.close(c);a._change(c)},150)}});this._initSource();this.response=function(){return a._response.apply(a,arguments)};
+this.menu=d("<ul></ul>").addClass("ui-autocomplete").appendTo(d(this.options.appendTo||"body",b)[0]).mousedown(function(c){var f=a.menu.element[0];d(c.target).closest(".ui-menu-item").length||setTimeout(function(){d(document).one("mousedown",function(h){h.target!==a.element[0]&&h.target!==f&&!d.ui.contains(f,h.target)&&a.close()})},1);setTimeout(function(){clearTimeout(a.closing)},13)}).menu({focus:function(c,f){f=f.item.data("item.autocomplete");false!==a._trigger("focus",c,{item:f})&&/^key/.test(c.originalEvent.type)&&
+a.element.val(f.value)},selected:function(c,f){var h=f.item.data("item.autocomplete"),i=a.previous;if(a.element[0]!==b.activeElement){a.element.focus();a.previous=i;setTimeout(function(){a.previous=i;a.selectedItem=h},1)}false!==a._trigger("select",c,{item:h})&&a.element.val(h.value);a.term=a.element.val();a.close(c);a.selectedItem=h},blur:function(){a.menu.element.is(":visible")&&a.element.val()!==a.term&&a.element.val(a.term)}}).zIndex(this.element.zIndex()+1).css({top:0,left:0}).hide().data("menu");
+d.fn.bgiframe&&this.menu.element.bgiframe()},destroy:function(){this.element.removeClass("ui-autocomplete-input").removeAttr("autocomplete").removeAttr("role").removeAttr("aria-autocomplete").removeAttr("aria-haspopup");this.menu.element.remove();d.Widget.prototype.destroy.call(this)},_setOption:function(a,b){d.Widget.prototype._setOption.apply(this,arguments);a==="source"&&this._initSource();if(a==="appendTo")this.menu.element.appendTo(d(b||"body",this.element[0].ownerDocument)[0]);a==="disabled"&&
+b&&this.xhr&&this.xhr.abort()},_initSource:function(){var a=this,b,g;if(d.isArray(this.options.source)){b=this.options.source;this.source=function(c,f){f(d.ui.autocomplete.filter(b,c.term))}}else if(typeof this.options.source==="string"){g=this.options.source;this.source=function(c,f){a.xhr&&a.xhr.abort();a.xhr=d.ajax({url:g,data:c,dataType:"json",autocompleteRequest:++e,success:function(h){this.autocompleteRequest===e&&f(h)},error:function(){this.autocompleteRequest===e&&f([])}})}}else this.source=
+this.options.source},search:function(a,b){a=a!=null?a:this.element.val();this.term=this.element.val();if(a.length<this.options.minLength)return this.close(b);clearTimeout(this.closing);if(this._trigger("search",b)!==false)return this._search(a)},_search:function(a){this.pending++;this.element.addClass("ui-autocomplete-loading");this.source({term:a},this.response)},_response:function(a){if(!this.options.disabled&&a&&a.length){a=this._normalize(a);this._suggest(a);this._trigger("open")}else this.close();
+this.pending--;this.pending||this.element.removeClass("ui-autocomplete-loading")},close:function(a){clearTimeout(this.closing);if(this.menu.element.is(":visible")){this.menu.element.hide();this.menu.deactivate();this._trigger("close",a)}},_change:function(a){this.previous!==this.element.val()&&this._trigger("change",a,{item:this.selectedItem})},_normalize:function(a){if(a.length&&a[0].label&&a[0].value)return a;return d.map(a,function(b){if(typeof b==="string")return{label:b,value:b};return d.extend({label:b.label||
+b.value,value:b.value||b.label},b)})},_suggest:function(a){var b=this.menu.element.empty().zIndex(this.element.zIndex()+1);this._renderMenu(b,a);this.menu.deactivate();this.menu.refresh();b.show();this._resizeMenu();b.position(d.extend({of:this.element},this.options.position));this.options.autoFocus&&this.menu.next(new d.Event("mouseover"))},_resizeMenu:function(){var a=this.menu.element;a.outerWidth(Math.max(a.width("").outerWidth(),this.element.outerWidth()))},_renderMenu:function(a,b){var g=this;
+d.each(b,function(c,f){g._renderItem(a,f)})},_renderItem:function(a,b){return d("<li></li>").data("item.autocomplete",b).append(d("<a></a>").text(b.label)).appendTo(a)},_move:function(a,b){if(this.menu.element.is(":visible"))if(this.menu.first()&&/^previous/.test(a)||this.menu.last()&&/^next/.test(a)){this.element.val(this.term);this.menu.deactivate()}else this.menu[a](b);else this.search(null,b)},widget:function(){return this.menu.element}});d.extend(d.ui.autocomplete,{escapeRegex:function(a){return a.replace(/[-[\]{}()*+?.,\\^$|#\s]/g,
+"\\$&")},filter:function(a,b){var g=new RegExp(d.ui.autocomplete.escapeRegex(b),"i");return d.grep(a,function(c){return g.test(c.label||c.value||c)})}})})(jQuery);
+(function(d){d.widget("ui.menu",{_create:function(){var e=this;this.element.addClass("ui-menu ui-widget ui-widget-content ui-corner-all").attr({role:"listbox","aria-activedescendant":"ui-active-menuitem"}).click(function(a){if(d(a.target).closest(".ui-menu-item a").length){a.preventDefault();e.select(a)}});this.refresh()},refresh:function(){var e=this;this.element.children("li:not(.ui-menu-item):has(a)").addClass("ui-menu-item").attr("role","menuitem").children("a").addClass("ui-corner-all").attr("tabindex",
+-1).mouseenter(function(a){e.activate(a,d(this).parent())}).mouseleave(function(){e.deactivate()})},activate:function(e,a){this.deactivate();if(this.hasScroll()){var b=a.offset().top-this.element.offset().top,g=this.element.attr("scrollTop"),c=this.element.height();if(b<0)this.element.attr("scrollTop",g+b);else b>=c&&this.element.attr("scrollTop",g+b-c+a.height())}this.active=a.eq(0).children("a").addClass("ui-state-hover").attr("id","ui-active-menuitem").end();this._trigger("focus",e,{item:a})},
+deactivate:function(){if(this.active){this.active.children("a").removeClass("ui-state-hover").removeAttr("id");this._trigger("blur");this.active=null}},next:function(e){this.move("next",".ui-menu-item:first",e)},previous:function(e){this.move("prev",".ui-menu-item:last",e)},first:function(){return this.active&&!this.active.prevAll(".ui-menu-item").length},last:function(){return this.active&&!this.active.nextAll(".ui-menu-item").length},move:function(e,a,b){if(this.active){e=this.active[e+"All"](".ui-menu-item").eq(0);
+e.length?this.activate(b,e):this.activate(b,this.element.children(a))}else this.activate(b,this.element.children(a))},nextPage:function(e){if(this.hasScroll())if(!this.active||this.last())this.activate(e,this.element.children(".ui-menu-item:first"));else{var a=this.active.offset().top,b=this.element.height(),g=this.element.children(".ui-menu-item").filter(function(){var c=d(this).offset().top-a-b+d(this).height();return c<10&&c>-10});g.length||(g=this.element.children(".ui-menu-item:last"));this.activate(e,
+g)}else this.activate(e,this.element.children(".ui-menu-item").filter(!this.active||this.last()?":first":":last"))},previousPage:function(e){if(this.hasScroll())if(!this.active||this.first())this.activate(e,this.element.children(".ui-menu-item:last"));else{var a=this.active.offset().top,b=this.element.height();result=this.element.children(".ui-menu-item").filter(function(){var g=d(this).offset().top-a+b-d(this).height();return g<10&&g>-10});result.length||(result=this.element.children(".ui-menu-item:first"));
+this.activate(e,result)}else this.activate(e,this.element.children(".ui-menu-item").filter(!this.active||this.first()?":last":":first"))},hasScroll:function(){return this.element.height()<this.element.attr("scrollHeight")},select:function(e){this._trigger("selected",e,{item:this.active})}})})(jQuery);
+; \ No newline at end of file
diff --git a/rel/overlay/share/www/script/jquery.couch.js b/rel/overlay/share/www/script/jquery.couch.js
index 114e5801..634a03fe 100644
--- a/rel/overlay/share/www/script/jquery.couch.js
+++ b/rel/overlay/share/www/script/jquery.couch.js
@@ -22,25 +22,6 @@
return encodeURIComponent(docID);
};
- function prepareUserDoc(user_doc, new_password) {
- if (typeof hex_sha1 == "undefined") {
- alert("creating a user doc requires sha1.js to be loaded in the page");
- return;
- }
- var user_prefix = "org.couchdb.user:";
- user_doc._id = user_doc._id || user_prefix + user_doc.name;
- if (new_password) {
- // handle the password crypto
- user_doc.salt = $.couch.newUUID();
- user_doc.password_sha = hex_sha1(new_password + user_doc.salt);
- }
- user_doc.type = "user";
- if (!user_doc.roles) {
- user_doc.roles = [];
- }
- return user_doc;
- };
-
var uuidCache = [];
$.extend($.couch, {
@@ -87,8 +68,11 @@
options = options || {};
$.ajax({
type: "GET", url: this.urlPrefix + "/_session",
+ beforeSend: function(xhr) {
+ xhr.setRequestHeader('Accept', 'application/json');
+ },
complete: function(req) {
- var resp = $.httpData(req, "json");
+ var resp = httpData(req, "json");
if (req.status == 200) {
if (options.success) options.success(resp);
} else if (options.error) {
@@ -112,19 +96,41 @@
signup: function(user_doc, password, options) {
options = options || {};
// prepare user doc based on name and password
- user_doc = prepareUserDoc(user_doc, password);
+ user_doc = this.prepareUserDoc(user_doc, password);
$.couch.userDb(function(db) {
db.saveDoc(user_doc, options);
});
},
-
+
+ prepareUserDoc: function(user_doc, new_password) {
+ if (typeof hex_sha1 == "undefined") {
+ alert("creating a user doc requires sha1.js to be loaded in the page");
+ return;
+ }
+ var user_prefix = "org.couchdb.user:";
+ user_doc._id = user_doc._id || user_prefix + user_doc.name;
+ if (new_password) {
+ // handle the password crypto
+ user_doc.salt = $.couch.newUUID();
+ user_doc.password_sha = hex_sha1(new_password + user_doc.salt);
+ }
+ user_doc.type = "user";
+ if (!user_doc.roles) {
+ user_doc.roles = [];
+ }
+ return user_doc;
+ },
+
login: function(options) {
options = options || {};
$.ajax({
type: "POST", url: this.urlPrefix + "/_session", dataType: "json",
data: {name: options.name, password: options.password},
+ beforeSend: function(xhr) {
+ xhr.setRequestHeader('Accept', 'application/json');
+ },
complete: function(req) {
- var resp = $.httpData(req, "json");
+ var resp = httpData(req, "json");
if (req.status == 200) {
if (options.success) options.success(resp);
} else if (options.error) {
@@ -140,8 +146,11 @@
$.ajax({
type: "DELETE", url: this.urlPrefix + "/_session", dataType: "json",
username : "_", password : "_",
+ beforeSend: function(xhr) {
+ xhr.setRequestHeader('Accept', 'application/json');
+ },
complete: function(req) {
- var resp = $.httpData(req, "json");
+ var resp = httpData(req, "json");
if (req.status == 200) {
if (options.success) options.success(resp);
} else if (options.error) {
@@ -385,7 +394,7 @@
dataType: "json", data: toJSON(doc),
beforeSend : beforeSend,
complete: function(req) {
- var resp = $.httpData(req, "json");
+ var resp = httpData(req, "json");
if (req.status == 200 || req.status == 201 || req.status == 202) {
doc._id = resp.id;
doc._rev = resp.rev;
@@ -450,7 +459,7 @@
copyDoc: function(docId, options, ajaxOptions) {
ajaxOptions = $.extend(ajaxOptions, {
complete: function(req) {
- var resp = $.httpData(req, "json");
+ var resp = httpData(req, "json");
if (req.status == 201) {
if (options.success) options.success(resp);
} else if (options.error) {
@@ -563,7 +572,7 @@
replicate: function(source, target, ajaxOptions, repOpts) {
repOpts = $.extend({source: source, target: target}, repOpts);
- if (repOpts.continuous) {
+ if (repOpts.continuous && !repOpts.cancel) {
ajaxOptions.successStatus = 202;
}
ajax({
@@ -593,9 +602,36 @@
}
});
+ var httpData = $.httpData || function( xhr, type, s ) { // lifted from jq1.4.4
+ var ct = xhr.getResponseHeader("content-type") || "",
+ xml = type === "xml" || !type && ct.indexOf("xml") >= 0,
+ data = xml ? xhr.responseXML : xhr.responseText;
+
+ if ( xml && data.documentElement.nodeName === "parsererror" ) {
+ $.error( "parsererror" );
+ }
+ if ( s && s.dataFilter ) {
+ data = s.dataFilter( data, type );
+ }
+ if ( typeof data === "string" ) {
+ if ( type === "json" || !type && ct.indexOf("json") >= 0 ) {
+ data = $.parseJSON( data );
+ } else if ( type === "script" || !type && ct.indexOf("javascript") >= 0 ) {
+ $.globalEval( data );
+ }
+ }
+ return data;
+ };
+
function ajax(obj, options, errorMessage, ajaxOptions) {
+
+ var defaultAjaxOpts = {
+ contentType: "application/json",
+ headers:{"Accept": "application/json"}
+ };
+
options = $.extend({successStatus: 200}, options);
- ajaxOptions = $.extend({contentType: "application/json"}, ajaxOptions);
+ ajaxOptions = $.extend(defaultAjaxOpts, ajaxOptions);
errorMessage = errorMessage || "Unknown error";
$.ajax($.extend($.extend({
type: "GET", dataType: "json", cache : !$.browser.msie,
@@ -608,7 +644,7 @@
},
complete: function(req) {
try {
- var resp = $.httpData(req, "json");
+ var resp = httpData(req, "json");
} catch(e) {
if (options.error) {
options.error(req.status, req, e);
@@ -638,6 +674,7 @@
var commit = options.ensure_full_commit;
delete options.ensure_full_commit;
return function(xhr) {
+ xhr.setRequestHeader('Accept', 'application/json');
xhr.setRequestHeader("X-Couch-Full-Commit", commit.toString());
};
}
diff --git a/rel/overlay/share/www/script/json2.js b/rel/overlay/share/www/script/json2.js
index 39d8f370..a1a3b170 100644
--- a/rel/overlay/share/www/script/json2.js
+++ b/rel/overlay/share/www/script/json2.js
@@ -1,6 +1,6 @@
/*
http://www.JSON.org/json2.js
- 2009-09-29
+ 2010-03-20
Public Domain.
@@ -433,6 +433,7 @@ if (!this.JSON) {
// 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) {
diff --git a/rel/overlay/share/www/script/jspec/jspec.js b/rel/overlay/share/www/script/jspec/jspec.js
index d6daf5ef..b2ea4768 100644
--- a/rel/overlay/share/www/script/jspec/jspec.js
+++ b/rel/overlay/share/www/script/jspec/jspec.js
@@ -87,7 +87,7 @@
*/
Server : function(results, options) {
- var uri = options.uri || 'http://' + window.location.host + '/results'
+ var uri = options.uri || window.location.protocol + "//" + window.location.host + '/results'
JSpec.post(uri, {
stats: JSpec.stats,
options: options,
diff --git a/rel/overlay/share/www/script/test/all_docs.js b/rel/overlay/share/www/script/test/all_docs.js
index ab443605..1d83aa95 100644
--- a/rel/overlay/share/www/script/test/all_docs.js
+++ b/rel/overlay/share/www/script/test/all_docs.js
@@ -86,10 +86,51 @@ couchTests.all_docs = function(debug) {
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();
};
diff --git a/rel/overlay/share/www/script/test/attachment_names.js b/rel/overlay/share/www/script/test/attachment_names.js
index 988dd2d2..777b5ece 100644
--- a/rel/overlay/share/www/script/test/attachment_names.js
+++ b/rel/overlay/share/www/script/test/attachment_names.js
@@ -16,6 +16,24 @@ couchTests.attachment_names = function(debug) {
db.createDb();
if (debug) debugger;
+ var goodDoc = {
+ _id: "good_doc",
+ _attachments: {
+ "Колян.txt": {
+ content_type:"text/plain",
+ 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") == "text/plain");
+ T(xhr.getResponseHeader("Etag") == '"' + save_response.rev + '"');
+
var binAttDoc = {
_id: "bin_doc",
_attachments:{
@@ -27,28 +45,23 @@ couchTests.attachment_names = function(debug) {
};
// inline attachments
- try {
- db.save(binAttDoc);
- TEquals(1, 2, "Attachment name with non UTF-8 encoding saved. Should never show!");
- } catch (e) {
- TEquals("bad_request", e.error, "attachment_name: inline attachments");
- TEquals("Attachment name is not UTF-8 encoded", e.reason, "attachment_name: 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(400, xhr.status, "attachment_name: standalone API");
- TEquals("bad_request", resp.error, "attachment_name: standalone API");
- TEquals("Attachment name is not UTF-8 encoded", resp.reason, "attachment_name: standalone API");
-
+ TEquals(201, xhr.status, "attachment_name: standalone API");
+ TEquals("Created", xhr.statusText, "attachment_name: standalone API");
+ TEquals(true, resp.ok, "attachment_name: standalone API");
// bulk docs
var docs = { docs: [binAttDoc] };
@@ -57,10 +70,8 @@ couchTests.attachment_names = function(debug) {
body: JSON.stringify(docs)
});
- var resp = JSON.parse(xhr.responseText);
- TEquals(400, xhr.status, "attachment_name: bulk docs");
- TEquals("bad_request", resp.error, "attachment_name: bulk docs");
- TEquals("Attachment name is not UTF-8 encoded", resp.reason, "attachment_name: bulk docs");
+ TEquals(201, xhr.status, "attachment_name: bulk docs");
+ TEquals("Created", xhr.statusText, "attachment_name: bulk docs");
// leading underscores
diff --git a/rel/overlay/share/www/script/test/attachment_ranges.js b/rel/overlay/share/www/script/test/attachment_ranges.js
new file mode 100644
index 00000000..e1d40eae
--- /dev/null
+++ b/rel/overlay/share/www/script/test/attachment_ranges.js
@@ -0,0 +1,134 @@
+// 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_ranges = function(debug) {
+ var db = new CouchDB("test_suite_db", {
+ "X-Couch-Full-Commit": "false"
+ });
+ db.deleteDb();
+ db.createDb();
+
+ if (debug) debugger;
+
+ 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", {
+ 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 206.
+ var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc/foo.txt", {
+ headers: {
+ "Range": "bytes=0-"
+ }
+ });
+ TEquals(206, xhr.status, "fetch 0-");
+ TEquals("This is a base64 encoded text", xhr.responseText);
+ TEquals("bytes 0-28/29", xhr.getResponseHeader("Content-Range"));
+ TEquals("29", xhr.getResponseHeader("Content-Length"));
+
+ // Badly formed range header is a 200.
+ var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc/foo.txt", {
+ 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", {
+ 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", {
+ 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", {
+ 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", {
+ 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", {
+ 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", {
+ 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", {
+ headers: {
+ "Range": "bytes=300-310"
+ }
+ });
+ TEquals(416, xhr.status, "fetch 300-310");
+
+};
diff --git a/rel/overlay/share/www/script/test/attachments.js b/rel/overlay/share/www/script/test/attachments.js
index e16c384f..826373dc 100644
--- a/rel/overlay/share/www/script/test/attachments.js
+++ b/rel/overlay/share/www/script/test/attachments.js
@@ -93,6 +93,7 @@ couchTests.attachments= function(debug) {
});
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);
@@ -110,6 +111,7 @@ couchTests.attachments= function(debug) {
});
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);
diff --git a/rel/overlay/share/www/script/test/attachments_multipart.js b/rel/overlay/share/www/script/test/attachments_multipart.js
index f173d2bb..2fc8e3bd 100644
--- a/rel/overlay/share/www/script/test/attachments_multipart.js
+++ b/rel/overlay/share/www/script/test/attachments_multipart.js
@@ -78,15 +78,19 @@ couchTests.attachments_multipart= function(debug) {
// now edit an attachment
- var doc = db.open("multipart");
+ var doc = db.open("multipart", {att_encoding_info: true});
var firstrev = doc._rev;
T(doc._attachments["foo.txt"].stub == true);
T(doc._attachments["bar.txt"].stub == true);
T(doc._attachments["baz.txt"].stub == true);
+ TEquals("undefined", typeof doc._attachments["foo.txt"].encoding);
+ TEquals("undefined", typeof doc._attachments["bar.txt"].encoding);
+ TEquals("gzip", doc._attachments["baz.txt"].encoding);
//lets change attachment bar
delete doc._attachments["bar.txt"].stub; // remove stub member (or could set to false)
+ delete doc._attachments["bar.txt"].digest; // remove the digest (it's for the gzip form)
doc._attachments["bar.txt"].length = 18;
doc._attachments["bar.txt"].follows = true;
//lets delete attachment baz:
@@ -104,6 +108,7 @@ couchTests.attachments_multipart= function(debug) {
"this is 18 chars l" +
"\r\n--abc123--"
});
+ TEquals(201, xhr.status);
xhr = CouchDB.request("GET", "/test_suite_db/multipart/bar.txt");
@@ -115,8 +120,11 @@ couchTests.attachments_multipart= function(debug) {
// now test receiving multipart docs
function getBoundary(xhr) {
+ if (xhr instanceof XMLHttpRequest) {
var ctype = xhr.getResponseHeader("Content-Type");
-
+ } else {
+ var ctype = xhr.headers['Content-Type'];
+ }
var ctypeArgs = ctype.split("; ").slice(1);
var boundary = null;
for(var i=0; i<ctypeArgs.length; i++) {
@@ -134,7 +142,11 @@ couchTests.attachments_multipart= function(debug) {
function parseMultipart(xhr) {
var boundary = getBoundary(xhr);
+ if (xhr instanceof XMLHttpRequest) {
var mimetext = xhr.responseText;
+ } else {
+ var mimetext = xhr.body;
+ }
// strip off leading boundary
var leading = "--" + boundary + "\r\n";
var last = "\r\n--" + boundary + "--";
@@ -208,6 +220,33 @@ couchTests.attachments_multipart= function(debug) {
T(sections[1].body == "this is 18 chars l");
+ // try the atts_since parameter together with the open_revs parameter
+ xhr = CouchDB.request(
+ "GET",
+ '/test_suite_db/multipart?open_revs=["' +
+ doc._rev + '"]&atts_since=["' + firstrev + '"]',
+ {headers: {"accept": "multipart/mixed"}}
+ );
+
+ T(xhr.status === 200);
+
+ sections = parseMultipart(xhr);
+ // 1 section, with a multipart/related Content-Type
+ T(sections.length === 1);
+ T(sections[0].headers['Content-Type'].indexOf('multipart/related;') === 0);
+
+ var innerSections = parseMultipart(sections[0]);
+ // 2 inner sections: a document body section plus an attachment data section
+ T(innerSections.length === 2);
+ T(innerSections[0].headers['content-type'] === 'application/json');
+
+ doc = JSON.parse(innerSections[0].body);
+
+ T(doc._attachments['foo.txt'].stub === true);
+ T(doc._attachments['bar.txt'].follows === true);
+
+ T(innerSections[1].body === "this is 18 chars l");
+
// try it with a rev that doesn't exist (should get all attachments)
xhr = CouchDB.request("GET", "/test_suite_db/multipart?atts_since=[\"1-2897589\"]",
@@ -245,4 +284,130 @@ couchTests.attachments_multipart= function(debug) {
T(sections[1].body == "this is 18 chars l");
+
+ // check that with the document multipart/mixed API it's possible to receive
+ // attachments in compressed form (if they're stored in compressed form)
+
+ var server_config = [
+ {
+ section: "attachments",
+ key: "compression_level",
+ value: "8"
+ },
+ {
+ section: "attachments",
+ key: "compressible_types",
+ value: "text/plain"
+ }
+ ];
+
+ function testMultipartAttCompression() {
+ var doc = { _id: "foobar" };
+ var lorem =
+ CouchDB.request("GET", "/_utils/script/test/lorem.txt").responseText;
+ var helloData = "hello world";
+
+ TEquals(true, db.save(doc).ok);
+
+ var firstRev = doc._rev;
+ var xhr = CouchDB.request(
+ "PUT",
+ "/" + db.name + "/" + doc._id + "/data.bin?rev=" + firstRev,
+ {
+ body: helloData,
+ headers: {"Content-Type": "application/binary"}
+ }
+ );
+ TEquals(201, xhr.status);
+
+ var secondRev = db.open(doc._id)._rev;
+ xhr = CouchDB.request(
+ "PUT",
+ "/" + db.name + "/" + doc._id + "/lorem.txt?rev=" + secondRev,
+ {
+ body: lorem,
+ headers: {"Content-Type": "text/plain"}
+ }
+ );
+ TEquals(201, xhr.status);
+
+ var thirdRev = db.open(doc._id)._rev;
+
+ xhr = CouchDB.request(
+ "GET",
+ '/' + db.name + '/' + doc._id + '?open_revs=["' + thirdRev + '"]',
+ {
+ headers: {
+ "Accept": "multipart/mixed",
+ "X-CouchDB-Send-Encoded-Atts": "true"
+ }
+ }
+ );
+ TEquals(200, xhr.status);
+
+ var sections = parseMultipart(xhr);
+ // 1 section, with a multipart/related Content-Type
+ TEquals(1, sections.length);
+ TEquals(0,
+ sections[0].headers['Content-Type'].indexOf('multipart/related;'));
+
+ var innerSections = parseMultipart(sections[0]);
+ // 3 inner sections: a document body section plus 2 attachment data sections
+ TEquals(3, innerSections.length);
+ TEquals('application/json', innerSections[0].headers['content-type']);
+
+ doc = JSON.parse(innerSections[0].body);
+
+ TEquals(true, doc._attachments['lorem.txt'].follows);
+ TEquals("gzip", doc._attachments['lorem.txt'].encoding);
+ TEquals(true, doc._attachments['data.bin'].follows);
+ T(doc._attachments['data.bin'] !== "gzip");
+
+ if (innerSections[1].body === helloData) {
+ T(innerSections[2].body !== lorem);
+ } else if (innerSections[2].body === helloData) {
+ T(innerSections[1].body !== lorem);
+ } else {
+ T(false, "Could not found data.bin attachment data");
+ }
+
+ // now test that it works together with the atts_since parameter
+
+ xhr = CouchDB.request(
+ "GET",
+ '/' + db.name + '/' + doc._id + '?open_revs=["' + thirdRev + '"]' +
+ '&atts_since=["' + secondRev + '"]',
+ {
+ headers: {
+ "Accept": "multipart/mixed",
+ "X-CouchDB-Send-Encoded-Atts": "true"
+ }
+ }
+ );
+ TEquals(200, xhr.status);
+
+ sections = parseMultipart(xhr);
+ // 1 section, with a multipart/related Content-Type
+ TEquals(1, sections.length);
+ TEquals(0,
+ sections[0].headers['Content-Type'].indexOf('multipart/related;'));
+
+ innerSections = parseMultipart(sections[0]);
+ // 2 inner sections: a document body section plus 1 attachment data section
+ TEquals(2, innerSections.length);
+ TEquals('application/json', innerSections[0].headers['content-type']);
+
+ doc = JSON.parse(innerSections[0].body);
+
+ TEquals(true, doc._attachments['lorem.txt'].follows);
+ TEquals("gzip", doc._attachments['lorem.txt'].encoding);
+ TEquals("undefined", typeof doc._attachments['data.bin'].follows);
+ TEquals(true, doc._attachments['data.bin'].stub);
+ T(innerSections[1].body !== lorem);
+ }
+
+ run_on_modified_server(server_config, testMultipartAttCompression);
+
+ // cleanup
+ db.deleteDb();
};
diff --git a/rel/overlay/share/www/script/test/basics.js b/rel/overlay/share/www/script/test/basics.js
index 8885ba6e..30c27c11 100644
--- a/rel/overlay/share/www/script/test/basics.js
+++ b/rel/overlay/share/www/script/test/basics.js
@@ -37,9 +37,8 @@ couchTests.basics = function(debug) {
TEquals(dbname,
xhr.getResponseHeader("Location").substr(-dbname.length),
"should return Location header to newly created document");
-
- TEquals("http://",
- xhr.getResponseHeader("Location").substr(0, 7),
+ TEquals(CouchDB.protocol,
+ xhr.getResponseHeader("Location").substr(0, CouchDB.protocol.length),
"should return absolute Location header to newly created document");
});
@@ -160,8 +159,8 @@ couchTests.basics = function(debug) {
var loc = xhr.getResponseHeader("Location");
T(loc, "should have a Location header");
var locs = loc.split('/');
- T(locs[4] == resp.id);
- T(locs[3] == "test_suite_db");
+ T(locs[locs.length-1] == resp.id);
+ T(locs[locs.length-2] == "test_suite_db");
// test that that POST's with an _id aren't overriden with a UUID.
var xhr = CouchDB.request("POST", "/test_suite_db", {
@@ -181,9 +180,8 @@ couchTests.basics = function(debug) {
TEquals("/test_suite_db/newdoc",
xhr.getResponseHeader("Location").substr(-21),
"should return Location header to newly created document");
-
- TEquals("http://",
- xhr.getResponseHeader("Location").substr(0, 7),
+ TEquals(CouchDB.protocol,
+ xhr.getResponseHeader("Location").substr(0, CouchDB.protocol.length),
"should return absolute Location header to newly created document");
// deleting a non-existent doc should be 404
diff --git a/rel/overlay/share/www/script/test/changes.js b/rel/overlay/share/www/script/test/changes.js
index 50649508..ea22bfb3 100644
--- a/rel/overlay/share/www/script/test/changes.js
+++ b/rel/overlay/share/www/script/test/changes.js
@@ -12,7 +12,7 @@
function jsonp(obj) {
T(jsonp_flag == 0);
- T(obj.results.length == 1 && obj.last_seq==1, "jsonp")
+ T(obj.results.length == 1 && obj.last_seq == 1, "jsonp");
jsonp_flag = 1;
}
@@ -25,7 +25,7 @@ couchTests.changes = function(debug) {
var req = CouchDB.request("GET", "/test_suite_db/_changes");
var resp = JSON.parse(req.responseText);
- T(resp.results.length == 0 && resp.last_seq==0, "empty db")
+ T(resp.results.length == 0 && resp.last_seq == 0, "empty db");
var docFoo = {_id:"foo", bar:1};
T(db.save(docFoo).ok);
T(db.ensureFullCommit().ok);
@@ -35,8 +35,8 @@ couchTests.changes = function(debug) {
var resp = JSON.parse(req.responseText);
T(resp.last_seq == 1);
- T(resp.results.length == 1, "one doc db")
- T(resp.results[0].changes[0].rev == docFoo._rev)
+ T(resp.results.length == 1, "one doc db");
+ T(resp.results[0].changes[0].rev == docFoo._rev);
// test with callback
@@ -90,16 +90,16 @@ couchTests.changes = function(debug) {
change1 = JSON.parse(lines[0]);
change2 = JSON.parse(lines[1]);
if (change2.seq != 2) {
- throw "bad seq, try again"
+ throw "bad seq, try again";
}
}, "bar-only");
- T(change1.seq == 1)
- T(change1.id == "foo")
+ T(change1.seq == 1);
+ T(change1.id == "foo");
- T(change2.seq == 2)
- T(change2.id == "bar")
- T(change2.changes[0].rev == docBar._rev)
+ T(change2.seq == 2);
+ T(change2.id == "bar");
+ T(change2.changes[0].rev == docBar._rev);
var docBaz = {_id:"baz", baz:1};
@@ -110,7 +110,7 @@ couchTests.changes = function(debug) {
lines = xhr.responseText.split("\n");
change3 = JSON.parse(lines[2]);
if (change3.seq != 3) {
- throw "bad seq, try again"
+ throw "bad seq, try again";
}
});
@@ -133,8 +133,8 @@ couchTests.changes = function(debug) {
}
}, "heartbeat");
- T(str.charAt(str.length - 1) == "\n")
- T(str.charAt(str.length - 2) == "\n")
+ T(str.charAt(str.length - 1) == "\n");
+ T(str.charAt(str.length - 2) == "\n");
// otherwise we'll continue to receive heartbeats forever
xhr.abort();
@@ -164,10 +164,10 @@ couchTests.changes = function(debug) {
if (line.charAt(line.length-1) == ",") {
var linetrimmed = line.substring(0, line.length-1);
} else {
- var linetrimmed = line
+ var linetrimmed = line;
}
return JSON.parse(linetrimmed);
- }
+ };
waitForSuccess(function() {
lines = xhr.responseText.split("\n");
@@ -181,6 +181,8 @@ couchTests.changes = function(debug) {
T(change.id == "barz");
T(change.changes[0].rev == docBarz._rev);
T(lines[3]=='"last_seq":4}');
+
+
}
// test the filtered changes
@@ -195,7 +197,7 @@ couchTests.changes = function(debug) {
"userCtx" : stringFun(function(doc, req) {
return doc.user && (doc.user == req.userCtx.name);
}),
- "conflicted" : "function(doc, req) { return (doc._conflicts);}",
+ "conflicted" : "function(doc, req) { return (doc._conflicts);}"
},
options : {
local_seq : true
@@ -205,7 +207,7 @@ couchTests.changes = function(debug) {
map : "function(doc) {emit(doc._local_seq, null)}"
}
}
- }
+ };
db.save(ddoc);
@@ -251,8 +253,8 @@ couchTests.changes = function(debug) {
T(resp.results && resp.results.length > 0 && resp.results[0]["id"] == "bingo", "filter the correct update");
xhr.abort();
- timeout = 500;
- last_seq = 10
+ var timeout = 500;
+ var last_seq = 10;
while (true) {
// filter with continuous
@@ -267,15 +269,15 @@ couchTests.changes = function(debug) {
waitForSuccess(function() { // throws an error after 5 seconds
if (xhr.readyState != 4) {
- throw("still waiting")
+ throw("still waiting");
}
}, "continuous-rusty");
lines = xhr.responseText.split("\n");
+ var good = false;
try {
- JSON.parse(lines[3])
+ JSON.parse(lines[3]);
good = true;
} catch(e) {
- good = false;
}
if (good) {
T(JSON.parse(lines[1]).id == "bingo", lines[1]);
@@ -350,7 +352,7 @@ couchTests.changes = function(debug) {
req = CouchDB.request("GET", "/test_suite_db/_changes?limit=1");
resp = JSON.parse(req.responseText);
- TEquals(1, resp.results.length)
+ TEquals(1, resp.results.length);
//filter includes _conflicts
var id = db.save({'food' : 'pizza'}).id;
@@ -396,7 +398,116 @@ couchTests.changes = function(debug) {
T(resp.results.length === 2);
T(resp.results[0].id === "doc2");
T(resp.results[1].id === "doc4");
+
+ // test filtering on docids
+ //
+
+ var options = {
+ headers: {"Content-Type": "application/json"},
+ body: JSON.stringify({"doc_ids": ["something", "anotherthing", "andmore"]})
+ };
+
+ var req = CouchDB.request("POST", "/test_suite_db/_changes?filter=_doc_ids", options);
+ var resp = JSON.parse(req.responseText);
+ T(resp.results.length === 0);
+
+ T(db.save({"_id":"something", "bop" : "plankton"}).ok);
+ var req = CouchDB.request("POST", "/test_suite_db/_changes?filter=_doc_ids", options);
+ var resp = JSON.parse(req.responseText);
+ T(resp.results.length === 1);
+ T(resp.results[0].id === "something");
+
+ T(db.save({"_id":"anotherthing", "bop" : "plankton"}).ok);
+ var req = CouchDB.request("POST", "/test_suite_db/_changes?filter=_doc_ids", options);
+ var resp = JSON.parse(req.responseText);
+ T(resp.results.length === 2);
+ T(resp.results[0].id === "something");
+ T(resp.results[1].id === "anotherthing");
+
+ var docids = JSON.stringify(["something", "anotherthing", "andmore"]),
+ req = CouchDB.request("GET", "/test_suite_db/_changes?filter=_doc_ids&doc_ids="+docids, options);
+ var resp = JSON.parse(req.responseText);
+ T(resp.results.length === 2);
+ T(resp.results[0].id === "something");
+ T(resp.results[1].id === "anotherthing");
+
+ var req = CouchDB.request("GET", "/test_suite_db/_changes?filter=_design");
+ var resp = JSON.parse(req.responseText);
+ T(resp.results.length === 1);
+ T(resp.results[0].id === "_design/erlang");
+
+
+ if (!is_safari && xhr) {
+ // filter docids with continuous
+ xhr = CouchDB.newXhr();
+ xhr.open("POST", "/test_suite_db/_changes?feed=continuous&timeout=500&since=7&filter=_doc_ids", true);
+ xhr.setRequestHeader("Content-Type", "application/json");
+
+ xhr.send(options.body);
+
+ T(db.save({"_id":"andmore", "bop" : "plankton"}).ok);
+
+
+ waitForSuccess(function() {
+ if (xhr.readyState != 4) {
+ throw("still waiting");
+ }
+ }, "andmore-only");
+
+ var line = JSON.parse(xhr.responseText.split("\n")[0]);
+ T(line.seq == 8);
+ T(line.id == "andmore");
+ }
+
});
+ // COUCHDB-1037 - empty result for ?limit=1&filter=foo/bar in some cases
+ T(db.deleteDb());
+ T(db.createDb());
+
+ ddoc = {
+ _id: "_design/testdocs",
+ filters: {
+ testdocsonly: (function(doc, req) {
+ return (typeof doc.integer === "number");
+ }).toString()
+ }
+ };
+ T(db.save(ddoc));
+
+ ddoc = {
+ _id: "_design/foobar",
+ foo: "bar"
+ };
+ T(db.save(ddoc));
+
+ db.bulkSave(makeDocs(0, 5));
+
+ req = CouchDB.request("GET", "/" + db.name + "/_changes");
+ resp = JSON.parse(req.responseText);
+ TEquals(7, resp.last_seq);
+ TEquals(7, resp.results.length);
+
+ req = CouchDB.request(
+ "GET", "/"+ db.name + "/_changes?limit=1&filter=testdocs/testdocsonly");
+ resp = JSON.parse(req.responseText);
+ TEquals(3, resp.last_seq);
+ TEquals(1, resp.results.length);
+ TEquals("0", resp.results[0].id);
+
+ req = CouchDB.request(
+ "GET", "/" + db.name + "/_changes?limit=2&filter=testdocs/testdocsonly");
+ resp = JSON.parse(req.responseText);
+ TEquals(4, resp.last_seq);
+ TEquals(2, resp.results.length);
+ TEquals("0", resp.results[0].id);
+ TEquals("1", resp.results[1].id);
+
+ TEquals(0, CouchDB.requestStats('httpd', 'clients_requesting_changes').current);
+ CouchDB.request("GET", "/" + db.name + "/_changes");
+ TEquals(0, CouchDB.requestStats('httpd', 'clients_requesting_changes').current);
+
+ // cleanup
+ db.deleteDb();
};
diff --git a/rel/overlay/share/www/script/test/config.js b/rel/overlay/share/www/script/test/config.js
index ef74934b..e83ecfd9 100644
--- a/rel/overlay/share/www/script/test/config.js
+++ b/rel/overlay/share/www/script/test/config.js
@@ -29,19 +29,25 @@ couchTests.config = function(debug) {
*/
var server_port = CouchDB.host.split(':');
if(server_port.length == 1 && CouchDB.inBrowser) {
- var proto = window.location.protocol;
- if(proto == "http:") {
+ if(CouchDB.protocol == "http://") {
port = 80;
}
- if(proto == "https:") {
+ if(CouchDB.protocol == "https://") {
port = 443;
}
} else {
port = server_port.pop();
}
+ if(CouchDB.protocol == "http://") {
+ config_port = config.httpd.port;
+ }
+ if(CouchDB.protocol == "https://") {
+ config_port = config.ssl.port;
+ }
+
if(port) {
- T(config.httpd.port == port);
+ TEquals(config_port, port, "ports should match");
}
T(config.couchdb.database_dir);
@@ -50,7 +56,8 @@ couchTests.config = function(debug) {
T(config.log.level);
T(config.query_servers.javascript);
- // test that settings can be altered
+ // test that settings can be altered, and that an undefined whitelist allows any change
+ TEquals(undefined, config.httpd.config_whitelist, "Default whitelist is empty");
xhr = CouchDB.request("PUT", "/_config/test/foo",{
body : JSON.stringify("bar"),
headers: {"X-Couch-Persist": "false"}
@@ -64,4 +71,93 @@ couchTests.config = function(debug) {
xhr = CouchDB.request("GET", "/_config/test/foo");
config = JSON.parse(xhr.responseText);
T(config == "bar");
+
+ // Non-term whitelist values allow further modification of the whitelist.
+ xhr = CouchDB.request("PUT", "/_config/httpd/config_whitelist",{
+ body : JSON.stringify("!This is an invalid Erlang term!"),
+ headers: {"X-Couch-Persist": "false"}
+ });
+ TEquals(200, xhr.status, "Set config whitelist to an invalid Erlang term");
+ xhr = CouchDB.request("DELETE", "/_config/httpd/config_whitelist",{
+ headers: {"X-Couch-Persist": "false"}
+ });
+ TEquals(200, xhr.status, "Modify whitelist despite it being invalid syntax");
+
+ // Non-list whitelist values allow further modification of the whitelist.
+ xhr = CouchDB.request("PUT", "/_config/httpd/config_whitelist",{
+ body : JSON.stringify("{[yes, a_valid_erlang_term, but_unfortunately, not_a_list]}"),
+ headers: {"X-Couch-Persist": "false"}
+ });
+ TEquals(200, xhr.status, "Set config whitelist to an non-list term");
+ xhr = CouchDB.request("DELETE", "/_config/httpd/config_whitelist",{
+ headers: {"X-Couch-Persist": "false"}
+ });
+ TEquals(200, xhr.status, "Modify whitelist despite it not being a list");
+
+ // Keys not in the whitelist may not be modified.
+ xhr = CouchDB.request("PUT", "/_config/httpd/config_whitelist",{
+ body : JSON.stringify("[{httpd,config_whitelist}, {test,foo}]"),
+ headers: {"X-Couch-Persist": "false"}
+ });
+ TEquals(200, xhr.status, "Set config whitelist to something valid");
+
+ ["PUT", "DELETE"].forEach(function(method) {
+ ["test/not_foo", "not_test/foo", "neither_test/nor_foo"].forEach(function(pair) {
+ var path = "/_config/" + pair;
+ var test_name = method + " to " + path + " disallowed: not whitelisted";
+
+ xhr = CouchDB.request(method, path, {
+ body : JSON.stringify("Bummer! " + test_name),
+ headers: {"X-Couch-Persist": "false"}
+ });
+ TEquals(400, xhr.status, test_name);
+ });
+ });
+
+ // Keys in the whitelist may be modified.
+ ["PUT", "DELETE"].forEach(function(method) {
+ xhr = CouchDB.request(method, "/_config/test/foo",{
+ body : JSON.stringify(method + " to whitelisted config variable"),
+ headers: {"X-Couch-Persist": "false"}
+ });
+ TEquals(200, xhr.status, "Keys in the whitelist may be modified");
+ });
+
+ // Non-2-tuples in the whitelist are ignored
+ xhr = CouchDB.request("PUT", "/_config/httpd/config_whitelist",{
+ body : JSON.stringify("[{httpd,config_whitelist}, these, {are}, {nOt, 2, tuples}," +
+ " [so], [they, will], [all, become, noops], {test,foo}]"),
+ headers: {"X-Couch-Persist": "false"}
+ });
+ TEquals(200, xhr.status, "Set config whitelist with some inert values");
+ ["PUT", "DELETE"].forEach(function(method) {
+ xhr = CouchDB.request(method, "/_config/test/foo",{
+ body : JSON.stringify(method + " to whitelisted config variable"),
+ headers: {"X-Couch-Persist": "false"}
+ });
+ TEquals(200, xhr.status, "Update whitelisted variable despite invalid entries");
+ });
+
+ // Atoms, binaries, and strings suffice as whitelist sections and keys.
+ ["{test,foo}", '{"test","foo"}', '{<<"test">>,<<"foo">>}'].forEach(function(pair) {
+ xhr = CouchDB.request("PUT", "/_config/httpd/config_whitelist",{
+ body : JSON.stringify("[{httpd,config_whitelist}, " + pair + "]"),
+ headers: {"X-Couch-Persist": "false"}
+ });
+ TEquals(200, xhr.status, "Set config whitelist to include " + pair);
+
+ var pair_format = {"t":"tuple", '"':"string", "<":"binary"}[pair[1]];
+ ["PUT", "DELETE"].forEach(function(method) {
+ xhr = CouchDB.request(method, "/_config/test/foo",{
+ body : JSON.stringify(method + " with " + pair_format),
+ headers: {"X-Couch-Persist": "false"}
+ });
+ TEquals(200, xhr.status, "Whitelist works with " + pair_format);
+ });
+ });
+
+ xhr = CouchDB.request("DELETE", "/_config/httpd/config_whitelist",{
+ headers: {"X-Couch-Persist": "false"}
+ });
+ TEquals(200, xhr.status, "Reset config whitelist to undefined");
};
diff --git a/rel/overlay/share/www/script/test/conflicts.js b/rel/overlay/share/www/script/test/conflicts.js
index 7258bc31..65122581 100644
--- a/rel/overlay/share/www/script/test/conflicts.js
+++ b/rel/overlay/share/www/script/test/conflicts.js
@@ -61,4 +61,29 @@ couchTests.conflicts = function(debug) {
T(db.save(doc2).ok); // we can save a new document over a deletion without
// knowing the deletion rev.
+
+ // Verify COUCHDB-1178
+ var r1 = {"_id":"doc","foo":"bar"};
+ var r2 = {"_id":"doc","foo":"baz","_rev":"1-4c6114c65e295552ab1019e2b046b10e"};
+ var r3 = {"_id":"doc","foo":"bam","_rev":"2-cfcd6781f13994bde69a1c3320bfdadb"};
+ var r4 = {"_id":"doc","foo":"bat","_rev":"3-cc2f3210d779aef595cd4738be0ef8ff"};
+
+ T(db.save({"_id":"_design/couchdb-1178","validate_doc_update":"function(){}"}).ok);
+ T(db.save(r1).ok);
+ T(db.save(r2).ok);
+ T(db.save(r3).ok);
+
+ T(db.compact().ok);
+ while (db.info().compact_running) {};
+
+ TEquals({"_id":"doc",
+ "_rev":"3-cc2f3210d779aef595cd4738be0ef8ff",
+ "foo":"bam",
+ "_revisions":{"start":3,
+ "ids":["cc2f3210d779aef595cd4738be0ef8ff",
+ "cfcd6781f13994bde69a1c3320bfdadb",
+ "4c6114c65e295552ab1019e2b046b10e"]}},
+ db.open("doc", {"revs": true}));
+ TEquals([], db.bulkSave([r4, r3, r2], {"new_edits":false}), "no failures");
+
};
diff --git a/rel/overlay/share/www/script/test/cookie_auth.js b/rel/overlay/share/www/script/test/cookie_auth.js
index ef915602..e3548640 100644
--- a/rel/overlay/share/www/script/test/cookie_auth.js
+++ b/rel/overlay/share/www/script/test/cookie_auth.js
@@ -104,6 +104,18 @@ couchTests.cookie_auth = function(debug) {
T(CouchDB.login('Jason Davies', password).ok);
T(CouchDB.session().userCtx.name == 'Jason Davies');
+ // JSON login works
+ var xhr = CouchDB.request("POST", "/_session", {
+ headers: {"Content-Type": "application/json"},
+ body: JSON.stringify({
+ name: 'Jason Davies',
+ password: password
+ })
+ });
+
+ T(JSON.parse(xhr.responseText).ok);
+ T(CouchDB.session().userCtx.name == 'Jason Davies');
+
// update one's own credentials document
jasonUserDoc.foo=2;
T(usersDb.save(jasonUserDoc).ok);
diff --git a/rel/overlay/share/www/script/test/copy_doc.js b/rel/overlay/share/www/script/test/copy_doc.js
index a6de1892..99e3c7fe 100644
--- a/rel/overlay/share/www/script/test/copy_doc.js
+++ b/rel/overlay/share/www/script/test/copy_doc.js
@@ -36,6 +36,9 @@ couchTests.copy_doc = function(debug) {
});
T(xhr.status == 409); // conflict
+ var xhr = CouchDB.request("COPY", "/test_suite_db/doc_to_be_copied2");
+ T(xhr.status == 400); // bad request (no Destination header)
+
var rev = db.open("doc_to_be_overwritten")._rev;
var xhr = CouchDB.request("COPY", "/test_suite_db/doc_to_be_copied2", {
headers: {"Destination":"doc_to_be_overwritten?rev=" + rev}
diff --git a/rel/overlay/share/www/script/test/design_docs.js b/rel/overlay/share/www/script/test/design_docs.js
index a24167b2..702f0441 100644
--- a/rel/overlay/share/www/script/test/design_docs.js
+++ b/rel/overlay/share/www/script/test/design_docs.js
@@ -13,180 +13,415 @@
couchTests.design_docs = function(debug) {
var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
var db2 = new CouchDB("test_suite_db_a", {"X-Couch-Full-Commit":"false"});
+
+ if (debug) debugger;
+
db.deleteDb();
db.createDb();
db2.deleteDb();
db2.createDb();
- if (debug) debugger;
- run_on_modified_server(
- [{section: "query_server_config",
+ var server_config = [
+ {
+ section: "query_server_config",
key: "reduce_limit",
- value: "false"}],
-function() {
+ value: "false"
+ }
+ ];
- var numDocs = 500;
+ var testFun = function() {
+ var numDocs = 500;
- function makebigstring(power) {
- var str = "a";
- while(power-- > 0) {
- str = str + str;
- }
- return str;
- }
-
- var designDoc = {
- _id:"_design/test", // turn off couch.js id escaping?
- language: "javascript",
- whatever : {
- stringzone : "exports.string = 'plankton';",
- commonjs : {
- whynot : "exports.test = require('../stringzone'); exports.foo = require('whatever/stringzone');",
- upper : "exports.testing = require('./whynot').test.string.toUpperCase()+module.id+require('./whynot').foo.string"
+ function makebigstring(power) {
+ var str = "a";
+ while(power-- > 0) {
+ str = str + str;
}
- },
- views: {
- all_docs_twice: {map: "function(doc) { emit(doc.integer, null); emit(doc.integer, null) }"},
- no_docs: {map: "function(doc) {}"},
- single_doc: {map: "function(doc) { if (doc._id == \"1\") { emit(1, null) }}"},
- summate: {map:"function (doc) {emit(doc.integer, doc.integer)};",
- reduce:"function (keys, values) { return sum(values); };"},
- summate2: {map:"function (doc) {emit(doc.integer, doc.integer)};",
- reduce:"function (keys, values) { return sum(values); };"},
- huge_src_and_results: {map: "function(doc) { if (doc._id == \"1\") { emit(\"" + makebigstring(16) + "\", null) }}",
- reduce:"function (keys, values) { return \"" + makebigstring(16) + "\"; };"}
- },
- shows: {
- simple: "function() {return 'ok'};",
- requirey : "function() { var lib = require('whatever/commonjs/upper'); return lib.testing; };",
- circular : "function() { var lib = require('whatever/commonjs/upper'); return JSON.stringify(this); };"
+ return str;
}
- };
-
- var xhr = CouchDB.request("PUT", "/test_suite_db_a/_design/test", {body: JSON.stringify(designDoc)});
- var resp = JSON.parse(xhr.responseText);
-
- TEquals(resp.rev, db.save(designDoc).rev);
-
- // test that editing a show fun on the ddoc results in a change in output
- var xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_show/simple");
- T(xhr.status == 200);
- TEquals(xhr.responseText, "ok");
-
- designDoc.shows.simple = "function() {return 'ko'};"
- T(db.save(designDoc).ok);
-
- var xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_show/simple");
- T(xhr.status == 200);
- TEquals(xhr.responseText, "ko");
-
- var xhr = CouchDB.request("GET", "/test_suite_db_a/_design/test/_show/simple?cache=buster");
- T(xhr.status == 200);
- TEquals("ok", xhr.responseText, 'query server used wrong ddoc');
-
- // test commonjs require
- var xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_show/requirey");
- T(xhr.status == 200);
- TEquals("PLANKTONwhatever/commonjs/upperplankton", xhr.responseText);
-
- var xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_show/circular");
- T(xhr.status == 200);
- TEquals("javascript", JSON.parse(xhr.responseText).language);
-
- var prev_view_sig = db.designInfo("_design/test").view_index.signature;
-
- db.bulkSave(makeDocs(1, numDocs + 1));
-
- // test that we get design doc info back
- var dinfo = db.designInfo("_design/test");
- TEquals("test", dinfo.name);
- var vinfo = dinfo.view_index;
- TEquals(51, vinfo.disk_size);
- TEquals(false, vinfo.compact_running);
- // test that GET /db/_design/test/_info
- // hasn't triggered an update of the views
- TEquals(prev_view_sig, vinfo.signature, 'ddoc sig');
- for (var loop = 0; loop < 2; loop++) {
- T(db.view("test/all_docs_twice", {stale: "ok"}).total_rows === 0);
- T(db.view("test/single_doc", {stale: "ok"}).total_rows === 0);
- T(db.view("test/summate", {stale: "ok"}).rows.length === 0);
+
+ var designDoc = {
+ _id: "_design/test",
+ language: "javascript",
+ whatever : {
+ stringzone : "exports.string = 'plankton';",
+ commonjs : {
+ whynot : "exports.test = require('../stringzone'); " +
+ "exports.foo = require('whatever/stringzone');",
+ upper : "exports.testing = require('./whynot').test.string.toUpperCase()+" +
+ "module.id+require('./whynot').foo.string",
+ circular_one: "require('./circular_two'); exports.name = 'One';",
+ circular_two: "require('./circular_one'); exports.name = 'Two';"
+ },
+ // paths relative to parent
+ idtest1: {
+ a: {
+ b: {d: "module.exports = require('../c/e').id;"},
+ c: {e: "exports.id = module.id;"}
+ }
+ },
+ // multiple paths relative to parent
+ idtest2: {
+ a: {
+ b: {d: "module.exports = require('../../a/c/e').id;"},
+ c: {e: "exports.id = module.id;"}
+ }
+ },
+ // paths relative to module
+ idtest3: {
+ a: {
+ b: "module.exports = require('./c/d').id;",
+ c: {
+ d: "module.exports = require('./e');",
+ e: "exports.id = module.id;"
+ }
+ }
+ },
+ // paths relative to module and parent
+ idtest4: {
+ a: {
+ b: "module.exports = require('../a/./c/d').id;",
+ c: {
+ d: "module.exports = require('./e');",
+ e: "exports.id = module.id;"
+ }
+ }
+ },
+ // paths relative to root
+ idtest5: {
+ a: "module.exports = require('whatever/idtest5/b').id;",
+ b: "exports.id = module.id;"
+ }
+ },
+ views: {
+ all_docs_twice: {
+ map:
+ (function(doc) {
+ emit(doc.integer, null);
+ emit(doc.integer, null);
+ }).toString()
+ },
+ no_docs: {
+ map:
+ (function(doc) {
+ }).toString()
+ },
+ single_doc: {
+ map:
+ (function(doc) {
+ if (doc._id === "1") {
+ emit(1, null);
+ }
+ }).toString()
+ },
+ summate: {
+ map:
+ (function(doc) {
+ emit(doc.integer, doc.integer);
+ }).toString(),
+ reduce:
+ (function(keys, values) {
+ return sum(values);
+ }).toString()
+ },
+ summate2: {
+ map:
+ (function(doc) {
+ emit(doc.integer, doc.integer);
+ }).toString(),
+ reduce:
+ (function(keys, values) {
+ return sum(values);
+ }).toString()
+ },
+ huge_src_and_results: {
+ map:
+ (function(doc) {
+ if (doc._id === "1") {
+ emit(makebigstring(16), null);
+ }
+ }).toString(),
+ reduce:
+ (function(keys, values) {
+ return makebigstring(16);
+ }).toString()
+ },
+ lib : {
+ baz : "exports.baz = 'bam';",
+ foo : {
+ foo : "exports.foo = 'bar';",
+ boom : "exports.boom = 'ok';",
+ zoom : "exports.zoom = 'yeah';"
+ }
+ },
+ commonjs : {
+ map :
+ (function(doc) {
+ emit(null, require('views/lib/foo/boom').boom);
+ }).toString()
+ }
+ },
+ shows: {
+ simple:
+ (function() {
+ return 'ok';
+ }).toString(),
+ requirey:
+ (function() {
+ var lib = require('whatever/commonjs/upper');
+ return lib.testing;
+ }).toString(),
+ circular:
+ (function() {
+ var lib = require('whatever/commonjs/upper');
+ return JSON.stringify(this);
+ }).toString(),
+ circular_require:
+ (function() {
+ return require('whatever/commonjs/circular_one').name;
+ }).toString(),
+ idtest1: (function() {
+ return require('whatever/idtest1/a/b/d');
+ }).toString(),
+ idtest2: (function() {
+ return require('whatever/idtest2/a/b/d');
+ }).toString(),
+ idtest3: (function() {
+ return require('whatever/idtest3/a/b');
+ }).toString(),
+ idtest4: (function() {
+ return require('whatever/idtest4/a/b');
+ }).toString(),
+ idtest5: (function() {
+ return require('whatever/idtest5/a');
+ }).toString()
+ }
+ }; // designDoc
+
+ var xhr = CouchDB.request(
+ "PUT", "/test_suite_db_a/_design/test", {body: JSON.stringify(designDoc)}
+ );
+ var resp = JSON.parse(xhr.responseText);
+
+ TEquals(resp.rev, db.save(designDoc).rev);
+
+ // test that editing a show fun on the ddoc results in a change in output
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_show/simple");
+ T(xhr.status == 200);
+ TEquals(xhr.responseText, "ok");
+
+ designDoc.shows.simple = (function() {
+ return 'ko';
+ }).toString();
+ T(db.save(designDoc).ok);
+
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_show/simple");
+ T(xhr.status == 200);
+ TEquals(xhr.responseText, "ko");
+
+ xhr = CouchDB.request(
+ "GET", "/test_suite_db_a/_design/test/_show/simple?cache=buster"
+ );
+ T(xhr.status == 200);
+ TEquals("ok", xhr.responseText, 'query server used wrong ddoc');
+
+ // test commonjs require
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_show/requirey");
+ T(xhr.status == 200);
+ TEquals("PLANKTONwhatever/commonjs/upperplankton", xhr.responseText);
+
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_show/circular");
+ T(xhr.status == 200);
+ TEquals("javascript", JSON.parse(xhr.responseText).language);
+
+ // test circular commonjs dependencies
+ xhr = CouchDB.request(
+ "GET",
+ "/test_suite_db/_design/test/_show/circular_require"
+ );
+ TEquals(200, xhr.status);
+ TEquals("One", xhr.responseText);
+
+ // Test that changes to the design doc properly invalidate cached modules:
+
+ // update the designDoc and replace
+ designDoc.whatever.commonjs.circular_one = "exports.name = 'Updated';"
+ T(db.save(designDoc).ok);
+
+ // request circular_require show function again and check the response has
+ // changed
+ xhr = CouchDB.request(
+ "GET",
+ "/test_suite_db/_design/test/_show/circular_require"
+ );
+ TEquals(200, xhr.status);
+ TEquals("Updated", xhr.responseText);
+
+
+ // test module id values are as expected:
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_show/idtest1");
+ TEquals(200, xhr.status);
+ TEquals("whatever/idtest1/a/c/e", xhr.responseText);
+
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_show/idtest2");
+ TEquals(200, xhr.status);
+ TEquals("whatever/idtest2/a/c/e", xhr.responseText);
+
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_show/idtest3");
+ TEquals(200, xhr.status);
+ TEquals("whatever/idtest3/a/c/e", xhr.responseText);
+
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_show/idtest4");
+ TEquals(200, xhr.status);
+ TEquals("whatever/idtest4/a/c/e", xhr.responseText);
+
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_show/idtest5");
+ TEquals(200, xhr.status);
+ TEquals("whatever/idtest5/b", xhr.responseText);
+
+
+ var prev_view_sig = db.designInfo("_design/test").view_index.signature;
+ var prev_view_size = db.designInfo("_design/test").view_index.disk_size;
+
+ db.bulkSave(makeDocs(1, numDocs + 1));
T(db.ensureFullCommit().ok);
- restartServer();
- };
-
- // test that POST /db/_view_cleanup
- // doesn't trigger an update of the views
- T(db.viewCleanup().ok);
- for (var loop = 0; loop < 2; loop++) {
- T(db.view("test/all_docs_twice", {stale: "ok"}).total_rows == 0);
- T(db.view("test/single_doc", {stale: "ok"}).total_rows == 0);
- T(db.view("test/summate", {stale: "ok"}).rows.length == 0);
+
+ // test that we get correct design doc info back,
+ // and also that GET /db/_design/test/_info
+ // hasn't triggered an update of the views
+ db.view("test/summate", {stale: "ok"}); // make sure view group's open
+ for (var i = 0; i < 2; i++) {
+ var dinfo = db.designInfo("_design/test");
+ TEquals("test", dinfo.name);
+ var vinfo = dinfo.view_index;
+ TEquals(prev_view_size, vinfo.disk_size, "view group disk size didn't change");
+ TEquals(false, vinfo.compact_running);
+ TEquals(prev_view_sig, vinfo.signature, 'ddoc sig');
+ // wait some time (there were issues where an update
+ // of the views had been triggered in the background)
+ var start = new Date().getTime();
+ while (new Date().getTime() < start + 2000);
+ TEquals(0, db.view("test/all_docs_twice", {stale: "ok"}).total_rows, 'view info');
+ TEquals(0, db.view("test/single_doc", {stale: "ok"}).total_rows, 'view info');
+ TEquals(0, db.view("test/summate", {stale: "ok"}).rows.length, 'view info');
+ T(db.ensureFullCommit().ok);
+ restartServer();
+ };
+
+ db.bulkSave(makeDocs(numDocs + 1, numDocs * 2 + 1));
T(db.ensureFullCommit().ok);
- restartServer();
- };
- // test that the _all_docs view returns correctly with keys
- var results = db.allDocs({startkey:"_design", endkey:"_design0"});
- T(results.rows.length == 1);
+ // open view group
+ db.view("test/summate", {stale: "ok"});
+ // wait so the views can get initialized
+ var start = new Date().getTime();
+ while (new Date().getTime() < start + 2000);
- for (var loop = 0; loop < 2; loop++) {
- var rows = db.view("test/all_docs_twice").rows;
- for (var i = 0; i < numDocs; i++) {
- T(rows[2*i].key == i+1);
- T(rows[(2*i)+1].key == i+1);
+ // test that POST /db/_view_cleanup
+ // doesn't trigger an update of the views
+ var len1 = db.view("test/all_docs_twice", {stale: "ok"}).total_rows;
+ var len2 = db.view("test/single_doc", {stale: "ok"}).total_rows;
+ var len3 = db.view("test/summate", {stale: "ok"}).rows.length;
+ for (i = 0; i < 2; i++) {
+ T(db.viewCleanup().ok);
+ // wait some time (there were issues where an update
+ // of the views had been triggered in the background)
+ start = new Date().getTime();
+ while (new Date().getTime() < start + 2000);
+ TEquals(len1, db.view("test/all_docs_twice", {stale: "ok"}).total_rows, 'view cleanup');
+ TEquals(len2, db.view("test/single_doc", {stale: "ok"}).total_rows, 'view cleanup');
+ TEquals(len3, db.view("test/summate", {stale: "ok"}).rows.length, 'view cleanup');
+ T(db.ensureFullCommit().ok);
+ restartServer();
+ // we'll test whether the view group stays closed
+ // and the views stay uninitialized (they should!)
+ len1 = len2 = len3 = 0;
};
- T(db.view("test/no_docs").total_rows == 0);
- T(db.view("test/single_doc").total_rows == 1);
- T(db.ensureFullCommit().ok);
- restartServer();
- };
-
- // test when language not specified, Javascript is implied
- var designDoc2 = {
- _id:"_design/test2",
- // language: "javascript",
- views: {
- single_doc: {map: "function(doc) { if (doc._id == \"1\") { emit(1, null) }}"}
- }
- };
- T(db.save(designDoc2).ok);
- T(db.view("test2/single_doc").total_rows == 1);
+ // test commonjs in map functions
+ resp = db.view("test/commonjs", {limit:1});
+ T(resp.rows[0].value == 'ok');
+
+ // test that the _all_docs view returns correctly with keys
+ var results = db.allDocs({startkey:"_design", endkey:"_design0"});
+ T(results.rows.length == 1);
+
+ for (i = 0; i < 2; i++) {
+ var rows = db.view("test/all_docs_twice").rows;
+ for (var j = 0; j < numDocs; j++) {
+ T(rows[2 * j].key == (j + 1));
+ T(rows[(2 * j) + 1].key == (j + 1));
+ };
+ T(db.view("test/no_docs").total_rows == 0);
+ T(db.view("test/single_doc").total_rows == 1);
+ T(db.ensureFullCommit().ok);
+ restartServer();
+ };
- var summate = function(N) {return (N+1)*N/2;};
- var result = db.view("test/summate");
- T(result.rows[0].value == summate(numDocs));
+ // test when language not specified, Javascript is implied
+ var designDoc2 = {
+ _id: "_design/test2",
+ // language: "javascript",
+ views: {
+ single_doc: {
+ map:
+ (function(doc) {
+ if (doc._id === "1") {
+ emit(1, null);
+ }
+ }).toString()
+ }
+ }
+ };
- result = db.view("test/summate", {startkey:4,endkey:4});
- T(result.rows[0].value == 4);
+ T(db.save(designDoc2).ok);
+ T(db.view("test2/single_doc").total_rows == 1);
- result = db.view("test/summate", {startkey:4,endkey:5});
- T(result.rows[0].value == 9);
+ var summate = function(N) {
+ return (N + 1) * (N / 2);
+ };
+ var result = db.view("test/summate");
+ T(result.rows[0].value == summate(numDocs * 2));
- result = db.view("test/summate", {startkey:4,endkey:6});
- T(result.rows[0].value == 15);
+ result = db.view("test/summate", {startkey: 4, endkey: 4});
+ T(result.rows[0].value == 4);
- // Verify that a shared index (view def is an exact copy of "summate")
- // does not confuse the reduce stage
- result = db.view("test/summate2", {startkey:4,endkey:6});
- T(result.rows[0].value == 15);
+ result = db.view("test/summate", {startkey: 4, endkey: 5});
+ T(result.rows[0].value == 9);
- for(var i=1; i<numDocs/2; i+=30) {
- result = db.view("test/summate", {startkey:i,endkey:numDocs-i});
- T(result.rows[0].value == summate(numDocs-i) - summate(i-1));
- }
+ result = db.view("test/summate", {startkey: 4, endkey: 6});
+ T(result.rows[0].value == 15);
- T(db.deleteDoc(designDoc).ok);
- T(db.open(designDoc._id) == null);
- T(db.view("test/no_docs") == null);
+ // test start_key and end_key aliases
+ result = db.view("test/summate", {start_key: 4, end_key: 6});
+ T(result.rows[0].value == 15);
- T(db.ensureFullCommit().ok);
- restartServer();
- T(db.open(designDoc._id) == null);
- T(db.view("test/no_docs") == null);
+ // Verify that a shared index (view def is an exact copy of "summate")
+ // does not confuse the reduce stage
+ result = db.view("test/summate2", {startkey: 4, endkey: 6});
+ T(result.rows[0].value == 15);
- // trigger ddoc cleanup
- T(db.viewCleanup().ok);
+ for(i = 1; i < (numDocs / 2); i += 30) {
+ result = db.view("test/summate", {startkey: i, endkey: (numDocs - i)});
+ T(result.rows[0].value == summate(numDocs - i) - summate(i - 1));
+ }
+
+ T(db.deleteDoc(designDoc).ok);
+ T(db.open(designDoc._id) == null);
+ T(db.view("test/no_docs") == null);
-});
+ T(db.ensureFullCommit().ok);
+ restartServer();
+ T(db.open(designDoc._id) == null);
+ T(db.view("test/no_docs") == null);
+
+ // trigger ddoc cleanup
+ T(db.viewCleanup().ok);
+ }; // enf of testFun
+
+ run_on_modified_server(server_config, testFun);
+
+ // cleanup
+ db.deleteDb();
+ db2.deleteDb();
};
diff --git a/rel/overlay/share/www/script/test/etags_views.js b/rel/overlay/share/www/script/test/etags_views.js
index 7e1537bd..34116f71 100644
--- a/rel/overlay/share/www/script/test/etags_views.js
+++ b/rel/overlay/share/www/script/test/etags_views.js
@@ -11,23 +11,34 @@
// the License.
couchTests.etags_views = function(debug) {
- var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
+ var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"true"});
db.deleteDb();
db.createDb();
if (debug) debugger;
var designDoc = {
- _id:"_design/etags",
+ _id: "_design/etags",
language: "javascript",
views : {
+ fooView: {
+ map: stringFun(function(doc) {
+ if (doc.foo) {
+ emit("bar", 1);
+ }
+ }),
+ },
basicView : {
map : stringFun(function(doc) {
+ if(doc.integer && doc.string) {
emit(doc.integer, doc.string);
+ }
})
},
withReduce : {
map : stringFun(function(doc) {
+ if(doc.integer && doc.string) {
emit(doc.integer, doc.string);
+ }
}),
reduce : stringFun(function(keys, values, rereduce) {
if (rereduce) {
@@ -40,9 +51,9 @@ couchTests.etags_views = function(debug) {
}
};
T(db.save(designDoc).ok);
+ db.bulkSave(makeDocs(0, 10));
+
var xhr;
- var docs = makeDocs(0, 10);
- db.bulkSave(docs);
// verify get w/Etag on map view
xhr = CouchDB.request("GET", "/test_suite_db/_design/etags/_view/basicView");
@@ -52,17 +63,92 @@ couchTests.etags_views = function(debug) {
headers: {"if-none-match": etag}
});
T(xhr.status == 304);
- // TODO GET with keys (when that is available)
+
+ // verify ETag doesn't change when an update
+ // doesn't change the view group's index
+ T(db.save({"_id":"doc1", "foo":"bar"}).ok);
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/etags/_view/basicView");
+ var etag1 = xhr.getResponseHeader("etag");
+ T(etag1 == etag);
+
+ // Verify that purges affect etags
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/etags/_view/fooView");
+ var foo_etag = xhr.getResponseHeader("etag");
+ var doc1 = db.open("doc1");
+ xhr = CouchDB.request("POST", "/test_suite_db/_purge", {
+ body: JSON.stringify({"doc1":[doc1._rev]})
+ });
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/etags/_view/fooView");
+ var etag1 = xhr.getResponseHeader("etag");
+ T(etag1 != foo_etag);
+
+ // Test that _purge didn't affect the other view etags.
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/etags/_view/basicView");
+ var etag1 = xhr.getResponseHeader("etag");
+ T(etag1 == etag);
+
+ // verify different views in the same view group may have different ETags
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/etags/_view/fooView");
+ var etag1 = xhr.getResponseHeader("etag");
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/etags/_view/basicView");
+ var etag2 = xhr.getResponseHeader("etag");
+ T(etag1 != etag2);
+
+ // verify ETag changes when an update changes the view group's index.
+ db.bulkSave(makeDocs(10, 20));
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/etags/_view/basicView");
+ var etag1 = xhr.getResponseHeader("etag");
+ T(etag1 != etag);
+
+ // verify ETag is the same after a restart
+ restartServer();
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/etags/_view/basicView");
+ var etag2 = xhr.getResponseHeader("etag");
+ T(etag1 == etag2);
// reduce view
xhr = CouchDB.request("GET", "/test_suite_db/_design/etags/_view/withReduce");
T(xhr.status == 200);
var etag = xhr.getResponseHeader("etag");
- xhr = CouchDB.request("GET", "/test_suite_db/_design/etags/_view/withReduce", {
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/etags/_view/withReduce",{
headers: {"if-none-match": etag}
});
T(xhr.status == 304);
+ // verify ETag doesn't change when an update
+ // doesn't change the view group's index
+ T(db.save({"_id":"doc3", "foo":"bar"}).ok);
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/etags/_view/withReduce");
+ var etag1 = xhr.getResponseHeader("etag");
+ T(etag1 == etag);
+ // purge
+ var doc3 = db.open("doc3");
+ xhr = CouchDB.request("POST", "/test_suite_db/_purge", {
+ body: JSON.stringify({"doc3":[doc3._rev]})
+ });
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/etags/_view/withReduce");
+ var etag1 = xhr.getResponseHeader("etag");
+ T(etag1 == etag);
+
+ // verify different views in the same view group may have different ETags
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/etags/_view/fooView");
+ var etag1 = xhr.getResponseHeader("etag");
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/etags/_view/withReduce");
+ var etag2 = xhr.getResponseHeader("etag");
+ T(etag1 != etag2);
+
+ // verify ETag changes when an update changes the view group's index
+ db.bulkSave(makeDocs(20, 30));
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/etags/_view/withReduce");
+ var etag1 = xhr.getResponseHeader("etag");
+ T(etag1 != etag);
+
+ // verify ETag is the same after a restart
+ restartServer();
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/etags/_view/withReduce");
+ var etag2 = xhr.getResponseHeader("etag");
+ T(etag1 == etag2);
+
// confirm ETag changes with different POST bodies
xhr = CouchDB.request("POST", "/test_suite_db/_design/etags/_view/basicView",
{body: JSON.stringify({keys:[1]})}
diff --git a/rel/overlay/share/www/script/test/http.js b/rel/overlay/share/www/script/test/http.js
index 8a2e09b8..5f46af52 100644
--- a/rel/overlay/share/www/script/test/http.js
+++ b/rel/overlay/share/www/script/test/http.js
@@ -25,7 +25,7 @@ couchTests.http = function(debug) {
var xhr = CouchDB.request("PUT", "/test_suite_db/test", {body: "{}"});
var host = CouchDB.host;
- TEquals("http://" + host + "/test_suite_db/test",
+ TEquals(CouchDB.protocol + host + "/test_suite_db/test",
xhr.getResponseHeader("Location"),
"should include ip address");
@@ -34,7 +34,7 @@ couchTests.http = function(debug) {
headers: {"X-Forwarded-Host": "mysite.com"}
});
- TEquals("http://mysite.com/test_suite_db/test2",
+ TEquals(CouchDB.protocol + "mysite.com/test_suite_db/test2",
xhr.getResponseHeader("Location"),
"should include X-Forwarded-Host");
@@ -47,7 +47,7 @@ couchTests.http = function(debug) {
body: "{}",
headers: {"X-Host": "mysite2.com"}
});
- TEquals("http://mysite2.com/test_suite_db/test3",
+ TEquals(CouchDB.protocol + "mysite2.com/test_suite_db/test3",
xhr.getResponseHeader("Location"),
"should include X-Host");
});
diff --git a/rel/overlay/share/www/script/test/jsonp.js b/rel/overlay/share/www/script/test/jsonp.js
index 6bec63ab..9aba7189 100644
--- a/rel/overlay/share/www/script/test/jsonp.js
+++ b/rel/overlay/share/www/script/test/jsonp.js
@@ -65,7 +65,7 @@ couchTests.jsonp = function(debug) {
views: {
all_docs: {map: "function(doc) {if(doc.a) emit(null, doc.a);}"}
}
- }
+ };
T(db.save(designDoc).ok);
var url = "/test_suite_db/_design/test/_view/all_docs?callback=jsonp_chunk";
diff --git a/rel/overlay/share/www/script/test/list_views.js b/rel/overlay/share/www/script/test/list_views.js
index 44afa899..2c1ac321 100644
--- a/rel/overlay/share/www/script/test/list_views.js
+++ b/rel/overlay/share/www/script/test/list_views.js
@@ -156,6 +156,9 @@ couchTests.list_views = function(debug) {
var row = getRow();
send(row.doc.integer);
return "tail";
+ }),
+ secObj: stringFun(function(head, req) {
+ return toJSON(req.secObj);
})
}
};
@@ -201,6 +204,7 @@ couchTests.list_views = function(debug) {
T(xhr.status == 200, "standard get should be 200");
T(/head0123456789tail/.test(xhr.responseText));
+
// test that etags are available
var etag = xhr.getResponseHeader("etag");
xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/basicBasic/basicView", {
@@ -323,6 +327,16 @@ couchTests.list_views = function(debug) {
T(/FirstKey: 2/.test(xhr.responseText));
T(/LastKey: 7/.test(xhr.responseText));
+ // multi-key fetch with GET
+ var xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/simpleForm/basicView" +
+ "?keys=[2,4,5,7]");
+
+ T(xhr.status == 200, "multi key");
+ T(!(/Key: 1 /.test(xhr.responseText)));
+ T(/Key: 2/.test(xhr.responseText));
+ T(/FirstKey: 2/.test(xhr.responseText));
+ T(/LastKey: 7/.test(xhr.responseText));
+
// no multi-key fetch allowed when group=false
xhr = CouchDB.request("POST", "/test_suite_db/_design/lists/_list/simpleForm/withReduce?group=false", {
body: '{"keys":[2,4,5,7]}'
@@ -405,6 +419,12 @@ couchTests.list_views = function(debug) {
T(/FirstKey: -2/.test(xhr.responseText));
T(/LastKey: -7/.test(xhr.responseText));
+ // Test if secObj is available
+ var xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/secObj/basicView");
+ T(xhr.status == 200, "standard get should be 200");
+ var resp = JSON.parse(xhr.responseText);
+ T(typeof(resp) == "object");
+
var erlViewTest = function() {
T(db.save(erlListDoc).ok);
var url = "/test_suite_db/_design/erlang/_list/simple/views/basicView" +
@@ -419,10 +439,37 @@ couchTests.list_views = function(debug) {
}
};
+
+
run_on_modified_server([{
section: "native_query_servers",
key: "erlang",
value: "{couch_native_process, start_link, []}"
}], erlViewTest);
+ // COUCHDB-1113
+ var ddoc = {
+ _id: "_design/test",
+ views: {
+ me: {
+ map: (function(doc) { emit(null,null)}).toString()
+ }
+ },
+ lists: {
+ you: (function(head, req) {
+ var row;
+ while(row = getRow()) {
+ send(row);
+ }
+ }).toString()
+ }
+ };
+ db.save(ddoc);
+
+ var resp = CouchDB.request("GET", "/" + db.name + "/_design/test/_list/you/me", {
+ headers: {
+ "Content-Type": "application/x-www-form-urlencoded"
+ }
+ });
+ TEquals(200, resp.status, "should return a 200 response");
};
diff --git a/rel/overlay/share/www/script/test/oauth.js b/rel/overlay/share/www/script/test/oauth.js
index b439b4db..82ebe8a4 100644
--- a/rel/overlay/share/www/script/test/oauth.js
+++ b/rel/overlay/share/www/script/test/oauth.js
@@ -71,7 +71,7 @@ couchTests.oauth = function(debug) {
var host = CouchDB.host;
var dbPair = {
source: {
- url: "http://" + host + "/test_suite_db_a",
+ url: CouchDB.protocol + host + "/test_suite_db_a",
auth: {
oauth: {
consumer_key: "key",
@@ -82,7 +82,7 @@ couchTests.oauth = function(debug) {
}
},
target: {
- url: "http://" + host + "/test_suite_db_b",
+ url: CouchDB.protocol + host + "/test_suite_db_b",
headers: {"Authorization": adminBasicAuthHeaderValue()}
}
};
@@ -90,7 +90,7 @@ couchTests.oauth = function(debug) {
// this function will be called on the modified server
var testFun = function () {
try {
- CouchDB.request("PUT", "http://" + host + "/_config/admins/testadmin", {
+ CouchDB.request("PUT", CouchDB.protocol + host + "/_config/admins/testadmin", {
headers: {"X-Couch-Persist": "false"},
body: JSON.stringify(testadminPassword)
});
@@ -98,7 +98,7 @@ couchTests.oauth = function(debug) {
waitForSuccess(function() {
//loop until the couch server has processed the password
i += 1;
- var xhr = CouchDB.request("GET", "http://" + host + "/_config/admins/testadmin?foo="+i,{
+ var xhr = CouchDB.request("GET", CouchDB.protocol + host + "/_config/admins/testadmin?foo="+i,{
headers: {
"Authorization": adminBasicAuthHeaderValue()
}});
@@ -109,7 +109,7 @@ couchTests.oauth = function(debug) {
CouchDB.newUuids(2); // so we have one to make the salt
- CouchDB.request("PUT", "http://" + host + "/_config/couch_httpd_auth/require_valid_user", {
+ CouchDB.request("PUT", CouchDB.protocol + host + "/_config/couch_httpd_auth/require_valid_user", {
headers: {
"X-Couch-Persist": "false",
"Authorization": adminBasicAuthHeaderValue()
@@ -157,11 +157,11 @@ couchTests.oauth = function(debug) {
};
// Get request token via Authorization header
- xhr = oauthRequest("GET", "http://" + host + "/_oauth/request_token", message, accessor);
+ xhr = oauthRequest("GET", CouchDB.protocol + host + "/_oauth/request_token", message, accessor);
T(xhr.status == expectedCode);
// GET request token via query parameters
- xhr = oauthRequest("GET", "http://" + host + "/_oauth/request_token", message, accessor);
+ xhr = oauthRequest("GET", CouchDB.protocol + host + "/_oauth/request_token", message, accessor);
T(xhr.status == expectedCode);
responseMessage = OAuth.decodeForm(xhr.responseText);
@@ -171,7 +171,7 @@ couchTests.oauth = function(debug) {
//xhr = CouchDB.request("GET", authorization_url + '?oauth_token=' + responseMessage.oauth_token);
//T(xhr.status == expectedCode);
- xhr = oauthRequest("GET", "http://" + host + "/_session", message, accessor);
+ xhr = oauthRequest("GET", CouchDB.protocol + host + "/_session", message, accessor);
T(xhr.status == expectedCode);
if (xhr.status == expectedCode == 200) {
data = JSON.parse(xhr.responseText);
@@ -179,11 +179,11 @@ couchTests.oauth = function(debug) {
T(data.roles[0] == "test");
}
- xhr = oauthRequest("GET", "http://" + host + "/_session?foo=bar", message, accessor);
+ xhr = oauthRequest("GET", CouchDB.protocol + host + "/_session?foo=bar", message, accessor);
T(xhr.status == expectedCode);
// Test HEAD method
- xhr = oauthRequest("HEAD", "http://" + host + "/_session?foo=bar", message, accessor);
+ xhr = oauthRequest("HEAD", CouchDB.protocol + host + "/_session?foo=bar", message, accessor);
T(xhr.status == expectedCode);
// Replication
@@ -207,7 +207,7 @@ couchTests.oauth = function(debug) {
oauth_version: "1.0"
}
};
- xhr = oauthRequest("GET", "http://" + host + "/_session?foo=bar", message, adminAccessor);
+ xhr = oauthRequest("GET", CouchDB.protocol + host + "/_session?foo=bar", message, adminAccessor);
if (xhr.status == expectedCode == 200) {
data = JSON.parse(xhr.responseText);
T(data.name == "testadmin");
@@ -216,13 +216,13 @@ couchTests.oauth = function(debug) {
// Test when the user's token doesn't exist.
message.parameters.oauth_token = "not a token!";
- xhr = oauthRequest("GET", "http://" + host + "/_session?foo=bar",
+ xhr = oauthRequest("GET", CouchDB.protocol + host + "/_session?foo=bar",
message, adminAccessor);
T(xhr.status == 400, "Request should be invalid.");
}
}
} finally {
- var xhr = CouchDB.request("PUT", "http://" + host + "/_config/couch_httpd_auth/require_valid_user", {
+ var xhr = CouchDB.request("PUT", CouchDB.protocol + host + "/_config/couch_httpd_auth/require_valid_user", {
headers: {
"Authorization": adminBasicAuthHeaderValue(),
"X-Couch-Persist": "false"
@@ -231,7 +231,7 @@ couchTests.oauth = function(debug) {
});
T(xhr.status == 200);
- var xhr = CouchDB.request("DELETE", "http://" + host + "/_config/admins/testadmin", {
+ var xhr = CouchDB.request("DELETE", CouchDB.protocol + host + "/_config/admins/testadmin", {
headers: {
"Authorization": adminBasicAuthHeaderValue(),
"X-Couch-Persist": "false"
diff --git a/rel/overlay/share/www/script/test/proxyauth.js b/rel/overlay/share/www/script/test/proxyauth.js
index 91e2f221..dff39415 100644
--- a/rel/overlay/share/www/script/test/proxyauth.js
+++ b/rel/overlay/share/www/script/test/proxyauth.js
@@ -127,4 +127,4 @@ couchTests.proxyauth = function(debug) {
TestFun
);
-} \ No newline at end of file
+};
diff --git a/rel/overlay/share/www/script/test/purge.js b/rel/overlay/share/www/script/test/purge.js
index f8f45138..29689137 100644
--- a/rel/overlay/share/www/script/test/purge.js
+++ b/rel/overlay/share/www/script/test/purge.js
@@ -110,4 +110,36 @@ couchTests.purge = function(debug) {
T(rows[(2*(i-4))+1].key == i+1);
}
T(db.view("test/single_doc").total_rows == 0);
+
+ // COUCHDB-1065
+ var dbA = new CouchDB("test_suite_db_a");
+ var dbB = new CouchDB("test_suite_db_b");
+ dbA.deleteDb();
+ dbA.createDb();
+ dbB.deleteDb();
+ dbB.createDb();
+ var docA = {_id:"test", a:1};
+ var docB = {_id:"test", a:2};
+ dbA.save(docA);
+ dbB.save(docB);
+ CouchDB.replicate(dbA.name, dbB.name);
+ var xhr = CouchDB.request("POST", "/" + dbB.name + "/_purge", {
+ body: JSON.stringify({"test":[docA._rev]})
+ });
+ TEquals(200, xhr.status, "single rev purge after replication succeeds");
+
+ var xhr = CouchDB.request("GET", "/" + dbB.name + "/test?rev=" + docA._rev);
+ TEquals(404, xhr.status, "single rev purge removes revision");
+
+ var xhr = CouchDB.request("POST", "/" + dbB.name + "/_purge", {
+ body: JSON.stringify({"test":[docB._rev]})
+ });
+ TEquals(200, xhr.status, "single rev purge after replication succeeds");
+ var xhr = CouchDB.request("GET", "/" + dbB.name + "/test?rev=" + docB._rev);
+ TEquals(404, xhr.status, "single rev purge removes revision");
+
+ var xhr = CouchDB.request("POST", "/" + dbB.name + "/_purge", {
+ body: JSON.stringify({"test":[docA._rev, docB._rev]})
+ });
+ TEquals(200, xhr.status, "all rev purge after replication succeeds");
};
diff --git a/rel/overlay/share/www/script/test/reduce_builtin.js b/rel/overlay/share/www/script/test/reduce_builtin.js
index c9d41fa4..b3cc3cc7 100644
--- a/rel/overlay/share/www/script/test/reduce_builtin.js
+++ b/rel/overlay/share/www/script/test/reduce_builtin.js
@@ -150,5 +150,30 @@ couchTests.reduce_builtin = function(debug) {
T(equals(results.rows[5], {key:["d","b"],value:10*i}));
T(equals(results.rows[6], {key:["d","c"],value:10*i}));
};
+
+ map = function (doc) { emit(doc.keys, [1, 1]); };
+
+ var results = db.query(map, "_sum", {group:true});
+ T(equals(results.rows[0], {key:["a"],value:[20*i,20*i]}));
+ T(equals(results.rows[1], {key:["a","b"],value:[20*i,20*i]}));
+ T(equals(results.rows[2], {key:["a", "b", "c"],value:[10*i,10*i]}));
+ T(equals(results.rows[3], {key:["a", "b", "d"],value:[10*i,10*i]}));
+
+ var results = db.query(map, "_sum", {group: true, limit: 2});
+ T(equals(results.rows[0], {key: ["a"], value: [20*i,20*i]}));
+ T(equals(results.rows.length, 2));
+
+ var results = db.query(map, "_sum", {group_level:1});
+ T(equals(results.rows[0], {key:["a"],value:[70*i,70*i]}));
+ T(equals(results.rows[1], {key:["d"],value:[40*i,40*i]}));
+
+ var results = db.query(map, "_sum", {group_level:2});
+ T(equals(results.rows[0], {key:["a"],value:[20*i,20*i]}));
+ T(equals(results.rows[1], {key:["a","b"],value:[40*i,40*i]}));
+ T(equals(results.rows[2], {key:["a","c"],value:[10*i,10*i]}));
+ T(equals(results.rows[3], {key:["d"],value:[10*i,10*i]}));
+ T(equals(results.rows[4], {key:["d","a"],value:[10*i,10*i]}));
+ T(equals(results.rows[5], {key:["d","b"],value:[10*i,10*i]}));
+ T(equals(results.rows[6], {key:["d","c"],value:[10*i,10*i]}));
}
}
diff --git a/rel/overlay/share/www/script/test/replication.js b/rel/overlay/share/www/script/test/replication.js
index 7cc1f823..25746625 100644
--- a/rel/overlay/share/www/script/test/replication.js
+++ b/rel/overlay/share/www/script/test/replication.js
@@ -12,16 +12,30 @@
couchTests.replication = function(debug) {
if (debug) debugger;
+
+ 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);
+ }
+
var host = CouchDB.host;
var dbPairs = [
{source:"test_suite_db_a",
target:"test_suite_db_b"},
{source:"test_suite_db_a",
- target:"http://" + host + "/test_suite_db_b"},
- {source:"http://" + host + "/test_suite_db_a",
+ target:CouchDB.protocol + host + "/test_suite_db_b"},
+ {source:CouchDB.protocol + host + "/test_suite_db_a",
target:"test_suite_db_b"},
- {source:"http://" + host + "/test_suite_db_a",
- target:"http://" + host + "/test_suite_db_b"}
+ {source:CouchDB.protocol + host + "/test_suite_db_a",
+ target:CouchDB.protocol + host + "/test_suite_db_b"}
];
var dbA = new CouchDB("test_suite_db_a", {"X-Couch-Full-Commit":"false"});
var dbB = new CouchDB("test_suite_db_b", {"X-Couch-Full-Commit":"false"});
@@ -296,7 +310,7 @@ couchTests.replication = function(debug) {
// remote
dbB.deleteDb();
- CouchDB.replicate(dbA.name, "http://" + CouchDB.host + "/test_suite_db_b", {
+ CouchDB.replicate(dbA.name, CouchDB.protocol + CouchDB.host + "/test_suite_db_b", {
body: {"create_target": true}
});
TEquals("test_suite_db_b", dbB.info().db_name,
@@ -310,14 +324,14 @@ couchTests.replication = function(debug) {
T(continuousResult._local_id);
var cancelResult = CouchDB.replicate(dbA.name, "test_suite_db_b", {
- body: {"cancel": true}
+ body: {"continuous":true, "cancel": true}
});
T(cancelResult.ok);
T(continuousResult._local_id == cancelResult._local_id);
try {
var cancelResult2 = CouchDB.replicate(dbA.name, "test_suite_db_b", {
- body: {"cancel": true}
+ body: {"continuous":true, "cancel": true}
});
} catch (e) {
T(e.error == "not_found");
@@ -372,11 +386,11 @@ couchTests.replication = function(debug) {
{source:"test_suite_rep_docs_db_a",
target:"test_suite_rep_docs_db_b"},
{source:"test_suite_rep_docs_db_a",
- target:"http://" + host + "/test_suite_rep_docs_db_b"},
- {source:"http://" + host + "/test_suite_rep_docs_db_a",
+ target:CouchDB.protocol + host + "/test_suite_rep_docs_db_b"},
+ {source:CouchDB.protocol + host + "/test_suite_rep_docs_db_a",
target:"test_suite_rep_docs_db_b"},
- {source:"http://" + host + "/test_suite_rep_docs_db_a",
- target:"http://" + host + "/test_suite_rep_docs_db_b"}
+ {source:CouchDB.protocol + host + "/test_suite_rep_docs_db_a",
+ target:CouchDB.protocol + host + "/test_suite_rep_docs_db_b"}
];
var target_doc_ids = [
@@ -399,24 +413,25 @@ couchTests.replication = function(debug) {
var valid_doc_ids = [];
var invalid_doc_ids = [];
- $.each(doc_ids, function(index, id) {
- var found = false;
+ for (var p = 0; p < doc_ids.length; p++) {
+ var id = doc_ids[p];
+ var found = false;
- for (var k = 0; k < all_docs.length; k++) {
- var doc = all_docs[k];
+ for (var k = 0; k < all_docs.length; k++) {
+ var doc = all_docs[k];
- if (id === doc._id) {
- found = true;
- break;
- }
- }
+ if (id === doc._id) {
+ found = true;
+ break;
+ }
+ }
- if (found) {
- valid_doc_ids.push(id);
- } else {
- invalid_doc_ids.push(id);
- }
- });
+ if (found) {
+ valid_doc_ids.push(id);
+ } else {
+ invalid_doc_ids.push(id);
+ }
+ };
dbB.deleteDb();
dbB.createDb();
@@ -434,7 +449,7 @@ couchTests.replication = function(debug) {
var doc = all_docs[k];
var tgt_doc = dbB.open(doc._id);
- if ($.inArray(doc._id, doc_ids) >= 0) {
+ if (doc_ids.indexOf(doc._id) >= 0) {
T(tgt_doc !== null);
T(tgt_doc.value === doc.value);
} else {
@@ -451,45 +466,50 @@ couchTests.replication = function(debug) {
}
// test filtered replication
-
- var sourceDb = new CouchDB(
- "test_suite_filtered_rep_db_a", {"X-Couch-Full-Commit":"false"}
- );
-
- sourceDb.deleteDb();
- sourceDb.createDb();
-
- T(sourceDb.save({_id:"foo1",value:1}).ok);
- T(sourceDb.save({_id:"foo2",value:2}).ok);
- T(sourceDb.save({_id:"foo3",value:3}).ok);
- T(sourceDb.save({_id:"foo4",value:4}).ok);
- T(sourceDb.save({
- "_id": "_design/mydesign",
- "language" : "javascript",
- "filters" : {
- "myfilter" : (function(doc, req) {
- if (doc.value < Number(req.query.maxvalue)) {
- return true;
- } else {
- return false;
- }
- }).toString()
+ var filterFun1 = (function(doc, req) {
+ if (doc.value < Number(req.query.maxvalue)) {
+ return true;
+ } else {
+ return false;
}
- }).ok);
+ }).toString();
+
+ var filterFun2 = (function(doc, req) {
+ return true;
+ }).toString();
var dbPairs = [
{source:"test_suite_filtered_rep_db_a",
target:"test_suite_filtered_rep_db_b"},
{source:"test_suite_filtered_rep_db_a",
- target:"http://" + host + "/test_suite_filtered_rep_db_b"},
- {source:"http://" + host + "/test_suite_filtered_rep_db_a",
+ target:CouchDB.protocol + host + "/test_suite_filtered_rep_db_b"},
+ {source:CouchDB.protocol + host + "/test_suite_filtered_rep_db_a",
target:"test_suite_filtered_rep_db_b"},
- {source:"http://" + host + "/test_suite_filtered_rep_db_a",
- target:"http://" + host + "/test_suite_filtered_rep_db_b"}
+ {source:CouchDB.protocol + host + "/test_suite_filtered_rep_db_a",
+ target:CouchDB.protocol + host + "/test_suite_filtered_rep_db_b"}
];
+ var sourceDb = new CouchDB("test_suite_filtered_rep_db_a");
+ var targetDb = new CouchDB("test_suite_filtered_rep_db_b");
for (var i = 0; i < dbPairs.length; i++) {
- var targetDb = new CouchDB("test_suite_filtered_rep_db_b");
+ sourceDb.deleteDb();
+ sourceDb.createDb();
+
+ T(sourceDb.save({_id: "foo1", value: 1}).ok);
+ T(sourceDb.save({_id: "foo2", value: 2}).ok);
+ T(sourceDb.save({_id: "foo3", value: 3}).ok);
+ T(sourceDb.save({_id: "foo4", value: 4}).ok);
+
+ var ddoc = {
+ "_id": "_design/mydesign",
+ "language": "javascript",
+ "filters": {
+ "myfilter": filterFun1
+ }
+ };
+
+ T(sourceDb.save(ddoc).ok);
+
targetDb.deleteDb();
targetDb.createDb();
@@ -506,7 +526,7 @@ couchTests.replication = function(debug) {
});
T(repResult.ok);
- T($.isArray(repResult.history));
+ T(repResult.history instanceof Array);
T(repResult.history.length === 1);
T(repResult.history[0].docs_written === 2);
T(repResult.history[0].docs_read === 2);
@@ -525,6 +545,45 @@ couchTests.replication = function(debug) {
var docFoo4 = targetDb.open("foo4");
T(docFoo4 === null);
+
+ // replication should start from scratch after the filter's code changed
+
+ ddoc.filters.myfilter = filterFun2;
+ T(sourceDb.save(ddoc).ok);
+
+ repResult = CouchDB.replicate(dbA, dbB, {
+ body: {
+ "filter" : "mydesign/myfilter",
+ "query_params" : {
+ "maxvalue": "3"
+ }
+ }
+ });
+
+ T(repResult.ok);
+ T(repResult.history instanceof Array);
+ T(repResult.history.length === 1);
+ T(repResult.history[0].docs_written === 3);
+ T(repResult.history[0].docs_read === 3);
+ T(repResult.history[0].doc_write_failures === 0);
+
+ docFoo1 = targetDb.open("foo1");
+ T(docFoo1 !== null);
+ T(docFoo1.value === 1);
+
+ docFoo2 = targetDb.open("foo2");
+ T(docFoo2 !== null);
+ T(docFoo2.value === 2);
+
+ docFoo3 = targetDb.open("foo3");
+ T(docFoo3 !== null);
+ T(docFoo3.value === 3);
+
+ docFoo4 = targetDb.open("foo4");
+ T(docFoo4 !== null);
+ T(docFoo4.value === 4);
+
+ T(targetDb.open("_design/mydesign") !== null);
}
// test for COUCHDB-868 - design docs' attachments not getting replicated
@@ -610,7 +669,7 @@ couchTests.replication = function(debug) {
T(CouchDB.session().userCtx.roles.indexOf("_admin") === -1);
var repResult = CouchDB.replicate(
- "http://fdmanana:qwerty@" + host + "/" + dbA.name,
+ CouchDB.protocol + "fdmanana:qwerty@" + host + "/" + dbA.name,
dbB.name
);
T(repResult.ok === true);
@@ -662,7 +721,7 @@ couchTests.replication = function(debug) {
T(CouchDB.session().userCtx.roles.indexOf("_admin") === -1);
try {
repResult = CouchDB.replicate(
- "http://fdmanana:qwerty@" + host + "/" + dbA.name,
+ CouchDB.protocol + "fdmanana:qwerty@" + host + "/" + dbA.name,
dbB.name
);
T(false, "replication should have failed");
@@ -679,6 +738,132 @@ couchTests.replication = function(debug) {
run_on_modified_server(server_config, test_fun);
+ // COUCHDB-1093 - filtered and continuous _changes feed dies when the
+ // database is compacted
+ dbA = new CouchDB("test_suite_db_a");
+ dbB = new CouchDB("test_suite_db_b");
+
+ dbA.deleteDb();
+ dbA.createDb();
+ dbB.deleteDb();
+ dbB.createDb();
+
+ var docs = makeDocs(1, 10);
+ docs.push({
+ _id: "_design/foo",
+ language: "javascript",
+ filters: {
+ myfilter: (function(doc, req) { return true; }).toString()
+ }
+ });
+ dbA.bulkSave(docs).ok;
+
+ var repResult = CouchDB.replicate(
+ CouchDB.protocol + host + "/" + dbA.name,
+ dbB.name,
+ {
+ body: {
+ continuous: true,
+ filter: "foo/myfilter"
+ }
+ }
+ );
+ TEquals(true, repResult.ok);
+ TEquals('string', typeof repResult._local_id);
+
+ var xhr = CouchDB.request("GET", "/_active_tasks");
+ var tasks = JSON.parse(xhr.responseText);
+
+ TEquals(true, dbA.compact().ok);
+ while (dbA.info().compact_running) {};
+
+ TEquals(true, dbA.save(makeDocs(30, 31)[0]).ok);
+ xhr = CouchDB.request("GET", "/_active_tasks");
+
+ var tasksAfter = JSON.parse(xhr.responseText);
+ TEquals(tasks.length, tasksAfter.length);
+ waitForSeq(dbA, dbB);
+ T(dbB.open("30") !== null);
+
+ repResult = CouchDB.replicate(
+ CouchDB.protocol + host + "/" + dbA.name,
+ dbB.name,
+ {
+ body: {
+ continuous: true,
+ filter: "foo/myfilter",
+ cancel: true
+ }
+ }
+ );
+ TEquals(true, repResult.ok);
+ TEquals('string', typeof repResult._local_id);
+
+
+ // COUCHDB-885 - push replication of a doc with attachment causes a
+ // conflict in the target.
+ dbA = new CouchDB("test_suite_db_a");
+ dbB = new CouchDB("test_suite_db_b");
+
+ dbA.deleteDb();
+ dbA.createDb();
+ dbB.deleteDb();
+ dbB.createDb();
+
+ var doc = {
+ _id: "doc1"
+ };
+ TEquals(true, dbA.save(doc).ok);
+
+ repResult = CouchDB.replicate(
+ dbA.name,
+ CouchDB.protocol + host + "/" + dbB.name
+ );
+ TEquals(true, repResult.ok);
+ TEquals(true, repResult.history instanceof Array);
+ TEquals(1, repResult.history.length);
+ TEquals(1, repResult.history[0].docs_written);
+ TEquals(1, repResult.history[0].docs_read);
+ TEquals(0, repResult.history[0].doc_write_failures);
+
+ doc["_attachments"] = {
+ "hello.txt": {
+ "content_type": "text/plain",
+ "data": "aGVsbG8gd29ybGQ=" // base64:encode("hello world")
+ },
+ "foo.dat": {
+ "content_type": "not/compressible",
+ "data": "aSBhbSBub3QgZ3ppcGVk" // base64:encode("i am not gziped")
+ }
+ };
+
+ TEquals(true, dbA.save(doc).ok);
+ repResult = CouchDB.replicate(
+ dbA.name,
+ CouchDB.protocol + host + "/" + dbB.name
+ );
+ TEquals(true, repResult.ok);
+ TEquals(true, repResult.history instanceof Array);
+ TEquals(2, repResult.history.length);
+ TEquals(1, repResult.history[0].docs_written);
+ TEquals(1, repResult.history[0].docs_read);
+ TEquals(0, repResult.history[0].doc_write_failures);
+
+ var copy = dbB.open(doc._id, {
+ conflicts: true, deleted_conflicts: true, attachments: true,
+ att_encoding_info: true});
+ T(copy !== null);
+ TEquals("undefined", typeof copy._conflicts);
+ TEquals("undefined", typeof copy._deleted_conflicts);
+ TEquals("text/plain", copy._attachments["hello.txt"]["content_type"]);
+ TEquals("aGVsbG8gd29ybGQ=", copy._attachments["hello.txt"]["data"]);
+ TEquals("gzip", copy._attachments["hello.txt"]["encoding"]);
+ TEquals("not/compressible", copy._attachments["foo.dat"]["content_type"]);
+ TEquals("aSBhbSBub3QgZ3ppcGVk", copy._attachments["foo.dat"]["data"]);
+ TEquals("undefined", typeof copy._attachments["foo.dat"]["encoding"]);
+ // end of test for COUCHDB-885
+
+
// cleanup
dbA.deleteDb();
dbB.deleteDb();
diff --git a/rel/overlay/share/www/script/test/replicator_db.js b/rel/overlay/share/www/script/test/replicator_db.js
new file mode 100644
index 00000000..058b6a7a
--- /dev/null
+++ b/rel/overlay/share/www/script/test/replicator_db.js
@@ -0,0 +1,1447 @@
+// 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.replicator_db = function(debug) {
+
+ if (debug) debugger;
+
+ var wait_rep_doc = 500; // number of millisecs to wait after saving a Rep Doc
+ var host = CouchDB.host;
+ var dbA = new CouchDB("test_suite_rep_db_a", {"X-Couch-Full-Commit":"false"});
+ var dbB = new CouchDB("test_suite_rep_db_b", {"X-Couch-Full-Commit":"false"});
+ var repDb = new CouchDB("test_suite_rep_db", {"X-Couch-Full-Commit":"false"});
+ var usersDb = new CouchDB("test_suite_auth", {"X-Couch-Full-Commit":"false"});
+
+ var docs1 = [
+ {
+ _id: "foo1",
+ value: 11
+ },
+ {
+ _id: "foo2",
+ value: 22
+ },
+ {
+ _id: "foo3",
+ value: 33
+ }
+ ];
+
+ function waitForRep(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);
+ }
+
+ 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);
+ }
+
+ 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;
+ }
+
+ function wait(ms) {
+ var t0 = new Date(), t1;
+ do {
+ CouchDB.request("GET", "/");
+ t1 = new Date();
+ } while ((t1 - t0) <= ms);
+ }
+
+
+ function populate_db(db, docs) {
+ db.deleteDb();
+ db.createDb();
+ for (var i = 0; i < docs.length; i++) {
+ var d = docs[i];
+ delete d._rev;
+ T(db.save(d).ok);
+ }
+ }
+
+ function simple_replication() {
+ populate_db(dbA, docs1);
+ populate_db(dbB, []);
+
+ var repDoc = {
+ _id: "foo_simple_rep",
+ source: dbA.name,
+ target: dbB.name
+ };
+ T(repDb.save(repDoc).ok);
+
+ waitForRep(repDb, repDoc, "completed");
+ for (var i = 0; i < docs1.length; i++) {
+ var doc = docs1[i];
+ var copy = dbB.open(doc._id);
+ T(copy !== null);
+ T(copy.value === doc.value);
+ }
+
+ var repDoc1 = repDb.open(repDoc._id);
+ T(repDoc1 !== null);
+ T(repDoc1.source === repDoc.source);
+ T(repDoc1.target === repDoc.target);
+ T(repDoc1._replication_state === "completed", "simple");
+ T(typeof repDoc1._replication_state_time === "string");
+ T(typeof repDoc1._replication_id === "string");
+ }
+
+
+ function filtered_replication() {
+ var docs2 = docs1.concat([
+ {
+ _id: "_design/mydesign",
+ language : "javascript",
+ filters : {
+ myfilter : (function(doc, req) {
+ return (doc.value % 2) !== Number(req.query.myparam);
+ }).toString()
+ }
+ }
+ ]);
+
+ populate_db(dbA, docs2);
+ populate_db(dbB, []);
+
+ var repDoc = {
+ _id: "foo_filt_rep_doc",
+ source: "http://" + host + "/" + dbA.name,
+ target: dbB.name,
+ filter: "mydesign/myfilter",
+ query_params: {
+ myparam: 1
+ }
+ };
+ T(repDb.save(repDoc).ok);
+
+ waitForRep(repDb, repDoc, "completed");
+ for (var i = 0; i < docs2.length; i++) {
+ var doc = docs2[i];
+ var copy = dbB.open(doc._id);
+
+ if (typeof doc.value === "number") {
+ if ((doc.value % 2) !== 1) {
+ T(copy !== null);
+ T(copy.value === doc.value);
+ } else {
+ T(copy === null);
+ }
+ }
+ }
+
+ var repDoc1 = repDb.open(repDoc._id);
+ T(repDoc1 !== null);
+ T(repDoc1.source === repDoc.source);
+ T(repDoc1.target === repDoc.target);
+ T(repDoc1._replication_state === "completed", "filtered");
+ T(typeof repDoc1._replication_state_time === "string");
+ T(typeof repDoc1._replication_id === "string");
+ }
+
+
+ function continuous_replication() {
+ populate_db(dbA, docs1);
+ populate_db(dbB, []);
+
+ var repDoc = {
+ _id: "foo_cont_rep_doc",
+ source: "http://" + host + "/" + dbA.name,
+ target: dbB.name,
+ continuous: true,
+ user_ctx: {
+ roles: ["_admin"]
+ }
+ };
+
+ T(repDb.save(repDoc).ok);
+
+ waitForSeq(dbA, dbB);
+ for (var i = 0; i < docs1.length; i++) {
+ var doc = docs1[i];
+ var copy = dbB.open(doc._id);
+ T(copy !== null);
+ T(copy.value === doc.value);
+ }
+
+ // add another doc to source, it will be replicated to target
+ var docX = {
+ _id: "foo1000",
+ value: 1001
+ };
+
+ T(dbA.save(docX).ok);
+
+ waitForSeq(dbA, dbB);
+ var copy = dbB.open("foo1000");
+ T(copy !== null);
+ T(copy.value === 1001);
+
+ var repDoc1 = repDb.open(repDoc._id);
+ T(repDoc1 !== null);
+ T(repDoc1.source === repDoc.source);
+ T(repDoc1.target === repDoc.target);
+ T(repDoc1._replication_state === "triggered");
+ T(typeof repDoc1._replication_state_time === "string");
+ T(typeof repDoc1._replication_id === "string");
+
+ // Design documents are only replicated to local targets if the respective
+ // replication document has a user_ctx filed with the "_admin" role in it.
+ var ddoc = {
+ _id: "_design/foobar",
+ language: "javascript"
+ };
+
+ T(dbA.save(ddoc).ok);
+
+ waitForSeq(dbA, dbB);
+ var ddoc_copy = dbB.open("_design/foobar");
+ T(ddoc_copy !== null);
+ T(ddoc.language === "javascript");
+
+ // update the design doc on source, test that the new revision is replicated
+ ddoc.language = "erlang";
+ T(dbA.save(ddoc).ok);
+ T(ddoc._rev.indexOf("2-") === 0);
+
+ waitForSeq(dbA, dbB);
+ ddoc_copy = dbB.open("_design/foobar");
+ T(ddoc_copy !== null);
+ T(ddoc_copy._rev === ddoc._rev);
+ T(ddoc.language === "erlang");
+
+ // stop replication by deleting the replication document
+ T(repDb.deleteDoc(repDoc1).ok);
+
+ // add another doc to source, it will NOT be replicated to target
+ var docY = {
+ _id: "foo666",
+ value: 999
+ };
+
+ T(dbA.save(docY).ok);
+
+ wait(200); // is there a way to avoid wait here?
+ var copy = dbB.open("foo666");
+ T(copy === null);
+ }
+
+
+ function by_doc_ids_replication() {
+ // to test that we can replicate docs with slashes in their IDs
+ var docs2 = docs1.concat([
+ {
+ _id: "_design/mydesign",
+ language : "javascript"
+ }
+ ]);
+
+ populate_db(dbA, docs2);
+ populate_db(dbB, []);
+
+ var repDoc = {
+ _id: "foo_cont_rep_doc",
+ source: "http://" + host + "/" + dbA.name,
+ target: dbB.name,
+ doc_ids: ["foo666", "foo3", "_design/mydesign", "foo999", "foo1"]
+ };
+ T(repDb.save(repDoc).ok);
+
+ waitForRep(repDb, repDoc, "completed");
+ var copy = dbB.open("foo1");
+ T(copy !== null);
+ T(copy.value === 11);
+
+ copy = dbB.open("foo2");
+ T(copy === null);
+
+ copy = dbB.open("foo3");
+ T(copy !== null);
+ T(copy.value === 33);
+
+ copy = dbB.open("foo666");
+ T(copy === null);
+
+ copy = dbB.open("foo999");
+ T(copy === null);
+
+ copy = dbB.open("_design/mydesign");
+ T(copy === null);
+ }
+
+
+ function successive_identical_replications() {
+ populate_db(dbA, docs1);
+ populate_db(dbB, []);
+
+ var repDoc1 = {
+ _id: "foo_ident_rep_1",
+ source: dbA.name,
+ target: dbB.name
+ };
+ T(repDb.save(repDoc1).ok);
+
+ waitForRep(repDb, repDoc1, "completed");
+ for (var i = 0; i < docs1.length; i++) {
+ var doc = docs1[i];
+ var copy = dbB.open(doc._id);
+ T(copy !== null);
+ T(copy.value === doc.value);
+ }
+
+ var repDoc1_copy = repDb.open(repDoc1._id);
+ T(repDoc1_copy !== null);
+ T(repDoc1_copy.source === repDoc1.source);
+ T(repDoc1_copy.target === repDoc1.target);
+ T(repDoc1_copy._replication_state === "completed");
+ T(typeof repDoc1_copy._replication_state_time === "string");
+ T(typeof repDoc1_copy._replication_id === "string");
+
+ var newDoc = {
+ _id: "doc666",
+ value: 666
+ };
+ T(dbA.save(newDoc).ok);
+
+ wait(200);
+ var newDoc_copy = dbB.open(newDoc._id);
+ // not replicated because first replication is complete (not continuous)
+ T(newDoc_copy === null);
+
+ var repDoc2 = {
+ _id: "foo_ident_rep_2",
+ source: dbA.name,
+ target: dbB.name
+ };
+ T(repDb.save(repDoc2).ok);
+
+ waitForRep(repDb, repDoc2, "completed");
+ var newDoc_copy = dbB.open(newDoc._id);
+ T(newDoc_copy !== null);
+ T(newDoc_copy.value === newDoc.value);
+
+ var repDoc2_copy = repDb.open(repDoc2._id);
+ T(repDoc2_copy !== null);
+ T(repDoc2_copy.source === repDoc1.source);
+ T(repDoc2_copy.target === repDoc1.target);
+ T(repDoc2_copy._replication_state === "completed");
+ T(typeof repDoc2_copy._replication_state_time === "string");
+ T(typeof repDoc2_copy._replication_id === "string");
+ T(repDoc2_copy._replication_id === repDoc1_copy._replication_id);
+ }
+
+
+ // test the case where multiple replication docs (different IDs)
+ // describe in fact the same replication (source, target, etc)
+ function identical_rep_docs() {
+ populate_db(dbA, docs1);
+ populate_db(dbB, []);
+
+ var repDoc1 = {
+ _id: "foo_dup_rep_doc_1",
+ source: "http://" + host + "/" + dbA.name,
+ target: dbB.name
+ };
+ var repDoc2 = {
+ _id: "foo_dup_rep_doc_2",
+ source: "http://" + host + "/" + dbA.name,
+ target: dbB.name
+ };
+
+ T(repDb.save(repDoc1).ok);
+ T(repDb.save(repDoc2).ok);
+
+ waitForRep(repDb, repDoc1, "completed");
+ for (var i = 0; i < docs1.length; i++) {
+ var doc = docs1[i];
+ var copy = dbB.open(doc._id);
+ T(copy !== null);
+ T(copy.value === doc.value);
+ }
+
+ repDoc1 = repDb.open("foo_dup_rep_doc_1");
+ T(repDoc1 !== null);
+ T(repDoc1._replication_state === "completed", "identical");
+ T(typeof repDoc1._replication_state_time === "string");
+ T(typeof repDoc1._replication_id === "string");
+
+ repDoc2 = repDb.open("foo_dup_rep_doc_2");
+ T(repDoc2 !== null);
+ T(typeof repDoc2._replication_state === "undefined");
+ T(typeof repDoc2._replication_state_time === "undefined");
+ T(repDoc2._replication_id === repDoc1._replication_id);
+ }
+
+
+ // test the case where multiple replication docs (different IDs)
+ // describe in fact the same continuous replication (source, target, etc)
+ function identical_continuous_rep_docs() {
+ populate_db(dbA, docs1);
+ populate_db(dbB, []);
+
+ var repDoc1 = {
+ _id: "foo_dup_cont_rep_doc_1",
+ source: "http://" + host + "/" + dbA.name,
+ target: dbB.name,
+ continuous: true
+ };
+ var repDoc2 = {
+ _id: "foo_dup_cont_rep_doc_2",
+ source: "http://" + host + "/" + dbA.name,
+ target: dbB.name,
+ continuous: true
+ };
+
+ T(repDb.save(repDoc1).ok);
+ T(repDb.save(repDoc2).ok);
+
+ waitForSeq(dbA, dbB);
+ for (var i = 0; i < docs1.length; i++) {
+ var doc = docs1[i];
+ var copy = dbB.open(doc._id);
+ T(copy !== null);
+ T(copy.value === doc.value);
+ }
+
+ repDoc1 = repDb.open("foo_dup_cont_rep_doc_1");
+ T(repDoc1 !== null);
+ T(repDoc1._replication_state === "triggered");
+ T(typeof repDoc1._replication_state_time === "string");
+ T(typeof repDoc1._replication_id === "string");
+
+ repDoc2 = repDb.open("foo_dup_cont_rep_doc_2");
+ T(repDoc2 !== null);
+ T(typeof repDoc2._replication_state === "undefined");
+ T(typeof repDoc2._replication_state_time === "undefined");
+ T(repDoc2._replication_id === repDoc1._replication_id);
+
+ var newDoc = {
+ _id: "foo666",
+ value: 999
+ };
+ T(dbA.save(newDoc).ok);
+
+ waitForSeq(dbA, dbB);
+ var copy = dbB.open("foo666");
+ T(copy !== null);
+ T(copy.value === 999);
+
+ // deleting second replication doc, doesn't affect the 1st one and
+ // neither it stops the replication
+ T(repDb.deleteDoc(repDoc2).ok);
+ repDoc1 = repDb.open("foo_dup_cont_rep_doc_1");
+ T(repDoc1 !== null);
+ T(repDoc1._replication_state === "triggered");
+ T(typeof repDoc1._replication_state_time === "string");
+
+ var newDoc2 = {
+ _id: "foo5000",
+ value: 5000
+ };
+ T(dbA.save(newDoc2).ok);
+
+ waitForSeq(dbA, dbB);
+ var copy = dbB.open("foo5000");
+ T(copy !== null);
+ T(copy.value === 5000);
+
+ // deleting the 1st replication document stops the replication
+ T(repDb.deleteDoc(repDoc1).ok);
+ var newDoc3 = {
+ _id: "foo1983",
+ value: 1983
+ };
+ T(dbA.save(newDoc3).ok);
+
+ wait(wait_rep_doc); //how to remove wait?
+ var copy = dbB.open("foo1983");
+ T(copy === null);
+ }
+
+
+ function test_replication_credentials_delegation() {
+ populate_db(usersDb, []);
+
+ var joeUserDoc = CouchDB.prepareUserDoc({
+ name: "joe",
+ roles: ["god", "erlanger"]
+ }, "erly");
+ T(usersDb.save(joeUserDoc).ok);
+
+ var ddoc = {
+ _id: "_design/beer",
+ language: "javascript"
+ };
+ populate_db(dbA, docs1.concat([ddoc]));
+ populate_db(dbB, []);
+
+ T(dbB.setSecObj({
+ admins: {
+ names: [],
+ roles: ["god"]
+ }
+ }).ok);
+
+ var server_admins_config = [
+ {
+ section: "admins",
+ key: "fdmanana",
+ value: "qwerty"
+ }
+ ];
+
+ run_on_modified_server(server_admins_config, function() {
+
+ T(CouchDB.login("fdmanana", "qwerty").ok);
+ T(CouchDB.session().userCtx.name === "fdmanana");
+ T(CouchDB.session().userCtx.roles.indexOf("_admin") !== -1);
+
+ var repDoc = {
+ _id: "foo_rep_del_doc_1",
+ source: dbA.name,
+ target: dbB.name,
+ user_ctx: {
+ name: "joe",
+ roles: ["erlanger"]
+ }
+ };
+
+ T(repDb.save(repDoc).ok);
+
+ waitForRep(repDb, repDoc, "completed");
+ for (var i = 0; i < docs1.length; i++) {
+ var doc = docs1[i];
+ var copy = dbB.open(doc._id);
+ T(copy !== null);
+ T(copy.value === doc.value);
+ }
+
+ // design doc was not replicated, because joe is not an admin of db B
+ var doc = dbB.open(ddoc._id);
+ T(doc === null);
+
+ // now test the same replication but putting the role "god" in the
+ // delegation user context property
+ var repDoc2 = {
+ _id: "foo_rep_del_doc_2",
+ source: dbA.name,
+ target: dbB.name,
+ user_ctx: {
+ name: "joe",
+ roles: ["erlanger", "god"]
+ }
+ };
+ T(repDb.save(repDoc2).ok);
+
+ waitForRep(repDb, repDoc2, "completed");
+ for (var i = 0; i < docs1.length; i++) {
+ var doc = docs1[i];
+ var copy = dbB.open(doc._id);
+ T(copy !== null);
+ T(copy.value === doc.value);
+ }
+
+ // because anyone with a 'god' role is an admin of db B, a replication
+ // that is delegated to a 'god' role can write design docs to db B
+ doc = dbB.open(ddoc._id);
+ T(doc !== null);
+ T(doc.language === ddoc.language);
+ });
+ }
+
+
+ function continuous_replication_survives_restart() {
+ var origRepDbName = CouchDB.request(
+ "GET", "/_config/replicator/db").responseText;
+
+ repDb.deleteDb();
+
+ var xhr = CouchDB.request("PUT", "/_config/replicator/db", {
+ body : JSON.stringify(repDb.name),
+ headers: {"X-Couch-Persist": "false"}
+ });
+ T(xhr.status === 200);
+
+ populate_db(dbA, docs1);
+ populate_db(dbB, []);
+
+ var repDoc = {
+ _id: "foo_cont_rep_survives_doc",
+ source: "http://" + host + "/" + dbA.name,
+ target: dbB.name,
+ continuous: true
+ };
+
+ T(repDb.save(repDoc).ok);
+
+ waitForSeq(dbA, dbB);
+ for (var i = 0; i < docs1.length; i++) {
+ var doc = docs1[i];
+ var copy = dbB.open(doc._id);
+ T(copy !== null);
+ T(copy.value === doc.value);
+ }
+
+ repDb.ensureFullCommit();
+ dbA.ensureFullCommit();
+
+ restartServer();
+
+ xhr = CouchDB.request("PUT", "/_config/replicator/db", {
+ body : JSON.stringify(repDb.name),
+ headers: {"X-Couch-Persist": "false"}
+ });
+
+ T(xhr.status === 200);
+
+ // add another doc to source, it will be replicated to target
+ var docX = {
+ _id: "foo1000",
+ value: 1001
+ };
+
+ T(dbA.save(docX).ok);
+
+ waitForSeq(dbA, dbB);
+ var copy = dbB.open("foo1000");
+ T(copy !== null);
+ T(copy.value === 1001);
+
+ repDoc = waitForDocPos(repDb, "foo_cont_rep_survives_doc", 3);
+ T(repDoc !== null);
+ T(repDoc.continuous === true);
+
+ // stop replication
+ T(repDb.deleteDoc(repDoc).ok);
+
+ xhr = CouchDB.request("PUT", "/_config/replicator/db", {
+ body : origRepDbName,
+ headers: {"X-Couch-Persist": "false"}
+ });
+ T(xhr.status === 200);
+ }
+
+
+ function rep_db_write_authorization() {
+ populate_db(dbA, docs1);
+ populate_db(dbB, []);
+
+ var server_admins_config = [
+ {
+ section: "admins",
+ key: "fdmanana",
+ value: "qwerty"
+ }
+ ];
+
+ run_on_modified_server(server_admins_config, function() {
+ var repDoc = {
+ _id: "foo_rep_doc",
+ source: dbA.name,
+ target: dbB.name,
+ continuous: true
+ };
+
+ T(CouchDB.login("fdmanana", "qwerty").ok);
+ T(CouchDB.session().userCtx.name === "fdmanana");
+ T(CouchDB.session().userCtx.roles.indexOf("_admin") !== -1);
+
+ T(repDb.save(repDoc).ok);
+
+ waitForRep(repDb, repDoc, "completed");
+
+ for (var i = 0; i < docs1.length; i++) {
+ var doc = docs1[i];
+ var copy = dbB.open(doc._id);
+
+ T(copy !== null);
+ T(copy.value === doc.value);
+ }
+
+ repDoc = repDb.open("foo_rep_doc");
+ T(repDoc !== null);
+ repDoc.target = "test_suite_foo_db";
+ repDoc.create_target = true;
+
+ // Only the replicator can update replication documents.
+ // Admins can only add and delete replication documents.
+ try {
+ repDb.save(repDoc);
+ T(false && "Should have thrown an exception");
+ } catch (x) {
+ T(x["error"] === "forbidden");
+ }
+ });
+ }
+
+
+ function test_user_ctx_validation() {
+ populate_db(dbA, docs1);
+ populate_db(dbB, []);
+ populate_db(usersDb, []);
+
+ var joeUserDoc = CouchDB.prepareUserDoc({
+ name: "joe",
+ roles: ["erlanger", "bar"]
+ }, "erly");
+ var fdmananaUserDoc = CouchDB.prepareUserDoc({
+ name: "fdmanana",
+ roles: ["a", "b", "c"]
+ }, "qwerty");
+
+ TEquals(true, usersDb.save(joeUserDoc).ok);
+ TEquals(true, usersDb.save(fdmananaUserDoc).ok);
+
+ T(dbB.setSecObj({
+ admins: {
+ names: [],
+ roles: ["god"]
+ },
+ readers: {
+ names: [],
+ roles: ["foo"]
+ }
+ }).ok);
+
+ TEquals(true, CouchDB.login("joe", "erly").ok);
+ TEquals("joe", CouchDB.session().userCtx.name);
+ TEquals(-1, CouchDB.session().userCtx.roles.indexOf("_admin"));
+
+ var repDoc = {
+ _id: "foo_rep",
+ source: CouchDB.protocol + host + "/" + dbA.name,
+ target: dbB.name
+ };
+
+ try {
+ repDb.save(repDoc);
+ T(false, "Should have failed, user_ctx missing.");
+ } catch (x) {
+ TEquals("forbidden", x.error);
+ }
+
+ repDoc.user_ctx = {
+ name: "john",
+ roles: ["erlanger"]
+ };
+
+ try {
+ repDb.save(repDoc);
+ T(false, "Should have failed, wrong user_ctx.name.");
+ } catch (x) {
+ TEquals("forbidden", x.error);
+ }
+
+ repDoc.user_ctx = {
+ name: "joe",
+ roles: ["bar", "god", "erlanger"]
+ };
+
+ try {
+ repDb.save(repDoc);
+ T(false, "Should have failed, a bad role in user_ctx.roles.");
+ } catch (x) {
+ TEquals("forbidden", x.error);
+ }
+
+ // user_ctx.roles might contain only a subset of the user's roles
+ repDoc.user_ctx = {
+ name: "joe",
+ roles: ["erlanger"]
+ };
+
+ TEquals(true, repDb.save(repDoc).ok);
+ CouchDB.logout();
+
+ waitForRep(repDb, repDoc, "error");
+ var repDoc1 = repDb.open(repDoc._id);
+ T(repDoc1 !== null);
+ TEquals(repDoc.source, repDoc1.source);
+ TEquals(repDoc.target, repDoc1.target);
+ TEquals("error", repDoc1._replication_state);
+ TEquals("string", typeof repDoc1._replication_id);
+ TEquals("string", typeof repDoc1._replication_state_time);
+
+ TEquals(true, CouchDB.login("fdmanana", "qwerty").ok);
+ TEquals("fdmanana", CouchDB.session().userCtx.name);
+ TEquals(-1, CouchDB.session().userCtx.roles.indexOf("_admin"));
+
+ try {
+ T(repDb.deleteDoc(repDoc1).ok);
+ T(false, "Shouldn't be able to delete replication document.");
+ } catch (x) {
+ TEquals("forbidden", x.error);
+ }
+
+ CouchDB.logout();
+ TEquals(true, CouchDB.login("joe", "erly").ok);
+ TEquals("joe", CouchDB.session().userCtx.name);
+ TEquals(-1, CouchDB.session().userCtx.roles.indexOf("_admin"));
+
+ T(repDb.deleteDoc(repDoc1).ok);
+ CouchDB.logout();
+
+ for (var i = 0; i < docs1.length; i++) {
+ var doc = docs1[i];
+ var copy = dbB.open(doc._id);
+
+ TEquals(null, copy);
+ }
+
+ T(dbB.setSecObj({
+ admins: {
+ names: [],
+ roles: ["god", "erlanger"]
+ },
+ readers: {
+ names: [],
+ roles: ["foo"]
+ }
+ }).ok);
+
+ TEquals(true, CouchDB.login("joe", "erly").ok);
+ TEquals("joe", CouchDB.session().userCtx.name);
+ TEquals(-1, CouchDB.session().userCtx.roles.indexOf("_admin"));
+
+ repDoc = {
+ _id: "foo_rep_2",
+ source: CouchDB.protocol + host + "/" + dbA.name,
+ target: dbB.name,
+ user_ctx: {
+ name: "joe",
+ roles: ["erlanger"]
+ }
+ };
+
+ TEquals(true, repDb.save(repDoc).ok);
+ CouchDB.logout();
+
+ waitForRep(repDb, repDoc, "complete");
+ repDoc1 = repDb.open(repDoc._id);
+ T(repDoc1 !== null);
+ TEquals(repDoc.source, repDoc1.source);
+ TEquals(repDoc.target, repDoc1.target);
+ TEquals("completed", repDoc1._replication_state);
+ TEquals("string", typeof repDoc1._replication_id);
+ TEquals("string", typeof repDoc1._replication_state_time);
+
+ for (var i = 0; i < docs1.length; i++) {
+ var doc = docs1[i];
+ var copy = dbB.open(doc._id);
+
+ T(copy !== null);
+ TEquals(doc.value, copy.value);
+ }
+
+ // Admins don't need to supply a user_ctx property in replication docs.
+ // If they do not, the implicit user_ctx "user_ctx": {name: null, roles: []}
+ // is used, meaning that design documents will not be replicated into
+ // local targets
+ T(dbB.setSecObj({
+ admins: {
+ names: [],
+ roles: []
+ },
+ readers: {
+ names: [],
+ roles: []
+ }
+ }).ok);
+
+ var ddoc = { _id: "_design/foo" };
+ TEquals(true, dbA.save(ddoc).ok);
+
+ repDoc = {
+ _id: "foo_rep_3",
+ source: CouchDB.protocol + host + "/" + dbA.name,
+ target: dbB.name
+ };
+
+ TEquals(true, repDb.save(repDoc).ok);
+ waitForRep(repDb, repDoc, "complete");
+ repDoc1 = repDb.open(repDoc._id);
+ T(repDoc1 !== null);
+ TEquals(repDoc.source, repDoc1.source);
+ TEquals(repDoc.target, repDoc1.target);
+ TEquals("completed", repDoc1._replication_state);
+ TEquals("string", typeof repDoc1._replication_id);
+ TEquals("string", typeof repDoc1._replication_state_time);
+
+ var ddoc_copy = dbB.open(ddoc._id);
+ T(ddoc_copy === null);
+
+ repDoc = {
+ _id: "foo_rep_4",
+ source: CouchDB.protocol + host + "/" + dbA.name,
+ target: dbB.name,
+ user_ctx: {
+ roles: ["_admin"]
+ }
+ };
+
+ TEquals(true, repDb.save(repDoc).ok);
+ waitForRep(repDb, repDoc, "complete");
+ repDoc1 = repDb.open(repDoc._id);
+ T(repDoc1 !== null);
+ TEquals(repDoc.source, repDoc1.source);
+ TEquals(repDoc.target, repDoc1.target);
+ TEquals("completed", repDoc1._replication_state);
+ TEquals("string", typeof repDoc1._replication_id);
+ TEquals("string", typeof repDoc1._replication_state_time);
+
+ ddoc_copy = dbB.open(ddoc._id);
+ T(ddoc_copy !== null);
+ }
+
+
+ function rep_doc_with_bad_rep_id() {
+ populate_db(dbA, docs1);
+ populate_db(dbB, []);
+
+ var repDoc = {
+ _id: "foo_rep",
+ source: dbA.name,
+ target: dbB.name,
+ replication_id: "1234abc"
+ };
+ T(repDb.save(repDoc).ok);
+
+ waitForRep(repDb, repDoc, "completed");
+ for (var i = 0; i < docs1.length; i++) {
+ var doc = docs1[i];
+ var copy = dbB.open(doc._id);
+ T(copy !== null);
+ T(copy.value === doc.value);
+ }
+
+ var repDoc1 = repDb.open(repDoc._id);
+ T(repDoc1 !== null);
+ T(repDoc1.source === repDoc.source);
+ T(repDoc1.target === repDoc.target);
+ T(repDoc1._replication_state === "completed",
+ "replication document with bad replication id failed");
+ T(typeof repDoc1._replication_state_time === "string");
+ T(typeof repDoc1._replication_id === "string");
+ T(repDoc1._replication_id !== "1234abc");
+ }
+
+
+ function swap_rep_db() {
+ var repDb2 = new CouchDB("test_suite_rep_db_2");
+ var dbA = new CouchDB("test_suite_rep_db_a");
+ var dbA_copy = new CouchDB("test_suite_rep_db_a_copy");
+ var dbB = new CouchDB("test_suite_rep_db_b");
+ var dbB_copy = new CouchDB("test_suite_rep_db_b_copy");
+ var dbC = new CouchDB("test_suite_rep_db_c");
+ var dbC_copy = new CouchDB("test_suite_rep_db_c_copy");
+ var repDoc1, repDoc2, repDoc3;
+ var xhr, i, doc, copy, new_doc;
+
+ populate_db(dbA, docs1);
+ populate_db(dbB, docs1);
+ populate_db(dbC, docs1);
+ populate_db(dbA_copy, []);
+ populate_db(dbB_copy, []);
+ populate_db(dbC_copy, []);
+ populate_db(repDb2, []);
+
+ repDoc1 = {
+ _id: "rep1",
+ source: CouchDB.protocol + host + "/" + dbA.name,
+ target: dbA_copy.name,
+ continuous: true
+ };
+ repDoc2 = {
+ _id: "rep2",
+ source: CouchDB.protocol + host + "/" + dbB.name,
+ target: dbB_copy.name,
+ continuous: true
+ };
+ repDoc3 = {
+ _id: "rep3",
+ source: CouchDB.protocol + host + "/" + dbC.name,
+ target: dbC_copy.name,
+ continuous: true
+ };
+
+ TEquals(true, repDb.save(repDoc1).ok);
+ TEquals(true, repDb.save(repDoc2).ok);
+
+ waitForSeq(dbA, dbA_copy);
+ waitForSeq(dbB, dbB_copy);
+
+ xhr = CouchDB.request("PUT", "/_config/replicator/db",{
+ body : JSON.stringify(repDb2.name),
+ headers: {"X-Couch-Persist": "false"}
+ });
+ TEquals(200, xhr.status);
+
+ new_doc = {
+ _id: "foo666",
+ value: 666
+ };
+
+ TEquals(true, dbA.save(new_doc).ok);
+ TEquals(true, dbB.save(new_doc).ok);
+ waitForSeq(dbA, dbA_copy);
+ waitForSeq(dbB, dbB_copy);
+
+ TEquals(true, repDb2.save(repDoc3).ok);
+ waitForSeq(dbC, dbC_copy);
+
+ for (i = 0; i < docs1.length; i++) {
+ doc = docs1[i];
+ copy = dbA_copy.open(doc._id);
+ T(copy !== null);
+ TEquals(doc.value, copy.value);
+ copy = dbB_copy.open(doc._id);
+ T(copy !== null);
+ TEquals(doc.value, copy.value);
+ copy = dbC_copy.open(doc._id);
+ T(copy !== null);
+ TEquals(doc.value, copy.value);
+ }
+
+ // replications rep1 and rep2 should have been stopped when the replicator
+ // database was swapped
+ copy = dbA_copy.open(new_doc._id);
+ TEquals(null, copy);
+ copy = dbB_copy.open(new_doc._id);
+ TEquals(null, copy);
+
+ xhr = CouchDB.request("PUT", "/_config/replicator/db",{
+ body : JSON.stringify(repDb.name),
+ headers: {"X-Couch-Persist": "false"}
+ });
+ TEquals(200, xhr.status);
+
+ // after setting the replicator database to the former, replications rep1
+ // and rep2 should have been resumed, while rep3 was stopped
+ TEquals(true, dbC.save(new_doc).ok);
+ wait(1000);
+
+ waitForSeq(dbA, dbA_copy);
+ waitForSeq(dbB, dbB_copy);
+
+ copy = dbA_copy.open(new_doc._id);
+ T(copy !== null);
+ TEquals(new_doc.value, copy.value);
+ copy = dbB_copy.open(new_doc._id);
+ T(copy !== null);
+ TEquals(new_doc.value, copy.value);
+ copy = dbC_copy.open(new_doc._id);
+ TEquals(null, copy);
+ }
+
+
+ function compact_rep_db() {
+ var dbA_copy = new CouchDB("test_suite_rep_db_a_copy");
+ var dbB_copy = new CouchDB("test_suite_rep_db_b_copy");
+ var repDoc1, repDoc2;
+ var xhr, i, doc, copy, new_doc;
+ var docs = makeDocs(1, 50);
+
+ populate_db(dbA, docs);
+ populate_db(dbB, docs);
+ populate_db(dbA_copy, []);
+ populate_db(dbB_copy, []);
+
+ repDoc1 = {
+ _id: "rep1",
+ source: CouchDB.protocol + host + "/" + dbA.name,
+ target: dbA_copy.name,
+ continuous: true
+ };
+ repDoc2 = {
+ _id: "rep2",
+ source: CouchDB.protocol + host + "/" + dbB.name,
+ target: dbB_copy.name,
+ continuous: true
+ };
+
+ TEquals(true, repDb.save(repDoc1).ok);
+ TEquals(true, repDb.save(repDoc2).ok);
+
+ TEquals(true, repDb.compact().ok);
+ TEquals(202, repDb.last_req.status);
+
+ waitForSeq(dbA, dbA_copy);
+ waitForSeq(dbB, dbB_copy);
+
+ while (repDb.info().compact_running) {};
+
+ for (i = 0; i < docs.length; i++) {
+ copy = dbA_copy.open(docs[i]._id);
+ T(copy !== null);
+ copy = dbB_copy.open(docs[i]._id);
+ T(copy !== null);
+ }
+
+ new_doc = {
+ _id: "foo666",
+ value: 666
+ };
+
+ TEquals(true, dbA.save(new_doc).ok);
+ TEquals(true, dbB.save(new_doc).ok);
+
+ waitForSeq(dbA, dbA_copy);
+ waitForSeq(dbB, dbB_copy);
+
+ copy = dbA.open(new_doc._id);
+ T(copy !== null);
+ TEquals(666, copy.value);
+ copy = dbB.open(new_doc._id);
+ T(copy !== null);
+ TEquals(666, copy.value);
+ }
+
+
+ function error_state_replication() {
+ populate_db(dbA, docs1);
+
+ var repDoc = {
+ _id: "foo_error_rep",
+ source: dbA.name,
+ target: "nonexistent_test_db"
+ };
+ T(repDb.save(repDoc).ok);
+
+ waitForRep(repDb, repDoc, "error");
+ var repDoc1 = repDb.open(repDoc._id);
+ T(repDoc1 !== null);
+ T(repDoc1._replication_state === "error");
+ T(typeof repDoc1._replication_state_time === "string");
+ T(typeof repDoc1._replication_id === "string");
+ }
+
+
+ function rep_doc_field_validation() {
+ var docs = makeDocs(1, 5);
+
+ populate_db(dbA, docs);
+ populate_db(dbB, []);
+
+ var repDoc = {
+ _id: "rep1",
+ target: dbB.name
+ };
+
+ try {
+ repDb.save(repDoc);
+ T(false, "should have failed because source field is missing");
+ } catch (x) {
+ TEquals("forbidden", x.error);
+ }
+
+ repDoc = {
+ _id: "rep1",
+ source: 123,
+ target: dbB.name
+ };
+
+ try {
+ repDb.save(repDoc);
+ T(false, "should have failed because source field is a number");
+ } catch (x) {
+ TEquals("forbidden", x.error);
+ }
+
+ repDoc = {
+ _id: "rep1",
+ source: dbA.name
+ };
+
+ try {
+ repDb.save(repDoc);
+ T(false, "should have failed because target field is missing");
+ } catch (x) {
+ TEquals("forbidden", x.error);
+ }
+
+ repDoc = {
+ _id: "rep1",
+ source: dbA.name,
+ target: null
+ };
+
+ try {
+ repDb.save(repDoc);
+ T(false, "should have failed because target field is null");
+ } catch (x) {
+ TEquals("forbidden", x.error);
+ }
+
+ repDoc = {
+ _id: "rep1",
+ source: dbA.name,
+ target: { url: 123 }
+ };
+
+ try {
+ repDb.save(repDoc);
+ T(false, "should have failed because target.url field is not a string");
+ } catch (x) {
+ TEquals("forbidden", x.error);
+ }
+
+ repDoc = {
+ _id: "rep1",
+ source: dbA.name,
+ target: { url: dbB.name, auth: null }
+ };
+
+ try {
+ repDb.save(repDoc);
+ T(false, "should have failed because target.auth field is null");
+ } catch (x) {
+ TEquals("forbidden", x.error);
+ }
+
+ repDoc = {
+ _id: "rep1",
+ source: dbA.name,
+ target: { url: dbB.name, auth: "foo:bar" }
+ };
+
+ try {
+ repDb.save(repDoc);
+ T(false, "should have failed because target.auth field is not an object");
+ } catch (x) {
+ TEquals("forbidden", x.error);
+ }
+
+ repDoc = {
+ _id: "rep1",
+ source: dbA.name,
+ target: dbB.name,
+ continuous: "true"
+ };
+
+ try {
+ repDb.save(repDoc);
+ T(false, "should have failed because continuous is not a boolean");
+ } catch (x) {
+ TEquals("forbidden", x.error);
+ }
+
+ repDoc = {
+ _id: "rep1",
+ source: dbA.name,
+ target: dbB.name,
+ filter: 123
+ };
+
+ try {
+ repDb.save(repDoc);
+ T(false, "should have failed because filter is not a string");
+ } catch (x) {
+ TEquals("forbidden", x.error);
+ }
+ }
+
+
+ function test_invalid_filter() {
+ // COUCHDB-1199 - replication document with a filter field that was invalid
+ // crashed the CouchDB server.
+ var repDoc1 = {
+ _id: "rep1",
+ source: "couch_foo_test_db",
+ target: "couch_bar_test_db",
+ filter: "test/foofilter"
+ };
+
+ TEquals(true, repDb.save(repDoc1).ok);
+
+ waitForRep(repDb, repDoc1, "error");
+ repDoc1 = repDb.open(repDoc1._id);
+ TEquals("undefined", typeof repDoc1._replication_id);
+ TEquals("error", repDoc1._replication_state);
+
+ populate_db(dbA, docs1);
+ populate_db(dbB, []);
+
+ var repDoc2 = {
+ _id: "rep2",
+ source: dbA.name,
+ target: dbB.name,
+ filter: "test/foofilter"
+ };
+
+ TEquals(true, repDb.save(repDoc2).ok);
+
+ waitForRep(repDb, repDoc2, "error");
+ repDoc2 = repDb.open(repDoc2._id);
+ TEquals("undefined", typeof repDoc2._replication_id);
+ TEquals("error", repDoc2._replication_state);
+
+ var ddoc = {
+ _id: "_design/mydesign",
+ language : "javascript",
+ filters : {
+ myfilter : (function(doc, req) {
+ return true;
+ }).toString()
+ }
+ };
+
+ TEquals(true, dbA.save(ddoc).ok);
+
+ var repDoc3 = {
+ _id: "rep3",
+ source: dbA.name,
+ target: dbB.name,
+ filter: "mydesign/myfilter"
+ };
+
+ TEquals(true, repDb.save(repDoc3).ok);
+
+ waitForRep(repDb, repDoc3, "completed");
+ repDoc3 = repDb.open(repDoc3._id);
+ TEquals("string", typeof repDoc3._replication_id);
+ TEquals("completed", repDoc3._replication_state);
+ }
+
+
+ // run all the tests
+ var server_config = [
+ {
+ section: "replicator",
+ key: "db",
+ value: repDb.name
+ }
+ ];
+
+ repDb.deleteDb();
+ run_on_modified_server(server_config, simple_replication);
+
+ repDb.deleteDb();
+ restartServer();
+ run_on_modified_server(server_config, filtered_replication);
+
+ repDb.deleteDb();
+ restartServer();
+ run_on_modified_server(server_config, continuous_replication);
+
+ repDb.deleteDb();
+ restartServer();
+ run_on_modified_server(server_config, by_doc_ids_replication);
+
+ repDb.deleteDb();
+ restartServer();
+ run_on_modified_server(server_config, successive_identical_replications);
+
+ repDb.deleteDb();
+ restartServer();
+ run_on_modified_server(server_config, identical_rep_docs);
+
+ repDb.deleteDb();
+ restartServer();
+ run_on_modified_server(server_config, identical_continuous_rep_docs);
+
+ repDb.deleteDb();
+ restartServer();
+ run_on_modified_server(server_config, rep_db_write_authorization);
+
+ repDb.deleteDb();
+ restartServer();
+ run_on_modified_server(server_config, rep_doc_with_bad_rep_id);
+
+ var server_config_2 = server_config.concat([
+ {
+ section: "couch_httpd_auth",
+ key: "authentication_db",
+ value: usersDb.name
+ }
+ ]);
+
+ repDb.deleteDb();
+ restartServer();
+ run_on_modified_server(server_config_2, test_user_ctx_validation);
+
+ repDb.deleteDb();
+ restartServer();
+ run_on_modified_server(server_config_2, test_replication_credentials_delegation);
+
+ repDb.deleteDb();
+ restartServer();
+ continuous_replication_survives_restart();
+
+ repDb.deleteDb();
+ restartServer();
+ run_on_modified_server(server_config, swap_rep_db);
+
+ repDb.deleteDb();
+ restartServer();
+ run_on_modified_server(server_config, compact_rep_db);
+
+ repDb.deleteDb();
+ restartServer();
+ run_on_modified_server(server_config, rep_doc_field_validation);
+
+
+ repDb.deleteDb();
+ restartServer();
+ run_on_modified_server(server_config, test_invalid_filter);
+
+/*
+ * Disabled, since error state would be set on the document only after
+ * the exponential backoff retry done by the replicator database listener
+ * terminates, which takes too much time for a unit test.
+ */
+/*
+ * repDb.deleteDb();
+ * restartServer();
+ * run_on_modified_server(server_config, error_state_replication);
+ */
+
+
+ // cleanup
+ repDb.deleteDb();
+ usersDb.deleteDb();
+ dbA.deleteDb();
+ dbB.deleteDb();
+ (new CouchDB("test_suite_rep_db_2")).deleteDb();
+ (new CouchDB("test_suite_rep_db_c")).deleteDb();
+ (new CouchDB("test_suite_rep_db_a_copy")).deleteDb();
+ (new CouchDB("test_suite_rep_db_b_copy")).deleteDb();
+ (new CouchDB("test_suite_rep_db_c_copy")).deleteDb();
+};
diff --git a/rel/overlay/share/www/script/test/rewrite.js b/rel/overlay/share/www/script/test/rewrite.js
index ff2d3822..ac5257da 100644
--- a/rel/overlay/share/www/script/test/rewrite.js
+++ b/rel/overlay/share/www/script/test/rewrite.js
@@ -84,6 +84,24 @@ couchTests.rewrite = function(debug) {
"method": "GET"
},
{
+ "from": "/welcome4/*",
+ "to" : "_show/welcome3",
+ "query": {
+ "name": "*"
+ }
+ },
+ {
+ "from": "/welcome5/*",
+ "to" : "_show/*",
+ "query": {
+ "name": "*"
+ }
+ },
+ {
+ "from": "basicView",
+ "to": "_view/basicView",
+ },
+ {
"from": "simpleForm/basicView",
"to": "_list/simpleForm/basicView",
},
@@ -101,6 +119,10 @@ couchTests.rewrite = function(debug) {
"query": {
"startkey": ":start",
"endkey": ":end"
+ },
+ "formats": {
+ "start": "int",
+ "end": "int"
}
},
{
@@ -144,6 +166,22 @@ couchTests.rewrite = function(debug) {
"query": {
"key": [":a", ":b"]
}
+ },
+ {
+ "from": "simpleForm/complexView7/:a/:b",
+ "to": "_view/complexView3",
+ "query": {
+ "key": [":a", ":b"],
+ "include_docs": ":doc"
+ },
+ "format": {
+ "doc": "bool"
+ }
+
+ },
+ {
+ "from": "/",
+ "to": "_view/basicView",
}
],
lists: {
@@ -169,6 +207,9 @@ couchTests.rewrite = function(debug) {
"welcome2": stringFun(function(doc, req) {
return "Welcome " + doc.name;
}),
+ "welcome3": stringFun(function(doc,req) {
+ return "Welcome " + req.query["name"];
+ })
},
updates: {
"hello" : stringFun(function(doc, req) {
@@ -289,6 +330,20 @@ couchTests.rewrite = function(debug) {
xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/welcome3/test");
T(xhr.responseText == "Welcome test");
+ req = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/welcome4/user");
+ T(req.responseText == "Welcome user");
+
+ req = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/welcome5/welcome3");
+ T(req.responseText == "Welcome welcome3");
+
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/basicView");
+ T(xhr.status == 200, "view call");
+ T(/{"total_rows":9/.test(xhr.responseText));
+
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/");
+ T(xhr.status == 200, "view call");
+ T(/{"total_rows":9/.test(xhr.responseText));
+
// get with query params
xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/simpleForm/basicView?startkey=3&endkey=8");
@@ -342,6 +397,11 @@ couchTests.rewrite = function(debug) {
T(xhr.status == 200, "with query params");
T(/Value: doc 4/.test(xhr.responseText));
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/simpleForm/complexView7/test/essai?doc=true");
+ T(xhr.status == 200, "with query params");
+ var result = JSON.parse(xhr.responseText);
+ T(typeof(result.rows[0].doc) === "object");
+
// test path relative to server
designDoc.rewrites.push({
"from": "uuids",
diff --git a/rel/overlay/share/www/script/test/security_validation.js b/rel/overlay/share/www/script/test/security_validation.js
index dd3b202e..42aa11c9 100644
--- a/rel/overlay/share/www/script/test/security_validation.js
+++ b/rel/overlay/share/www/script/test/security_validation.js
@@ -250,16 +250,16 @@ couchTests.security_validation = function(debug) {
target:"test_suite_db_b"},
{source:"test_suite_db_a",
- target:{url: "http://" + host + "/test_suite_db_b",
+ target:{url: CouchDB.protocol + host + "/test_suite_db_b",
headers: AuthHeaders}},
- {source:{url:"http://" + host + "/test_suite_db_a",
+ {source:{url:CouchDB.protocol + host + "/test_suite_db_a",
headers: AuthHeaders},
target:"test_suite_db_b"},
- {source:{url:"http://" + host + "/test_suite_db_a",
+ {source:{url:CouchDB.protocol + host + "/test_suite_db_a",
headers: AuthHeaders},
- target:{url:"http://" + host + "/test_suite_db_b",
+ target:{url:CouchDB.protocol + host + "/test_suite_db_b",
headers: AuthHeaders}},
]
var adminDbA = new CouchDB("test_suite_db_a", {"X-Couch-Full-Commit":"false"});
diff --git a/rel/overlay/share/www/script/test/show_documents.js b/rel/overlay/share/www/script/test/show_documents.js
index e06bcadc..55ed9698 100644
--- a/rel/overlay/share/www/script/test/show_documents.js
+++ b/rel/overlay/share/www/script/test/show_documents.js
@@ -157,6 +157,9 @@ couchTests.show_documents = function(debug) {
}),
"withSlash": stringFun(function(doc, req) {
return { json: doc }
+ }),
+ "secObj": stringFun(function(doc, req) {
+ return { json: req.secObj };
})
}
};
@@ -410,5 +413,24 @@ couchTests.show_documents = function(debug) {
db.deleteDoc(doc);
var xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/show-deleted/testdoc");
TEquals("No doc testdoc", xhr.responseText, "should return 'no doc testdoc'");
+
+
+ run_on_modified_server(
+ [{section: "httpd",
+ key: "authentication_handlers",
+ value: "{couch_httpd_auth, special_test_authentication_handler}"},
+ {section:"httpd",
+ key: "WWW-Authenticate",
+ value: "X-Couch-Test-Auth"}],
+
+ function() {
+ T(db.setDbProperty("_security", {foo: true}).ok);
+ T(db.save(doc).ok);
+
+ xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/secObj");
+ var resp = JSON.parse(xhr.responseText);
+ T(resp.foo == true);
+ }
+ );
};
diff --git a/rel/overlay/share/www/script/test/update_documents.js b/rel/overlay/share/www/script/test/update_documents.js
index da68d621..49d3b68a 100644
--- a/rel/overlay/share/www/script/test/update_documents.js
+++ b/rel/overlay/share/www/script/test/update_documents.js
@@ -113,9 +113,13 @@ couchTests.update_documents = function(debug) {
T(JSON.parse(xhr.responseText).error == "method_not_allowed");
// // hello update world (non-existing docid)
+ xhr = CouchDB.request("GET", "/test_suite_db/nonExistingDoc");
+ T(xhr.status == 404);
xhr = CouchDB.request("PUT", "/test_suite_db/_design/update/_update/hello/nonExistingDoc");
T(xhr.status == 201);
T(xhr.responseText == "<p>New World</p>");
+ xhr = CouchDB.request("GET", "/test_suite_db/nonExistingDoc");
+ T(xhr.status == 200);
// in place update
xhr = CouchDB.request("PUT", "/test_suite_db/_design/update/_update/in-place/"+docid+'?field=title&value=test');
diff --git a/rel/overlay/share/www/script/test/view_errors.js b/rel/overlay/share/www/script/test/view_errors.js
index c05000b7..e8bd08e4 100644
--- a/rel/overlay/share/www/script/test/view_errors.js
+++ b/rel/overlay/share/www/script/test/view_errors.js
@@ -177,5 +177,13 @@ couchTests.view_errors = function(debug) {
T(xhr.status == 500);
result = JSON.parse(xhr.responseText);
T(result.error == "reduce_overflow_error");
+
+ try {
+ db.query(function() {emit(null, null)}, null, {startkey: 2, endkey:1});
+ T(0 == 1);
+ } catch(e) {
+ T(e.error == "query_parse_error");
+ T(e.reason.match(/no rows can match/i));
+ }
});
};
diff --git a/rel/overlay/share/www/script/test/view_include_docs.js b/rel/overlay/share/www/script/test/view_include_docs.js
index 06aafc56..944c9103 100644
--- a/rel/overlay/share/www/script/test/view_include_docs.js
+++ b/rel/overlay/share/www/script/test/view_include_docs.js
@@ -135,4 +135,58 @@ couchTests.view_include_docs = function(debug) {
T(!resp.rows[0].doc);
T(resp.rows[0].doc == null);
T(resp.rows[1].doc.integer == 23);
+
+ // COUCHDB-549 - include_docs=true with conflicts=true
+
+ var dbA = new CouchDB("test_suite_db_a", {"X-Couch-Full-Commit":"false"});
+ var dbB = new CouchDB("test_suite_db_b", {"X-Couch-Full-Commit":"false"});
+
+ dbA.deleteDb();
+ dbA.createDb();
+ dbB.deleteDb();
+ dbB.createDb();
+
+ var ddoc = {
+ _id: "_design/mydesign",
+ language : "javascript",
+ views : {
+ myview : {
+ map: (function(doc) {
+ emit(doc.value, 1);
+ }).toString()
+ }
+ }
+ };
+ TEquals(true, dbA.save(ddoc).ok);
+
+ var doc1a = {_id: "foo", value: 1, str: "1"};
+ TEquals(true, dbA.save(doc1a).ok);
+
+ var doc1b = {_id: "foo", value: 1, str: "666"};
+ TEquals(true, dbB.save(doc1b).ok);
+
+ var doc2 = {_id: "bar", value: 2, str: "2"};
+ TEquals(true, dbA.save(doc2).ok);
+
+ TEquals(true, CouchDB.replicate(dbA.name, dbB.name).ok);
+
+ doc1b = dbB.open("foo", {conflicts: true});
+ TEquals(true, doc1b._conflicts instanceof Array);
+ TEquals(1, doc1b._conflicts.length);
+ var conflictRev = doc1b._conflicts[0];
+
+ doc2 = dbB.open("bar", {conflicts: true});
+ TEquals("undefined", typeof doc2._conflicts);
+
+ resp = dbB.view("mydesign/myview", {include_docs: true, conflicts: true});
+
+ TEquals(2, resp.rows.length);
+ TEquals(true, resp.rows[0].doc._conflicts instanceof Array);
+ TEquals(1, resp.rows[0].doc._conflicts.length);
+ TEquals(conflictRev, resp.rows[0].doc._conflicts[0]);
+ TEquals("undefined", typeof resp.rows[1].doc._conflicts);
+
+ // cleanup
+ dbA.deleteDb();
+ dbB.deleteDb();
};
diff --git a/rel/overlay/share/www/script/test/view_multi_key_all_docs.js b/rel/overlay/share/www/script/test/view_multi_key_all_docs.js
index 62e49665..1113be4d 100644
--- a/rel/overlay/share/www/script/test/view_multi_key_all_docs.js
+++ b/rel/overlay/share/www/script/test/view_multi_key_all_docs.js
@@ -25,24 +25,52 @@ couchTests.view_multi_key_all_docs = function(debug) {
for(var i=0; i<rows.length; i++)
T(rows[i].id == keys[i]);
+ // keys in GET parameters
+ rows = db.allDocs({keys:keys}, null).rows;
+ T(rows.length == keys.length);
+ for(var i=0; i<rows.length; i++)
+ T(rows[i].id == keys[i]);
+
rows = db.allDocs({limit: 1}, keys).rows;
T(rows.length == 1);
T(rows[0].id == keys[0]);
+ // keys in GET parameters
+ rows = db.allDocs({limit: 1, keys: keys}, null).rows;
+ T(rows.length == 1);
+ T(rows[0].id == keys[0]);
+
rows = db.allDocs({skip: 2}, keys).rows;
T(rows.length == 3);
for(var i=0; i<rows.length; i++)
T(rows[i].id == keys[i+2]);
+ // keys in GET parameters
+ rows = db.allDocs({skip: 2, keys: keys}, null).rows;
+ T(rows.length == 3);
+ for(var i=0; i<rows.length; i++)
+ T(rows[i].id == keys[i+2]);
+
rows = db.allDocs({descending: "true"}, keys).rows;
T(rows.length == keys.length);
for(var i=0; i<rows.length; i++)
T(rows[i].id == keys[keys.length-i-1]);
+ // keys in GET parameters
+ rows = db.allDocs({descending: "true", keys: keys}, null).rows;
+ T(rows.length == keys.length);
+ for(var i=0; i<rows.length; i++)
+ T(rows[i].id == keys[keys.length-i-1]);
+
rows = db.allDocs({descending: "true", skip: 3, limit:1}, keys).rows;
T(rows.length == 1);
T(rows[0].id == keys[1]);
+ // keys in GET parameters
+ rows = db.allDocs({descending: "true", skip: 3, limit:1, keys: keys}, null).rows;
+ T(rows.length == 1);
+ T(rows[0].id == keys[1]);
+
// Check we get invalid rows when the key doesn't exist
rows = db.allDocs({}, [1, "i_dont_exist", "0"]).rows;
T(rows.length == 3);
@@ -51,4 +79,13 @@ couchTests.view_multi_key_all_docs = function(debug) {
T(rows[1].error == "not_found");
T(!rows[1].id);
T(rows[2].id == rows[2].key && rows[2].key == "0");
+
+ // keys in GET parameters
+ rows = db.allDocs({keys: [1, "i_dont_exist", "0"]}, null).rows;
+ T(rows.length == 3);
+ T(rows[0].error == "not_found");
+ T(!rows[0].id);
+ T(rows[1].error == "not_found");
+ T(!rows[1].id);
+ T(rows[2].id == rows[2].key && rows[2].key == "0");
};
diff --git a/rel/overlay/share/www/script/test/view_multi_key_design.js b/rel/overlay/share/www/script/test/view_multi_key_design.js
index c39e73d9..38396955 100644
--- a/rel/overlay/share/www/script/test/view_multi_key_design.js
+++ b/rel/overlay/share/www/script/test/view_multi_key_design.js
@@ -54,6 +54,13 @@ couchTests.view_multi_key_design = function(debug) {
T(rows[i].key == rows[i].value);
}
+ // with GET keys
+ rows = db.view("test/all_docs",{keys:keys},null).rows;
+ for(var i=0;i<rows.length; i++) {
+ T(keys.indexOf(rows[i].key) != -1);
+ T(rows[i].key == rows[i].value);
+ }
+
var reduce = db.view("test/summate",{group:true},keys).rows;
T(reduce.length == keys.length);
for(var i=0; i<reduce.length; i++) {
@@ -61,8 +68,18 @@ couchTests.view_multi_key_design = function(debug) {
T(reduce[i].key == reduce[i].value);
}
+ // with GET keys
+ reduce = db.view("test/summate",{group:true,keys:keys},null).rows;
+ T(reduce.length == keys.length);
+ for(var i=0; i<reduce.length; i++) {
+ T(keys.indexOf(reduce[i].key) != -1);
+ T(reduce[i].key == reduce[i].value);
+ }
+
// Test that invalid parameter combinations get rejected
var badargs = [{startkey:0}, {endkey:0}, {key: 0}, {group_level: 2}];
+ var getbadargs = [{startkey:0, keys:keys}, {endkey:0, keys:keys},
+ {key:0, keys:keys}, {group_level: 2, keys:keys}];
for(var i in badargs)
{
try {
@@ -71,6 +88,13 @@ couchTests.view_multi_key_design = function(debug) {
} catch (e) {
T(e.error == "query_parse_error");
}
+
+ try {
+ db.view("test/all_docs",getbadargs[i],null);
+ T(0==1);
+ } catch (e) {
+ T(e.error = "query_parse_error");
+ }
}
try {
@@ -80,10 +104,20 @@ couchTests.view_multi_key_design = function(debug) {
T(e.error == "query_parse_error");
}
+ try {
+ db.view("test/summate",{keys:keys},null);
+ T(0==1);
+ } catch (e) {
+ T(e.error == "query_parse_error");
+ }
+
// Test that a map & reduce containing func support keys when reduce=false
var resp = db.view("test/summate", {reduce: false}, keys);
T(resp.rows.length == 5);
+ resp = db.view("test/summate", {reduce: false, keys: keys}, null);
+ T(resp.rows.length == 5);
+
// Check that limiting by startkey_docid and endkey_docid get applied
// as expected.
var curr = db.view("test/multi_emit", {startkey_docid: 21, endkey_docid: 23}, [0, 2]).rows;
@@ -96,34 +130,66 @@ couchTests.view_multi_key_design = function(debug) {
T(curr[i].value == exp_val[i]);
}
+ curr = db.view("test/multi_emit", {startkey_docid: 21, endkey_docid: 23, keys: [0, 2]}, null).rows;
+ T(curr.length == 6);
+ for( var i = 0 ; i < 6 ; i++)
+ {
+ T(curr[i].key == exp_key[i]);
+ T(curr[i].value == exp_val[i]);
+ }
+
// Check limit works
curr = db.view("test/all_docs", {limit: 1}, keys).rows;
T(curr.length == 1);
T(curr[0].key == 10);
+ curr = db.view("test/all_docs", {limit: 1, keys: keys}, null).rows;
+ T(curr.length == 1);
+ T(curr[0].key == 10);
+
// Check offset works
curr = db.view("test/multi_emit", {skip: 1}, [0]).rows;
T(curr.length == 99);
T(curr[0].value == 1);
+ curr = db.view("test/multi_emit", {skip: 1, keys: [0]}, null).rows;
+ T(curr.length == 99);
+ T(curr[0].value == 1);
+
// Check that dir works
curr = db.view("test/multi_emit", {descending: "true"}, [1]).rows;
T(curr.length == 100);
T(curr[0].value == 99);
T(curr[99].value == 0);
+ curr = db.view("test/multi_emit", {descending: "true", keys: [1]}, null).rows;
+ T(curr.length == 100);
+ T(curr[0].value == 99);
+ T(curr[99].value == 0);
+
// Check a couple combinations
curr = db.view("test/multi_emit", {descending: "true", skip: 3, limit: 2}, [2]).rows;
T(curr.length, 2);
T(curr[0].value == 96);
T(curr[1].value == 95);
+ curr = db.view("test/multi_emit", {descending: "true", skip: 3, limit: 2, keys: [2]}, null).rows;
+ T(curr.length, 2);
+ T(curr[0].value == 96);
+ T(curr[1].value == 95);
+
curr = db.view("test/multi_emit", {skip: 2, limit: 3, startkey_docid: "13"}, [0]).rows;
T(curr.length == 3);
T(curr[0].value == 15);
T(curr[1].value == 16);
T(curr[2].value == 17);
+ curr = db.view("test/multi_emit", {skip: 2, limit: 3, startkey_docid: "13", keys: [0]}, null).rows;
+ T(curr.length == 3);
+ T(curr[0].value == 15);
+ T(curr[1].value == 16);
+ T(curr[2].value == 17);
+
curr = db.view("test/multi_emit",
{skip: 1, limit: 5, startkey_docid: "25", endkey_docid: "27"}, [1]).rows;
T(curr.length == 2);
@@ -131,8 +197,20 @@ couchTests.view_multi_key_design = function(debug) {
T(curr[1].value == 27);
curr = db.view("test/multi_emit",
+ {skip: 1, limit: 5, startkey_docid: "25", endkey_docid: "27", keys: [1]}, null).rows;
+ T(curr.length == 2);
+ T(curr[0].value == 26);
+ T(curr[1].value == 27);
+
+ curr = db.view("test/multi_emit",
{skip: 1, limit: 5, startkey_docid: "28", endkey_docid: "26", descending: "true"}, [1]).rows;
T(curr.length == 2);
T(curr[0].value == 27);
T(curr[1].value == 26);
+
+ curr = db.view("test/multi_emit",
+ {skip: 1, limit: 5, startkey_docid: "28", endkey_docid: "26", descending: "true", keys: [1]}, null).rows;
+ T(curr.length == 2);
+ T(curr[0].value == 27);
+ T(curr[1].value == 26);
};
diff --git a/rel/overlay/share/www/script/test/view_pagination.js b/rel/overlay/share/www/script/test/view_pagination.js
index 1af2df35..ed3a7ee1 100644
--- a/rel/overlay/share/www/script/test/view_pagination.js
+++ b/rel/overlay/share/www/script/test/view_pagination.js
@@ -19,7 +19,7 @@ couchTests.view_pagination = function(debug) {
var docs = makeDocs(0, 100);
db.bulkSave(docs);
- var queryFun = function(doc) { emit(doc.integer, null) };
+ var queryFun = function(doc) { emit(doc.integer, null); };
var i;
// page through the view ascending
@@ -29,13 +29,26 @@ couchTests.view_pagination = function(debug) {
startkey_docid: i,
limit: 10
});
- T(queryResults.rows.length == 10)
- T(queryResults.total_rows == docs.length)
- T(queryResults.offset == i)
+ T(queryResults.rows.length == 10);
+ T(queryResults.total_rows == docs.length);
+ T(queryResults.offset == i);
var j;
for (j = 0; j < 10;j++) {
T(queryResults.rows[j].key == i + j);
}
+
+ // test aliases start_key and start_key_doc_id
+ queryResults = db.query(queryFun, null, {
+ start_key: i,
+ start_key_doc_id: i,
+ limit: 10
+ });
+ T(queryResults.rows.length == 10);
+ T(queryResults.total_rows == docs.length);
+ T(queryResults.offset == i);
+ for (j = 0; j < 10;j++) {
+ T(queryResults.rows[j].key == i + j);
+ }
}
// page through the view descending
@@ -46,9 +59,9 @@ couchTests.view_pagination = function(debug) {
descending: true,
limit: 10
});
- T(queryResults.rows.length == 10)
- T(queryResults.total_rows == docs.length)
- T(queryResults.offset == docs.length - i - 1)
+ T(queryResults.rows.length == 10);
+ T(queryResults.total_rows == docs.length);
+ T(queryResults.offset == docs.length - i - 1);
var j;
for (j = 0; j < 10; j++) {
T(queryResults.rows[j].key == i - j);
@@ -63,60 +76,72 @@ couchTests.view_pagination = function(debug) {
descending: false,
limit: 10
});
- T(queryResults.rows.length == 10)
- T(queryResults.total_rows == docs.length)
- T(queryResults.offset == i)
+ T(queryResults.rows.length == 10);
+ T(queryResults.total_rows == docs.length);
+ T(queryResults.offset == i);
var j;
for (j = 0; j < 10;j++) {
T(queryResults.rows[j].key == i + j);
}
}
+ function testEndkeyDocId(queryResults) {
+ T(queryResults.rows.length == 35);
+ T(queryResults.total_rows == docs.length);
+ T(queryResults.offset == 1);
+ T(queryResults.rows[0].id == "1");
+ T(queryResults.rows[1].id == "10");
+ T(queryResults.rows[2].id == "11");
+ T(queryResults.rows[3].id == "12");
+ T(queryResults.rows[4].id == "13");
+ T(queryResults.rows[5].id == "14");
+ T(queryResults.rows[6].id == "15");
+ T(queryResults.rows[7].id == "16");
+ T(queryResults.rows[8].id == "17");
+ T(queryResults.rows[9].id == "18");
+ T(queryResults.rows[10].id == "19");
+ T(queryResults.rows[11].id == "2");
+ T(queryResults.rows[12].id == "20");
+ T(queryResults.rows[13].id == "21");
+ T(queryResults.rows[14].id == "22");
+ T(queryResults.rows[15].id == "23");
+ T(queryResults.rows[16].id == "24");
+ T(queryResults.rows[17].id == "25");
+ T(queryResults.rows[18].id == "26");
+ T(queryResults.rows[19].id == "27");
+ T(queryResults.rows[20].id == "28");
+ T(queryResults.rows[21].id == "29");
+ T(queryResults.rows[22].id == "3");
+ T(queryResults.rows[23].id == "30");
+ T(queryResults.rows[24].id == "31");
+ T(queryResults.rows[25].id == "32");
+ T(queryResults.rows[26].id == "33");
+ T(queryResults.rows[27].id == "34");
+ T(queryResults.rows[28].id == "35");
+ T(queryResults.rows[29].id == "36");
+ T(queryResults.rows[30].id == "37");
+ T(queryResults.rows[31].id == "38");
+ T(queryResults.rows[32].id == "39");
+ T(queryResults.rows[33].id == "4");
+ T(queryResults.rows[34].id == "40");
+ }
+
// test endkey_docid
- var queryResults = db.query(function(doc) { emit(null, null);}, null, {
+ var queryResults = db.query(function(doc) { emit(null, null); }, null, {
startkey: null,
startkey_docid: 1,
endkey: null,
endkey_docid: 40
});
+ testEndkeyDocId(queryResults);
- T(queryResults.rows.length == 35)
- T(queryResults.total_rows == docs.length)
- T(queryResults.offset == 1)
- T(queryResults.rows[0].id == "1");
- T(queryResults.rows[1].id == "10");
- T(queryResults.rows[2].id == "11");
- T(queryResults.rows[3].id == "12");
- T(queryResults.rows[4].id == "13");
- T(queryResults.rows[5].id == "14");
- T(queryResults.rows[6].id == "15");
- T(queryResults.rows[7].id == "16");
- T(queryResults.rows[8].id == "17");
- T(queryResults.rows[9].id == "18");
- T(queryResults.rows[10].id == "19");
- T(queryResults.rows[11].id == "2");
- T(queryResults.rows[12].id == "20");
- T(queryResults.rows[13].id == "21");
- T(queryResults.rows[14].id == "22");
- T(queryResults.rows[15].id == "23");
- T(queryResults.rows[16].id == "24");
- T(queryResults.rows[17].id == "25");
- T(queryResults.rows[18].id == "26");
- T(queryResults.rows[19].id == "27");
- T(queryResults.rows[20].id == "28");
- T(queryResults.rows[21].id == "29");
- T(queryResults.rows[22].id == "3");
- T(queryResults.rows[23].id == "30");
- T(queryResults.rows[24].id == "31");
- T(queryResults.rows[25].id == "32");
- T(queryResults.rows[26].id == "33");
- T(queryResults.rows[27].id == "34");
- T(queryResults.rows[28].id == "35");
- T(queryResults.rows[29].id == "36");
- T(queryResults.rows[30].id == "37");
- T(queryResults.rows[31].id == "38");
- T(queryResults.rows[32].id == "39");
- T(queryResults.rows[33].id == "4");
- T(queryResults.rows[34].id == "40");
+ // test aliases end_key_doc_id and end_key
+ queryResults = db.query(function(doc) { emit(null, null); }, null, {
+ start_key: null,
+ start_key_doc_id: 1,
+ end_key: null,
+ end_key_doc_id: 40
+ });
+ testEndkeyDocId(queryResults);
};
diff --git a/rel/overlay/share/www/script/test/view_update_seq.js b/rel/overlay/share/www/script/test/view_update_seq.js
index 9757caa1..69b8c42d 100644
--- a/rel/overlay/share/www/script/test/view_update_seq.js
+++ b/rel/overlay/share/www/script/test/view_update_seq.js
@@ -73,17 +73,34 @@ couchTests.view_update_seq = function(debug) {
T(resp.rows.length == 1);
T(resp.update_seq == 101);
+ db.save({"id":"00"});
+ resp = db.view('test/all_docs',
+ {limit: 1, stale: "update_after", update_seq: true});
+ T(resp.rows.length == 1);
+ T(resp.update_seq == 101);
+
+ // wait 5 seconds for the next assertions to pass in very slow machines
+ var t0 = new Date(), t1;
+ do {
+ CouchDB.request("GET", "/");
+ t1 = new Date();
+ } while ((t1 - t0) < 5000);
+
+ resp = db.view('test/all_docs', {limit: 1, stale: "ok", update_seq: true});
+ T(resp.rows.length == 1);
+ T(resp.update_seq == 103);
+
resp = db.view('test/all_docs', {limit: 1, update_seq:true});
T(resp.rows.length == 1);
- T(resp.update_seq == 102);
+ T(resp.update_seq == 103);
resp = db.view('test/all_docs',{update_seq:true},["0","1"]);
- T(resp.update_seq == 102);
+ T(resp.update_seq == 103);
resp = db.view('test/all_docs',{update_seq:true},["0","1"]);
- T(resp.update_seq == 102);
+ T(resp.update_seq == 103);
resp = db.view('test/summate',{group:true, update_seq:true},["0","1"]);
- T(resp.update_seq == 102);
+ T(resp.update_seq == 103);
};