couchdb-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From kxe...@apache.org
Subject [06/39] couchdb commit: updated refs/heads/master to 9950caa
Date Wed, 10 Dec 2014 11:08:13 GMT
http://git-wip-us.apache.org/repos/asf/couchdb/blob/6a4893aa/test/javascript/tests/recreate_doc.js
----------------------------------------------------------------------
diff --git a/test/javascript/tests/recreate_doc.js b/test/javascript/tests/recreate_doc.js
new file mode 100644
index 0000000..f972379
--- /dev/null
+++ b/test/javascript/tests/recreate_doc.js
@@ -0,0 +1,145 @@
+// 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.recreate_doc = function(debug) {
+  var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
+  db.deleteDb();
+  db.createDb();
+  if (debug) debugger;
+
+  // First create a new document with the ID "foo", and delete it again
+  var doc = {_id: "foo", a: "bar", b: 42};
+  var result = db.save(doc);
+  T(result.ok);
+  var firstRev = result.rev;
+  T(db.deleteDoc(doc).ok);
+
+  // Now create a new document with the same ID, save it, and then modify it
+  for (var i = 0; i < 10; i++) {
+    doc = {_id: "foo"};
+    T(db.save(doc).ok);
+    doc = db.open("foo");
+    doc.a = "baz";
+    T(db.save(doc).ok);
+    T(db.deleteDoc(doc).rev != undefined);
+  }
+
+  try {
+    // COUCHDB-292 now attempt to save the document with a prev that's since
+    // been deleted and this should generate a conflict exception
+    db.save({_id:"foo", _rev:firstRev, bar:1});
+    T("no save conflict 1" && false); // we shouldn't hit here
+  } catch (e) {
+    T(e.error == "conflict");
+  }
+  
+  var binAttDoc = {
+    _id: "foo",
+    _rev:firstRev,
+    _attachments:{
+      "foo.txt": {
+        content_type:"text/plain",
+        data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
+      }
+    }
+  };
+  try {
+    // same as before, but with binary
+    db.save(binAttDoc);
+    T("no save conflict 2" && false); // we shouldn't hit here
+  } catch (e) {
+    T(e.error == "conflict");
+  }
+
+
+  try {
+    // random non-existant prev rev
+    db.save({_id:"foo", _rev:"1-asfafasdf", bar:1});
+    T("no save conflict 3" && false); // we shouldn't hit here
+  } catch (e) {
+    T(e.error == "conflict");
+  }
+  
+  try {
+    // random non-existant prev rev with bin
+    binAttDoc._rev = "1-aasasfasdf";
+    db.save(binAttDoc);
+    T("no save conflict 4" && false); // we shouldn't hit here
+  } catch (e) {
+    T(e.error == "conflict");
+  }
+
+  db.deleteDb();
+  db.createDb();
+
+  // Helper function to create a doc with multiple revisions
+  // that are compacted away to ?REV_MISSING.
+
+  var createDoc = function(docid) {
+    var ret = [{_id: docid, count: 0}];
+    T(db.save(ret[0]).ok);
+    for(var i = 0; i < 2; i++) {
+      ret[ret.length] = {
+        _id: docid,
+        _rev: ret[ret.length-1]._rev,
+        count: ret[ret.length-1].count+1
+      };
+      T(db.save(ret[ret.length-1]).ok);
+    }
+    db.compact();
+    while(db.info().compact_running) {}
+    return ret;
+  }
+
+  // Helper function to check that there are no duplicates
+  // in the changes feed and that it has proper update
+  // sequence ordering.
+
+  var checkChanges = function() {
+    // Assert that there are no duplicates in _changes.
+    var req = CouchDB.request("GET", "/test_suite_db/_changes");
+    var resp = JSON.parse(req.responseText);
+    var docids = {};
+    var prev_seq = -1;
+    for(var i = 0; i < resp.results.length; i++) {
+      row = resp.results[i];
+      T(row.seq > prev_seq, "Unordered _changes feed.");
+      T(docids[row.id] === undefined, "Duplicates in _changes feed.");
+      prev_seq = row.seq;
+      docids[row.id] = true;
+    }
+  };
+
+  // COUCHDB-1265 - Check that the changes feed remains proper
+  // after we try and break the update_seq tree.
+
+  // This first case is the one originally reported and "fixed"
+  // in COUCHDB-1265. Reinserting an old revision into the
+  // revision tree causes duplicates in the update_seq tree.
+
+  var revs = createDoc("a");
+  T(db.save(revs[1], {new_edits: false}).ok);
+  T(db.save(revs[revs.length-1]).ok);
+  checkChanges();
+
+  // The original fix for COUCHDB-1265 is not entirely correct
+  // as it didn't consider the possibility that a compaction
+  // might run after the original tree screw up.
+
+  revs = createDoc("b");
+  T(db.save(revs[1], {new_edits: false}).ok);
+  db.compact();
+  while(db.info().compact_running) {}
+  T(db.save(revs[revs.length-1]).ok);
+  checkChanges();
+
+};

