Port old JS Spec tests to Mocha

Changes that happened to jquery.couch.js which were not
reflected by the current failing JSpec Testsuite and
required changes to the testuite:

 - jquery.couch returns the time for the request and not an
   object with an url property
 - jquery.couch does not show alerts any more
 - success callbacks don't return a status any more
 - db.copyDoc expects an docid as target in the first option object,
   not a third parameter with an `Destination` property

COUCHDB-1566
diff --git a/README.md b/README.md
index 3403e2d..5fae08e 100644
--- a/README.md
+++ b/README.md
@@ -10,4 +10,11 @@
 $ bower install
 ```
 
-Open `test/runner.html` in a browser to run the testsuite.
+Enable CORS:
+
+```
+curl -X PUT http://localhost:5984/_config/httpd/enable_cors -d '"true"'
+curl -X PUT http://localhost:5984/_config/cors/origins -d '"*"'
+```
+
+Restart CouchDB & open `test/runner.html` in a browser to run the testsuite.
diff --git a/test/runner.html b/test/runner.html
index 3512877..da5766e 100644
--- a/test/runner.html
+++ b/test/runner.html
@@ -32,6 +32,9 @@
     </script>
     <script src="../jquery.couch.js"></script>
     <script src="test.js"></script>
+    <script src="test_spec_1.js"></script>
+    <script src="test_spec_2.js"></script>
+    <script src="test_spec_3.js"></script>
     <script>
       mocha.checkLeaks();
       mocha.globals(['jQuery']);
diff --git a/test/test.js b/test/test.js
index a3cf33c..a74dfad 100644
--- a/test/test.js
+++ b/test/test.js
@@ -15,7 +15,7 @@
 (function () {
   var assert = chai.assert;
 
-  describe('jquery.couch.js', function () {
+  describe('test.js', function () {
     it('should be an object as a jquery function', function () {
       assert.equal(typeof $.couch, 'object');
     });
diff --git a/test/test_spec_1.js b/test/test_spec_1.js
new file mode 100644
index 0000000..5b7bb64
--- /dev/null
+++ b/test/test_spec_1.js
@@ -0,0 +1,258 @@
+// 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.
+
+// Specs for jquery_couch.js lines 163-209
+
+
+(function () {
+  'use strict';
+
+  var assert = chai.assert;
+
+  function successCallback (resp) {
+    console.log('No error message here unexpectedly, successful response instead.');
+    throw('No error message here unexpectedly, successful response instead.');
+  }
+
+  function errorCallback (status, error, reason) {
+    console.log('Unexpected ' + status + ' error: ' + error + ' - ' + reason);
+    throw('Unexpected ' + status + ' error: ' + error + ' - ' + reason);
+  }
+
+  describe('test_spec_1.js', function () {
+    var db;
+
+    function dropCb (db, done) {
+      db.drop({
+        success: function () {
+          done();
+        },
+        error: function () {
+          done();
+        }
+      });
+    }
+
+    function createCb (db, done) {
+      db.create({
+        success: function () {
+          done();
+        },
+        error: function () {
+          done();
+        }
+      });
+    }
+
+    beforeEach(function () {
+      $.couch.urlPrefix = 'http://localhost:5984';
+      db = $.couch.db('spec_db');
+    });
+
+    describe('constructor', function () {
+      it('should set the name', function () {
+        assert.equal(db.name, 'spec_db');
+      });
+
+      it('should set the uri', function () {
+        assert.equal(db.uri, 'http://localhost:5984/spec_db/');
+      });
+    });
+
+    describe('triggering db functions', function () {
+      beforeEach(function (done) {
+        createCb(db, done);
+      });
+
+      afterEach(function (done) {
+        dropCb(db, done);
+      });
+
+      describe('compact', function () {
+        it('should return ok true', function (done) {
+          db.compact({
+            success: function (resp) {
+              assert.ok(resp.ok);
+              done();
+            },
+            error: errorCallback
+          });
+        });
+
+        it('retuns a request time', function (done) {
+          db.compact({
+            success: function (resp, time) {
+              assert.equal(typeof time, 'number');
+              done();
+            },
+            error: errorCallback
+          });
+        });
+      });
+
+      describe('viewCleanup', function () {
+        it('should return ok true', function (done) {
+          db.viewCleanup({
+            success: function (resp) {
+              assert.ok(resp.ok);
+              done();
+            },
+            error: errorCallback
+          });
+        });
+
+        it('retuns a request time', function (done) {
+          db.viewCleanup({
+            success: function (resp, time) {
+              assert.equal(typeof time, 'number');
+              done();
+            },
+            error: errorCallback
+          });
+        });
+      });
+
+      describe('compactView', function () {
+        beforeEach(function (done) {
+          createCb(db, done);
+          var designDoc = {
+            'views' : {
+              'people' : {
+                'map' : 'function(doc) { emit(doc._id, doc); }'
+              }
+            },
+            '_id' : '_design/myview'
+          };
+          db.saveDoc(designDoc);
+          db.saveDoc({'Name' : 'Felix Gaeta', '_id' : '123'});
+        });
+
+        afterEach(function (done) {
+          dropCb(db, done);
+        });
+
+        it('should return ok true', function (done) {
+          db.compactView('/myview', {
+            success: function (resp) {
+              assert.ok(resp.ok);
+              done();
+            },
+            error: errorCallback
+          });
+        });
+
+        it('retuns a request time', function (done) {
+          db.compactView('/myview', {
+            success: function (resp, time) {
+              assert.equal(typeof time, 'number');
+              done();
+            },
+            error: errorCallback
+          });
+        });
+
+        it('should return raise a 404 error when the design name doesnt exist', function (done) {
+          db.compactView('non_existing_design_name', {
+            error: function (status, error, reason) {
+              assert.equal(status, 404);
+              assert.equal(error, 'not_found');
+              assert.equal(reason, 'missing');
+              done();
+            },
+            success: function (resp) {
+              successCallback(resp);
+            }
+          });
+        });
+      });
+
+      describe('create', function () {
+        beforeEach(function (done) {
+          dropCb(db, done);
+        });
+
+        after(function (done) {
+          dropCb(db, done);
+        });
+
+        it('should return ok true', function (done) {
+          db.create({
+            success: function (resp) {
+              assert.ok(resp.ok);
+              done();
+            },
+            error: function (status, error, reason) {
+              errorCallback(status, error, reason);
+              done();
+            }
+          });
+        });
+
+        it('should result in a created db', function (done) {
+          db.create({
+            success: function () {
+              db.create({
+                error: function (status, error, reason) {
+                  assert.equal(status, 412);
+                  assert.equal(error, 'file_exists');
+                  assert.equal(reason, 'The database could not be created, the file already exists.');
+                  done();
+                },
+                success: function (resp) {
+                  successCallback(resp);
+                }
+              });
+            }
+          });
+        });
+      });
+
+      describe('drop', function () {
+        beforeEach(function (done) {
+          createCb(db, done);
+        });
+
+        after(function (done) {
+          dropCb(db, done);
+        });
+
+        it('should return ok true', function (done) {
+          db.drop({
+            success: function (resp) {
+              assert.ok(resp.ok);
+              done();
+            },
+            error: errorCallback
+          });
+        });
+
+        it('should result in a deleted db', function (done) {
+          db.drop({
+            success: function () {
+              db.drop({
+                error: function (status, error, reason) {
+                  assert.equal(status, 404);
+                  assert.equal(error, 'not_found');
+                  assert.equal(reason, 'missing');
+                  done();
+                },
+                success: function (resp) {
+                  successCallback(resp);
+                }
+              });
+            }
+          });
+        });
+      });
+    });
+  });
+})();
diff --git a/test/test_spec_2.js b/test/test_spec_2.js
new file mode 100644
index 0000000..d8d1f09
--- /dev/null
+++ b/test/test_spec_2.js
@@ -0,0 +1,478 @@
+// 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.
+
+// Specs for jquery_couch.js lines 210-299
+
+
+(function () {
+  'use strict';
+
+  var assert = chai.assert;
+
+  function successCallback (resp) {
+    console.log('No error message here unexpectedly, successful response instead.');
+    throw('No error message here unexpectedly, successful response instead.');
+  }
+
+  function errorCallback (status, error, reason) {
+    console.log('Unexpected ' + status + ' error: ' + error + ' - ' + reason);
+    throw('Unexpected ' + status + ' error: ' + error + ' - ' + reason);
+  }
+
+  describe('test_spec_2.js', function () {
+    var db;
+
+    function dropCb (db, done) {
+      db.drop({
+        success: function () {
+          done();
+        },
+        error: function () {
+          done();
+        }
+      });
+    }
+
+    function createCb (db, done) {
+      db.create({
+        success: function () {
+          done();
+        },
+        error: function () {
+          done();
+        }
+      });
+    }
+
+    beforeEach(function (done) {
+      $.couch.urlPrefix = 'http://localhost:5984';
+      db = $.couch.db('spec_db');
+      createCb(db, done);
+    });
+
+    afterEach(function (done) {
+      dropCb(db, done);
+    });
+
+    describe('info', function () {
+      var res;
+      beforeEach(function (done) {
+        db.info({
+          success: function (resp) {
+            res = resp;
+            done();
+          },
+          error: errorCallback
+        });
+      });
+
+      it('should return the name of the database', function () {
+        assert.equal(res.db_name, 'spec_db');
+      });
+
+      it('should return the number of documents', function () {
+        assert.equal(res.doc_count, 0);
+      });
+
+      it('should return the start time of the db instance', function () {
+        assert.equal(typeof res.instance_start_time, 'string');
+      });
+    });
+
+    describe('allDocs', function () {
+      var res;
+
+      it('should return no docs when there arent any', function (done) {
+        db.allDocs({
+          success: function (resp) {
+            assert.equal(resp.total_rows, 0);
+            assert.deepEqual(resp.rows, []);
+            done();
+          },
+          error: errorCallback
+        });
+      });
+
+      describe('with docs', function () {
+        beforeEach(function (done) {
+          db.saveDoc({'Name': 'Felix Gaeta', '_id': '123'}, {
+            success: function () {
+              db.saveDoc({'Name': 'Samuel T. Anders', '_id': '456'}, {
+                success: function () {
+                  done();
+                }
+              });
+            },
+            error: errorCallback
+          });
+        });
+
+        it('should return all docs', function (done) {
+          db.allDocs({
+            success: function (resp) {
+              assert.equal(resp.total_rows, 2);
+              assert.equal(resp.rows.length, 2);
+              assert.equal(resp.rows[0].id, '123');
+              assert.equal(resp.rows[0].key, '123');
+              assert.ok(resp.rows[0].value.rev.length > 30);
+              assert.equal(resp.rows[1].id, '456');
+              done();
+            },
+            error: errorCallback
+          });
+        });
+
+        it('should pass through the options', function (done) {
+          db.allDocs({
+            'startkey': '123',
+            'limit': '1',
+            success: function (resp) {
+              assert.equal(resp.rows.length, 1);
+              assert.equal(resp.rows[0].id, '123');
+              done();
+            },
+            error: errorCallback
+          });
+        });
+      });
+    });
+
+    describe('allDesignDocs', function () {
+      it('should return nothing when there arent any design docs', function (done) {
+        db.saveDoc({'Name': 'Felix Gaeta', '_id': '123'}, {
+          success: function () {
+            db.allDesignDocs({
+              success: function (resp) {
+                assert.deepEqual(resp.rows, []);
+                done();
+              },
+              error: errorCallback
+            });
+          }
+        });
+      });
+
+      it('should return all design docs', function (done) {
+        var designDoc = {
+          'views' : {
+            'people' : {
+              'map' : 'function(doc) { emit(doc._id, doc); }'
+            }
+          },
+          '_id' : '_design/spec_db'
+        };
+        db.saveDoc(designDoc, {
+          success: cb
+        });
+
+        function cb () {
+          db.saveDoc({'Name': 'Felix Gaeta', '_id': '123'}, {
+            success: function () {
+              db.allDesignDocs({
+                success: function (resp) {
+                  db.allDesignDocs({
+                    success: function (resp) {
+                      assert.equal(resp.total_rows, 2);
+                      assert.equal(resp.rows.length, 1);
+                      assert.equal(resp.rows[0].id, '_design/spec_db');
+                      assert.equal(resp.rows[0].key, '_design/spec_db');
+                      assert.ok(resp.rows[0].value.rev.length > 30);
+                      done();
+                    },
+                    error: errorCallback
+                  });
+                },
+                error: errorCallback
+              });
+            }
+          });
+        }
+      });
+    });
+
+    describe('allApps', function () {
+      it('should provide a custom function with appName, appPath and design' +
+         'document when there is an attachment with index.html', function (done) {
+
+        var designDoc = {'_id' : '_design/with_attachments'};
+
+        designDoc._attachments = {
+          'index.html' : {
+            'content_type': 'text\/html',
+            // this is '<html><p>Hi, here is index!</p></html>', base64 encoded
+            'data': 'PGh0bWw+PHA+SGksIGhlcmUgaXMgaW5kZXghPC9wPjwvaHRtbD4='
+          }
+        };
+        db.saveDoc(designDoc, {
+          success: function () {
+            db.allApps({
+              eachApp: function (appName, appPath, ddoc) {
+                assert.equal(appName, 'with_attachments');
+                assert.equal(appPath, '/spec_db/_design/with_attachments/index.html');
+                assert.equal(ddoc._id, '_design/with_attachments');
+                assert.equal(ddoc._attachments['index.html'].content_type, 'text/html');
+                assert.equal(ddoc._attachments['index.html'].length,
+                  '<html><p>Hi, here is index!</p></html>'.length);
+                done();
+              },
+              error: errorCallback
+            });
+          }
+        });
+      });
+
+      it('should provide a custom function with appName, appPath' +
+         'and design document when there is a couchapp with index file', function (done) {
+
+        var designDoc = {'_id' : '_design/with_index'};
+        designDoc.couchapp = {
+          'index': 'cylon'
+        };
+        db.saveDoc(designDoc, {
+          success: function () {
+            db.allApps({
+              eachApp: function (appName, appPath, ddoc) {
+                assert.equal(appName, 'with_index');
+                assert.equal(appPath, '/spec_db/_design/with_index/cylon');
+                assert.equal(ddoc._id, '_design/with_index');
+                assert.equal(ddoc.couchapp.index, 'cylon');
+                done();
+              },
+              error: errorCallback
+            });
+          }
+        });
+      });
+    });
+
+    describe('openDoc', function () {
+      var doc = {'Name': 'Louanne Katraine', 'Callsign': 'Kat', '_id': '123'};
+      beforeEach(function (done) {
+        db.saveDoc(doc, {
+          success: function () {
+            done();
+          }
+        });
+      });
+
+      it('should open the document', function (done) {
+        db.openDoc('123', {
+          success: function (resp) {
+            assert.deepEqual(resp, doc);
+            done();
+          },
+          error: errorCallback
+        });
+      });
+
+      it('should raise a 404 error when there is no document with' +
+          'the given ID', function (done) {
+        db.openDoc('non_existing', {
+          success: function (status, error, reason) {
+
+          },
+          error: function (status, error, reason) {
+            assert.equal(status, 404);
+            assert.equal(error, 'not_found');
+            assert.equal(reason, 'missing');
+            done();
+          }
+        });
+      });
+
+      it('should pass through the options', function (done) {
+        doc.name = 'Sasha';
+        db.saveDoc(doc, {
+          success: function () {
+            db.openDoc('123', {
+              revs: true,
+              success: function (resp) {
+                assert.equal(resp._revisions.start, 4);
+                assert.equal(resp._revisions.ids.length, 3);
+                done();
+              },
+              error: errorCallback
+            });
+          }
+        });
+      });
+    });
+
+    describe('saveDoc', function () {
+      var doc = {'Name': 'Kara Thrace', 'Callsign': 'Starbuck'};
+
+      it('should save the document and return ok: true', function (done) {
+        db.saveDoc(doc, {
+          success: function (resp) {
+            assert.ok(resp.ok);
+            done();
+          },
+          error: errorCallback
+        });
+      });
+
+      it('should return ID and revision of the document', function (done) {
+        db.saveDoc(doc, {
+          success: function (resp) {
+            assert.equal(typeof resp.id, 'string');
+            assert.equal(typeof resp.rev, 'string');
+            assert.ok(resp.rev.length > 30);
+            done();
+          },
+          error: errorCallback
+        });
+      });
+
+      it('should result in a saved document with generated ID', function (done) {
+        db.saveDoc(doc, {
+          success: function (resp) {
+            db.openDoc(resp.id, {
+              success: function (resp2) {
+                assert.equal(resp2.Name, 'Kara Thrace');
+                assert.equal(resp2.Callsign, 'Starbuck');
+                done();
+              },
+              error: errorCallback
+            });
+          },
+          error: errorCallback
+        });
+      });
+
+      it('should save the document with the specified ID', function (done) {
+        doc._id = '123';
+        db.saveDoc(doc, {
+          success: function (resp) {
+            assert.equal(resp.id, '123');
+            done();
+          },
+          error: errorCallback
+        });
+      });
+    });
+
+    describe('bulkSave', function () {
+      var doc  = {'Name': 'Kara Thrace', 'Callsign': 'Starbuck'},
+          doc2 = {'Name': 'Karl C. Agathon', 'Callsign': 'Helo'},
+          doc3 = {'Name': 'Sharon Valerii', 'Callsign': 'Boomer'},
+          docs = [doc, doc2, doc3];
+
+      it('should save all documents', function (done) {
+        db.bulkSave({'docs': docs}, {
+          success: function (resp) {
+            db.allDocs({
+              success: function (resp) {
+                assert.equal(resp.total_rows, 3);
+                done();
+              },
+              error: errorCallback
+            });
+          },
+          error: errorCallback
+        });
+      });
+
+      it('should result in saved documents', function (done) {
+        doc3._id = '789';
+        db.bulkSave({'docs': docs}, {
+          success: function (resp) {
+            db.openDoc('789', {
+              success: function (resp) {
+                assert.equal(resp.Name, 'Sharon Valerii');
+                assert.equal(resp.Callsign, 'Boomer');
+                done();
+              },
+              error: errorCallback
+            });
+          },
+          error: errorCallback
+        });
+      });
+
+      it('should return ID and revision of the documents', function (done) {
+        db.bulkSave({'docs': docs}, {
+          success: function (resp) {
+            assert.equal(typeof resp[0].id, 'string');
+            assert.equal(typeof resp[1].id, 'string');
+            assert.equal(typeof resp[2].id, 'string');
+
+            assert.equal(typeof resp[0].rev, 'string');
+            assert.equal(typeof resp[1].rev, 'string');
+            assert.equal(typeof resp[2].rev, 'string');
+
+            assert.ok(resp[0].rev.length > 30);
+            assert.ok(resp[1].rev.length > 30);
+            assert.ok(resp[2].rev.length > 30);
+            done();
+          },
+          error: errorCallback
+        });
+      });
+
+      it('should return ID and revision of the documents', function (done) {
+        doc._id  = '123';
+        doc2._id = '456';
+        docs = [doc, doc2, doc3];
+
+        db.bulkSave({'docs': docs}, {
+          success: function (resp) {
+            assert.equal(resp[0].id, '123');
+            assert.equal(resp[1].id, '456');
+            done();
+          },
+          error: errorCallback
+        });
+      });
+
+      it('should pass through the options', function (done) {
+        // a lengthy way to test that a conflict can't be created with the
+        // all_or_nothing option set to false, but can be when it's true.
+
+        var oldDoc = {'Name': 'Louanne Katraine', 'Callsign': 'Kat', '_id': '123'};
+        var newDoc = {'Name': 'Sasha', 'Callsign': 'Kat', '_id': '123'};
+        db.saveDoc(oldDoc, {
+          success: function (resp) {
+            oldDoc._rev = resp.rev;
+            db.bulkSave({'docs': [newDoc], 'all_or_nothing': false}, {
+              success: function (resp) {
+                assert.equal(resp[0].id, '123');
+                assert.equal(resp[0].error, 'conflict');
+                assert.equal(resp[0].reason, 'Document update conflict.');
+                next();
+              },
+              error: errorCallback
+            });
+          },
+          error: errorCallback
+        });
+
+        function next () {
+          db.bulkSave({'docs': [newDoc], 'all_or_nothing': true}, {
+            success: function (resp) {
+              assert.equal(resp[0].id, '123');
+              assert.notEqual(resp[0].rev, oldDoc._rev);
+              db.openDoc('123', {
+                'conflicts': true,
+                success: function (resp) {
+                  assert.equal(resp._conflicts[0], oldDoc._rev);
+                  done();
+                },
+                error: errorCallback
+              });
+            },
+            error: errorCallback
+          });
+        }
+      });
+    });
+  });
+})();
diff --git a/test/test_spec_3.js b/test/test_spec_3.js
new file mode 100644
index 0000000..652a5c2
--- /dev/null
+++ b/test/test_spec_3.js
@@ -0,0 +1,626 @@
+// 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.
+
+// Specs for jquery_couch.js lines 300-411
+
+
+(function () {
+  'use strict';
+
+  var assert = chai.assert;
+
+  function successCallback (resp) {
+    console.log('No error message here unexpectedly, successful response instead.');
+    throw('No error message here unexpectedly, successful response instead.');
+  }
+
+  function errorCallback (status, error, reason) {
+    console.log('Unexpected ' + status + ' error: ' + error + ' - ' + reason);
+    throw('Unexpected ' + status + ' error: ' + error + ' - ' + reason);
+  }
+
+  describe('test_spec_3.js', function () {
+    var db;
+
+    function dropCb (db, done) {
+      db.drop({
+        success: function () {
+          done();
+        },
+        error: function () {
+          done();
+        }
+      });
+    }
+
+    function createCb (db, done) {
+      db.create({
+        success: function () {
+          done();
+        },
+        error: function () {
+          done();
+        }
+      });
+    }
+
+    beforeEach(function (done) {
+      $.couch.urlPrefix = 'http://localhost:5984';
+      db = $.couch.db('spec_db');
+      createCb(db, done);
+    });
+
+    afterEach(function (done) {
+      dropCb(db, done);
+    });
+
+    describe('removeDoc', function () {
+      var doc = {'Name': 'Louanne Katraine', 'Callsign': 'Kat', '_id': '345'},
+          savedDoc;
+
+      beforeEach(function (done) {
+        db.saveDoc(doc, {
+          success: function (resp) {
+            savedDoc = resp;
+            done();
+          },
+          error: errorCallback
+        });
+      });
+
+      it('should result in a deleted document', function (done) {
+        db.removeDoc({_id : '345', _rev: savedDoc.rev}, {
+          success: function (resp) {
+            db.openDoc('345', {
+              error: function (status, error, reason) {
+                assert.equal(status, 404);
+                assert.equal(error, 'not_found');
+                assert.equal(reason, 'deleted');
+                done();
+              },
+              success: successCallback
+            });
+          },
+          error: errorCallback
+        });
+      });
+
+      it('should return ok true, the ID and the revision of the deleted document', function (done) {
+        db.removeDoc({_id : '345', _rev: savedDoc.rev}, {
+          success: function (resp) {
+            assert.ok(resp.ok);
+            assert.equal(resp.id, '345');
+            assert.equal(typeof resp.rev, 'string');
+            done();
+          },
+          error: errorCallback
+        });
+      });
+
+      it('should record the revision in the deleted document', function (done) {
+        db.removeDoc({_id : '345', _rev: savedDoc.rev}, {
+          success: function (resp) {
+            db.openDoc('345', {
+              rev: resp.rev,
+              success: function (resp2) {
+                assert.equal(resp2._id, resp.id);
+                assert.equal(resp2._rev, resp.rev);
+                assert.ok(resp2._deleted);
+                done();
+              },
+              error: errorCallback
+            });
+          },
+          error: errorCallback
+        });
+      });
+    });
+
+    describe('bulkRemove', function () {
+      var doc, doc2, doc3, docs;
+
+      beforeEach(function (done) {
+        doc  = {'Name': 'Kara Thrace', 'Callsign': 'Starbuck', '_id': '123'};
+        doc2 = {'Name': 'Karl C. Agathon', 'Callsign': 'Helo', '_id': '456'};
+        doc3 = {'Name': 'Sharon Valerii', 'Callsign': 'Boomer', '_id': '789'};
+        docs = [doc, doc2, doc3];
+
+        db.bulkSave({'docs': docs}, {
+          ensure_full_commit: true,
+          success: function (resp) {
+            for (var i = 0; i < docs.length; i++) {
+              docs[i]._rev = resp[i].rev;
+            }
+            done();
+          },
+          error: errorCallback
+        });
+      });
+
+      it('should remove all documents specified', function (done) {
+        db.bulkRemove({'docs': docs}, {
+          ensure_full_commit: true,
+          success: function () {
+            db.allDocs({
+              success: function (resp) {
+                assert.equal(resp.total_rows, 0);
+                done();
+              },
+              error: errorCallback
+            });
+          },
+          error: errorCallback
+        });
+      });
+
+      it('should not remove documents that should not have been deleted', function (done) {
+        db.bulkRemove({'docs': [doc3]}, {
+          success: function (resp) {
+            db.allDocs({
+              success: function (resp) {
+                assert.equal(resp.total_rows, 2);
+                done();
+              },
+              error: errorCallback
+            });
+          },
+          error: errorCallback
+        });
+      });
+
+      it('should result in deleted documents', function (done) {
+        db.bulkRemove({'docs': docs}, {
+          success: function (resp) {
+            db.openDoc('123', {
+              error: function (status, error, reason) {
+                assert.equal(status, 404);
+                assert.equal(error, 'not_found');
+                assert.equal(reason, 'deleted');
+                done();
+              },
+              success: successCallback
+            });
+          },
+          error: errorCallback
+        });
+      });
+
+      it('should return the ID and the revision of the deleted documents', function (done) {
+        db.bulkRemove({'docs': docs}, {
+          success: function (resp) {
+            assert.equal(resp[0].id, '123');
+            assert.equal(resp[1].id, '456');
+            assert.equal(resp[2].id, '789');
+
+            assert.equal(typeof resp[0].rev, 'string');
+            assert.equal(typeof resp[1].rev, 'string');
+            assert.equal(typeof resp[2].rev, 'string');
+
+            assert.ok(resp[0].rev.length > 30);
+            assert.ok(resp[1].rev.length > 30);
+            assert.ok(resp[2].rev.length > 30);
+            done();
+          },
+          error: errorCallback
+        });
+      });
+
+      it('should record the revision in the deleted documents', function (done) {
+        db.bulkRemove({'docs': docs}, {
+          success: function (resp) {
+            db.openDoc('123', {
+              rev: resp[0].rev,
+              success: function (resp2) {
+                assert.equal(resp2._rev, resp[0].rev);
+                assert.equal(resp2._id, resp[0].id);
+                assert.ok(resp2._deleted);
+                done();
+              },
+              error: errorCallback
+            });
+          },
+          error: errorCallback
+        });
+      });
+    });
+
+    describe('copyDoc', function () {
+      var doc;
+      beforeEach(function (done) {
+        doc = {'Name': 'Sharon Agathon', 'Callsign': 'Athena', '_id': '123'};
+        db.saveDoc(doc, {
+          success: function () {
+            done();
+          },
+          error: errorCallback
+        });
+      });
+
+      it('should result in another document with same data and new id', function (done) {
+        db.copyDoc('123', {
+          docid: '456',
+          success: function (resp) {
+            assert.equal(resp.id, '456');
+            assert.ok(resp.rev.length > 30);
+            db.openDoc('456', {
+              success: function (resp) {
+                assert.equal(resp.Name, 'Sharon Agathon');
+                assert.equal(resp.Callsign, 'Athena');
+                done();
+              },
+              error: errorCallback
+            });
+          },
+          error: errorCallback
+        });
+      });
+
+      it('should throw an error when trying to overwrite a document' +
+          'without providing a revision', function (done) {
+        var doc2 = {'Name': 'Louanne Katraine', 'Callsign': 'Kat', '_id': '456'};
+        db.saveDoc(doc2, {
+          success: function (resp) {
+            db.copyDoc('123', {
+              docid: '456',
+              error: function (status, error, reason) {
+                assert.equal(status, 409);
+                assert.equal(error, 'conflict');
+                assert.equal(reason, 'Document update conflict.');
+                done();
+              },
+              success: successCallback
+            });
+          },
+          error: errorCallback
+        });
+      });
+
+      it('should overwrite a document with the correct revision', function (done) {
+        var doc2 = {'Name': 'Louanne Katraine', 'Callsign': 'Kat', '_id': '456'},
+            doc2Rev;
+
+        db.saveDoc(doc2, {
+          success: function (resp) {
+            doc2Rev = resp.rev;
+            next();
+          },
+          error: errorCallback
+        });
+
+        function next () {
+          db.copyDoc('123', {
+            docid: 456,
+            rev: doc2Rev,
+            success: function (resp) {
+              assert.equal(resp.id, '456');
+              assert.ok(resp.rev.length > 30);
+              db.openDoc('456', {
+                success: function (resp) {
+                  assert.equal(resp.Name, 'Sharon Agathon');
+                  assert.equal(resp.Callsign, 'Athena');
+                  done();
+                },
+                error: errorCallback
+              });
+            },
+            error: errorCallback
+          });
+        }
+      });
+    });
+
+    describe('query', function () {
+      var docs, mapFunction, reduceFunction;
+      beforeEach(function (done) {
+        docs = [
+          {'Name': 'Cally Tyrol', 'job': 'deckhand', '_id': '789'},
+          {'Name': 'Felix Gaeta', 'job': 'officer', '_id': '123'},
+          {'Name': 'Samuel T. Anders', 'job': 'pilot', '_id': '456'},
+        ];
+        mapFunction = 'function (doc) { emit(doc._id, 1); }';
+        reduceFunction = 'function (key, values, rereduce) { return sum(values); }';
+
+        db.bulkSave({'docs': docs}, {
+          ensure_full_commit: true,
+          success: function (resp) {
+            done();
+          },
+          error: errorCallback
+        });
+      });
+
+      it('should apply the map function', function (done) {
+        db.query(mapFunction, null, null, {
+          success: function (resp) {
+            assert.equal(resp.rows.length, 3);
+
+            assert.equal(resp.rows[0].id, '123');
+            assert.equal(resp.rows[0].key, '123');
+            assert.equal(resp.rows[0].value, 1);
+
+            assert.equal(resp.rows[1].id, '456');
+            assert.equal(resp.rows[1].key, '456');
+            assert.equal(resp.rows[1].value, 1);
+
+            assert.equal(resp.rows[2].id, '789');
+            assert.equal(resp.rows[2].key, '789');
+            assert.equal(resp.rows[2].value, 1);
+
+            done();
+          },
+          error: errorCallback
+        });
+      });
+
+      it('should apply the reduct function', function (done) {
+        db.query(mapFunction, reduceFunction, null, {
+          success: function (resp) {
+            assert.equal(resp.rows.length, 1);
+            assert.equal(resp.rows[0].key, null);
+            assert.equal(resp.rows[0].value, 3);
+
+            done();
+          },
+          error: errorCallback
+        });
+      });
+
+      it('should pass through the options', function (done) {
+        db.query(mapFunction, null, null, {
+          startkey: '456',
+          success: function (resp) {
+            assert.equal(resp.rows.length, 2);
+
+            assert.equal(resp.rows[0].id, '456');
+            assert.equal(resp.rows[0].key, '456');
+            assert.equal(resp.rows[0].value, 1);
+
+            assert.equal(resp.rows[1].id, '789');
+            assert.equal(resp.rows[1].key, '789');
+            assert.equal(resp.rows[1].value, 1);
+
+            done();
+          },
+          error: errorCallback
+        });
+      });
+
+      it('should pass through the keys', function (done) {
+        db.query(mapFunction, null, null, {
+          keys: ['456', '123'],
+          success: function (resp) {
+            assert.equal(resp.rows.length, 2);
+
+            assert.equal(resp.rows[0].id, '456');
+            assert.equal(resp.rows[0].key, '456');
+            assert.equal(resp.rows[0].value, 1);
+
+            assert.equal(resp.rows[1].id, '123');
+            assert.equal(resp.rows[1].key, '123');
+            assert.equal(resp.rows[1].value, 1);
+
+            done();
+          },
+          error: errorCallback
+        });
+      });
+
+      it('should pass through the options and keys', function (done) {
+        db.query(mapFunction, null, null, {
+          keys: ['456'],
+          include_docs: true,
+          success: function (resp) {
+            assert.equal(resp.rows.length, 1);
+
+            assert.equal(resp.rows[0].id, '456');
+            assert.equal(resp.rows[0].key, '456');
+            assert.equal(resp.rows[0].value, 1);
+
+            assert.equal(resp.rows[0].doc.job, 'pilot');
+            assert.ok(resp.rows[0].doc._rev.length > 30);
+
+            done();
+          },
+          error: errorCallback
+        });
+      });
+    });
+    describe('view', function () {
+      var docs, view;
+      beforeEach(function (done) {
+        docs = [
+          {'Name': 'Cally Tyrol', 'job': 'deckhand', '_id': '789'},
+          {'Name': 'Felix Gaeta', 'job': 'officer', '_id': '123'},
+          {'Name': 'Samuel T. Anders', 'job': 'pilot', '_id': '456'}
+        ];
+        view = {
+          'views': {
+            'people': {
+              'map': 'function (doc) { emit(doc._id, doc.Name); }'
+            }
+          },
+          '_id': '_design/spec_db'
+        };
+
+        db.bulkSave({'docs': docs}, {
+          success: function (resp) {
+            db.saveDoc(view, {
+              success: function () {
+                done();
+              },
+              error: errorCallback
+            });
+
+          },
+          error: errorCallback
+        });
+      });
+
+      it('should apply the view', function (done) {
+        db.view('spec_db/people', {
+          success: function (resp) {
+            assert.equal(resp.rows.length, 3);
+
+            assert.equal(resp.rows[0].id, '123');
+            assert.equal(resp.rows[0].key, '123');
+            assert.equal(resp.rows[0].value, 'Felix Gaeta');
+
+            assert.equal(resp.rows[1].id, '456');
+            assert.equal(resp.rows[1].key, '456');
+            assert.equal(resp.rows[1].value, 'Samuel T. Anders');
+
+            assert.equal(resp.rows[2].id, '789');
+            assert.equal(resp.rows[2].key, '789');
+            assert.equal(resp.rows[2].value, 'Cally Tyrol');
+            done();
+          },
+          error: errorCallback
+        });
+      });
+
+      it('should pass through the options', function (done) {
+        db.view('spec_db/people', {
+          skip: 2,
+          success: function (resp) {
+            assert.equal(resp.rows.length, 1);
+            assert.equal(resp.rows[0].id, '789');
+            assert.equal(resp.rows[0].key, '789');
+            assert.equal(resp.rows[0].value, 'Cally Tyrol');
+            done();
+          },
+          error: errorCallback
+        });
+      });
+
+      it('should pass through the keys', function (done) {
+        db.view('spec_db/people', {
+          keys: ['456', '123'],
+          success: function (resp) {
+            assert.equal(resp.rows[0].id, '456');
+            assert.equal(resp.rows[0].key, '456');
+            assert.equal(resp.rows[0].value, 'Samuel T. Anders');
+            assert.equal(resp.rows[1].id, '123');
+            assert.equal(resp.rows[1].key, '123');
+            assert.equal(resp.rows[1].value, 'Felix Gaeta');
+            done();
+          },
+          error: errorCallback
+        });
+      });
+
+      it('should pass through the keys', function (done) {
+        db.view('spec_db/people', {
+          keys: ['456'],
+          include_docs: true,
+          success: function (resp) {
+            assert.equal(resp.rows.length, 1);
+
+            assert.equal(resp.rows[0].id, '456');
+            assert.equal(resp.rows[0].key, '456');
+            assert.equal(resp.rows[0].value, 'Samuel T. Anders');
+
+            assert.equal(resp.rows[0].doc.job, 'pilot');
+            assert.ok(resp.rows[0].doc._rev.length > 30);
+            done();
+          },
+          error: errorCallback
+        });
+      });
+
+      it('should throw a 404 when the view doesnt exist', function (done) {
+        db.view('spec_db/non_existing_view', {
+          keys: ['456'],
+          include_docs: true,
+          error: function (status, error, reason) {
+            assert.equal(status, 404);
+            assert.equal(error, 'not_found');
+            assert.equal(reason, 'missing_named_view');
+            done();
+          },
+          success: successCallback
+        });
+      });
+    });
+
+    describe('setDbProperty', function () {
+      it('should return ok true', function (done) {
+        db.setDbProperty('_revs_limit', 1500, {
+          success: function (resp) {
+            assert.ok(resp.ok);
+            done();
+          },
+          error: errorCallback
+        });
+      });
+
+      it('should set a db property', function (done) {
+        db.setDbProperty('_revs_limit', 1500, {
+          success: function (resp) {
+            db.getDbProperty('_revs_limit', {
+              success: function (resp) {
+                assert.equal(resp, 1500);
+                next();
+              },
+              error: errorCallback
+            });
+          },
+          error: errorCallback
+        });
+
+        function next () {
+          db.setDbProperty('_revs_limit', 1200, {
+            success: function () {
+              db.getDbProperty('_revs_limit', {
+                success: function (resp) {
+                  assert.equal(resp, 1200);
+                  done();
+                },
+                error: errorCallback
+              });
+            },
+            error: errorCallback
+          });
+        }
+      });
+    });
+
+    describe('getDbProperty', function () {
+      it('should get a db property', function (done) {
+        db.setDbProperty('_revs_limit', 1337, {
+          success: function (resp) {
+            db.getDbProperty('_revs_limit', {
+              success: function (resp) {
+                assert.equal(resp, 1337);
+                done();
+              },
+              error: errorCallback
+            });
+          },
+          error: errorCallback
+        });
+      });
+
+      it('should throw a 404 when the property doesnt exist', function (done) {
+        db.getDbProperty('_doesnt_exist', {
+          success: successCallback,
+          error: function (status, error, reason) {
+            assert.equal(status, 404);
+            assert.equal(error, 'not_found');
+            assert.equal(reason, 'missing');
+            done();
+          }
+        });
+      });
+    });
+
+  });
+})();