You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by gl...@apache.org on 2020/02/12 11:25:54 UTC

[couchdb-nano] 06/15: document tests complete

This is an automated email from the ASF dual-hosted git repository.

glynnbird pushed a commit to branch jest
in repository https://gitbox.apache.org/repos/asf/couchdb-nano.git

commit 286db832c8c0ad96b74ac08e940e7abf26a8ed37
Author: Glynn Bird <gl...@gmail.com>
AuthorDate: Thu Feb 6 15:48:41 2020 +0000

    document tests complete
---
 lib/nano.js                        |  56 ++++++++--
 test/database.listAsStream.test.js |   2 +-
 test/document.bulk.test.js         |  52 ++++++++++
 test/document.copy.test.js         |  76 ++++++++++++++
 test/document.destroy.test.js      |  51 ++++++++++
 test/document.fetch.test.js        | 160 +++++++++++++++++++++++++++++
 test/document.fetchRevs.test.js    | 124 ++++++++++++++++++++++
 test/document.get.test.js          |  65 ++++++++++++
 test/document.head.test.js         |  66 ++++++++++++
 test/document.insert.test.js       | 128 +++++++++++++++++++++++
 test/document.list.test.js         | 104 +++++++++++++++++++
 test/document.listAsStream.test.js | 108 ++++++++++++++++++++
 test/nano.request.test.js          | 204 ++++++++++++++++++++++++++++++++++---
 13 files changed, 1174 insertions(+), 22 deletions(-)