http://git-wip-us.apache.org/repos/asf/couchdb/blob/6a4893aa/test/javascript/tests/reduce.js
----------------------------------------------------------------------
diff --git a/test/javascript/tests/reduce.js b/test/javascript/tests/reduce.js
new file mode 100644
index 0000000..36e5cb7
--- /dev/null
+++ b/test/javascript/tests/reduce.js
@@ -0,0 +1,414 @@
+// 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.reduce = function(debug) {
+  var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
+  db.deleteDb();
+  db.createDb();
+  if (debug) debugger;
+  var numDocs = 500;
+  var docs = makeDocs(1,numDocs + 1);
+  db.bulkSave(docs);
+  var summate = function(N) {return (N+1)*N/2;};
+
+  var map = function (doc) {
+      emit(doc.integer, doc.integer);
+      emit(doc.integer, doc.integer);
+  };
+  var reduce = function (keys, values) { return sum(values); };
+  var result = db.query(map, reduce);
+  T(result.rows[0].value == 2*summate(numDocs));
+
+  result = db.query(map, reduce, {startkey: 4, endkey: 4});
+  T(result.rows[0].value == 8);
+
+  result = db.query(map, reduce, {startkey: 4, endkey: 5});
+  T(result.rows[0].value == 18);
+
+  result = db.query(map, reduce, {startkey: 4, endkey: 6});
+  T(result.rows[0].value == 30);
+
+  result = db.query(map, reduce, {group:true, limit:3});
+  T(result.rows[0].value == 2);
+  T(result.rows[1].value == 4);
+  T(result.rows[2].value == 6);
+
+  for(var i=1; i<numDocs/2; i+=30) {
+    result = db.query(map, reduce, {startkey: i, endkey: numDocs - i});
+    T(result.rows[0].value == 2*(summate(numDocs-i) - summate(i-1)));
+  }
+
+  db.deleteDb();
+  db.createDb();
+
+  for(var i=1; i <= 5; i++) {
+
+    for(var j=0; j < 10; j++) {
+      // these docs are in the order of the keys collation, for clarity
+      var docs = [];
+      docs.push({keys:["a"]});
+      docs.push({keys:["a"]});
+      docs.push({keys:["a", "b"]});
+      docs.push({keys:["a", "b"]});
+      docs.push({keys:["a", "b", "c"]});
+      docs.push({keys:["a", "b", "d"]});
+      docs.push({keys:["a", "c", "d"]});
+      docs.push({keys:["d"]});
+      docs.push({keys:["d", "a"]});
+      docs.push({keys:["d", "b"]});
+      docs.push({keys:["d", "c"]});
+      db.bulkSave(docs);
+      T(db.info().doc_count == ((i - 1) * 10 * 11) + ((j + 1) * 11));
+    }
+
+    map = function (doc) { emit(doc.keys, 1); };
+    reduce = function (keys, values) { return sum(values); };
+
+    var results = db.query(map, reduce, {group:true});
+
+    //group by exact key match
+    T(equals(results.rows[0], {key:["a"],value:20*i}));
+    T(equals(results.rows[1], {key:["a","b"],value:20*i}));
+    T(equals(results.rows[2], {key:["a", "b", "c"],value:10*i}));
+    T(equals(results.rows[3], {key:["a", "b", "d"],value:10*i}));
+
+    // test to make sure group reduce and limit params provide valid json
+    var results = db.query(map, reduce, {group: true, limit: 2});
+    T(equals(results.rows[0], {key: ["a"], value: 20*i}));
+    T(equals(results.rows.length, 2));
+
+    //group by the first element in the key array
+    var results = db.query(map, reduce, {group_level:1});
+    T(equals(results.rows[0], {key:["a"],value:70*i}));
+    T(equals(results.rows[1], {key:["d"],value:40*i}));
+
+    //group by the first 2 elements in the key array
+    var results = db.query(map, reduce, {group_level:2});
+    T(equals(results.rows[0], {key:["a"],value:20*i}));
+    T(equals(results.rows[1], {key:["a","b"],value:40*i}));
+    T(equals(results.rows[2], {key:["a","c"],value:10*i}));
+    T(equals(results.rows[3], {key:["d"],value:10*i}));
+    T(equals(results.rows[4], {key:["d","a"],value:10*i}));
+    T(equals(results.rows[5], {key:["d","b"],value:10*i}));
+    T(equals(results.rows[6], {key:["d","c"],value:10*i}));
+
+    // endkey test with inclusive_end=true
+    var results = db.query(map, reduce, {group_level:2,endkey:["d"],inclusive_end:true});
+    T(equals(results.rows[0], {key:["a"],value:20*i}));
+    T(equals(results.rows[1], {key:["a","b"],value:40*i}));
+    T(equals(results.rows[2], {key:["a","c"],value:10*i}));
+    T(equals(results.rows[3], {key:["d"],value:10*i}));
+    TEquals(4, results.rows.length);
+
+    // endkey test with inclusive_end=false
+    var results = db.query(map, reduce, {group_level:2,endkey:["d"],inclusive_end:false});
+    T(equals(results.rows[0], {key:["a"],value:20*i}));
+    T(equals(results.rows[1], {key:["a","b"],value:40*i}));
+    T(equals(results.rows[2], {key:["a","c"],value:10*i}));
+    TEquals(3, results.rows.length);
+  }
+
+  // now test out more complex reductions that need to use the combine option.
+
+  db.deleteDb();
+  db.createDb();
+
+
+  var map = function (doc) { emit(doc.val, doc.val); };
+  var reduceCombine = function (keys, values, rereduce) {
+      // This computes the standard deviation of the mapped results
+      var stdDeviation=0.0;
+      var count=0;
+      var total=0.0;
+      var sqrTotal=0.0;
+
+      if (!rereduce) {
+        // This is the reduce phase, we are reducing over emitted values from
+        // the map functions.
+        for(var i in values) {
+          total = total + values[i];
+          sqrTotal = sqrTotal + (values[i] * values[i]);
+        }
+        count = values.length;
+      }
+      else {
+        // This is the rereduce phase, we are re-reducing previosuly
+        // reduced values.
+        for(var i in values) {
+          count = count + values[i].count;
+          total = total + values[i].total;
+          sqrTotal = sqrTotal + values[i].sqrTotal;
+        }
+      }
+
+      var variance =  (sqrTotal - ((total * total)/count)) / count;
+      stdDeviation = Math.sqrt(variance);
+
+      // the reduce result. It contains enough information to be rereduced
+      // with other reduce results.
+      return {"stdDeviation":stdDeviation,"count":count,
+          "total":total,"sqrTotal":sqrTotal};
+    };
+
+    // Save a bunch a docs.
+
+  for(var i=0; i < 10; i++) {
+    var docs = [];
+    docs.push({val:10});
+    docs.push({val:20});
+    docs.push({val:30});
+    docs.push({val:40});
+    docs.push({val:50});
+    docs.push({val:60});
+    docs.push({val:70});
+    docs.push({val:80});
+    docs.push({val:90});
+    docs.push({val:100});
+    db.bulkSave(docs);
+  }
+
+  var results = db.query(map, reduceCombine);
+
+  var difference = results.rows[0].value.stdDeviation - 28.722813232690143;
+  // account for floating point rounding error
+  T(Math.abs(difference) < 0.0000000001);
+
+  function testReducePagination() {
+    var ddoc = {
+      "_id": "_design/test",
+      "language": "javascript",
+      "views": {
+        "view1": {
+          "map": "function(doc) {" +
+             "emit(doc.int, doc._id);" +
+             "emit(doc.int + 1, doc._id);" +
+             "emit(doc.int + 2, doc._id);" +
+          "}",
+          "reduce": "_count"
+        }
+      }
+    };
+    var result, docs = [];
+
+    function randVal() {
+        return Math.random() * 100000000;
+    }
+
+    db.deleteDb();
+    db.createDb();
+
+    for (var i = 0; i < 1123; i++) {
+      docs.push({"_id": String(i), "int": i});
+    }
+    db.bulkSave(docs.concat([ddoc]));
+
+    // ?group=false tests
+    result = db.view('test/view1', {startkey: 400, endkey: 402, foobar: randVal()});
+    TEquals(9, result.rows[0].value);
+    result = db.view('test/view1', {startkey: 402, endkey: 400, descending: true,
+      foobar: randVal()});
+    TEquals(9, result.rows[0].value);
+
+    result = db.view('test/view1', {startkey: 400, endkey: 402, inclusive_end: false,
+      foobar: randVal()});
+    TEquals(6, result.rows[0].value);
+    result = db.view('test/view1', {startkey: 402, endkey: 400, inclusive_end: false,
+      descending: true, foobar: randVal()});
+    TEquals(6, result.rows[0].value);
+
+    result = db.view('test/view1', {startkey: 400, endkey: 402, endkey_docid: "400",
+      foobar: randVal()});
+    TEquals(7, result.rows[0].value);
+    result = db.view('test/view1', {startkey: 400, endkey: 402, endkey_docid: "400",
+      inclusive_end: false, foobar: randVal()});
+    TEquals(6, result.rows[0].value);
+
+    result = db.view('test/view1', {startkey: 400, endkey: 402, endkey_docid: "401",
+      foobar: randVal()});
+    TEquals(8, result.rows[0].value);
+    result = db.view('test/view1', {startkey: 400, endkey: 402, endkey_docid: "401",
+      inclusive_end: false, foobar: randVal()});
+    TEquals(7, result.rows[0].value);
+
+    result = db.view('test/view1', {startkey: 400, endkey: 402, endkey_docid: "402",
+      foobar: randVal()});
+    TEquals(9, result.rows[0].value);
+    result = db.view('test/view1', {startkey: 400, endkey: 402, endkey_docid: "402",
+      inclusive_end: false, foobar: randVal()});
+    TEquals(8, result.rows[0].value);
+
+    result = db.view('test/view1', {startkey: 402, endkey: 400, endkey_docid: "398",
+      descending: true, foobar: randVal()});
+    TEquals(9, result.rows[0].value);
+    result = db.view('test/view1', {startkey: 402, endkey: 400, endkey_docid: "398",
+      descending: true, inclusive_end: false, foobar: randVal()}),
+    TEquals(8, result.rows[0].value);
+
+    result = db.view('test/view1', {startkey: 402, endkey: 400, endkey_docid: "399",
+      descending: true, foobar: randVal()});
+    TEquals(8, result.rows[0].value);
+    result = db.view('test/view1', {startkey: 402, endkey: 400, endkey_docid: "399",
+      descending: true, inclusive_end: false, foobar: randVal()}),
+    TEquals(7, result.rows[0].value);
+
+    result = db.view('test/view1', {startkey: 402, endkey: 400, endkey_docid: "400",
+      descending: true, foobar: randVal()}),
+    TEquals(7, result.rows[0].value);
+    result = db.view('test/view1', {startkey: 402, endkey: 400, endkey_docid: "400",
+      descending: true, inclusive_end: false, foobar: randVal()}),
+    TEquals(6, result.rows[0].value);
+
+    result = db.view('test/view1', {startkey: 402, startkey_docid: "400", endkey: 400,
+      descending: true, foobar: randVal()});
+    TEquals(7, result.rows[0].value);
+
+    result = db.view('test/view1', {startkey: 402, startkey_docid: "401", endkey: 400,
+      descending: true, inclusive_end: false, foobar: randVal()});
+    TEquals(5, result.rows[0].value);
+
+    // ?group=true tests
+    result = db.view('test/view1', {group: true, startkey: 400, endkey: 402,
+      foobar: randVal()});
+    TEquals(3, result.rows.length);
+    TEquals(400, result.rows[0].key);
+    TEquals(3, result.rows[0].value);
+    TEquals(401, result.rows[1].key);
+    TEquals(3, result.rows[1].value);
+    TEquals(402, result.rows[2].key);
+    TEquals(3, result.rows[2].value);
+
+    result = db.view('test/view1', {group: true, startkey: 402, endkey: 400,
+      descending: true, foobar: randVal()});
+    TEquals(3, result.rows.length);
+    TEquals(402, result.rows[0].key);
+    TEquals(3, result.rows[0].value);
+    TEquals(401, result.rows[1].key);
+    TEquals(3, result.rows[1].value);
+    TEquals(400, result.rows[2].key);
+    TEquals(3, result.rows[2].value);
+
+    result = db.view('test/view1', {group: true, startkey: 400, endkey: 402,
+      inclusive_end: false, foobar: randVal()});
+    TEquals(2, result.rows.length);
+    TEquals(400, result.rows[0].key);
+    TEquals(3, result.rows[0].value);
+    TEquals(401, result.rows[1].key);
+    TEquals(3, result.rows[1].value);
+
+    result = db.view('test/view1', {group: true, startkey: 402, endkey: 400,
+      descending: true, inclusive_end: false, foobar: randVal()});
+    TEquals(2, result.rows.length);
+    TEquals(402, result.rows[0].key);
+    TEquals(3, result.rows[0].value);
+    TEquals(401, result.rows[1].key);
+    TEquals(3, result.rows[1].value);
+
+    result = db.view('test/view1', {group: true, startkey: 400, endkey: 402,
+      endkey_docid: "401", foobar: randVal()});
+    TEquals(3, result.rows.length);
+    TEquals(400, result.rows[0].key);
+    TEquals(3, result.rows[0].value);
+    TEquals(401, result.rows[1].key);
+    TEquals(3, result.rows[1].value);
+    TEquals(402, result.rows[2].key);
+    TEquals(2, result.rows[2].value);
+
+    result = db.view('test/view1', {group: true, startkey: 400, endkey: 402,
+      endkey_docid: "400", foobar: randVal()});
+    TEquals(3, result.rows.length);
+    TEquals(400, result.rows[0].key);
+    TEquals(3, result.rows[0].value);
+    TEquals(401, result.rows[1].key);
+    TEquals(3, result.rows[1].value);
+    TEquals(402, result.rows[2].key);
+    TEquals(1, result.rows[2].value);
+
+    result = db.view('test/view1', {group: true, startkey: 402, startkey_docid: "401",
+      endkey: 400, descending: true, foobar: randVal()});
+    TEquals(3, result.rows.length);
+    TEquals(402, result.rows[0].key);
+    TEquals(2, result.rows[0].value);
+    TEquals(401, result.rows[1].key);
+    TEquals(3, result.rows[1].value);
+    TEquals(400, result.rows[2].key);
+    TEquals(3, result.rows[2].value);
+
+    result = db.view('test/view1', {group: true, startkey: 402, startkey_docid: "400",
+      endkey: 400, descending: true, foobar: randVal()});
+    TEquals(3, result.rows.length);
+    TEquals(402, result.rows[0].key);
+    TEquals(1, result.rows[0].value);
+    TEquals(401, result.rows[1].key);
+    TEquals(3, result.rows[1].value);
+    TEquals(400, result.rows[2].key);
+    TEquals(3, result.rows[2].value);
+
+    result = db.view('test/view1', {group: true, startkey: 402, startkey_docid: "401",
+      endkey: 400, descending: true, inclusive_end: false, foobar: randVal()});
+    TEquals(2, result.rows.length);
+    TEquals(402, result.rows[0].key);
+    TEquals(2, result.rows[0].value);
+    TEquals(401, result.rows[1].key);
+    TEquals(3, result.rows[1].value);
+
+    result = db.view('test/view1', {group: true, startkey: 402, startkey_docid: "400",
+      endkey: 400, descending: true, inclusive_end: false, foobar: randVal()});
+    TEquals(2, result.rows.length);
+    TEquals(402, result.rows[0].key);
+    TEquals(1, result.rows[0].value);
+    TEquals(401, result.rows[1].key);
+    TEquals(3, result.rows[1].value);
+
+    result = db.view('test/view1', {group: true, startkey: 402, endkey: 400,
+      endkey_docid: "398", descending: true, inclusive_end: true, foobar: randVal()});
+    TEquals(3, result.rows.length);
+    TEquals(402, result.rows[0].key);
+    TEquals(3, result.rows[0].value);
+    TEquals(401, result.rows[1].key);
+    TEquals(3, result.rows[1].value);
+    TEquals(400, result.rows[2].key);
+    TEquals(3, result.rows[2].value);
+
+    result = db.view('test/view1', {group: true, startkey: 402, endkey: 400,
+      endkey_docid: "399", descending: true, inclusive_end: true, foobar: randVal()});
+    TEquals(3, result.rows.length);
+    TEquals(402, result.rows[0].key);
+    TEquals(3, result.rows[0].value);
+    TEquals(401, result.rows[1].key);
+    TEquals(3, result.rows[1].value);
+    TEquals(400, result.rows[2].key);
+    TEquals(2, result.rows[2].value);
+
+    result = db.view('test/view1', {group: true, startkey: 402, endkey: 400,
+      endkey_docid: "399", descending: true, inclusive_end: false, foobar: randVal()});
+    TEquals(3, result.rows.length);
+    TEquals(402, result.rows[0].key);
+    TEquals(3, result.rows[0].value);
+    TEquals(401, result.rows[1].key);
+    TEquals(3, result.rows[1].value);
+    TEquals(400, result.rows[2].key);
+    TEquals(1, result.rows[2].value);
+
+    result = db.view('test/view1', {group: true, startkey: 402, endkey: 400,
+      endkey_docid: "400", descending: true, inclusive_end: false, foobar: randVal()});
+    TEquals(2, result.rows.length);
+    TEquals(402, result.rows[0].key);
+    TEquals(3, result.rows[0].value);
+    TEquals(401, result.rows[1].key);
+    TEquals(3, result.rows[1].value);
+
+    db.deleteDb();
+  }
+
+  testReducePagination();
+
+};

