(#4071) - remove http dead code and better tests
diff --git a/lib/adapters/http/index.js b/lib/adapters/http/index.js
index eda4bbd..de58de6 100644
--- a/lib/adapters/http/index.js
+++ b/lib/adapters/http/index.js
@@ -23,6 +23,7 @@
 var log = require('debug')('pouchdb:http');
 var createMultipart = require('../../deps/ajax/multipart');
 var blufferToBase64 = require('../../deps/binary/blobOrBufferToBase64');
+var parseDoc = require('../../deps/docs/parseDoc');
 
 function readAttachmentsAsBlobOrBuffer(row) {
   var atts = row.doc && row.doc._attachments;
@@ -63,51 +64,40 @@
 // Get all the information you possibly can about the URI given by name and
 // return it as a suitable object.
 function getHost(name, opts) {
-  // If the given name contains "http:"
-  if (/http(s?):/.test(name)) {
-    // Prase the URI into all its little bits
-    var uri = utils.parseUri(name);
+  // Prase the URI into all its little bits
+  var uri = utils.parseUri(name);
 
-    // Store the fact that it is a remote URI
-    uri.remote = true;
-
-    // Store the user and password as a separate auth object
-    if (uri.user || uri.password) {
-      uri.auth = {username: uri.user, password: uri.password};
-    }
-
-    // Split the path part of the URI into parts using '/' as the delimiter
-    // after removing any leading '/' and any trailing '/'
-    var parts = uri.path.replace(/(^\/|\/$)/g, '').split('/');
-
-    // Store the first part as the database name and remove it from the parts
-    // array
-    uri.db = parts.pop();
-
-    // Restore the path by joining all the remaining parts (all the parts
-    // except for the database name) with '/'s
-    uri.path = parts.join('/');
-    opts = opts || {};
-    opts = clone(opts);
-    uri.headers = opts.headers || (opts.ajax && opts.ajax.headers) || {};
-
-    if (opts.auth || uri.auth) {
-      var nAuth = opts.auth || uri.auth;
-      var token = btoa(nAuth.username + ':' + nAuth.password);
-      uri.headers.Authorization = 'Basic ' + token;
-    }
-
-    if (opts.headers) {
-      uri.headers = opts.headers;
-    }
-
-    return uri;
+  // Store the user and password as a separate auth object
+  if (uri.user || uri.password) {
+    uri.auth = {username: uri.user, password: uri.password};
   }
 
-  // If the given name does not contain 'http:' then return a very basic object
-  // with no host, the current path, the given name as the database name and no
-  // username/password
-  return {host: '', path: '/', db: name, auth: false};
+  // Split the path part of the URI into parts using '/' as the delimiter
+  // after removing any leading '/' and any trailing '/'
+  var parts = uri.path.replace(/(^\/|\/$)/g, '').split('/');
+
+  // Store the first part as the database name and remove it from the parts
+  // array
+  uri.db = parts.pop();
+
+  // Restore the path by joining all the remaining parts (all the parts
+  // except for the database name) with '/'s
+  uri.path = parts.join('/');
+  opts = opts || {};
+  opts = clone(opts);
+  uri.headers = opts.headers || (opts.ajax && opts.ajax.headers) || {};
+
+  if (opts.auth || uri.auth) {
+    var nAuth = opts.auth || uri.auth;
+    var token = btoa(nAuth.username + ':' + nAuth.password);
+    uri.headers.Authorization = 'Basic ' + token;
+  }
+
+  if (opts.headers) {
+    uri.headers = opts.headers;
+  }
+
+  return uri;
 }
 
 // Generate a URL with the host data given by opts and the given path
@@ -117,28 +107,31 @@
 
 // Generate a URL with the host data given by opts and the given path
 function genUrl(opts, path) {
-  if (opts.remote) {
-    // If the host already has a path, then we need to have a path delimiter
-    // Otherwise, the path delimiter is the empty string
-    var pathDel = !opts.path ? '' : '/';
+  // If the host already has a path, then we need to have a path delimiter
+  // Otherwise, the path delimiter is the empty string
+  var pathDel = !opts.path ? '' : '/';
 
-    // If the host already has a path, then we need to have a path delimiter
-    // Otherwise, the path delimiter is the empty string
-    return opts.protocol + '://' + opts.host + ':' + opts.port + '/' +
-           opts.path + pathDel + path;
-  }
-
-  return '/' + path;
+  // If the host already has a path, then we need to have a path delimiter
+  // Otherwise, the path delimiter is the empty string
+  return opts.protocol + '://' + opts.host + ':' + opts.port + '/' +
+         opts.path + pathDel + path;
 }
 
 // Implements the PouchDB API for dealing with CouchDB instances over HTTP
 function HttpPouch(opts, callback) {
   // The functions that will be publicly available for HttpPouch
   var api = this;
-  api.getHost = opts.getHost ? opts.getHost : getHost;
 
   // Parse the URI given by opts.name into an easy-to-use object
-  var host = api.getHost(opts.name, opts);
+  var getHostFun = getHost;
+
+  // TODO: this seems to only be used by yarong for the Thali project.
+  // Verify whether or not it's still needed.
+  /* istanbul ignore:next */
+  if (opts.getHost) {
+    getHostFun = opts.getHost;
+  }
+  var host = getHostFun(opts.name, opts);
 
   // Generate the database URL based on the host
   var dbUrl = genDBUrl(host, '');
@@ -173,6 +166,7 @@
       url: dbUrl
     }, function (err) {
       // If we get an "Unauthorized" error
+      /* istanbul ignore else */
       if (err && err.status === 401) {
         // Test if the database already exists
         ajax({headers: clone(host.headers), method: 'HEAD', url: dbUrl},
@@ -231,11 +225,11 @@
       method: 'GET',
       url: genUrl(host, '')
     }, function (err, result) {
+      /* istanbul ignore next */
       if (err) {
         return callback(err);
       }
-      var uuid = (result && result.uuid) ?
-        result.uuid + host.db : genDBUrl(host, '');
+      var uuid = result.uuid + host.db;
       callback(null, uuid);
     });
   });
@@ -269,9 +263,7 @@
         });
       }
       // Ping the http if it's finished compaction