diff --git a/lib/nano.js b/lib/nano.js
index 6e6e535..7b63bc3 100644
--- a/lib/nano.js
+++ b/lib/nano.js
@@ -519,11 +519,11 @@ module.exports = exports = function dbScope (cfg) {
     // http://docs.couchdb.org/en/latest/api/document/common.html#delete--db-docid
     function destroyDoc (docName, rev, callback) {
       if (!docName) {
-        const msg = 'Invalid doc id'
+        const e = new Error('Invalid doc id')
         if (callback) {
-          callback(msg, null)
+          return callback(e, null)
         } else {
-          return Promise.reject(msg)
+          return Promise.reject(e)
         }
       } else {
         return relax({
@@ -540,7 +540,12 @@ module.exports = exports = function dbScope (cfg) {
       const { opts, callback } = getCallback(qs0, callback0)
 
       if (!docName) {
-        if (callback) { callback(new Error('Invalid doc id'), null) }
+        const e = new Error('Invalid doc id')
+        if (callback) {
+          return callback(e, null)
+        } else {
+          return Promise.reject(e)
+        }
       } else {
         return relax({ db: dbName, doc: docName, qs: opts }, callback)
       }
@@ -548,6 +553,14 @@ module.exports = exports = function dbScope (cfg) {
 
     // http://docs.couchdb.org/en/latest/api/document/common.html#head--db-docid
     function headDoc (docName, callback) {
+      if (!docName) {
+        const e = new Error('Invalid doc id')
+        if (callback) {
+          return callback(e, null)
+        } else {
+          return Promise.reject(e)
+        }
+      }
       if (callback) {
         relax({
           db: dbName,
@@ -579,6 +592,15 @@ module.exports = exports = function dbScope (cfg) {
     function copyDoc (docSrc, docDest, opts0, callback0) {
       const { opts, callback } = getCallback(opts0, callback0)
 
+      if (!docSrc || !docDest) {
+        const e = new Error('Invalid doc id')
+        if (callback) {
+          return callback(e, null)
+        } else {
+          return Promise.reject(e)
+        }
+      }
+
       const qs = {
         db: dbName,
         doc: docSrc,
@@ -593,7 +615,7 @@ module.exports = exports = function dbScope (cfg) {
               qs.headers.Destination += '?rev=' +
                 h.etag.substring(1, h.etag.length - 1)
             }
-            relax(qs, callback)
+            return relax(qs, callback)
           },
           function (e) {
             if (e && e.statusCode !== 404) {
@@ -603,7 +625,7 @@ module.exports = exports = function dbScope (cfg) {
                 return Promise.reject(e)
               }
             } else {
-              relax(qs, callback)
+              return relax(qs, callback)
             }
           }
         )
@@ -631,6 +653,17 @@ module.exports = exports = function dbScope (cfg) {
       const { opts, callback } = getCallback(qs0, callback0)
       opts.include_docs = true
 
+      if (!docNames || typeof docNames !== 'object' ||
+          !docNames.keys || !Array.isArray(docNames.keys) ||
+          docNames.keys.length === 0) {
+        const e = new Error('Invalid keys')
+        if (callback) {
+          return callback(e, null)
+        } else {
+          return Promise.reject(e)
+        }
+      }
+
       return relax({
         db: dbName,
         path: '_all_docs',
@@ -642,6 +675,17 @@ module.exports = exports = function dbScope (cfg) {
 
     function fetchRevs (docNames, qs0, callback0) {
       const { opts, callback } = getCallback(qs0, callback0)
+
+      if (!docNames || typeof docNames !== 'object' ||
+      !docNames.keys || !Array.isArray(docNames.keys) ||
+      docNames.keys.length === 0) {
+        const e = new Error('Invalid keys')
+        if (callback) {
+          return callback(e, null)
+        } else {
+          return Promise.reject(e)
+        }
+      }
       return relax({
         db: dbName,
         path: '_all_docs',
diff --git a/test/database.listAsStream.test.js b/test/database.listAsStream.test.js
index 6568e26..25b6ed2 100644
--- a/test/database.listAsStream.test.js
+++ b/test/database.listAsStream.test.js
@@ -23,7 +23,7 @@ test('should get a streamed list of databases - GET /_all_dbs - nano.db.listAsSt
     .reply(200, response)
 
   return new Promise((resolve, reject) => {
-    // test GET /db/_all_dbs
+    // test GET /_all_dbs
     const s = nano.db.listAsStream()
     expect(typeof s).toBe('object')
     let buffer = ''
diff --git a/test/document.bulk.test.js b/test/document.bulk.test.js
new file mode 100644
index 0000000..672df9d
--- /dev/null
+++ b/test/document.bulk.test.js
@@ -0,0 +1,52 @@
+// 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.
+
+const Nano = require('..')
+const COUCH_URL = 'http://localhost:5984'
+const nano = Nano(COUCH_URL)
+const nock = require('nock')
+
+test('should be able to insert documents in bulk - POST /db/_bulk_docs - db.bulk', async () => {
+  // mocks
+  const docs = [{ a: 1, b: 2 }, { a: 2, b: 3 }, { a: 3, b: 4 }]
+  const response = [
+    { ok: true, id: 'x', rev: '1-123' },
+    { ok: true, id: 'y', rev: '1-456' },
+    { ok: true, id: 'z', rev: '1-789' }
+  ]
+  const scope = nock(COUCH_URL)
+    .post('/db/_bulk_docs', { docs: docs })
+    .reply(200, response)
+
+  // test POST /db/_bulk_docs
+  const db = nano.db.use('db')
+  const p = await db.bulk({ docs: docs })
+  expect(p).toStrictEqual(response)
+  expect(scope.isDone()).toBe(true)
+})
+
+test('should be able to handle missing database - POST /db/_bulk_docs - db.bulk', async () => {
+  // mocks
+  const docs = [{ a: 1, b: 2 }, { a: 2, b: 3 }, { a: 3, b: 4 }]
+  const response = {
+    error: 'not_found',
+    reason: 'Database does not exist.'
+  }
+  const scope = nock(COUCH_URL)
+    .post('/db/_bulk_docs', { docs: docs })
+    .reply(404, response)
+
+  // test POST /db/_bulk_docs
+  const db = nano.db.use('db')
+  await expect(db.bulk({ docs: docs })).rejects.toThrow('Database does not exist.')
+  expect(scope.isDone()).toBe(true)
+})
diff --git a/test/document.copy.test.js b/test/document.copy.test.js
new file mode 100644
index 0000000..29d2d6a
--- /dev/null
+++ b/test/document.copy.test.js
@@ -0,0 +1,76 @@
+// 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.
+
+const Nano = require('..')
+const COUCH_URL = 'http://localhost:5984'
+const nano = Nano(COUCH_URL)
+const nock = require('nock')
+
+test('should be able to copy a document - db.copy', async () => {
+  // mocks
+  const response = { ok: true, id: 'rabbit2', rev: '1-123' }
+  const scope = nock(COUCH_URL, { reqheaders: { destination: 'rabbit2' } })
+    .intercept('/db/rabbit1', 'COPY')
+    .reply(200, response)
+
+  // test GET /db
+  const db = nano.db.use('db')
+  const p = await db.copy('rabbit1', 'rabbit2')
+  expect(p).toStrictEqual(response)
+  expect(scope.isDone()).toBe(true)
+})
+
+test('should detect missing source doc id - db.copy', async () => {
+  const db = nano.db.use('db')
+  await expect(db.copy(undefined, 'rabbbit2')).rejects.toThrow('Invalid doc id')
+})
+
+test('should detect missing target doc id - db.copy', async () => {
+  const db = nano.db.use('db')
+  await expect(db.copy('rabbit1')).rejects.toThrow('Invalid doc id')
+})
+
+test('should be able to copy a document in overwrite mode - db.copy', async () => {
+  // mocks
+  const response = { ok: true, id: 'rabbit2', rev: '1-123' }
+  const scope = nock(COUCH_URL)
+    .head('/db/rabbit2')
+    .reply(200, '', { ETag: '1-123' })
+    .intercept('/db/rabbit1', 'COPY')
+    .reply(200, response)
+
+  // test GET /db
+  const db = nano.db.use('db')
+  const p = await db.copy('rabbit1', 'rabbit2', { overwrite: true })
+  expect(p).toStrictEqual(response)
+  expect(scope.isDone()).toBe(true)
+})
+
+test('should be able to copy a document in overwrite mode missing target - db.copy', async () => {
+  // mocks
+  const response = { ok: true, id: 'rabbit2', rev: '1-123' }
+  const errResponse = {
+    error: 'not_found',
+    reason: 'missing'
+  }
+  const scope = nock(COUCH_URL)
+    .head('/db/rabbit2')
+    .reply(404, errResponse)
+    .intercept('/db/rabbit1', 'COPY')
+    .reply(200, response)
+
+  // test GET /db
+  const db = nano.db.use('db')
+  const p = await db.copy('rabbit1', 'rabbit2', { overwrite: true })
+  expect(p).toStrictEqual(response)
+  expect(scope.isDone()).toBe(true)
+})
diff --git a/test/document.destroy.test.js b/test/document.destroy.test.js
new file mode 100644
index 0000000..2177992
--- /dev/null
+++ b/test/document.destroy.test.js
@@ -0,0 +1,51 @@
+// 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.
+
+const Nano = require('..')
+const COUCH_URL = 'http://localhost:5984'
+const nano = Nano(COUCH_URL)
+const nock = require('nock')
+
+test('should be able to destroy a document - DELETE /db/id - db.destroy', async () => {
+  // mocks
+  const response = { ok: true, id: 'id', rev: '2-456' }
+  const scope = nock(COUCH_URL)
+    .delete('/db/id?rev=1-123')
+    .reply(200, response)
+
+  // test DELETE /db/id
+  const db = nano.db.use('db')
+  const p = await db.destroy('id', '1-123')
+  expect(p).toStrictEqual(response)
+  expect(scope.isDone()).toBe(true)
+})
+
+test('should be able to handle 409 conflicts - DELETE /db/id - db.destroy', async () => {
+  // mocks
+  const response = {
+    error: 'conflict',
+    reason: 'Document update conflict.'
+  }
+  const scope = nock(COUCH_URL)
+    .delete('/db/id?rev=1-123')
+    .reply(409, response)
+
+  // test DELETE /db/id
+  const db = nano.db.use('db')
+  await expect(db.destroy('id', '1-123')).rejects.toThrow('Document update conflict.')
+  expect(scope.isDone()).toBe(true)
+})
+
+test('should detect missing doc id - db.destroy', async () => {
+  const db = nano.db.use('db')
+  await expect(db.destroy(undefined, '1-123')).rejects.toThrow('Invalid doc id')
+})
diff --git a/test/document.fetch.test.js b/test/document.fetch.test.js
new file mode 100644
index 0000000..55281fc
--- /dev/null
+++ b/test/document.fetch.test.js
@@ -0,0 +1,160 @@
+// Licensed under the Apache License, Version 2.0 (the 'License'); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an 'AS IS' BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+const Nano = require('..')
+const COUCH_URL = 'http://localhost:5984'
+const nano = Nano(COUCH_URL)
+const nock = require('nock')
+
+test('should be able to fetch a list of documents - POST /db/_all_docs - db.fetch', async () => {
+  // mocks
+  const keys = ['1000501', '1000543', '100077']
+  const response = {
+    total_rows: 23516,
+    offset: 0,
+    rows: [
+      {
+        id: '1000501',
+        key: '1000501',
+        value: {
+          rev: '2-46dcf6bf2f8d428504f5290e591aa182'
+        },
+        doc: {
+          _id: '1000501',
+          _rev: '2-46dcf6bf2f8d428504f5290e591aa182',
+          a: 1,
+          b: 2
+        }
+      },
+      {
+        id: '1000543',
+        key: '1000543',
+        value: {
+          rev: '1-3256046064953e2f0fdb376211fe78ab'
+        },
+        doc: {
+          _id: '1000543',
+          _rev: '2-3256046064953e2f0fdb376211fe78ab',
+          a: 3,
+          b: 4
+        }
+      },
+      {
+        id: '100077',
+        key: '100077',
+        value: {
+          rev: '1-101bff1251d4bd75beb6d3c232d05a5c'
+        },
+        doc: {
+          _id: '100077',
+          _rev: '2-101bff1251d4bd75beb6d3c232d05a5c',
+          a: 5,
+          b: 6
+        }
+      }
+    ]
+  }
+  const scope = nock(COUCH_URL)
+    .post('/db/_all_docs?include_docs=true', { keys: keys })
+    .reply(200, response)
+
+  // test POST /db/_all_docs
+  const db = nano.db.use('db')
+  const p = await db.fetch({ keys: keys })
+  expect(p).toStrictEqual(response)
+  expect(scope.isDone()).toBe(true)
+})
+
+test('should be able to fetch a list of documents with opts - GET /db/_all_docs - db.fetch', async () => {
+  // mocks
+  const keys = ['1000501', '1000543', '100077']
+  const response = {
+    total_rows: 23516,
+    offset: 0,
+    rows: [
+      {
+        id: '1000501',
+        key: '1000501',
+        value: {
+          rev: '2-46dcf6bf2f8d428504f5290e591aa182'
+        },
+        doc: {
+          _id: '1000501',
+          _rev: '2-46dcf6bf2f8d428504f5290e591aa182',
+          a: 1,
+          b: 2
+        }
+      },
+      {
+        id: '1000543',
+        key: '1000543',
+        value: {
+          rev: '1-3256046064953e2f0fdb376211fe78ab'
+        },
+        doc: {
+          _id: '1000543',
+          _rev: '2-3256046064953e2f0fdb376211fe78ab',
+          a: 3,
+          b: 4
+        }
+      },
+      {
+        id: '100077',
+        key: '100077',
+        value: {
+          rev: '1-101bff1251d4bd75beb6d3c232d05a5c'
+        },
+        doc: {
+          _id: '100077',
+          _rev: '2-101bff1251d4bd75beb6d3c232d05a5c',
+          a: 5,
+          b: 6
+        }
+      }
+    ]
+  }
+  const scope = nock(COUCH_URL)
+    .post('/db/_all_docs?include_docs=true&descending=true', { keys: keys })
+    .reply(200, response)
+
+  // test POST /db/_all_docs
+  const db = nano.db.use('db')
+  const p = await db.fetch({ keys: keys }, { descending: true })
+  expect(p).toStrictEqual(response)
+  expect(scope.isDone()).toBe(true)
+})
+
+test('should be able to handle 404 - POST /db/_all_docs - db.fetch', async () => {
+  // mocks
+  const keys = ['1000501', '1000543', '100077']
+  const response = {
+    error: 'not_found',
+    reason: 'missing'
+  }
+  const scope = nock(COUCH_URL)
+    .post('/db/_all_docs?include_docs=true', { keys: keys })
+    .reply(404, response)
+
+  // test POST /db/_all_docs
+  const db = nano.db.use('db')
+  await expect(db.fetch({ keys: keys })).rejects.toThrow('missing')
+  expect(scope.isDone()).toBe(true)
+})
+
+test('should detect missing keys - db.fetch', async () => {
+  const db = nano.db.use('db')
+  await expect(db.fetch()).rejects.toThrow('Invalid keys')
+  await expect(db.fetch({})).rejects.toThrow('Invalid keys')
+  await expect(db.fetch({ keys: {} })).rejects.toThrow('Invalid keys')
+  await expect(db.fetch({ keys: '123' })).rejects.toThrow('Invalid keys')
+  await expect(db.fetch({ keys: [] })).rejects.toThrow('Invalid keys')
+})
diff --git a/test/document.fetchRevs.test.js b/test/document.fetchRevs.test.js
new file mode 100644
index 0000000..80eda1c
--- /dev/null
+++ b/test/document.fetchRevs.test.js
@@ -0,0 +1,124 @@
+// 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.
+
+const Nano = require('..')
+const COUCH_URL = 'http://localhost:5984'
+const nano = Nano(COUCH_URL)
+const nock = require('nock')
+
+test('should be able to fetch a list of document revisions - POST /db/_all_docs - db.fetch', async () => {
+  // mocks
+  const keys = ['1000501', '1000543', '100077']
+  const response = {
+    total_rows: 23516,
+    offset: 0,
+    rows: [
+      {
+        id: '1000501',
+        key: '1000501',
+        value: {
+          rev: '2-46dcf6bf2f8d428504f5290e591aa182'
+        }
+      },
+      {
+        id: '1000543',
+        key: '1000543',
+        value: {
+          rev: '1-3256046064953e2f0fdb376211fe78ab'
+        }
+      },
+      {
+        id: '100077',
+        key: '100077',
+        value: {
+          rev: '1-101bff1251d4bd75beb6d3c232d05a5c'
+        }
+      }
+    ]
+  }
+  const scope = nock(COUCH_URL)
+    .post('/db/_all_docs', { keys: keys })
+    .reply(200, response)
+
+  // test POST /db/_all_docs
+  const db = nano.db.use('db')
+  const p = await db.fetchRevs({ keys: keys })
+  expect(p).toStrictEqual(response)
+  expect(scope.isDone()).toBe(true)
+})
+
+test('should be able to fetch a list of document revisions  with opts - GET /db/_all_docs - db.fetch', async () => {
+  // mocks
+  const keys = ['1000501', '1000543', '100077']
+  const response = {
+    total_rows: 23516,
+    offset: 0,
+    rows: [
+      {
+        id: '1000501',
+        key: '1000501',
+        value: {
+          rev: '2-46dcf6bf2f8d428504f5290e591aa182'
+        }
+      },
+      {
+        id: '1000543',
+        key: '1000543',
+        value: {
+          rev: '1-3256046064953e2f0fdb376211fe78ab'
+        }
+      },
+      {
+        id: '100077',
+        key: '100077',
+        value: {
+          rev: '1-101bff1251d4bd75beb6d3c232d05a5c'
+        }
+      }
+    ]
+  }
+  const scope = nock(COUCH_URL)
+    .post('/db/_all_docs?descending=true', { keys: keys })
+    .reply(200, response)
+
+  // test POST /db/_all_docs
+  const db = nano.db.use('db')
+  const p = await db.fetchRevs({ keys: keys }, { descending: true })
+  expect(p).toStrictEqual(response)
+  expect(scope.isDone()).toBe(true)
+})
+
+test('should be able to handle 404 - POST /db/_all_docs - db.fetch', async () => {
+  // mocks
+  const keys = ['1000501', '1000543', '100077']
+  const response = {
+    error: 'not_found',
+    reason: 'missing'
+  }
+  const scope = nock(COUCH_URL)
+    .post('/db/_all_docs', { keys: keys })
+    .reply(404, response)
+
+  // test POST /db/_all_docs
+  const db = nano.db.use('db')
+  await expect(db.fetchRevs({ keys: keys })).rejects.toThrow('missing')
+  expect(scope.isDone()).toBe(true)
+})
+
+test('should detect missing keys - db.fetch', async () => {
+  const db = nano.db.use('db')
+  await expect(db.fetchRevs()).rejects.toThrow('Invalid keys')
+  await expect(db.fetchRevs({})).rejects.toThrow('Invalid keys')
+  await expect(db.fetchRevs({ keys: {} })).rejects.toThrow('Invalid keys')
+  await expect(db.fetchRevs({ keys: '123' })).rejects.toThrow('Invalid keys')
+  await expect(db.fetchRevs({ keys: [] })).rejects.toThrow('Invalid keys')
+})
diff --git a/test/document.get.test.js b/test/document.get.test.js
new file mode 100644
index 0000000..a2cfcca
--- /dev/null
+++ b/test/document.get.test.js
@@ -0,0 +1,65 @@
+// 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.
+
+const Nano = require('..')
+const COUCH_URL = 'http://localhost:5984'
+const nano = Nano(COUCH_URL)
+const nock = require('nock')
+
+test('should be able to get a document - GET /db/id - db.get', async () => {
+  // mocks
+  const response = { _id: 'id', rev: '1-123', a: 1, b: 'two', c: true }
+  const scope = nock(COUCH_URL)
+    .get('/db/id')
+    .reply(200, response)
+
+  // test GET /db
+  const db = nano.db.use('db')
+  const p = await db.get('id')
+  expect(p).toStrictEqual(response)
+  expect(scope.isDone()).toBe(true)
+})
+
+test('should be able to get a document with options - GET /db/id?conflicts=true - db.get', async () => {
+  // mocks
+  const response = { _id: 'id', rev: '1-123', a: 1, b: 'two', c: true }
+  const scope = nock(COUCH_URL)
+    .get('/db/id?conflicts=true')
+    .reply(200, response)
+
+  // test GET /db
+  const db = nano.db.use('db')
+  const p = await db.get('id', { conflicts: true })
+  expect(p).toStrictEqual(response)
+  expect(scope.isDone()).toBe(true)
+})
+
+test('should be able to handle 404 - GET /db/id - db.get', async () => {
+  // mocks
+  const response = {
+    error: 'not_found',
+    reason: 'missing'
+  }
+  const scope = nock(COUCH_URL)
+    .get('/db/id')
+    .reply(404, response)
+
+  // test GET /db
+  const db = nano.db.use('db')
+  await expect(db.get('id')).rejects.toThrow('missing')
+  expect(scope.isDone()).toBe(true)
+})
+
+test('should detect missing doc id - db.get', async () => {
+  const db = nano.db.use('db')
+  await expect(db.get()).rejects.toThrow('Invalid doc id')
+})
diff --git a/test/document.head.test.js b/test/document.head.test.js
new file mode 100644
index 0000000..dc2cb99
--- /dev/null
+++ b/test/document.head.test.js
@@ -0,0 +1,66 @@
+// 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.
+
+const Nano = require('..')
+const COUCH_URL = 'http://localhost:5984'
+const nano = Nano(COUCH_URL)
+const nock = require('nock')
+
+test('should be able to head a document - HEAD /db/id - db.head', async () => {
+  // mocks
+  const scope = nock(COUCH_URL)
+    .head('/db/id')
+    .reply(200, '', { ETag: '1-123' })
+
+  // test GET /db
+  const db = nano.db.use('db')
+  const p = await db.head('id')
+  // headers get lowercased
+  expect(p.etag).toBe('1-123')
+  expect(scope.isDone()).toBe(true)
+})
+
+test('should be able to head a document with callback - HEAD /db/id - db.head', async () => {
+  // mocks
+  const scope = nock(COUCH_URL)
+    .head('/db/id')
+    .reply(200, '', { ETag: '1-123' })
+
+  // test GET /db
+  return new Promise((resolve, reject) => {
+    const db = nano.db.use('db')
+    db.head('id', (err, data, headers) => {
+      // headers get lowercased
+      expect(err).toBeNull()
+      expect(headers.etag).toBe('1-123')
+      expect(scope.isDone()).toBe(true)
+      resolve()
+    })
+  })
+})
+
+test('should be able to head a missing document - HEAD /db/id - db.head', async () => {
+  // mocks
+  const scope = nock(COUCH_URL)
+    .head('/db/id')
+    .reply(404, '')
+
+  // test GET /db
+  const db = nano.db.use('db')
+  await expect(db.head('id')).rejects.toThrow('couch returned 404')
+  expect(scope.isDone()).toBe(true)
+})
+
+test('should detect missing doc id - db.head', async () => {
+  const db = nano.db.use('db')
+  await expect(db.head()).rejects.toThrow('Invalid doc id')
+})
diff --git a/test/document.insert.test.js b/test/document.insert.test.js
new file mode 100644
index 0000000..812775b
--- /dev/null
+++ b/test/document.insert.test.js
@@ -0,0 +1,128 @@
+// 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.
+
+const Nano = require('..')
+const COUCH_URL = 'http://localhost:5984'
+const nano = Nano(COUCH_URL)
+const nock = require('nock')
+
+test('should be able to insert document - POST /db - db.insert', async () => {
+  // mocks
+  const doc = { a: 1, b: 2 }
+  const response = { ok: true, id: '8s8g8h8h9', rev: '1-123' }
+  const scope = nock(COUCH_URL)
+    .post('/db', doc)
+    .reply(200, response)
+
+  // test POST /db
+  const db = nano.db.use('db')
+  const p = await db.insert(doc)
+  expect(p).toStrictEqual(response)
+  expect(scope.isDone()).toBe(true)
+})
+
+test('should be able to insert document with opts - POST /db?batch=ok - db.insert', async () => {
+  // mocks
+  const doc = { a: 1, b: 2 }
+  const response = { ok: true, id: '8s8g8h8h9', rev: '1-123' }
+  const scope = nock(COUCH_URL)
+    .post('/db?batch=ok', doc)
+    .reply(200, response)
+
+  // test POST /db
+  const db = nano.db.use('db')
+  const p = await db.insert(doc, { batch: 'ok' })
+  expect(p).toStrictEqual(response)
+  expect(scope.isDone()).toBe(true)
+})
+
+test('should be able to insert document with known id - PUT /db/id - db.insert', async () => {
+  // mocks
+  const doc = { a: 1, b: 2 }
+  const response = { ok: true, id: 'myid', rev: '1-123' }
+
+  const scope = nock(COUCH_URL)
+    .put('/db/myid', doc)
+    .reply(200, response)
+
+  // test PUT /db
+  const db = nano.db.use('db')
+  const p = await db.insert(doc, 'myid')
+  expect(p).toStrictEqual(response)
+  expect(scope.isDone()).toBe(true)
+})
+
+test('should be able to insert document with id in object - POST /db - db.insert', async () => {
+  // mocks
+  const doc = { _id: 'myid', a: 1, b: 2 }
+  const response = { ok: true, id: 'myid', rev: '1-123' }
+
+  const scope = nock(COUCH_URL)
+    .post('/db', doc)
+    .reply(200, response)
+
+  // test POST /db
+  const db = nano.db.use('db')
+  const p = await db.insert(doc)
+  expect(p).toStrictEqual(response)
+  expect(scope.isDone()).toBe(true)
+})
+
+test('should be able to update document with id/rev in object - POST /db - db.insert', async () => {
+  // mocks
+  const doc = { _id: 'myid', _rev: '1-123', a: 2, b: 2 }
+  const response = { ok: true, id: 'myid', rev: '2-456' }
+
+  const scope = nock(COUCH_URL)
+    .post('/db', doc)
+    .reply(200, response)
+
+  // test POST /db
+  const db = nano.db.use('db')
+  const p = await db.insert(doc)
+  expect(p).toStrictEqual(response)
+  expect(scope.isDone()).toBe(true)
+})
+
+test('should be able to handle 409 conflicts - POST /db - db.insert', async () => {
+  // mocks
+  const doc = { _id: 'myid', _rev: '1-123', a: 2, b: 2 }
+  const response = {
+    error: 'conflict',
+    reason: 'Document update conflict.'
+  }
+  const scope = nock(COUCH_URL)
+    .post('/db', doc)
+    .reply(409, response)
+
+  // test POST /db
+  const db = nano.db.use('db')
+  await expect(db.insert(doc)).rejects.toThrow('Document update conflict.')
+  expect(scope.isDone()).toBe(true)
+})
+
+test('should be able to handle missing database - POST /db - db.insert', async () => {
+  // mocks
+  const doc = { a: 1, b: 2 }
+  const response = {
+    error: 'not_found',
+    reason: 'Database does not exist.'
+  }
+  const scope = nock(COUCH_URL)
+    .post('/db', doc)
+    .reply(404, response)
+
+  // test POST /db
+  const db = nano.db.use('db')
+  await expect(db.insert(doc)).rejects.toThrow('Database does not exist.')
+  expect(scope.isDone()).toBe(true)
+})
diff --git a/test/document.list.test.js b/test/document.list.test.js
new file mode 100644
index 0000000..3e0705f
--- /dev/null
+++ b/test/document.list.test.js
@@ -0,0 +1,104 @@
+// 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.
+
+const Nano = require('..')
+const COUCH_URL = 'http://localhost:5984'
+const nano = Nano(COUCH_URL)
+const nock = require('nock')
+
+test('should be able to get a list of documents - GET /db/_all_docs - db.list', async () => {
+  // mocks
+  const response = {
+    total_rows: 23516,
+    offset: 0,
+    rows: [
+      {
+        id: '1000501',
+        key: '1000501',
+        value: {
+          rev: '2-46dcf6bf2f8d428504f5290e591aa182'
+        }
+      },
+      {
+        id: '1000543',
+        key: '1000543',
+        value: {
+          rev: '1-3256046064953e2f0fdb376211fe78ab'
+        }
+      },
+      {
+        id: '100077',
+        key: '100077',
+        value: {
+          rev: '1-101bff1251d4bd75beb6d3c232d05a5c'
+        }
+      }
+    ]
+  }
+  const scope = nock(COUCH_URL)
+    .get('/db/_all_docs')
+    .reply(200, response)
+
+  // test GET /db/_all_docs
+  const db = nano.db.use('db')
+  const p = await db.list()
+  expect(p).toStrictEqual(response)
+  expect(scope.isDone()).toBe(true)
+})
+
+test('should be able to get a list of documents with opts - GET /db/_all_docs - db.list', async () => {
+  // mocks
+  const response = {
+    total_rows: 23516,
+    offset: 0,
+    rows: [
+      {
+        id: '1000501',
+        key: '1000501',
+        value: {
+          rev: '2-46dcf6bf2f8d428504f5290e591aa182'
+        },
+        doc: {
+          _id: '1000501',
+          _rev: '2-46dcf6bf2f8d428504f5290e591aa182',
+          a: 1,
+          b: 2
+        }
+      }
+    ]
+  }
+  const scope = nock(COUCH_URL)
+    .get('/db/_all_docs?include_docs=true&limit=1')
+    .reply(200, response)
+
+  // test GET /db/_all_docs
+  const db = nano.db.use('db')
+  const p = await db.list({ include_docs: true, limit: 1 })
+  expect(p).toStrictEqual(response)
+  expect(scope.isDone()).toBe(true)
+})
+
+test('should be able to handle 404 - GET /db/_all_docs - db.list', async () => {
+  // mocks
+  const response = {
+    error: 'not_found',
+    reason: 'missing'
+  }
+  const scope = nock(COUCH_URL)
+    .get('/db/_all_docs')
+    .reply(404, response)
+
+  // test GET /db/_all_docs
+  const db = nano.db.use('db')
+  await expect(db.list()).rejects.toThrow('missing')
+  expect(scope.isDone()).toBe(true)
+})
diff --git a/test/document.listAsStream.test.js b/test/document.listAsStream.test.js
new file mode 100644
index 0000000..45cdf49
--- /dev/null
+++ b/test/document.listAsStream.test.js
@@ -0,0 +1,108 @@
+// 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.
+
+const Nano = require('..')
+const COUCH_URL = 'http://localhost:5984'
+const nano = Nano(COUCH_URL)
+const nock = require('nock')
+
+test('should get a streamed list of documents - GET /db/_all_docs - db.listAsStream', async () => {
+  // mocks
+  const response = {
+    total_rows: 23516,
+    offset: 0,
+    rows: [
+      {
+        id: '1000501',
+        key: '1000501',
+        value: {
+          rev: '2-46dcf6bf2f8d428504f5290e591aa182'
+        }
+      },
+      {
+        id: '1000543',
+        key: '1000543',
+        value: {
+          rev: '1-3256046064953e2f0fdb376211fe78ab'
+        }
+      },
+      {
+        id: '100077',
+        key: '100077',
+        value: {
+          rev: '1-101bff1251d4bd75beb6d3c232d05a5c'
+        }
+      }
+    ]
+  }
+  const scope = nock(COUCH_URL)
+    .get('/db/_all_docs')
+    .reply(200, response)
+
+  return new Promise((resolve, reject) => {
+    // test GET /db/_all_docs
+    const db = nano.db.use('db')
+    const s = db.listAsStream()
+    expect(typeof s).toBe('object')
+    let buffer = ''
+    s.on('data', (chunk) => {
+      buffer += chunk.toString()
+    })
+    s.on('end', () => {
+      expect(buffer).toBe(JSON.stringify(response))
+      expect(scope.isDone()).toBe(true)
+      resolve()
+    })
+  })
+})
+
+test('should get a streamed list of documents with opts- GET /db/_all_docs - db.listAsStream', async () => {
+  // mocks
+  const response = {
+    total_rows: 23516,
+    offset: 0,
+    rows: [
+      {
+        id: '1000501',
+        key: '1000501',
+        value: {
+          rev: '2-46dcf6bf2f8d428504f5290e591aa182'
+        },
+        doc: {
+          _id: '1000501',
+          _rev: '2-46dcf6bf2f8d428504f5290e591aa182',
+          a: 1,
+          b: 2
+        }
+      }
+    ]
+  }
+  const scope = nock(COUCH_URL)
+    .get('/db/_all_docs?limit=1&include_docs=true')
+    .reply(200, response)
+
+  return new Promise((resolve, reject) => {
+    // test GET /db/_all_docs
+    const db = nano.db.use('db')
+    const s = db.listAsStream({ limit: 1, include_docs: true })
+    expect(typeof s).toBe('object')
+    let buffer = ''
+    s.on('data', (chunk) => {
+      buffer += chunk.toString()
+    })
+    s.on('end', () => {
+      expect(buffer).toBe(JSON.stringify(response))
+      expect(scope.isDone()).toBe(true)
+      resolve()
+    })
+  })
+})
diff --git a/test/nano.request.test.js b/test/nano.request.test.js
index dc23da1..045071d 100644
--- a/test/nano.request.test.js
+++ b/test/nano.request.test.js
@@ -19,13 +19,13 @@ test('check request can do GET requests - nano.request', async () => {
   // mocks
   const response = { ok: true }
   const scope = nock(COUCH_URL)
-    .get('/mydb?a=1&b=2')
+    .get('/db?a=1&b=2')
     .reply(200, response)
 
-  // test GET /db
+  // test GET /db?a=1&b=2
   const req = {
     method: 'get',
-    db: 'mydb',
+    db: 'db',
     qs: { a: 1, b: 2 }
   }
   const p = await nano.request(req)
@@ -37,13 +37,13 @@ test('check request can do POST requests - nano.request', async () => {
   // mocks
   const response = { ok: true }
   const scope = nock(COUCH_URL)
-    .post('/mydb', { _id: '1', a: true })
+    .post('/db', { _id: '1', a: true })
     .reply(200, response)
 
-  // test GET /db
+  // test POST /db
   const req = {
     method: 'post',
-    db: 'mydb',
+    db: 'db',
     body: { _id: '1', a: true }
   }
   const p = await nano.request(req)
@@ -55,13 +55,13 @@ test('check request can do PUT requests - nano.request', async () => {
   // mocks
   const response = { ok: true }
   const scope = nock(COUCH_URL)
-    .put('/mydb/1', { _id: '1', a: true })
+    .put('/db/1', { _id: '1', a: true })
     .reply(200, response)
 
-  // test GET /db
+  // test PUT /db
   const req = {
     method: 'put',
-    db: 'mydb',
+    db: 'db',
     path: '1',
     body: { _id: '1', a: true }
   }
@@ -74,14 +74,14 @@ test('check request can do DELETE requests - nano.request', async () => {
   // mocks
   const response = { ok: true }
   const scope = nock(COUCH_URL)
-    .delete('/mydb/mydoc')
+    .delete('/db/mydoc')
     .query({ rev: '1-123' })
     .reply(200, response)
 
-  // test GET /db
+  // test DELETE /db
   const req = {
     method: 'delete',
-    db: 'mydb',
+    db: 'db',
     path: 'mydoc',
     qs: { rev: '1-123' }
 
@@ -95,16 +95,190 @@ test('check request can do HEAD requests - nano.request', async () => {
   // mocks
   const response = ''
   const scope = nock(COUCH_URL)
-    .head('/mydb/mydoc')
+    .head('/db/mydoc')
     .reply(200, '')
 
-  // test GET /db
+  // test HEAD /db/mydoc
   const req = {
     method: 'head',
-    db: 'mydb',
+    db: 'db',
     path: 'mydoc'
   }
   const p = await nano.request(req)
   expect(p).toStrictEqual(response)
   expect(scope.isDone()).toBe(true)
 })
+
+test('check request can do GET requests with callback - nano.request', async () => {
+  // mocks
+  const response = { ok: true }
+  const scope = nock(COUCH_URL)
+    .get('/db?a=1&b=2')
+    .reply(200, response)
+
+  // test GET /db?a=1&b=2
+  const req = {
+    method: 'get',
+    db: 'db',
+    qs: { a: 1, b: 2 }
+  }
+  return new Promise((resolve, reject) => {
+    nano.request(req, (err, data) => {
+      expect(err).toBe(null)
+      expect(data).toStrictEqual(response)
+      expect(scope.isDone()).toBe(true)
+      resolve()
+    })
+  })
+})
+
+test('check request can do failed GET requests with callback - nano.request', async () => {
+  // mocks
+  const response = {
+    error: 'not_found',
+    reason: 'missing'
+  }
+  const scope = nock(COUCH_URL)
+    .get('/db/a')
+    .reply(404, response)
+
+  // test GET /db/a
+  const req = {
+    method: 'get',
+    db: 'db',
+    path: 'a'
+  }
+  return new Promise((resolve, reject) => {
+    nano.request(req, (err, data) => {
+      expect(err).not.toBe(null)
+      expect(scope.isDone()).toBe(true)
+      resolve()
+    })
+  })
+})
+
+test('check request formats keys properly - nano.request', async () => {
+  // mocks
+  const response = { ok: true }
+  const arr = ['a', 'b', 'c']
+  const scope = nock(COUCH_URL)
+    .get('/db/_all_docs')
+    .query({ keys: JSON.stringify(arr) })
+    .reply(200, response)
+
+  // test GET /db/_all_docs?keys=[]
+  const req = {
+    method: 'get',
+    db: 'db',
+    path: '_all_docs',
+    qs: { keys: arr }
+  }
+  const p = await nano.request(req)
+  expect(p).toStrictEqual(response)
+  expect(scope.isDone()).toBe(true)
+})
+
+test('check request formats startkey properly - nano.request', async () => {
+  // mocks
+  const response = { ok: true }
+  const val = 'x'
+  const scope = nock(COUCH_URL)
+    .get('/db/_all_docs')
+    .query({ startkey: JSON.stringify(val) })
+    .reply(200, response)
+
+  // test GET /db/_all_docs?startkey=
+  const req = {
+    method: 'get',
+    db: 'db',
+    path: '_all_docs',
+    qs: { startkey: val }
+  }
+  const p = await nano.request(req)
+  expect(p).toStrictEqual(response)
+  expect(scope.isDone()).toBe(true)
+})
+
+test('check request formats start_key properly - nano.request', async () => {
+  // mocks
+  const response = { ok: true }
+  const val = 'x'
+  const scope = nock(COUCH_URL)
+    .get('/db/_all_docs')
+    .query({ start_key: JSON.stringify(val) })
+    .reply(200, response)
+
+  // test GET /db/_all_docs?start_key=
+  const req = {
+    method: 'get',
+    db: 'db',
+    path: '_all_docs',
+    qs: { start_key: val }
+  }
+  const p = await nano.request(req)
+  expect(p).toStrictEqual(response)
+  expect(scope.isDone()).toBe(true)
+})
+
+test('check request formats endkey properly - nano.request', async () => {
+  // mocks
+  const response = { ok: true }
+  const val = 'x'
+  const scope = nock(COUCH_URL)
+    .get('/db/_all_docs')
+    .query({ endkey: JSON.stringify(val) })
+    .reply(200, response)
+
+  // test GET /db/_all_docs?endkey=
+  const req = {
+    method: 'get',
+    db: 'db',
+    path: '_all_docs',
+    qs: { endkey: val }
+  }
+  const p = await nano.request(req)
+  expect(p).toStrictEqual(response)
+  expect(scope.isDone()).toBe(true)
+})
+
+test('check request formats end_key properly - nano.request', async () => {
+  // mocks
+  const response = { ok: true }
+  const val = 'x'
+  const scope = nock(COUCH_URL)
+    .get('/db/_all_docs')
+    .query({ end_key: JSON.stringify(val) })
+    .reply(200, response)
+
+  // test GET /db/_all_docs?end_key=
+  const req = {
+    method: 'get',
+    db: 'db',
+    path: '_all_docs',
+    qs: { end_key: val }
+  }
+  const p = await nano.request(req)
+  expect(p).toStrictEqual(response)
+  expect(scope.isDone()).toBe(true)
+})
+
+test('check request formats key properly - nano.request', async () => {
+  // mocks
+  const response = { ok: true }
+  const val = 'x'
+  const scope = nock(COUCH_URL)
+    .get('/db/_all_docs')
+    .query({ key: JSON.stringify(val) })
+    .reply(200, response)
+
+  // test GET /db/_all_docs?key=
+  const req = {
+    method: 'get',
+    db: 'db',
+    path: '_all_docs',
+    qs: { key: val }
+  }
+  const p = await nano.request(req)
+  expect(p).toStrictEqual(response)
+  expect(scope.isDone()).toBe(true)
+})