http://git-wip-us.apache.org/repos/asf/couchdb/blob/6a4893aa/test/javascript/tests/reduce_builtin.js
----------------------------------------------------------------------
diff --git a/test/javascript/tests/reduce_builtin.js b/test/javascript/tests/reduce_builtin.js
new file mode 100644
index 0000000..b3cc3cc
--- /dev/null
+++ b/test/javascript/tests/reduce_builtin.js
@@ -0,0 +1,179 @@
+// 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.reduce_builtin = function(debug) {
+  var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
+  db.deleteDb();
+  db.createDb();
+  if (debug) debugger;
+
+  var numDocs = 500;
+  var docs = makeDocs(1,numDocs + 1);
+  db.bulkSave(docs);
+
+  var summate = function(N) {return (N+1)*N/2;};
+
+  var sumsqr = function(N) { 
+    var acc = 0;
+    for (var i=1; i<=N; ++i) {
+      acc += i*i;
+    }
+    return acc;
+  };
+
+  // this is the same test as the reduce.js test
+  // only we'll let CouchDB run reduce in Erlang
+  var map = function (doc) {
+      emit(doc.integer, doc.integer);
+      emit(doc.integer, doc.integer);
+  };
+
+  var result = db.query(map, "_sum");
+  T(result.rows[0].value == 2*summate(numDocs));
+  result = db.query(map, "_count");
+  T(result.rows[0].value == 1000);
+  result = db.query(map, "_stats");
+  T(result.rows[0].value.sum == 2*summate(numDocs));
+  T(result.rows[0].value.count == 1000);
+  T(result.rows[0].value.min == 1);
+  T(result.rows[0].value.max == 500);
+  T(result.rows[0].value.sumsqr == 2*sumsqr(numDocs));
+
+  result = db.query(map, "_sum", {startkey: 4, endkey: 4});
+  T(result.rows[0].value == 8);
+  result = db.query(map, "_count", {startkey: 4, endkey: 4});
+  T(result.rows[0].value == 2);
+
+  result = db.query(map, "_sum", {startkey: 4, endkey: 5});
+  T(result.rows[0].value == 18);
+  result = db.query(map, "_count", {startkey: 4, endkey: 5});
+  T(result.rows[0].value == 4);
+
+  result = db.query(map, "_sum", {startkey: 4, endkey: 6});
+  T(result.rows[0].value == 30);
+  result = db.query(map, "_count", {startkey: 4, endkey: 6});
+  T(result.rows[0].value == 6);
+
+  result = db.query(map, "_sum", {group:true, limit:3});
+  T(result.rows[0].value == 2);
+  T(result.rows[1].value == 4);
+  T(result.rows[2].value == 6);
+
+  for(var i=1; i<numDocs/2; i+=30) {
+    result = db.query(map, "_sum", {startkey: i, endkey: numDocs - i});
+    T(result.rows[0].value == 2*(summate(numDocs-i) - summate(i-1)));
+  }
+
+  // test for trailing characters after builtin functions, desired behaviour
+  // is to disregard any trailing characters
+  // I think the behavior should be a prefix test, so that even "_statsorama" 
+  // or "_stats\nare\awesome" should work just as "_stats" does. - JChris
+
+  var trailing = ["\u000a", "orama", "\nare\nawesome", " ", "     \n  "];
+
+  for(var i=0; i < trailing.length; i++) {
+    result = db.query(map, "_sum" + trailing[i]);
+    T(result.rows[0].value == 2*summate(numDocs));
+    result = db.query(map, "_count" + trailing[i]);
+    T(result.rows[0].value == 1000);
+    result = db.query(map, "_stats" + trailing[i]);
+    T(result.rows[0].value.sum == 2*summate(numDocs));
+    T(result.rows[0].value.count == 1000);
+    T(result.rows[0].value.min == 1);
+    T(result.rows[0].value.max == 500);
+    T(result.rows[0].value.sumsqr == 2*sumsqr(numDocs));
+  }
+
+  db.deleteDb();
+  db.createDb();
+
+  for(var i=1; i <= 5; i++) {
+
+    for(var j=0; j < 10; j++) {
+      // these docs are in the order of the keys collation, for clarity
+      var docs = [];
+      docs.push({keys:["a"]});
+      docs.push({keys:["a"]});
+      docs.push({keys:["a", "b"]});
+      docs.push({keys:["a", "b"]});
+      docs.push({keys:["a", "b", "c"]});
+      docs.push({keys:["a", "b", "d"]});
+      docs.push({keys:["a", "c", "d"]});
+      docs.push({keys:["d"]});
+      docs.push({keys:["d", "a"]});
+      docs.push({keys:["d", "b"]});
+      docs.push({keys:["d", "c"]});
+      db.bulkSave(docs);
+      T(db.info().doc_count == ((i - 1) * 10 * 11) + ((j + 1) * 11));
+    }
+
+    map = function (doc) { emit(doc.keys, 1); };
+    // with emitted values being 1, count should be the same as sum
+    var builtins = ["_sum", "_count"];
+
+    for (var b=0; b < builtins.length; b++) {
+      var fun = builtins[b];
+      var results = db.query(map, fun, {group:true});
+
+      //group by exact key match
+      T(equals(results.rows[0], {key:["a"],value:20*i}));
+      T(equals(results.rows[1], {key:["a","b"],value:20*i}));
+      T(equals(results.rows[2], {key:["a", "b", "c"],value:10*i}));
+      T(equals(results.rows[3], {key:["a", "b", "d"],value:10*i}));
+
+      // test to make sure group reduce and limit params provide valid json
+      var results = db.query(map, fun, {group: true, limit: 2});
+      T(equals(results.rows[0], {key: ["a"], value: 20*i}));
+      T(equals(results.rows.length, 2));
+
+      //group by the first element in the key array
+      var results = db.query(map, fun, {group_level:1});
+      T(equals(results.rows[0], {key:["a"],value:70*i}));
+      T(equals(results.rows[1], {key:["d"],value:40*i}));
+
+      //group by the first 2 elements in the key array
+      var results = db.query(map, fun, {group_level:2});
+      T(equals(results.rows[0], {key:["a"],value:20*i}));
+      T(equals(results.rows[1], {key:["a","b"],value:40*i}));
+      T(equals(results.rows[2], {key:["a","c"],value:10*i}));
+      T(equals(results.rows[3], {key:["d"],value:10*i}));
+      T(equals(results.rows[4], {key:["d","a"],value:10*i}));
+      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]}));
+  }
+}

http://git-wip-us.apache.org/repos/asf/couchdb/blob/6a4893aa/test/javascript/tests/reduce_false.js
----------------------------------------------------------------------
diff --git a/test/javascript/tests/reduce_false.js b/test/javascript/tests/reduce_false.js
new file mode 100644
index 0000000..699b258
--- /dev/null
+++ b/test/javascript/tests/reduce_false.js
@@ -0,0 +1,44 @@
+// 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.reduce_false = function(debug) {
+  var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
+  db.deleteDb();
+  db.createDb();
+  if (debug) debugger;
+
+  var numDocs = 5;
+  var docs = makeDocs(1,numDocs + 1);
+  db.bulkSave(docs);
+  var summate = function(N) {return (N+1)*N/2;};
+
+  var designDoc = {
+    _id:"_design/test",
+    language: "javascript",
+    views: {
+      summate: {map:"function (doc) { emit(doc.integer, doc.integer); }",
+                reduce:"function (keys, values) { return sum(values); }"},
+    }
+  };
+  T(db.save(designDoc).ok);
+
+  // Test that the reduce works
+  var res = db.view('test/summate');
+  T(res.rows.length == 1 && res.rows[0].value == summate(5));
+
+  //Test that we get our docs back
+  res = db.view('test/summate', {reduce: false});
+  T(res.rows.length == 5);
+  for(var i=0; i<5; i++) {
+    T(res.rows[i].value == i+1);
+  }
+};

http://git-wip-us.apache.org/repos/asf/couchdb/blob/6a4893aa/test/javascript/tests/reduce_false_temp.js
----------------------------------------------------------------------
diff --git a/test/javascript/tests/reduce_false_temp.js b/test/javascript/tests/reduce_false_temp.js
new file mode 100644
index 0000000..d45f05b
--- /dev/null
+++ b/test/javascript/tests/reduce_false_temp.js
@@ -0,0 +1,37 @@
+// 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.reduce_false_temp = function(debug) {
+  var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
+  db.deleteDb();
+  db.createDb();
+  if (debug) debugger;
+
+  var numDocs = 5;
+  var docs = makeDocs(1,numDocs + 1);
+  db.bulkSave(docs);
+  var summate = function(N) {return (N+1)*N/2;};
+
+  var mapFun = "function (doc) { emit(doc.integer, doc.integer); }";
+  var reduceFun = "function (keys, values) { return sum(values); }";
+
+  // Test that the reduce works
+  var res = db.query(mapFun, reduceFun);
+  T(res.rows.length == 1 && res.rows[0].value == summate(5));
+
+  //Test that we get our docs back
+  res = db.query(mapFun, reduceFun, {reduce: false});
+  T(res.rows.length == 5);
+  for(var i=0; i<5; i++) {
+    T(res.rows[i].value == i+1);
+  }
+};