-      if (typeof callback === "function") {
-        ping();
-      }
+      ping();
     });
   });
 
@@ -284,12 +276,12 @@
       method: 'GET',
       url: genDBUrl(host, '')
     }, function (err, res) {
+      /* istanbul ignore next */
       if (err) {
-        callback(err);
-      } else {
-        res.host = genDBUrl(host, '');
-        callback(null, res);
+        return callback(err);
       }
+      res.host = genDBUrl(host, '');
+      callback(null, res);
     });
   };
 
@@ -501,11 +493,6 @@
       blob = rev;
       rev = null;
     }
-    if (typeof type === 'undefined') {
-      type = blob;
-      blob = rev;
-      rev = null;
-    }
     var id = encodeDocId(docId) + '/' + encodeAttachmentId(attachmentId);
     var url = genDBUrl(host, id);
     if (rev) {
@@ -567,10 +554,10 @@
         }
       }
       opts = opts || {};
-      var error = utils.invalidIdError(doc._id);
-      if (error) {
-        throw error;
-      }
+
+      // check for any errors
+      // TODO: rename this function
+      parseDoc.invalidIdError(doc._id);
 
       // List of parameter to add to the PUT request
       var params = [];
@@ -646,14 +633,10 @@
   // Update/create multiple documents given by req in the database
   // given by host.
   api._bulkDocs = function (req, opts, callback) {
-    // If opts.new_edits exists add it to the document data to be
-    // send to the database.
     // If new_edits=false then it prevents the database from creating
     // new revision numbers for the documents. Instead it just uses
     // the old ones. This is used in database replication.
-    if (typeof opts.new_edits !== 'undefined') {
-      req.new_edits = opts.new_edits;
-    }
+    req.new_edits = opts.new_edits;
 
     Promise.all(req.docs.map(preprocessAttachments)).then(function () {
       // Update/create the documents
@@ -796,9 +779,6 @@
     // an ok timeout
     var params = { timeout: opts.timeout - (5 * 1000) };
     var limit = (typeof opts.limit !== 'undefined') ? opts.limit : false;
-    if (limit === 0) {
-      limit = 1;
-    }
     var returnDocs;
     if ('returnDocs' in opts) {
       returnDocs = opts.returnDocs;
@@ -845,6 +825,7 @@
     // These parameters may be used by the filter on the source database.
     if (opts.query_params && typeof opts.query_params === 'object') {
       for (var param_name in opts.query_params) {
+        /* istanbul ignore else */
         if (opts.query_params.hasOwnProperty(param_name)) {
           params[param_name] = opts.query_params[param_name];
         }
@@ -884,6 +865,8 @@
         return;
       }
       params.since = since;
+      // "since" can be any kind of json object in Coudant/CouchDB 2.x
+      /* istanbul ignore next */
       if (typeof params.since === "object") {
         params.since = JSON.stringify(params.since);
       }
@@ -1095,14 +1078,14 @@
       method: 'DELETE',
       headers: clone(host.headers)
     }, function (err, resp) {
+      /* istanbul ignore next */
       if (err) {
         api.emit('error', err);
-        callback(err);
-      } else {
-        api.emit('destroyed');
-        api.constructor.emit('destroyed', opts.name);
-        callback(null, resp);
+        return callback(err);
       }
+      api.emit('destroyed');
+      api.constructor.emit('destroyed', opts.name);
+      callback(null, resp);
     });
   };
 }
diff --git a/lib/deps/docs/updateDoc.js b/lib/deps/docs/updateDoc.js
index 06dfe0c..c3c50a6 100644
--- a/lib/deps/docs/updateDoc.js
+++ b/lib/deps/docs/updateDoc.js
@@ -50,8 +50,9 @@
 
   var newRev = docInfo.metadata.rev;
   docInfo.metadata.rev_tree = merged.tree;
+  /* istanbul ignore else */
   if (prev.rev_map) {
-    docInfo.metadata.rev_map = prev.rev_map; // used by leveldb
+    docInfo.metadata.rev_map = prev.rev_map; // used only by leveldb
   }
 
   // recalculate
diff --git a/tests/integration/test.attachments.js b/tests/integration/test.attachments.js
index c2cd4ca..fa6296a 100644
--- a/tests/integration/test.attachments.js
+++ b/tests/integration/test.attachments.js
@@ -85,6 +85,43 @@
       }
     };
 
+    it('fetch atts with open_revs and missing', function () {
+      var db = new PouchDB(dbs.name);
+      var doc = {
+        _id: 'frog',
+        _rev: '1-x',
+        _revisions: {
+          start: 1,
+          ids: ['x']
+        },
+        _attachments: {
+          'foo.txt': {
+            content_type: 'text/plain',
+            data: ''
+          }
+        }
+      };
+      return db.bulkDocs({
+        docs: [doc],
+        new_edits: false
+      }).then(function () {
+        return db.get('frog', {
+          revs: true,
+          open_revs: ['1-x', '2-fake'],
+          attachments: true
+        });
+      }).then(function (res) {
+        // there should be exactly one "ok" result
+        // and one result with attachments
+        res.filter(function (x) {
+          return x.ok;
+        }).should.have.length(1);
+        res.filter(function (x) {
+          return x.ok && x.ok._attachments;
+        }).should.have.length(1);
+      });
+    });
+
     it('issue 2803 should throw 412', function () {
       var db = new PouchDB(dbs.name);
       return db.put(binAttDoc).then(function () {
diff --git a/tests/integration/test.basics.js b/tests/integration/test.basics.js
index 2236e02..cfcfac0 100644
--- a/tests/integration/test.basics.js
+++ b/tests/integration/test.basics.js
@@ -77,6 +77,14 @@
       }, done);
     });
 
+    it('Add a doc with opts object', function (done) {
+      var db = new PouchDB(dbs.name);
+      db.post({test: 'somestuff'}, {}, function (err, info) {
+        should.not.exist(err);
+        done();
+      });
+    });
+
     it('Modify a doc', function (done) {
       var db = new PouchDB(dbs.name);
       db.post({test: 'somestuff'}, function (err, info) {
diff --git a/tests/integration/test.compaction.js b/tests/integration/test.compaction.js
index 21c7070..2cd037c 100644
--- a/tests/integration/test.compaction.js
+++ b/tests/integration/test.compaction.js
@@ -30,6 +30,13 @@
       });
     });
 