http://git-wip-us.apache.org/repos/asf/couchdb/blob/6a4893aa/test/javascript/tests/replication.js
----------------------------------------------------------------------
diff --git a/test/javascript/tests/replication.js b/test/javascript/tests/replication.js
new file mode 100644
index 0000000..21a4304
--- /dev/null
+++ b/test/javascript/tests/replication.js
@@ -0,0 +1,1795 @@
+// 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.replication = function(debug) {
+
+  if (debug) debugger;
+
+  var host = CouchDB.host;
+  var sourceDb = new CouchDB("test_suite_db_a",{"X-Couch-Full-Commit":"false"});
+  var targetDb = new CouchDB("test_suite_db_b",{"X-Couch-Full-Commit":"false"});
+
+  var dbPairs = [
+    {
+      source: sourceDb.name,
+      target: targetDb.name
+    },
+    {
+      source: CouchDB.protocol + host + "/" + sourceDb.name,
+      target: targetDb.name
+    },
+    {
+      source: sourceDb.name,
+      target: CouchDB.protocol + host + "/" + targetDb.name
+    },
+    {
+      source: CouchDB.protocol + host + "/" + sourceDb.name,
+      target: CouchDB.protocol + host + "/" + targetDb.name
+    }
+  ];
+
+  var att1_data = CouchDB.request("GET", "/_utils/script/test/lorem.txt");
+  att1_data = att1_data.responseText;
+
+  var att2_data = CouchDB.request("GET", "/_utils/script/test/lorem_b64.txt");
+  att2_data = att2_data.responseText;
+
+  var sourceInfo, targetInfo;
+  var docs, doc, copy;
+  var repResult;
+  var i, j, k;
+
+
+  function makeAttData(minSize) {
+    var data = att1_data;
+
+    while (data.length < minSize) {
+      data = data + att1_data;
+    }
+    return data;
+  }
+
+
+  function enableAttCompression(level, types) {
+    var xhr = CouchDB.request(
+      "PUT",
+      "/_config/attachments/compression_level",
+      {
+        body: JSON.stringify(level),
+        headers: {"X-Couch-Persist": "false"}
+      }
+    );
+    T(xhr.status === 200);
+    xhr = CouchDB.request(
+      "PUT",
+      "/_config/attachments/compressible_types",
+      {
+        body: JSON.stringify(types),
+        headers: {"X-Couch-Persist": "false"}
+      }
+    );
+    T(xhr.status === 200);
+  }
+
+
+  function disableAttCompression() {
+    var xhr = CouchDB.request(
+      "PUT",
+      "/_config/attachments/compression_level",
+      {
+        body: JSON.stringify("0"),
+        headers: {"X-Couch-Persist": "false"}
+      }
+    );
+    T(xhr.status === 200);
+  }
+
+
+  function populateDb(db, docs, dontRecreateDb) {
+    if (dontRecreateDb !== true) {
+      db.deleteDb();
+      db.createDb();
+    }
+    for (var i = 0; i < docs.length; i++) {
+      var doc = docs[i];
+      delete doc._rev;
+    }
+    if (docs.length > 0) {
+      db.bulkSave(docs);
+    }
+  }
+
+
+  function addAtt(db, doc, attName, attData, type) {
+    var uri = "/" + db.name + "/" + encodeURIComponent(doc._id) + "/" + attName;
+
+    if (doc._rev) {
+      uri += "?rev=" + doc._rev;
+    }
+
+    var xhr = CouchDB.request("PUT", uri, {
+      headers: {
+        "Content-Type": type
+      },
+      body: attData
+    });
+
+    T(xhr.status === 201);
+    doc._rev = JSON.parse(xhr.responseText).rev;
+  }
+
+
+  function compareObjects(o1, o2) {
+    for (var p in o1) {
+      if (o1[p] === null && o2[p] !== null) {
+        return false;
+      } else if (typeof o1[p] === "object") {
+        if ((typeof o2[p] !== "object") || o2[p] === null) {
+          return false;
+        }
+        if (!arguments.callee(o1[p], o2[p])) {
+          return false;
+        }
+      } else {
+        if (o1[p] !== o2[p]) {
+          return false;
+        }
+      }
+    }
+    return true;
+  }
+
+
+  function getTask(rep_id, delay) {
+    var t0 = new Date();
+    var t1;
+    do {
+      var xhr = CouchDB.request("GET", "/_active_tasks");
+      var tasks = JSON.parse(xhr.responseText);
+      for(var i = 0; i < tasks.length; i++) {
+        if(tasks[i].replication_id == repResult._local_id) {
+          return tasks[i];
+        }
+      }
+      t1 = new Date();
+    } while((t1 - t0) <= delay);
+
+    return null;
+  }
+
+
+  function waitForSeq(sourceDb, targetDb, rep_id) {
+    var sourceSeq = sourceDb.info().update_seq,
+        t0 = new Date(),
+        t1,
+        ms = 3000;
+
+    do {
+      var task = getTask(rep_id, 0);
+      if(task && task["through_seq"] == sourceSeq) {
+        return;
+      }
+      t1 = new Date();
+    } while (((t1 - t0) <= ms));
+  }
+
+
+  // test simple replications (not continuous, not filtered), including
+  // conflict creation
+  docs = makeDocs(1, 21);
+  docs.push({
+    _id: "_design/foo",
+    language: "javascript",
+    value: "ddoc"
+  });
+
+  for (i = 0; i < dbPairs.length; i++) {
+    populateDb(sourceDb, docs);
+    populateDb(targetDb, []);
+
+    // add some attachments
+    for (j = 10; j < 15; j++) {
+      addAtt(sourceDb, docs[j], "readme.txt", att1_data, "text/plain");
+    }
+
+    repResult = CouchDB.replicate(dbPairs[i].source, dbPairs[i].target);
+    TEquals(true, repResult.ok);
+
+    sourceInfo = sourceDb.info();
+    targetInfo = targetDb.info();
+
+    TEquals(sourceInfo.doc_count, targetInfo.doc_count);
+
+    TEquals('string', typeof repResult.session_id);
+    TEquals(repResult.source_last_seq, sourceInfo.update_seq);
+    TEquals(true, repResult.history instanceof Array);
+    TEquals(1, repResult.history.length);
+    TEquals(repResult.history[0].session_id, repResult.session_id);
+    TEquals('string', typeof repResult.history[0].start_time);
+    TEquals('string', typeof repResult.history[0].end_time);
+    TEquals(0, repResult.history[0].start_last_seq);
+    TEquals(sourceInfo.update_seq, repResult.history[0].end_last_seq);
+    TEquals(sourceInfo.update_seq, repResult.history[0].recorded_seq);
+    TEquals(sourceInfo.doc_count, repResult.history[0].missing_checked);
+    TEquals(sourceInfo.doc_count, repResult.history[0].missing_found);
+    TEquals(sourceInfo.doc_count, repResult.history[0].docs_read);
+    TEquals(sourceInfo.doc_count, repResult.history[0].docs_written);
+    TEquals(0, repResult.history[0].doc_write_failures);
+
+    for (j = 0; j < docs.length; j++) {
+      doc = docs[j];
+      copy = targetDb.open(doc._id);
+
+      T(copy !== null);
+      TEquals(true, compareObjects(doc, copy));
+
+      if (j >= 10 && j < 15) {
+        var atts = copy._attachments;
+        TEquals('object', typeof atts);
+        TEquals('object', typeof atts["readme.txt"]);
+        TEquals(2, atts["readme.txt"].revpos);
+        TEquals(0, atts["readme.txt"].content_type.indexOf("text/plain"));
+        TEquals(true, atts["readme.txt"].stub);
+
+        var att_copy = CouchDB.request(
+          "GET", "/" + targetDb.name + "/" + copy._id + "/readme.txt"
+        ).responseText;
+        TEquals(att1_data.length, att_copy.length);
+        TEquals(att1_data, att_copy);
+      }
+    }
+
+
+    // add one more doc to source, more attachments to some existing docs
+    // and replicate again
+    var newDoc = {
+      _id: "foo666",
+      value: "d"
+    };
+    TEquals(true, sourceDb.save(newDoc).ok);
+
+    // add some more attachments
+    for (j = 10; j < 15; j++) {
+      addAtt(sourceDb, docs[j], "data.dat", att2_data, "application/binary");
+    }
+
+    repResult = CouchDB.replicate(dbPairs[i].source, dbPairs[i].target);
+    TEquals(true, repResult.ok);
+
+    sourceInfo = sourceDb.info();
+    targetInfo = targetDb.info();
+
+    TEquals(targetInfo.doc_count, sourceInfo.doc_count);
+
+    TEquals('string', typeof repResult.session_id);
+    TEquals(sourceInfo.update_seq, repResult.source_last_seq);
+    TEquals(true, repResult.history instanceof Array);
+    TEquals(2, repResult.history.length);
+    TEquals(repResult.history[0].session_id, repResult.session_id);
+    TEquals('string', typeof repResult.history[0].start_time);
+    TEquals('string', typeof repResult.history[0].end_time);
+    TEquals((sourceInfo.update_seq - 6), repResult.history[0].start_last_seq);
+    TEquals(sourceInfo.update_seq, repResult.history[0].end_last_seq);
+    TEquals(sourceInfo.update_seq, repResult.history[0].recorded_seq);
+    TEquals(6, repResult.history[0].missing_checked);
+    TEquals(6, repResult.history[0].missing_found);
+    TEquals(6, repResult.history[0].docs_read);
+    TEquals(6, repResult.history[0].docs_written);
+    TEquals(0, repResult.history[0].doc_write_failures);
+
+    copy = targetDb.open(newDoc._id);
+    T(copy !== null);
+    TEquals(newDoc._id, copy._id);
+    TEquals(newDoc.value, copy.value);
+
+    for (j = 10; j < 15; j++) {
+      doc = docs[j];
+      copy = targetDb.open(doc._id);
+
+      T(copy !== null);
+      TEquals(true, compareObjects(doc, copy));
+
+      var atts = copy._attachments;
+      TEquals('object', typeof atts);
+      TEquals('object', typeof atts["readme.txt"]);
+      TEquals(2, atts["readme.txt"].revpos);
+      TEquals(0, atts["readme.txt"].content_type.indexOf("text/plain"));
+      TEquals(true, atts["readme.txt"].stub);
+
+      var att1_copy = CouchDB.request(
+        "GET", "/" + targetDb.name + "/" + copy._id + "/readme.txt"
+      ).responseText;
+      TEquals(att1_data.length, att1_copy.length);
+      TEquals(att1_data, att1_copy);
+
+      TEquals('object', typeof atts["data.dat"]);
+      TEquals(3, atts["data.dat"].revpos);
+      TEquals(0, atts["data.dat"].content_type.indexOf("application/binary"));
+      TEquals(true, atts["data.dat"].stub);
+
+      var att2_copy = CouchDB.request(
+        "GET", "/" + targetDb.name + "/" + copy._id + "/data.dat"
+      ).responseText;
+      TEquals(att2_data.length, att2_copy.length);
+      TEquals(att2_data, att2_copy);
+    }
+
+    // test deletion is replicated
+    doc = sourceDb.open(docs[1]._id);
+    TEquals(true, sourceDb.deleteDoc(doc).ok);
+
+    repResult = CouchDB.replicate(dbPairs[i].source, dbPairs[i].target);
+    TEquals(true, repResult.ok);
+
+    sourceInfo = sourceDb.info();
+    targetInfo = targetDb.info();
+
+    TEquals(targetInfo.doc_count, sourceInfo.doc_count);
+    TEquals(targetInfo.doc_del_count, sourceInfo.doc_del_count);
+    TEquals(1, targetInfo.doc_del_count);
+
+    TEquals(true, repResult.history instanceof Array);
+    TEquals(3, repResult.history.length);
+    TEquals((sourceInfo.update_seq - 1), repResult.history[0].start_last_seq);
+    TEquals(sourceInfo.update_seq, repResult.history[0].end_last_seq);
+    TEquals(sourceInfo.update_seq, repResult.history[0].recorded_seq);
+    TEquals(1, repResult.history[0].missing_checked);
+    TEquals(1, repResult.history[0].missing_found);
+    TEquals(1, repResult.history[0].docs_read);
+    TEquals(1, repResult.history[0].docs_written);
+    TEquals(0, repResult.history[0].doc_write_failures);
+
+    copy = targetDb.open(docs[1]._id);
+    TEquals(null, copy);
+
+    var changes = targetDb.changes({since: 0});
+    var idx = changes.results.length - 1;
+    TEquals(docs[1]._id, changes.results[idx].id);
+    TEquals(true, changes.results[idx].deleted);
+
+    // test conflict
+    doc = sourceDb.open(docs[0]._id);
+    doc.value = "white";
+    TEquals(true, sourceDb.save(doc).ok);
+
+    copy = targetDb.open(docs[0]._id);
+    copy.value = "black";
+    TEquals(true, targetDb.save(copy).ok);
+
+    repResult = CouchDB.replicate(dbPairs[i].source, dbPairs[i].target);
+    TEquals(true, repResult.ok);
+
+    sourceInfo = sourceDb.info();
+    targetInfo = targetDb.info();
+
+    TEquals(sourceInfo.doc_count, targetInfo.doc_count);
+
+    TEquals(true, repResult.history instanceof Array);
+    TEquals(4, repResult.history.length);
+    TEquals((sourceInfo.update_seq - 1), repResult.history[0].start_last_seq);
+    TEquals(sourceInfo.update_seq, repResult.history[0].end_last_seq);
+    TEquals(sourceInfo.update_seq, repResult.history[0].recorded_seq);
+    TEquals(1, repResult.history[0].missing_checked);
+    TEquals(1, repResult.history[0].missing_found);
+    TEquals(1, repResult.history[0].docs_read);
+    TEquals(1, repResult.history[0].docs_written);
+    TEquals(0, repResult.history[0].doc_write_failures);
+
+    copy = targetDb.open(docs[0]._id, {conflicts: true});
+
+    TEquals(0, copy._rev.indexOf("2-"));
+    TEquals(true, copy._conflicts instanceof Array);
+    TEquals(1, copy._conflicts.length);
+    TEquals(0, copy._conflicts[0].indexOf("2-"));
+
+    // replicate again with conflict
+    doc.value = "yellow";
+    TEquals(true, sourceDb.save(doc).ok);
+
+    repResult = CouchDB.replicate(dbPairs[i].source, dbPairs[i].target);
+    TEquals(true, repResult.ok);
+
+    sourceInfo = sourceDb.info();
+    targetInfo = targetDb.info();
+
+    TEquals(sourceInfo.doc_count, targetInfo.doc_count);
+
+    TEquals(true, repResult.history instanceof Array);
+    TEquals(5, repResult.history.length);
+    TEquals((sourceInfo.update_seq - 1), repResult.history[0].start_last_seq);
+    TEquals(sourceInfo.update_seq, repResult.history[0].end_last_seq);
+    TEquals(sourceInfo.update_seq, repResult.history[0].recorded_seq);
+    TEquals(1, repResult.history[0].missing_checked);
+    TEquals(1, repResult.history[0].missing_found);
+    TEquals(1, repResult.history[0].docs_read);
+    TEquals(1, repResult.history[0].docs_written);
+    TEquals(0, repResult.history[0].doc_write_failures);
+
+    copy = targetDb.open(docs[0]._id, {conflicts: true});
+
+    TEquals(0, copy._rev.indexOf("3-"));
+    TEquals(true, copy._conflicts instanceof Array);
+    TEquals(1, copy._conflicts.length);
+    TEquals(0, copy._conflicts[0].indexOf("2-"));
+
+    // resolve the conflict
+    TEquals(true, targetDb.deleteDoc({_id: copy._id, _rev: copy._conflicts[0]}).ok);
+
+    // replicate again, check there are no more conflicts
+    doc.value = "rainbow";
+    TEquals(true, sourceDb.save(doc).ok);
+
+    repResult = CouchDB.replicate(dbPairs[i].source, dbPairs[i].target);
+    TEquals(true, repResult.ok);
+
+    sourceInfo = sourceDb.info();
+    targetInfo = targetDb.info();
+
+    TEquals(sourceInfo.doc_count, targetInfo.doc_count);
+
+    TEquals(true, repResult.history instanceof Array);
+    TEquals(6, repResult.history.length);
+    TEquals((sourceInfo.update_seq - 1), repResult.history[0].start_last_seq);
+    TEquals(sourceInfo.update_seq, repResult.history[0].end_last_seq);
+    TEquals(sourceInfo.update_seq, repResult.history[0].recorded_seq);
+    TEquals(1, repResult.history[0].missing_checked);
+    TEquals(1, repResult.history[0].missing_found);
+    TEquals(1, repResult.history[0].docs_read);
+    TEquals(1, repResult.history[0].docs_written);
+    TEquals(0, repResult.history[0].doc_write_failures);
+
+    copy = targetDb.open(docs[0]._id, {conflicts: true});
+
+    TEquals(0, copy._rev.indexOf("4-"));
+    TEquals('undefined', typeof copy._conflicts);
+
+    // test that revisions already in a target are not copied
+    TEquals(true, sourceDb.save({_id: "foo1", value: 111}).ok);
+    TEquals(true, targetDb.save({_id: "foo1", value: 111}).ok);
+    TEquals(true, sourceDb.save({_id: "foo2", value: 222}).ok);
+    TEquals(true, sourceDb.save({_id: "foo3", value: 333}).ok);
+    TEquals(true, targetDb.save({_id: "foo3", value: 333}).ok);
+
+    repResult = CouchDB.replicate(dbPairs[i].source, dbPairs[i].target);
+    TEquals(true, repResult.ok);
+
+    sourceInfo = sourceDb.info();
+    TEquals(sourceInfo.update_seq, repResult.source_last_seq);
+    TEquals(sourceInfo.update_seq - 3, repResult.history[0].start_last_seq);
+    TEquals(sourceInfo.update_seq, repResult.history[0].end_last_seq);
+    TEquals(sourceInfo.update_seq, repResult.history[0].recorded_seq);
+    TEquals(3, repResult.history[0].missing_checked);
+    TEquals(1, repResult.history[0].missing_found);
+    TEquals(1, repResult.history[0].docs_read);
+    TEquals(1, repResult.history[0].docs_written);
+    TEquals(0, repResult.history[0].doc_write_failures);
+
+    TEquals(true, sourceDb.save({_id: "foo4", value: 444}).ok);
+    TEquals(true, targetDb.save({_id: "foo4", value: 444}).ok);
+    TEquals(true, sourceDb.save({_id: "foo5", value: 555}).ok);
+    TEquals(true, targetDb.save({_id: "foo5", value: 555}).ok);
+
+    repResult = CouchDB.replicate(dbPairs[i].source, dbPairs[i].target);
+    TEquals(true, repResult.ok);
+
+    sourceInfo = sourceDb.info();
+    TEquals(sourceInfo.update_seq, repResult.source_last_seq);
+    TEquals(sourceInfo.update_seq - 2, repResult.history[0].start_last_seq);
+    TEquals(sourceInfo.update_seq, repResult.history[0].end_last_seq);
+    TEquals(sourceInfo.update_seq, repResult.history[0].recorded_seq);
+    TEquals(2, repResult.history[0].missing_checked);
+    TEquals(0, repResult.history[0].missing_found);
+    TEquals(0, repResult.history[0].docs_read);
+    TEquals(0, repResult.history[0].docs_written);
+    TEquals(0, repResult.history[0].doc_write_failures);
+
+    repResult = CouchDB.replicate(dbPairs[i].source, dbPairs[i].target);
+    TEquals(true, repResult.ok);
+    TEquals(true, repResult.no_changes);
+    sourceInfo = sourceDb.info();
+    TEquals(sourceInfo.update_seq, repResult.source_last_seq);
+  }
+
+
+  // test error when source database does not exist
+  try {
+    CouchDB.replicate("foobar", "test_suite_db");
+    T(false, "should have failed with db_not_found error");
+  } catch (x) {
+    TEquals("db_not_found", x.error);
+  }
+
+  // validate COUCHDB-317
+  try {
+    CouchDB.replicate("/foobar", "test_suite_db");
+    T(false, "should have failed with db_not_found error");
+  } catch (x) {
+    TEquals("db_not_found", x.error);
+  }
+
+  try {
+    CouchDB.replicate(CouchDB.protocol + host + "/foobar", "test_suite_db");
+    T(false, "should have failed with db_not_found error");
+  } catch (x) {
+    TEquals("db_not_found", x.error);
+  }
+
+
+  // test since_seq parameter
+  docs = makeDocs(1, 6);
+
+  for (i = 0; i < dbPairs.length; i++) {
+    var since_seq = 3;
+    populateDb(sourceDb, docs);
+    populateDb(targetDb, []);
+
+    var expected_ids = [];
+    var changes = sourceDb.changes({since: since_seq});
+    for (j = 0; j < changes.results.length; j++) {
+      expected_ids.push(changes.results[j].id);
+    }
+    TEquals(2, expected_ids.length, "2 documents since since_seq");
+
+    // For OTP < R14B03, temporary child specs are kept in the supervisor
+    // after the child terminates, so cancel the replication to delete the
+    // child spec in those OTP releases, otherwise since_seq will have no
+    // effect.
+    try {
+      CouchDB.replicate(
+        dbPairs[i].source,
+        dbPairs[i].target,
+        {body: {cancel: true}}
+      );
+    } catch (x) {
+      // OTP R14B03 onwards
+      TEquals("not found", x.error);
+    }
+    repResult = CouchDB.replicate(
+      dbPairs[i].source,
+      dbPairs[i].target,
+      {body: {since_seq: since_seq}}
+    );
+    // Same reason as before. But here we don't want since_seq to affect
+    // subsequent replications, so we need to delete the child spec from the
+    // supervisor (since_seq is not used to calculate the replication ID).
+    try {
+      CouchDB.replicate(
+        dbPairs[i].source,
+        dbPairs[i].target,
+        {body: {cancel: true}}
+      );
+    } catch (x) {
+      // OTP R14B03 onwards
+      TEquals("not found", x.error);
+    }
+    TEquals(true, repResult.ok);
+    TEquals(2, repResult.history[0].missing_checked);
+    TEquals(2, repResult.history[0].missing_found);
+    TEquals(2, repResult.history[0].docs_read);
+    TEquals(2, repResult.history[0].docs_written);
+    TEquals(0, repResult.history[0].doc_write_failures);
+
+    for (j = 0; j < docs.length; j++) {
+      doc = docs[j];
+      copy = targetDb.open(doc._id);
+
+      if (expected_ids.indexOf(doc._id) === -1) {
+        T(copy === null);
+      } else {
+        T(copy !== null);
+        TEquals(true, compareObjects(doc, copy));
+      }
+    }
+  }
+
+
+  // test errors due to doc validate_doc_update functions in the target endpoint
+  docs = makeDocs(1, 8);
+  docs[2]["_attachments"] = {
+    "hello.txt": {
+      "content_type": "text/plain",
+      "data": "aGVsbG8gd29ybGQ="  // base64:encode("hello world")
+    }
+  };
+  var ddoc = {
+    _id: "_design/test",
+    language: "javascript",
+    validate_doc_update: (function(newDoc, oldDoc, userCtx, secObj) {
+      if ((newDoc.integer % 2) !== 0) {
+        throw {forbidden: "I only like multiples of 2."};
+      }
+    }).toString()
+  };
+
+  for (i = 0; i < dbPairs.length; i++) {
+    populateDb(sourceDb, docs);
+    populateDb(targetDb, [ddoc]);
+
+    repResult = CouchDB.replicate(
+      dbPairs[i].source,
+      dbPairs[i].target
+    );
+    TEquals(true, repResult.ok);
+    TEquals(7, repResult.history[0].missing_checked);
+    TEquals(7, repResult.history[0].missing_found);
+    TEquals(7, repResult.history[0].docs_read);
+    TEquals(3, repResult.history[0].docs_written);
+    TEquals(4, repResult.history[0].doc_write_failures);
+
+    for (j = 0; j < docs.length; j++) {
+      doc = docs[j];
+      copy = targetDb.open(doc._id);
+
+      if (doc.integer % 2 === 0) {
+        T(copy !== null);
+        TEquals(copy.integer, doc.integer);
+      } else {
+        T(copy === null);
+      }
+    }
+  }
+
+
+  // test create_target option
+  docs = makeDocs(1, 2);
+
+  for (i = 0; i < dbPairs.length; i++) {
+    populateDb(sourceDb, docs);
+    targetDb.deleteDb();
+
+    repResult = CouchDB.replicate(
+      dbPairs[i].source,
+      dbPairs[i].target,
+      {body: {create_target: true}}
+    );
+    TEquals(true, repResult.ok);
+
+    sourceInfo = sourceDb.info();
+    targetInfo = targetDb.info();
+
+    TEquals(sourceInfo.doc_count, targetInfo.doc_count);
+    TEquals(sourceInfo.update_seq, targetInfo.update_seq);
+  }
+
+
+  // test filtered replication
+  docs = makeDocs(1, 31);
+  docs.push({
+    _id: "_design/mydesign",
+    language: "javascript",
+    filters: {
+      myfilter: (function(doc, req) {
+        var modulus = Number(req.query.modulus);
+        var special = req.query.special;
+        return (doc.integer % modulus === 0) || (doc.string === special);
+      }).toString()
+    }
+  });
+
+  for (i = 0; i < dbPairs.length; i++) {
+    populateDb(sourceDb, docs);
+    populateDb(targetDb, []);
+
+    repResult = CouchDB.replicate(
+      dbPairs[i].source,
+      dbPairs[i].target,
+      {
+        body: {
+          filter: "mydesign/myfilter",
+          query_params: {
+            modulus: "2",
+            special: "7"
+          }
+        }
+      }
+    );
+
+    TEquals(true, repResult.ok);
+
+    for (j = 0; j < docs.length; j++) {
+      doc = docs[j];
+      copy = targetDb.open(doc._id);
+
+      if ((doc.integer && (doc.integer % 2 === 0)) || (doc.string === "7")) {
+
+        T(copy !== null);
+        TEquals(true, compareObjects(doc, copy));
+      } else {
+        TEquals(null, copy);
+      }
+    }
+
+    TEquals(true, repResult.history instanceof Array);
+    TEquals(1, repResult.history.length);
+    // We (incorrectly) don't record update sequences for things
+    // that don't pass the changse feed filter. Historically the
+    // last document to pass was the second to last doc which has
+    // an update sequence of 30. Work that has been applied to avoid
+    // conflicts from duplicate IDs breaking _bulk_docs updates added
+    // a sort to the logic which changes this. Now the last document
+    // to pass has an doc id of "8" and is at update_seq 29 (because only
+    // "9" and the design doc are after it).
+    //
+    // In the future the fix ought to be that we record that update
+    // sequence of the database. BigCouch has some existing work on
+    // this in the clustered case because if you have very few documents
+    // that pass the filter then (given single node's behavior) you end
+    // up having to rescan a large portion of the database.
+    TEquals(29, repResult.source_last_seq);
+    TEquals(0, repResult.history[0].start_last_seq);
+    TEquals(29, repResult.history[0].end_last_seq);
+    TEquals(29, repResult.history[0].recorded_seq);
+    // 16 => 15 docs with even integer field  + 1 doc with string field "7"
+    TEquals(16, repResult.history[0].missing_checked);
+    TEquals(16, repResult.history[0].missing_found);
+    TEquals(16, repResult.history[0].docs_read);
+    TEquals(16, repResult.history[0].docs_written);
+    TEquals(0, repResult.history[0].doc_write_failures);
+
+
+    // add new docs to source and resume the same replication
+    var newDocs = makeDocs(50, 56);
+    populateDb(sourceDb, newDocs, true);
+
+    repResult = CouchDB.replicate(
+      dbPairs[i].source,
+      dbPairs[i].target,
+      {
+        body: {
+          filter: "mydesign/myfilter",
+          query_params: {
+            modulus: "2",
+            special: "7"
+          }
+        }
+      }
+    );
+
+    TEquals(true, repResult.ok);
+
+    for (j = 0; j < newDocs.length; j++) {
+      doc = newDocs[j];
+      copy = targetDb.open(doc._id);
+
+      if (doc.integer && (doc.integer % 2 === 0)) {
+
+        T(copy !== null);
+        TEquals(true, compareObjects(doc, copy));
+      } else {
+        TEquals(null, copy);
+      }
+    }
+
+    // last doc has even integer field, so last replicated seq is 36
+    TEquals(36, repResult.source_last_seq);
+    TEquals(true, repResult.history instanceof Array);
+    TEquals(2, repResult.history.length);
+    TEquals(29, repResult.history[0].start_last_seq);
+    TEquals(36, repResult.history[0].end_last_seq);
+    TEquals(36, repResult.history[0].recorded_seq);
+    TEquals(3, repResult.history[0].missing_checked);
+    TEquals(3, repResult.history[0].missing_found);
+    TEquals(3, repResult.history[0].docs_read);
+    TEquals(3, repResult.history[0].docs_written);
+    TEquals(0, repResult.history[0].doc_write_failures);
+  }
+
+
+  // test filtered replication works as expected after changing the filter's
+  // code (ticket COUCHDB-892)
+  var filterFun1 = (function(doc, req) {
+    if (doc.value < Number(req.query.maxvalue)) {
+      return true;
+    } else {
+      return false;
+    }
+  }).toString();
+
+  var filterFun2 = (function(doc, req) {
+    return true;
+  }).toString();
+
+  for (i = 0; i < dbPairs.length; i++) {
+    populateDb(targetDb, []);
+    populateDb(sourceDb, []);
+
+    TEquals(true, sourceDb.save({_id: "foo1", value: 1}).ok);
+    TEquals(true, sourceDb.save({_id: "foo2", value: 2}).ok);
+    TEquals(true, sourceDb.save({_id: "foo3", value: 3}).ok);
+    TEquals(true, sourceDb.save({_id: "foo4", value: 4}).ok);
+
+    var ddoc = {
+      "_id": "_design/mydesign",
+      "language": "javascript",
+      "filters": {
+        "myfilter": filterFun1
+      }
+    };
+
+    TEquals(true, sourceDb.save(ddoc).ok);
+
+    repResult = CouchDB.replicate(
+      dbPairs[i].source,
+      dbPairs[i].target,
+      {
+        body: {
+          filter: "mydesign/myfilter",
+          query_params: {
+            maxvalue: "3"
+          }
+        }
+      }
+    );
+
+    TEquals(true, repResult.ok);
+    TEquals(true, repResult.history instanceof Array);
+    TEquals(1, repResult.history.length);
+    TEquals(2, repResult.history[0].docs_written);
+    TEquals(2, repResult.history[0].docs_read);
+    TEquals(0, repResult.history[0].doc_write_failures);
+
+    var docFoo1 = targetDb.open("foo1");
+    T(docFoo1 !== null);
+    TEquals(1, docFoo1.value);
+
+    var docFoo2 = targetDb.open("foo2");
+    T(docFoo2 !== null);
+    TEquals(2, docFoo2.value);
+
+    var docFoo3 = targetDb.open("foo3");
+    TEquals(null, docFoo3);
+
+    var docFoo4 = targetDb.open("foo4");
+    TEquals(null, docFoo4);
+
+    // replication should start from scratch after the filter's code changed
+
+    ddoc.filters.myfilter = filterFun2;
+    TEquals(true, sourceDb.save(ddoc).ok);
+
+    repResult = CouchDB.replicate(
+      dbPairs[i].source,
+      dbPairs[i].target,
+      {
+        body: {
+          filter: "mydesign/myfilter",
+          query_params : {
+            maxvalue: "3"
+          }
+        }
+      }
+    );
+
+    TEquals(true, repResult.ok);
+    TEquals(true, repResult.history instanceof Array);
+    TEquals(1, repResult.history.length);
+    TEquals(3, repResult.history[0].docs_written);
+    TEquals(3, repResult.history[0].docs_read);
+    TEquals(0, repResult.history[0].doc_write_failures);
+
+    docFoo1 = targetDb.open("foo1");
+    T(docFoo1 !== null);
+    TEquals(1, docFoo1.value);
+
+    docFoo2 = targetDb.open("foo2");
+    T(docFoo2 !== null);
+    TEquals(2, docFoo2.value);
+
+    docFoo3 = targetDb.open("foo3");
+    T(docFoo3 !== null);
+    TEquals(3, docFoo3.value);
+
+    docFoo4 = targetDb.open("foo4");
+    T(docFoo4 !== null);
+    TEquals(4, docFoo4.value);
+
+    T(targetDb.open("_design/mydesign") !== null);
+  }
+
+
+  // test replication by doc IDs
+  docs = makeDocs(1, 11);
+  docs.push({
+    _id: "_design/foo",
+    language: "javascript",
+    integer: 1
+  });
+
+  var target_doc_ids = [
+    { initial: ["1", "2", "10"], after: [], conflict_id: "2" },
+    { initial: ["1", "2"], after: ["7"], conflict_id: "1" },
+    { initial: ["1", "foo_666", "10"], after: ["7"], conflict_id: "10" },
+    { initial: ["_design/foo", "8"], after: ["foo_5"], conflict_id: "8" },
+    { initial: ["_design%2Ffoo", "8"], after: ["foo_5"], conflict_id: "8" },
+    { initial: [], after: ["foo_1000", "_design/foo", "1"], conflict_id: "1" }
+  ];
+  var doc_ids, after_doc_ids;
+  var id, num_inexistent_docs, after_num_inexistent_docs;
+  var total, after_total;
+
+  for (i = 0; i < dbPairs.length; i++) {
+
+    for (j = 0; j < target_doc_ids.length; j++) {
+      doc_ids = target_doc_ids[j].initial;
+      num_inexistent_docs = 0;
+
+      for (k = 0; k < doc_ids.length; k++) {
+        id = doc_ids[k];
+        if (id.indexOf("foo_") === 0) {
+          num_inexistent_docs += 1;
+        }
+      }
+
+      populateDb(sourceDb, docs);
+      populateDb(targetDb, []);
+
+      repResult = CouchDB.replicate(
+        dbPairs[i].source,
+        dbPairs[i].target,
+        {
+          body: {
+            doc_ids: doc_ids
+          }
+        }
+      );
+
+      total = doc_ids.length - num_inexistent_docs;
+      TEquals(true, repResult.ok);
+      if (total === 0) {
+        TEquals(true, repResult.no_changes);
+      } else {
+        TEquals('string', typeof repResult.start_time);
+        TEquals('string', typeof repResult.end_time);
+        TEquals(total, repResult.docs_read);
+        TEquals(total, repResult.docs_written);
+        TEquals(0, repResult.doc_write_failures);
+      }
+
+      for (k = 0; k < doc_ids.length; k++) {
+        id = decodeURIComponent(doc_ids[k]);
+        doc = sourceDb.open(id);
+        copy = targetDb.open(id);
+
+        if (id.indexOf("foo_") === 0) {
+          TEquals(null, doc);
+          TEquals(null, copy);
+        } else {
+          T(doc !== null);
+          T(copy !== null);
+          TEquals(true, compareObjects(doc, copy));
+        }
+      }
+
+      // be absolutely sure that other docs were not replicated
+      for (k = 0; k < docs.length; k++) {
+        var base_id = docs[k]._id;
+        id = encodeURIComponent(base_id);
+        doc = targetDb.open(base_id);
+
+        if ((doc_ids.indexOf(id) >= 0) || (doc_ids.indexOf(base_id) >= 0)) {
+            T(doc !== null);
+        } else {
+            TEquals(null, doc);
+        }
+      }
+
+      targetInfo = targetDb.info();
+      TEquals(total, targetInfo.doc_count);
+
+
+      // add more docs throught replication by doc IDs
+      after_doc_ids = target_doc_ids[j].after;
+      after_num_inexistent_docs = 0;
+
+      for (k = 0; k < after_doc_ids.length; k++) {
+        id = after_doc_ids[k];
+        if (id.indexOf("foo_") === 0) {
+          after_num_inexistent_docs += 1;
+        }
+      }
+
+      repResult = CouchDB.replicate(
+        dbPairs[i].source,
+        dbPairs[i].target,
+        {
+          body: {
+            doc_ids: after_doc_ids
+          }
+        }
+      );
+
+      after_total = after_doc_ids.length - after_num_inexistent_docs;
+      TEquals(true, repResult.ok);
+      if (after_total === 0) {
+        TEquals(true, repResult.no_changes);
+      } else {
+        TEquals('string', typeof repResult.start_time);
+        TEquals('string', typeof repResult.end_time);
+        TEquals(after_total, repResult.docs_read);
+        TEquals(after_total, repResult.docs_written);
+        TEquals(0, repResult.doc_write_failures);
+      }
+
+      for (k = 0; k < after_doc_ids.length; k++) {
+        id = after_doc_ids[k];
+        doc = sourceDb.open(id);
+        copy = targetDb.open(id);
+
+        if (id.indexOf("foo_") === 0) {
+          TEquals(null, doc);
+          TEquals(null, copy);
+        } else {
+          T(doc !== null);
+          T(copy !== null);
+          TEquals(true, compareObjects(doc, copy));
+        }
+      }
+
+      // be absolutely sure that other docs were not replicated
+      for (k = 0; k < docs.length; k++) {
+        var base_id = docs[k]._id;
+        id = encodeURIComponent(base_id);
+        doc = targetDb.open(base_id);
+
+        if ((doc_ids.indexOf(id) >= 0) || (after_doc_ids.indexOf(id) >= 0) ||
+            (doc_ids.indexOf(base_id) >= 0) ||
+            (after_doc_ids.indexOf(base_id) >= 0)) {
+            T(doc !== null);
+        } else {
+            TEquals(null, doc);
+        }
+      }
+
+      targetInfo = targetDb.info();
+      TEquals((total + after_total), targetInfo.doc_count);
+
+
+      // replicate again the same doc after updated on source (no conflict)
+      id = target_doc_ids[j].conflict_id;
+      doc = sourceDb.open(id);
+      T(doc !== null);
+      doc.integer = 666;
+      TEquals(true, sourceDb.save(doc).ok);
+      addAtt(sourceDb, doc, "readme.txt", att1_data, "text/plain");
+      addAtt(sourceDb, doc, "data.dat", att2_data, "application/binary");
+
+      repResult = CouchDB.replicate(
+        dbPairs[i].source,
+        dbPairs[i].target,
+        {
+          body: {
+            doc_ids: [id]
+          }
+        }
+      );
+
+      TEquals(true, repResult.ok);
+      TEquals(1, repResult.docs_read);
+      TEquals(1, repResult.docs_written);
+      TEquals(0, repResult.doc_write_failures);
+
+      copy = targetDb.open(id, {conflicts: true});
+
+      TEquals(666, copy.integer);
+      TEquals(0, copy._rev.indexOf("4-"));
+      TEquals('undefined', typeof copy._conflicts);
+
+      var atts = copy._attachments;
+      TEquals('object', typeof atts);
+      TEquals('object', typeof atts["readme.txt"]);
+      TEquals(3, atts["readme.txt"].revpos);
+      TEquals(0, atts["readme.txt"].content_type.indexOf("text/plain"));
+      TEquals(true, atts["readme.txt"].stub);
+
+      var att1_copy = CouchDB.request(
+        "GET", "/" + targetDb.name + "/" + copy._id + "/readme.txt"
+      ).responseText;
+      TEquals(att1_data.length, att1_copy.length);
+      TEquals(att1_data, att1_copy);
+
+      TEquals('object', typeof atts["data.dat"]);
+      TEquals(4, atts["data.dat"].revpos);
+      TEquals(0, atts["data.dat"].content_type.indexOf("application/binary"));
+      TEquals(true, atts["data.dat"].stub);
+
+      var att2_copy = CouchDB.request(
+        "GET", "/" + targetDb.name + "/" + copy._id + "/data.dat"
+      ).responseText;
+      TEquals(att2_data.length, att2_copy.length);
+      TEquals(att2_data, att2_copy);
+
+
+      // generate a conflict throught replication by doc IDs
+      id = target_doc_ids[j].conflict_id;
+      doc = sourceDb.open(id);
+      copy = targetDb.open(id);
+      T(doc !== null);
+      T(copy !== null);
+      doc.integer += 100;
+      copy.integer += 1;
+      TEquals(true, sourceDb.save(doc).ok);
+      TEquals(true, targetDb.save(copy).ok);
+
+      repResult = CouchDB.replicate(
+        dbPairs[i].source,
+        dbPairs[i].target,
+        {
+          body: {
+            doc_ids: [id]
+          }
+        }
+      );
+
+      TEquals(true, repResult.ok);
+      TEquals(1, repResult.docs_read);
+      TEquals(1, repResult.docs_written);
+      TEquals(0, repResult.doc_write_failures);
+
+      copy = targetDb.open(id, {conflicts: true});
+
+      TEquals(0, copy._rev.indexOf("5-"));
+      TEquals(true, copy._conflicts instanceof Array);
+      TEquals(1, copy._conflicts.length);
+      TEquals(0, copy._conflicts[0].indexOf("5-"));
+    }
+  }
+
+
+  docs = makeDocs(1, 25);
+  docs.push({
+    _id: "_design/foo",
+    language: "javascript",
+    filters: {
+      myfilter: (function(doc, req) { return true; }).toString()
+    }
+  });
+
+  for (i = 0; i < dbPairs.length; i++) {
+    populateDb(sourceDb, docs);
+    populateDb(targetDb, []);
+
+    // add some attachments
+    for (j = 10; j < 15; j++) {
+      addAtt(sourceDb, docs[j], "readme.txt", att1_data, "text/plain");
+    }
+
+    repResult = CouchDB.replicate(
+      dbPairs[i].source,
+      dbPairs[i].target,
+      {
+        body: {
+          continuous: true
+        }
+      }
+    );
+    TEquals(true, repResult.ok);
+    TEquals('string', typeof repResult._local_id);
+
+    var rep_id = repResult._local_id;
+
+    waitForSeq(sourceDb, targetDb, rep_id);
+
+    for (j = 0; j < docs.length; j++) {
+      doc = docs[j];
+      copy = targetDb.open(doc._id);
+
+      T(copy !== null);
+      TEquals(true, compareObjects(doc, copy));
+
+      if (j >= 10 && j < 15) {
+        var atts = copy._attachments;
+        TEquals('object', typeof atts);
+        TEquals('object', typeof atts["readme.txt"]);
+        TEquals(2, atts["readme.txt"].revpos);
+        TEquals(0, atts["readme.txt"].content_type.indexOf("text/plain"));
+        TEquals(true, atts["readme.txt"].stub);
+
+        var att_copy = CouchDB.request(
+          "GET", "/" + targetDb.name + "/" + copy._id + "/readme.txt"
+        ).responseText;
+        TEquals(att1_data.length, att_copy.length);
+        TEquals(att1_data, att_copy);
+      }
+    }
+
+    sourceInfo = sourceDb.info();
+    targetInfo = targetDb.info();
+
+    TEquals(sourceInfo.doc_count, targetInfo.doc_count);
+
+    // add attachments to docs in source
+    for (j = 10; j < 15; j++) {
+      addAtt(sourceDb, docs[j], "data.dat", att2_data, "application/binary");
+    }
+
+    var ddoc = docs[docs.length - 1]; // design doc
+    addAtt(sourceDb, ddoc, "readme.txt", att1_data, "text/plain");
+
+    waitForSeq(sourceDb, targetDb, rep_id);
+
+    var modifDocs = docs.slice(10, 15).concat([ddoc]);
+    for (j = 0; j < modifDocs.length; j++) {
+      doc = modifDocs[j];
+      copy = targetDb.open(doc._id);
+
+      T(copy !== null);
+      TEquals(true, compareObjects(doc, copy));
+
+      var atts = copy._attachments;
+      TEquals('object', typeof atts);
+      TEquals('object', typeof atts["readme.txt"]);
+      TEquals(2, atts["readme.txt"].revpos);
+      TEquals(0, atts["readme.txt"].content_type.indexOf("text/plain"));
+      TEquals(true, atts["readme.txt"].stub);
+
+      var att1_copy = CouchDB.request(
+        "GET", "/" + targetDb.name + "/" + copy._id + "/readme.txt"
+      ).responseText;
+      TEquals(att1_data.length, att1_copy.length);
+      TEquals(att1_data, att1_copy);
+
+      if (doc._id.indexOf("_design/") === -1) {
+        TEquals('object', typeof atts["data.dat"]);
+        TEquals(3, atts["data.dat"].revpos);
+        TEquals(0, atts["data.dat"].content_type.indexOf("application/binary"));
+        TEquals(true, atts["data.dat"].stub);
+
+        var att2_copy = CouchDB.request(
+          "GET", "/" + targetDb.name + "/" + copy._id + "/data.dat"
+        ).responseText;
+        TEquals(att2_data.length, att2_copy.length);
+        TEquals(att2_data, att2_copy);
+      }
+    }
+
+    sourceInfo = sourceDb.info();
+    targetInfo = targetDb.info();
+
+    TEquals(sourceInfo.doc_count, targetInfo.doc_count);
+
+    // add another attachment to the ddoc on source
+    addAtt(sourceDb, ddoc, "data.dat", att2_data, "application/binary");
+
+    waitForSeq(sourceDb, targetDb, rep_id);
+
+    copy = targetDb.open(ddoc._id);
+    var atts = copy._attachments;
+    TEquals('object', typeof atts);
+    TEquals('object', typeof atts["readme.txt"]);
+    TEquals(2, atts["readme.txt"].revpos);
+    TEquals(0, atts["readme.txt"].content_type.indexOf("text/plain"));
+    TEquals(true, atts["readme.txt"].stub);
+
+    var att1_copy = CouchDB.request(
+      "GET", "/" + targetDb.name + "/" + copy._id + "/readme.txt"
+    ).responseText;
+    TEquals(att1_data.length, att1_copy.length);
+    TEquals(att1_data, att1_copy);
+
+    TEquals('object', typeof atts["data.dat"]);
+    TEquals(3, atts["data.dat"].revpos);
+    TEquals(0, atts["data.dat"].content_type.indexOf("application/binary"));
+    TEquals(true, atts["data.dat"].stub);
+
+    var att2_copy = CouchDB.request(
+      "GET", "/" + targetDb.name + "/" + copy._id + "/data.dat"
+    ).responseText;
+    TEquals(att2_data.length, att2_copy.length);
+    TEquals(att2_data, att2_copy);
+
+    sourceInfo = sourceDb.info();
+    targetInfo = targetDb.info();
+
+    TEquals(sourceInfo.doc_count, targetInfo.doc_count);
+
+
+    // add more docs to source
+    var newDocs = makeDocs(25, 35);
+    populateDb(sourceDb, newDocs, true);
+
+    waitForSeq(sourceDb, targetDb, rep_id);
+
+    for (j = 0; j < newDocs.length; j++) {
+      doc = newDocs[j];
+      copy = targetDb.open(doc._id);
+
+      T(copy !== null);
+      TEquals(true, compareObjects(doc, copy));
+    }
+
+    sourceInfo = sourceDb.info();
+    targetInfo = targetDb.info();
+
+    TEquals(sourceInfo.doc_count, targetInfo.doc_count);
+
+    // delete docs from source
+    TEquals(true, sourceDb.deleteDoc(newDocs[0]).ok);
+    TEquals(true, sourceDb.deleteDoc(newDocs[6]).ok);
+
+    waitForSeq(sourceDb, targetDb, rep_id);
+
+    copy = targetDb.open(newDocs[0]._id);
+    TEquals(null, copy);
+    copy = targetDb.open(newDocs[6]._id);
+    TEquals(null, copy);
+
+    var changes = targetDb.changes({since: targetInfo.update_seq});
+    var line1 = changes.results[changes.results.length - 2];
+    var line2 = changes.results[changes.results.length - 1];
+    TEquals(newDocs[0]._id, line1.id);
+    TEquals(true, line1.deleted);
+    TEquals(newDocs[6]._id, line2.id);
+    TEquals(true, line2.deleted);
+
+    // cancel the replication
+    repResult = CouchDB.replicate(
+      dbPairs[i].source,
+      dbPairs[i].target,
+      {
+        body: {
+          continuous: true,
+          cancel: true
+        }
+      }
+    );
+    TEquals(true, repResult.ok);
+    TEquals(rep_id, repResult._local_id);
+
+    doc = {
+      _id: 'foobar',
+      value: 666
+    };
+    TEquals(true, sourceDb.save(doc).ok);
+
+    waitForSeq(sourceDb, targetDb, rep_id);
+    copy = targetDb.open(doc._id);
+    TEquals(null, copy);
+  }
+
+  // COUCHDB-1093 - filtered and continuous _changes feed dies when the
+  // database is compacted
+  docs = makeDocs(1, 10);
+  docs.push({
+    _id: "_design/foo",
+    language: "javascript",
+    filters: {
+      myfilter: (function(doc, req) { return true; }).toString()
+    }
+  });
+  populateDb(sourceDb, docs);
+  populateDb(targetDb, []);
+
+  repResult = CouchDB.replicate(
+    CouchDB.protocol + host + "/" + sourceDb.name,
+    targetDb.name,
+    {
+      body: {
+        continuous: true,
+        filter: "foo/myfilter"
+      }
+    }
+  );
+  TEquals(true, repResult.ok);
+  TEquals('string', typeof repResult._local_id);
+
+  TEquals(true, sourceDb.compact().ok);
+  while (sourceDb.info().compact_running) {};
+
+  TEquals(true, sourceDb.save(makeDocs(30, 31)[0]).ok);
+
+  var task = getTask(repResult._local_id, 1000);
+  T(task != null);
+
+  waitForSeq(sourceDb, targetDb, repResult._local_id);
+  T(sourceDb.open("30") !== null);
+
+  // cancel replication
+  repResult = CouchDB.replicate(
+    CouchDB.protocol + host + "/" + sourceDb.name,
+    targetDb.name,
+    {
+      body: {
+        continuous: true,
+        filter: "foo/myfilter",
+        cancel: true
+      }
+    }
+  );
+  TEquals(true, repResult.ok);
+  TEquals('string', typeof repResult._local_id);
+
+
+  //
+  // test replication of compressed attachments
+  //
+  doc = {
+    _id: "foobar"
+  };
+  var bigTextAtt = makeAttData(128 * 1024);
+  var attName = "readme.txt";
+  var xhr = CouchDB.request("GET", "/_config/attachments/compression_level");
+  var compressionLevel = JSON.parse(xhr.responseText);
+  xhr = CouchDB.request("GET", "/_config/attachments/compressible_types");
+  var compressibleTypes = JSON.parse(xhr.responseText);
+
+  for (i = 0; i < dbPairs.length; i++) {
+    populateDb(sourceDb, [doc]);
+    populateDb(targetDb, []);
+
+    // enable compression of text types
+    enableAttCompression("8", "text/*");
+
+    // add text attachment to foobar doc
+    xhr = CouchDB.request(
+      "PUT",
+      "/" + sourceDb.name + "/" + doc._id + "/" + attName + "?rev=" + doc._rev,
+      {
+        body: bigTextAtt,
+        headers: {"Content-Type": "text/plain"}
+      }
+    );
+    TEquals(201, xhr.status);
+
+    // disable compression and replicate
+    disableAttCompression();
+
+    repResult = CouchDB.replicate(dbPairs[i].source, dbPairs[i].target);
+    TEquals(true, repResult.ok);
+    TEquals(true, repResult.history instanceof Array);
+    TEquals(1, repResult.history.length);
+    TEquals(1, repResult.history[0].missing_checked);
+    TEquals(1, repResult.history[0].missing_found);
+    TEquals(1, repResult.history[0].docs_read);
+    TEquals(1, repResult.history[0].docs_written);
+    TEquals(0, repResult.history[0].doc_write_failures);
+
+    copy = targetDb.open(
+      doc._id,
+      {att_encoding_info: true, bypass_cache: Math.round(Math.random() * 1000)}
+    );
+    T(copy !== null);
+    T(attName in copy._attachments);
+    TEquals("gzip", copy._attachments[attName].encoding);
+    TEquals("number", typeof copy._attachments[attName].length);
+    TEquals("number", typeof copy._attachments[attName].encoded_length);
+    T(copy._attachments[attName].encoded_length < copy._attachments[attName].length);
+  }
+
+  delete bigTextAtt;
+  // restore original settings
+  enableAttCompression(compressionLevel, compressibleTypes);
+
+
+  //
+  // test replication triggered by non admins
+  //
+
+  // case 1) user triggering the replication is not a DB admin of the target DB
+  var joeUserDoc = CouchDB.prepareUserDoc({
+    name: "joe",
+    roles: ["erlanger"]
+  }, "erly");
+  var usersDb = new CouchDB("test_suite_auth", {"X-Couch-Full-Commit":"false"});
+  var server_config = [
+    {
+      section: "couch_httpd_auth",
+      key: "authentication_db",
+      value: usersDb.name
+    }
+  ];
+
+  docs = makeDocs(1, 6);
+  docs.push({
+    _id: "_design/foo",
+    language: "javascript"
+  });
+
+  dbPairs = [
+    {
+      source: sourceDb.name,
+      target: targetDb.name
+    },
+    {
+      source: CouchDB.protocol + host + "/" + sourceDb.name,
+      target: targetDb.name
+    },
+    {
+      source: sourceDb.name,
+      target: CouchDB.protocol + "joe:erly@" + host + "/" + targetDb.name
+    },
+    {
+      source: CouchDB.protocol + host + "/" + sourceDb.name,
+      target: CouchDB.protocol + "joe:erly@" + host + "/" + targetDb.name
+    }
+  ];
+
+  for (i = 0; i < dbPairs.length; i++) {
+    usersDb.deleteDb();
+    populateDb(sourceDb, docs);
+    populateDb(targetDb, []);
+
+    TEquals(true, targetDb.setSecObj({
+      admins: {
+        names: ["superman"],
+        roles: ["god"]
+      }
+    }).ok);
+
+    run_on_modified_server(server_config, function() {
+      delete joeUserDoc._rev;
+      TEquals(true, usersDb.save(joeUserDoc).ok);
+
+      TEquals(true, CouchDB.login("joe", "erly").ok);
+      TEquals('joe', CouchDB.session().userCtx.name);
+
+      repResult = CouchDB.replicate(dbPairs[i].source, dbPairs[i].target);
+
+      TEquals(true, CouchDB.logout().ok);
+
+      TEquals(true, repResult.ok);
+      TEquals(docs.length, repResult.history[0].docs_read);
+      TEquals((docs.length - 1), repResult.history[0].docs_written); // 1 ddoc
+      TEquals(1, repResult.history[0].doc_write_failures);
+    });
+
+    for (j = 0; j < docs.length; j++) {
+      doc = docs[j];
+      copy = targetDb.open(doc._id);
+
+      if (doc._id.indexOf("_design/") === 0) {
+        TEquals(null, copy);
+      } else {
+        T(copy !== null);
+        TEquals(true, compareObjects(doc, copy));
+      }
+    }
+  }
+
+  // case 2) user triggering the replication is not a reader (nor admin) of the
+  //         source DB
+  dbPairs = [
+    {
+      source: sourceDb.name,
+      target: targetDb.name
+    },
+    {
+      source: CouchDB.protocol + "joe:erly@" + host + "/" + sourceDb.name,
+      target: targetDb.name
+    },
+    {
+      source: sourceDb.name,
+      target: CouchDB.protocol + host + "/" + targetDb.name
+    },
+    {
+      source: CouchDB.protocol + "joe:erly@" + host + "/" + sourceDb.name,
+      target: CouchDB.protocol + host + "/" + targetDb.name
+    }
+  ];
+
+  for (i = 0; i < dbPairs.length; i++) {
+    usersDb.deleteDb();
+    populateDb(sourceDb, docs);
+    populateDb(targetDb, []);
+
+    TEquals(true, sourceDb.setSecObj({
+      admins: {
+        names: ["superman"],
+        roles: ["god"]
+      },
+      readers: {
+        names: ["john"],
+        roles: ["secret"]
+      }
+    }).ok);
+
+    run_on_modified_server(server_config, function() {
+      delete joeUserDoc._rev;
+      TEquals(true, usersDb.save(joeUserDoc).ok);
+
+      TEquals(true, CouchDB.login("joe", "erly").ok);
+      TEquals('joe', CouchDB.session().userCtx.name);
+
+      try {
+        CouchDB.replicate(dbPairs[i].source, dbPairs[i].target);
+        T(false, "should have raised an exception");
+      } catch (x) {
+        TEquals("unauthorized", x.error);
+      }
+
+      TEquals(true, CouchDB.logout().ok);
+    });
+
+    for (j = 0; j < docs.length; j++) {
+      doc = docs[j];
+      copy = targetDb.open(doc._id);
+      TEquals(null, copy);
+    }
+  }
+
+
+  // COUCHDB-885 - push replication of a doc with attachment causes a
+  //               conflict in the target.
+  sourceDb = new CouchDB("test_suite_db_a");
+  targetDb = new CouchDB("test_suite_db_b");
+
+  sourceDb.deleteDb();
+  sourceDb.createDb();
+  targetDb.deleteDb();
+  targetDb.createDb();
+
+  doc = {
+    _id: "doc1"
+  };
+  TEquals(true, sourceDb.save(doc).ok);
+
+  repResult = CouchDB.replicate(
+    sourceDb.name,
+    CouchDB.protocol + host + "/" + targetDb.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, sourceDb.save(doc).ok);
+  repResult = CouchDB.replicate(
+    sourceDb.name,
+    CouchDB.protocol + host + "/" + targetDb.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);
+
+  copy = targetDb.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
+
+  // Test for COUCHDB-1242 (reject non-string query_params)
+  try {
+    CouchDB.replicate(sourceDb, targetDb, {
+      body: {
+        filter : "mydesign/myfilter",
+        query_params : {
+          "maxvalue": 4
+        }
+      }
+    });
+  } catch (e) {
+    TEquals("bad_request", e.error);
+  }
+
+
+  // Test that we can cancel a replication just by POSTing an object
+  // like  {"replication_id": Id, "cancel": true}. The replication ID
+  // can be obtained from a continuous replication request response
+  // (_local_id field), from _active_tasks or from the log
+  populateDb(sourceDb, makeDocs(1, 6));
+  populateDb(targetDb, []);
+
+  repResult = CouchDB.replicate(
+    CouchDB.protocol + host + "/" + sourceDb.name,
+    targetDb.name,
+    {
+      body: {
+        continuous: true,
+        create_target: true
+      }
+    }
+  );
+  TEquals(true, repResult.ok);
+  TEquals('string', typeof repResult._local_id);
+  var repId = repResult._local_id;
+
+  var task = getTask(repId, 3000);
+  T(task != null);
+
+  TEquals(task["replication_id"], repId, "Replication found in _active_tasks");
+  xhr = CouchDB.request(
+    "POST", "/_replicate", {
+      body: JSON.stringify({"replication_id": repId, "cancel": true}),
+      headers: {"Content-Type": "application/json"}
+  });
+  TEquals(200, xhr.status, "Replication cancel request success");
+
+  task = getTask(repId);
+  TEquals(null, task, "Replication was canceled");
+
+  xhr = CouchDB.request(
+    "POST", "/_replicate", {
+      body: JSON.stringify({"replication_id": repId, "cancel": true}),
+      headers: {"Content-Type": "application/json"}
+  });
+  TEquals(404, xhr.status, "2nd replication cancel failed");
+
+  // Non-admin user can not cancel replications triggered by other users
+  var userDoc = CouchDB.prepareUserDoc({
+    name: "tony",
+    roles: ["mafia"]
+  }, "soprano");
+  usersDb = new CouchDB("test_suite_auth", {"X-Couch-Full-Commit":"false"});
+  server_config = [
+    {
+      section: "couch_httpd_auth",
+      key: "authentication_db",
+      value: usersDb.name
+    }
+  ];
+
+  run_on_modified_server(server_config, function() {
+    populateDb(sourceDb, makeDocs(1, 6));
+    populateDb(targetDb, []);
+    TEquals(true, usersDb.save(userDoc).ok);
+
+    repResult = CouchDB.replicate(
+      CouchDB.protocol + host + "/" + sourceDb.name,
+      targetDb.name,
+      {
+        body: {
+          continuous: true
+        }
+      }
+    );
+    TEquals(true, repResult.ok);
+    TEquals('string', typeof repResult._local_id);
+
+    TEquals(true, CouchDB.login("tony", "soprano").ok);
+    TEquals('tony', CouchDB.session().userCtx.name);
+
+    xhr = CouchDB.request(
+      "POST", "/_replicate", {
+        body: JSON.stringify({"replication_id": repResult._local_id, "cancel": true}),
+        headers: {"Content-Type": "application/json"}
+    });
+    TEquals(401, xhr.status, "Unauthorized to cancel replication");
+    TEquals("unauthorized", JSON.parse(xhr.responseText).error);
+
+    TEquals(true, CouchDB.logout().ok);
+
+    xhr = CouchDB.request(
+      "POST", "/_replicate", {
+        body: JSON.stringify({"replication_id": repResult._local_id, "cancel": true}),
+        headers: {"Content-Type": "application/json"}
+    });
+    TEquals(200, xhr.status, "Authorized to cancel replication");
+  });
+
+  // cleanup
+  usersDb.deleteDb();
+  sourceDb.deleteDb();
+  targetDb.deleteDb();
+};


Mime
View raw message