+    it('compact with options object', function () {
+      var db = new PouchDB(dbs.name);
+      return db.compact({}).then(function (result) {
+        result.should.eql({ok: true});
+      });
+    });
+
     it('#2913 massively parallel compaction', function () {
       var db = new PouchDB(dbs.name);
       var tasks = [];
diff --git a/tests/integration/test.http.js b/tests/integration/test.http.js
index 56b0e2f..d69333c 100644
--- a/tests/integration/test.http.js
+++ b/tests/integration/test.http.js
@@ -122,4 +122,72 @@
 
     PouchDB.utils.ajax = ajax;
   });
+
+  it('Test unauthorized user', function () {
+    var db = new PouchDB(dbs.name, {
+      auth: {
+        user: 'foo',
+        password: 'bar'
+      }
+    });
+    return db.info().then(function () {
+      if (testUtils.isExpressRouter()) {
+        return; // express-router doesn't do auth
+      }
+      throw new Error('expected an error');
+    }, function (err) {
+      should.exist(err); // 401 error
+    });
+  });
+
+  it('Test unauthorized user, user/pass in url itself', function () {
+    var dbname = dbs.name.replace(/\/\//, '//foo:bar@');
+    var db = new PouchDB(dbname);
+    return db.info().then(function () {
+      if (testUtils.isExpressRouter()) {
+        return; // express-router doesn't do auth
+      }
+      throw new Error('expected an error');
+    }, function (err) {
+      should.exist(err); // 401 error
+    });
+  });
+
+  it('Test custom header', function () {
+    var db = new PouchDB(dbs.name, {
+      headers: {
+        'X-Custom': 'some-custom-header'
+      }
+    });
+    return db.info();
+  });
+
+  it('getUrl() works (used by plugins)', function () {
+    var db = new PouchDB(dbs.name);
+    db.getUrl().should.match(/^http/);
+  });
+
+  it('getHeaders() works (used by plugins)', function () {
+    var db = new PouchDB(dbs.name);
+    db.getHeaders().should.deep.equal({});
+  });
+
+  it('test url too long error for allDocs()', function () {
+    var docs = [];
+    var numDocs = 75;
+    for (var i = 0; i < numDocs; i++) {
+      docs.push({
+        _id: 'fairly_long_doc_name_' + i
+      });
+    }
+    var db = new PouchDB(dbs.name);
+    return db.bulkDocs(docs).then(function () {
+      return db.allDocs({
+        keys: docs.map(function (x) { return x._id; })
+      });
+    }).then(function (res) {
+      res.rows.should.have.length(numDocs);
+    });
+  });
+
 });
diff --git a/tests/integration/test.revs_diff.js b/tests/integration/test.revs_diff.js
index deda990..13ff98d 100644
--- a/tests/integration/test.revs_diff.js
+++ b/tests/integration/test.revs_diff.js
@@ -44,6 +44,33 @@
       });
     });
 
+    it('Test revs diff with opts object', function (done) {
+      var db = new PouchDB(dbs.name, {auto_compaction: false});
+      var revs = [];
+      db.post({
+        test: 'somestuff',
+        _id: 'somestuff'
+      }, function (err, info) {
+        revs.push(info.rev);
+        db.put({
+          _id: info.id,
+          _rev: info.rev,
+          another: 'test'
+        }, function (err, info2) {
+          revs.push(info2.rev);
+          db.revsDiff({ 'somestuff': revs }, {}, function (err, results) {
+            results.should.not.include.keys('somestuff');
+            revs.push('2-randomid');
+            db.revsDiff({ 'somestuff': revs }, function (err, results) {
+              results.should.include.keys('somestuff');
+              results.somestuff.missing.should.have.length(1);
+              done();
+            });
+          });
+        });
+      });
+    });
+
     it('Missing docs should be returned with all revisions', function (done) {
       new PouchDB(dbs.name, function (err, db) {
         var revs = ['1-a', '2-a', '2-b'];
diff --git a/tests/integration/utils.js b/tests/integration/utils.js
index 9662778..38bd7a2 100644
--- a/tests/integration/utils.js
+++ b/tests/integration/utils.js
@@ -22,6 +22,11 @@
     testUtils.params().SERVER === 'sync-gateway';
 };
 
+testUtils.isExpressRouter = function () {
+  return 'SERVER' in testUtils.params() &&
+    testUtils.params().SERVER === 'pouchdb-express-router';
+};
+
 testUtils.params = function () {
   if (typeof module !== 'undefined' && module.exports) {
     return process.env;