From 1b0610dffd4cd2f0612eed77f7d21b15f050d47f Mon Sep 17 00:00:00 2001
From: Jon Ursenbach <jon@ursenba.ch>
Date: Tue, 1 Feb 2022 17:49:13 -0800
Subject: [PATCH 1/4] feat: adding an rdme-specific user agent to all api calls

---
 __tests__/.eslintrc             | 19 +-------
 __tests__/cmds/docs.test.js     | 82 ++++++++++++++++++---------------
 __tests__/cmds/login.test.js    | 12 ++---
 __tests__/cmds/openapi.test.js  | 29 ++++++------
 __tests__/cmds/versions.test.js | 36 ++++++---------
 __tests__/get-api-nock.js       | 15 ++++++
 __tests__/lib/commands.test.js  |  1 +
 __tests__/lib/prompts.test.js   |  1 +
 package.json                    |  1 +
 src/cmds/docs/edit.js           |  4 +-
 src/cmds/docs/index.js          |  4 +-
 src/cmds/login.js               |  4 +-
 src/cmds/openapi.js             |  2 +-
 src/cmds/versions/create.js     |  4 +-
 src/cmds/versions/delete.js     |  4 +-
 src/cmds/versions/index.js      |  4 +-
 src/cmds/versions/update.js     |  4 +-
 src/lib/fetch.js                | 30 ++++++++++++
 src/lib/handleRes.js            | 11 -----
 src/lib/versionSelect.js        |  4 +-
 20 files changed, 146 insertions(+), 125 deletions(-)
 create mode 100644 __tests__/get-api-nock.js
 create mode 100644 src/lib/fetch.js
 delete mode 100644 src/lib/handleRes.js

diff --git a/__tests__/.eslintrc b/__tests__/.eslintrc
index 727df504a..b99d491e0 100644
--- a/__tests__/.eslintrc
+++ b/__tests__/.eslintrc
@@ -1,20 +1,3 @@
 {
-  "extends": ["@readme/eslint-config/testing"],
-  "env": {
-    "jest": true
-  },
-  "rules": {
-    "jest/expect-expect": [
-      "error",
-      {
-        "assertFunctionNames": [
-          "expect",
-          "getNockWithVersionHeader.**.reply",
-          "nock.**.reply"
-        ]
-      }
-    ],
-
-    "jest/no-conditional-expect": "off"
-  }
+  "extends": ["@readme/eslint-config/testing"]
 }
diff --git a/__tests__/cmds/docs.test.js b/__tests__/cmds/docs.test.js
index fe14f22ba..ef8e4561a 100644
--- a/__tests__/cmds/docs.test.js
+++ b/__tests__/cmds/docs.test.js
@@ -7,6 +7,8 @@ const crypto = require('crypto');
 const frontMatter = require('gray-matter');
 
 const APIError = require('../../src/lib/apiError');
+const getApiNock = require('../get-api-nock');
+const { userAgent } = require('../get-api-nock');
 
 const DocsCommand = require('../../src/cmds/docs');
 const DocsEditCommand = require('../../src/cmds/docs/edit');
@@ -24,6 +26,7 @@ function getNockWithVersionHeader(v) {
   return nock(config.get('host'), {
     reqheaders: {
       'x-readme-version': v,
+      'User-Agent': userAgent,
     },
   });
 }
@@ -106,7 +109,7 @@ describe('rdme docs', () => {
         .basicAuth({ user: key })
         .reply(200, { category, slug: anotherDoc.slug, body: anotherDoc.doc.content });
 
-      const versionMock = nock(config.get('host'))
+      const versionMock = getApiNock()
         .get(`/api/v1/version/${version}`)
         .basicAuth({ user: key })
         .reply(200, { version });
@@ -140,7 +143,7 @@ describe('rdme docs', () => {
         .basicAuth({ user: key })
         .reply(200, { category, slug: anotherDoc.slug, lastUpdatedHash: anotherDoc.hash });
 
-      const versionMock = nock(config.get('host'))
+      const versionMock = getApiNock()
         .get(`/api/v1/version/${version}`)
         .basicAuth({ user: key })
         .reply(200, { version });
@@ -158,7 +161,7 @@ describe('rdme docs', () => {
   });
 
   describe('new docs', () => {
-    it('should create new doc', () => {
+    it('should create new doc', async () => {
       const slug = 'new-doc';
       const doc = frontMatter(fs.readFileSync(path.join(fixturesDir, `/new-docs/${slug}.md`)));
       const hash = hashFileContents(fs.readFileSync(path.join(fixturesDir, `/new-docs/${slug}.md`)));
@@ -178,16 +181,24 @@ describe('rdme docs', () => {
         .basicAuth({ user: key })
         .reply(201, { slug, body: doc.content, ...doc.data, lastUpdatedHash: hash });
 
-      const versionMock = nock(config.get('host'))
+      const versionMock = getApiNock()
         .get(`/api/v1/version/${version}`)
         .basicAuth({ user: key })
         .reply(200, { version });
 
-      return docs.run({ folder: './__tests__/__fixtures__/new-docs', key, version }).then(() => {
-        getMock.done();
-        postMock.done();
-        versionMock.done();
-      });
+      await expect(docs.run({ folder: './__tests__/__fixtures__/new-docs', key, version })).resolves.toStrictEqual([
+        {
+          slug: 'new-doc',
+          body: '\nBody\n',
+          category: '5ae122e10fdf4e39bb34db6f',
+          title: 'This is the document title',
+          lastUpdatedHash: 'a23046c1e9d8ab47f8875ae7c5e429cb95be1c48',
+        },
+      ]);
+
+      getMock.done();
+      postMock.done();
+      versionMock.done();
     });
 
     it('should fail if any docs are invalid', async () => {
@@ -247,7 +258,7 @@ describe('rdme docs', () => {
         .basicAuth({ user: key })
         .reply(400, errorObject);
 
-      const versionMock = nock(config.get('host'))
+      const versionMock = getApiNock()
         .get(`/api/v1/version/${version}`)
         .basicAuth({ user: key })
         .reply(200, { version });
@@ -270,12 +281,12 @@ describe('rdme docs', () => {
   });
 
   describe('slug metadata', () => {
-    it('should use provided slug', () => {
+    it('should use provided slug', async () => {
       const slug = 'new-doc-slug';
       const doc = frontMatter(fs.readFileSync(path.join(fixturesDir, `/slug-docs/${slug}.md`)));
       const hash = hashFileContents(fs.readFileSync(path.join(fixturesDir, `/slug-docs/${slug}.md`)));
 
-      const getMock = getNockWithVersionHeader(version)
+      const getMock = getApiNock()
         .get(`/api/v1/docs/${doc.data.slug}`)
         .basicAuth({ user: key })
         .reply(404, {
@@ -285,21 +296,29 @@ describe('rdme docs', () => {
           help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".',
         });
 
-      const postMock = getNockWithVersionHeader(version)
+      const postMock = getApiNock()
         .post('/api/v1/docs', { slug, body: doc.content, ...doc.data, lastUpdatedHash: hash })
         .basicAuth({ user: key })
         .reply(201, { slug: doc.data.slug, body: doc.content, ...doc.data, lastUpdatedHash: hash });
 
-      const versionMock = nock(config.get('host'))
+      const versionMock = getApiNock()
         .get(`/api/v1/version/${version}`)
         .basicAuth({ user: key })
         .reply(200, { version });
 
-      return docs.run({ folder: './__tests__/__fixtures__/slug-docs', key, version }).then(() => {
-        getMock.done();
-        postMock.done();
-        versionMock.done();
-      });
+      await expect(docs.run({ folder: './__tests__/__fixtures__/slug-docs', key, version })).resolves.toStrictEqual([
+        {
+          slug: 'marc-actually-wrote-a-test',
+          body: '\nBody\n',
+          category: 'CATEGORY_ID',
+          title: 'This is the document title',
+          lastUpdatedHash: 'c9cb7cc26e90775548e1d182ae7fcaa0eaba96bc',
+        },
+      ]);
+
+      getMock.done();
+      postMock.done();
+      versionMock.done();
     });
   });
 });
@@ -336,10 +355,7 @@ describe('rdme docs:edit', () => {
       .basicAuth({ user: key })
       .reply(200, { category, slug });
 
-    const versionMock = nock(config.get('host'))
-      .get(`/api/v1/version/${version}`)
-      .basicAuth({ user: key })
-      .reply(200, { version });
+    const versionMock = getApiNock().get(`/api/v1/version/${version}`).basicAuth({ user: key }).reply(200, { version });
 
     function mockEditor(filename, cb) {
       expect(filename).toBe(`${slug}.md`);
@@ -368,12 +384,9 @@ describe('rdme docs:edit', () => {
       help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".',
     };
 
-    const getMock = nock(config.get('host')).get(`/api/v1/docs/${slug}`).reply(404, errorObject);
+    const getMock = getApiNock().get(`/api/v1/docs/${slug}`).reply(404, errorObject);
 
-    const versionMock = nock(config.get('host'))
-      .get(`/api/v1/version/${version}`)
-      .basicAuth({ user: key })
-      .reply(200, { version });
+    const versionMock = getApiNock().get(`/api/v1/version/${version}`).basicAuth({ user: key }).reply(200, { version });
 
     await expect(docsEdit.run({ slug, key, version: '1.0.0' })).rejects.toThrow(new APIError(errorObject));
 
@@ -392,14 +405,9 @@ describe('rdme docs:edit', () => {
       help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".',
     };
 
-    const getMock = nock(config.get('host')).get(`/api/v1/docs/${slug}`).reply(200, { body });
-
-    const putMock = nock(config.get('host')).put(`/api/v1/docs/${slug}`).reply(400, errorObject);
-
-    const versionMock = nock(config.get('host'))
-      .get(`/api/v1/version/${version}`)
-      .basicAuth({ user: key })
-      .reply(200, { version });
+    const getMock = getApiNock().get(`/api/v1/docs/${slug}`).reply(200, { body });
+    const putMock = getApiNock().put(`/api/v1/docs/${slug}`).reply(400, errorObject);
+    const versionMock = getApiNock().get(`/api/v1/version/${version}`).basicAuth({ user: key }).reply(200, { version });
 
     function mockEditor(filename, cb) {
       return cb(0);
@@ -419,7 +427,7 @@ describe('rdme docs:edit', () => {
     const slug = 'getting-started';
     const body = 'abcdef';
 
-    const getMock = nock(config.get('host'))
+    const getMock = getApiNock()
       .get(`/api/v1/docs/${slug}`)
       .reply(200, { body })
       .get(`/api/v1/version/${version}`)
diff --git a/__tests__/cmds/login.test.js b/__tests__/cmds/login.test.js
index c3b4a8bbe..b3034b990 100644
--- a/__tests__/cmds/login.test.js
+++ b/__tests__/cmds/login.test.js
@@ -1,8 +1,8 @@
 const nock = require('nock');
-const config = require('config');
 const configStore = require('../../src/lib/configstore');
 const Command = require('../../src/cmds/login');
 const APIError = require('../../src/lib/apiError');
+const apiNock = require('../get-api-nock');
 
 const cmd = new Command();
 
@@ -32,7 +32,7 @@ describe('rdme login', () => {
   it('should post to /login on the API', async () => {
     const apiKey = 'abcdefg';
 
-    const mock = nock(config.get('host')).post('/api/v1/login', { email, password, project }).reply(200, { apiKey });
+    const mock = apiNock().post('/api/v1/login', { email, password, project }).reply(200, { apiKey });
 
     await expect(cmd.run({ email, password, project })).resolves.toMatchSnapshot();
 
@@ -52,7 +52,7 @@ describe('rdme login', () => {
       help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".',
     };
 
-    const mock = nock(config.get('host')).post('/api/v1/login', { email, password, project }).reply(401, errorResponse);
+    const mock = apiNock().post('/api/v1/login', { email, password, project }).reply(401, errorResponse);
 
     await expect(cmd.run({ email, password, project })).rejects.toStrictEqual(new APIError(errorResponse));
     mock.done();
@@ -66,7 +66,7 @@ describe('rdme login', () => {
       help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".',
     };
 
-    const mock = nock(config.get('host')).post('/api/v1/login', { email, password, project }).reply(401, errorResponse);
+    const mock = apiNock().post('/api/v1/login', { email, password, project }).reply(401, errorResponse);
 
     await expect(cmd.run({ email, password, project })).rejects.toStrictEqual(new APIError(errorResponse));
     mock.done();
@@ -75,9 +75,7 @@ describe('rdme login', () => {
   it('should send 2fa token if provided', async () => {
     const token = '123456';
 
-    const mock = nock(config.get('host'))
-      .post('/api/v1/login', { email, password, project, token })
-      .reply(200, { apiKey: '123' });
+    const mock = apiNock().post('/api/v1/login', { email, password, project, token }).reply(200, { apiKey: '123' });
 
     await expect(cmd.run({ email, password, project, token })).resolves.toMatchSnapshot();
     mock.done();
diff --git a/__tests__/cmds/openapi.test.js b/__tests__/cmds/openapi.test.js
index 857a0432c..37c70ebe7 100644
--- a/__tests__/cmds/openapi.test.js
+++ b/__tests__/cmds/openapi.test.js
@@ -6,6 +6,7 @@ const promptHandler = require('../../src/lib/prompts');
 const SwaggerCommand = require('../../src/cmds/swagger');
 const OpenAPICommand = require('../../src/cmds/openapi');
 const APIError = require('../../src/lib/apiError');
+const getApiNock = require('../get-api-nock');
 
 const openapi = new OpenAPICommand();
 const swagger = new SwaggerCommand();
@@ -62,7 +63,7 @@ describe('rdme openapi', () => {
       ['OpenAPI 3.1', 'json', '3.1'],
       ['OpenAPI 3.1', 'yaml', '3.1'],
     ])('should support uploading a %s definition (format: %s)', async (_, format, specVersion) => {
-      const mock = nock(config.get('host'))
+      const mock = getApiNock()
         .get('/api/v1/api-specification')
         .basicAuth({ user: key })
         .reply(200, [])
@@ -89,7 +90,7 @@ describe('rdme openapi', () => {
     it('should discover and upload an API definition if none is provided', async () => {
       promptHandler.createOasPrompt.mockResolvedValue({ option: 'create' });
 
-      const mock = nock(config.get('host'))
+      const mock = getApiNock()
         .get('/api/v1/version')
         .basicAuth({ user: key })
         .reply(200, [{ version }])
@@ -130,7 +131,7 @@ describe('rdme openapi', () => {
       ['OpenAPI 3.1', 'json', '3.1'],
       ['OpenAPI 3.1', 'yaml', '3.1'],
     ])('should support updating a %s definition (format: %s)', async (_, format, specVersion) => {
-      const mock = nock(config.get('host'))
+      const mock = getApiNock()
         .put(`/api/v1/api-specification/${id}`, body => body.match('form-data; name="spec"'))
         .basicAuth({ user: key })
         .reply(201, { _id: 1 }, { location: exampleRefLocation });
@@ -148,7 +149,7 @@ describe('rdme openapi', () => {
     });
 
     it('should still support `token`', async () => {
-      const mock = nock(config.get('host'))
+      const mock = getApiNock()
         .put(`/api/v1/api-specification/${id}`, body => body.match('form-data; name="spec"'))
         .basicAuth({ user: key })
         .reply(201, { _id: 1 }, { location: exampleRefLocation });
@@ -172,7 +173,7 @@ describe('rdme openapi', () => {
     });
 
     it('should return warning if providing `id` and `version`', async () => {
-      const mock = nock(config.get('host'))
+      const mock = getApiNock()
         .put(`/api/v1/api-specification/${id}`, body => body.match('form-data; name="spec"'))
         .basicAuth({ user: key })
         .reply(201, { _id: 1 }, { location: exampleRefLocation });
@@ -211,7 +212,7 @@ describe('rdme openapi', () => {
         ],
       };
 
-      const mock = nock(config.get('host')).get(`/api/v1/version/${invalidVersion}`).reply(404, errorObject);
+      const mock = getApiNock().get(`/api/v1/version/${invalidVersion}`).reply(404, errorObject);
 
       await expect(
         openapi.run({
@@ -230,7 +231,7 @@ describe('rdme openapi', () => {
         newVersion: '1.0.1',
       });
 
-      const mock = nock(config.get('host'))
+      const mock = getApiNock()
         .get('/api/v1/version')
         .basicAuth({ user: key })
         .reply(200, [{ version: '1.0.0' }])
@@ -254,7 +255,7 @@ describe('rdme openapi', () => {
 
   it('should bundle and upload the expected content', async () => {
     let requestBody = null;
-    const mock = nock(config.get('host'))
+    const mock = getApiNock()
       .get('/api/v1/api-specification')
       .basicAuth({ user: key })
       .reply(200, [])
@@ -304,7 +305,7 @@ describe('rdme openapi', () => {
         ],
       };
 
-      const mock = nock(config.get('host')).get('/api/v1/version').reply(401, errorObject);
+      const mock = getApiNock().get('/api/v1/version').reply(401, errorObject);
 
       await expect(
         openapi.run({ key, spec: require.resolve('@readme/oas-examples/3.1/json/petstore.json') })
@@ -314,7 +315,7 @@ describe('rdme openapi', () => {
     });
 
     it('should error if no file was provided or able to be discovered', async () => {
-      const mock = nock(config.get('host'))
+      const mock = getApiNock()
         .get(`/api/v1/version/${version}`)
         .basicAuth({ user: key })
         .reply(200, { version: '1.0.0' });
@@ -344,7 +345,7 @@ describe('rdme openapi', () => {
         help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".',
       };
 
-      const mock = nock(config.get('host'))
+      const mock = getApiNock()
         .get(`/api/v1/version/${version}`)
         .basicAuth({ user: key })
         .reply(200, { version: '1.0.0' })
@@ -376,7 +377,7 @@ describe('rdme openapi', () => {
         help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".',
       };
 
-      const mock = nock(config.get('host'))
+      const mock = getApiNock()
         .get(`/api/v1/version/${version}`)
         .basicAuth({ user: key })
         .reply(200, { version: '1.0.0' })
@@ -396,7 +397,7 @@ describe('rdme openapi', () => {
     });
 
     it('should error if API errors (generic upload error)', async () => {
-      const mock = nock(config.get('host'))
+      const mock = getApiNock()
         .get(`/api/v1/version/${version}`)
         .basicAuth({ user: key })
         .reply(200, { version: '1.0.0' })
@@ -416,7 +417,7 @@ describe('rdme openapi', () => {
     });
 
     it('should error if API errors (request timeout)', async () => {
-      const mock = nock(config.get('host'))
+      const mock = getApiNock()
         .get(`/api/v1/version/${version}`)
         .basicAuth({ user: key })
         .reply(200, { version: '1.0.0' })
diff --git a/__tests__/cmds/versions.test.js b/__tests__/cmds/versions.test.js
index f6100855f..c0bcbe5ab 100644
--- a/__tests__/cmds/versions.test.js
+++ b/__tests__/cmds/versions.test.js
@@ -1,7 +1,7 @@
 const nock = require('nock');
-const config = require('config');
 const promptHandler = require('../../src/lib/prompts');
 const APIError = require('../../src/lib/apiError');
+const apiNock = require('../get-api-nock');
 
 const VersionsCommand = require('../../src/cmds/versions');
 const CreateVersionCommand = require('../../src/cmds/versions/create');
@@ -49,7 +49,7 @@ describe('rdme versions*', () => {
     });
 
     it('should make a request to get a list of existing versions', async () => {
-      const mockRequest = nock(config.get('host'))
+      const mockRequest = apiNock()
         .get('/api/v1/version')
         .basicAuth({ user: key })
         .reply(200, [versionPayload, version2Payload]);
@@ -61,7 +61,7 @@ describe('rdme versions*', () => {
     });
 
     it('should make a request to get a list of existing versions and return them in a raw format', async () => {
-      const mockRequest = nock(config.get('host'))
+      const mockRequest = apiNock()
         .get('/api/v1/version')
         .basicAuth({ user: key })
         .reply(200, [versionPayload, version2Payload]);
@@ -72,7 +72,7 @@ describe('rdme versions*', () => {
     });
 
     it('should get a specific version object if version flag provided', async () => {
-      const mockRequest = nock(config.get('host'))
+      const mockRequest = apiNock()
         .get(`/api/v1/version/${version}`)
         .basicAuth({ user: key })
         .reply(200, versionPayload);
@@ -84,7 +84,7 @@ describe('rdme versions*', () => {
     });
 
     it('should get a specific version object if version flag provided and return it in a raw format', async () => {
-      const mockRequest = nock(config.get('host'))
+      const mockRequest = apiNock()
         .get(`/api/v1/version/${version}`)
         .basicAuth({ user: key })
         .reply(200, versionPayload);
@@ -99,19 +99,17 @@ describe('rdme versions*', () => {
     const createVersion = new CreateVersionCommand();
 
     it('should error if no api key provided', () => {
-      return createVersion.run({}).catch(err => {
-        expect(err.message).toBe('No project API key provided. Please use `--key`.');
-      });
+      return expect(createVersion.run({})).rejects.toThrow('No project API key provided. Please use `--key`.');
     });
 
-    it('should get a specific version object', () => {
+    it('should create a specific version', async () => {
       promptHandler.createVersionPrompt.mockResolvedValue({
         is_stable: true,
         is_beta: false,
         from: '1.0.0',
       });
 
-      const mockRequest = nock(config.get('host'))
+      const mockRequest = apiNock()
         .get('/api/v1/version')
         .basicAuth({ user: key })
         .reply(200, [{ version }, { version }])
@@ -119,9 +117,8 @@ describe('rdme versions*', () => {
         .basicAuth({ user: key })
         .reply(201, { version });
 
-      return createVersion.run({ key, version }).then(() => {
-        mockRequest.done();
-      });
+      await expect(createVersion.run({ key, version })).resolves.toBe('Version 1.0.0 created successfully.');
+      mockRequest.done();
     });
 
     it('should catch any post request errors', async () => {
@@ -138,10 +135,7 @@ describe('rdme versions*', () => {
         help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".',
       };
 
-      const mockRequest = nock(config.get('host'))
-        .post('/api/v1/version')
-        .basicAuth({ user: key })
-        .reply(400, errorResponse);
+      const mockRequest = apiNock().post('/api/v1/version').basicAuth({ user: key }).reply(400, errorResponse);
 
       await expect(createVersion.run({ key, version, fork: '0.0.5' })).rejects.toStrictEqual(
         new APIError(errorResponse)
@@ -160,7 +154,7 @@ describe('rdme versions*', () => {
     });
 
     it('should delete a specific version', async () => {
-      const mockRequest = nock(config.get('host'))
+      const mockRequest = apiNock()
         .delete(`/api/v1/version/${version}`)
         .basicAuth({ user: key })
         .reply(200, { removed: true })
@@ -181,7 +175,7 @@ describe('rdme versions*', () => {
         help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".',
       };
 
-      const mockRequest = nock(config.get('host'))
+      const mockRequest = apiNock()
         .delete(`/api/v1/version/${version}`)
         .basicAuth({ user: key })
         .reply(404, errorResponse)
@@ -210,7 +204,7 @@ describe('rdme versions*', () => {
         is_deprecated: true,
       });
 
-      const mockRequest = nock(config.get('host'))
+      const mockRequest = apiNock()
         .get(`/api/v1/version/${version}`)
         .basicAuth({ user: key })
         .reply(200, { version })
@@ -238,7 +232,7 @@ describe('rdme versions*', () => {
         help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".',
       };
 
-      const mockRequest = nock(config.get('host'))
+      const mockRequest = apiNock()
         .get(`/api/v1/version/${version}`)
         .basicAuth({ user: key })
         .reply(200, { version })
diff --git a/__tests__/get-api-nock.js b/__tests__/get-api-nock.js
new file mode 100644
index 000000000..451b63a07
--- /dev/null
+++ b/__tests__/get-api-nock.js
@@ -0,0 +1,15 @@
+const nock = require('nock');
+const config = require('config');
+const pkg = require('../package.json');
+
+const userAgent = `rdme/${pkg.version}`;
+
+module.exports = function () {
+  return nock(config.get('host'), {
+    reqheaders: {
+      'User-Agent': userAgent,
+    },
+  });
+};
+
+module.exports.userAgent = userAgent;
diff --git a/__tests__/lib/commands.test.js b/__tests__/lib/commands.test.js
index 1a683dd41..2ecc6c634 100644
--- a/__tests__/lib/commands.test.js
+++ b/__tests__/lib/commands.test.js
@@ -1,3 +1,4 @@
+/* eslint-disable jest/no-conditional-expect */
 const commands = require('../../src/lib/commands');
 
 describe('utils', () => {
diff --git a/__tests__/lib/prompts.test.js b/__tests__/lib/prompts.test.js
index 1e5ae1de4..45cc46083 100644
--- a/__tests__/lib/prompts.test.js
+++ b/__tests__/lib/prompts.test.js
@@ -48,6 +48,7 @@ describe('prompt test bed', () => {
             break;
 
           case 'versionSelection':
+            // eslint-disable-next-line jest/no-conditional-expect
             await expect(prompt.skip()).resolves.toBe(true);
             break;
 
diff --git a/package.json b/package.json
index 00d96409d..905a80870 100644
--- a/package.json
+++ b/package.json
@@ -81,6 +81,7 @@
       "./__tests__/set-node-env"
     ],
     "testPathIgnorePatterns": [
+      "./__tests__/get-api-nock",
       "./__tests__/set-node-env"
     ]
   },
diff --git a/src/cmds/docs/edit.js b/src/cmds/docs/edit.js
index 1b94474c6..72f9f485a 100644
--- a/src/cmds/docs/edit.js
+++ b/src/cmds/docs/edit.js
@@ -5,8 +5,8 @@ const { promisify } = require('util');
 const APIError = require('../../lib/apiError');
 const { cleanHeaders } = require('../../lib/cleanHeaders');
 const { getProjectVersion } = require('../../lib/versionSelect');
-const { handleRes } = require('../../lib/handleRes');
-const fetch = require('node-fetch');
+const fetch = require('../../lib/fetch');
+const { handleRes } = require('../../lib/fetch');
 
 const writeFile = promisify(fs.writeFile);
 const readFile = promisify(fs.readFile);
diff --git a/src/cmds/docs/index.js b/src/cmds/docs/index.js
index 91a1f7632..ebc5a963a 100644
--- a/src/cmds/docs/index.js
+++ b/src/cmds/docs/index.js
@@ -7,8 +7,8 @@ const frontMatter = require('gray-matter');
 const { promisify } = require('util');
 const { cleanHeaders } = require('../../lib/cleanHeaders');
 const { getProjectVersion } = require('../../lib/versionSelect');
-const { handleRes } = require('../../lib/handleRes');
-const fetch = require('node-fetch');
+const fetch = require('../../lib/fetch');
+const { handleRes } = require('../../lib/fetch');
 
 const readFile = promisify(fs.readFile);
 
diff --git a/src/cmds/login.js b/src/cmds/login.js
index 50a3ec0a3..470ccd19a 100644
--- a/src/cmds/login.js
+++ b/src/cmds/login.js
@@ -4,8 +4,8 @@ const { validate: isEmail } = require('isemail');
 const { promisify } = require('util');
 const read = promisify(require('read'));
 const configStore = require('../lib/configstore');
-const { handleRes } = require('../lib/handleRes');
-const fetch = require('node-fetch');
+const fetch = require('../lib/fetch');
+const { handleRes } = require('../lib/fetch');
 
 const testing = process.env.NODE_ENV === 'testing';
 
diff --git a/src/cmds/openapi.js b/src/cmds/openapi.js
index 73c12f784..496fe04cc 100644
--- a/src/cmds/openapi.js
+++ b/src/cmds/openapi.js
@@ -7,7 +7,7 @@ const promptOpts = require('../lib/prompts');
 const APIError = require('../lib/apiError');
 const { cleanHeaders } = require('../lib/cleanHeaders');
 const { getProjectVersion } = require('../lib/versionSelect');
-const fetch = require('node-fetch');
+const fetch = require('../lib/fetch');
 const FormData = require('form-data');
 const parse = require('parse-link-header');
 const { file: tmpFile } = require('tmp-promise');
diff --git a/src/cmds/versions/create.js b/src/cmds/versions/create.js
index 940bf9c31..9b9f845bf 100644
--- a/src/cmds/versions/create.js
+++ b/src/cmds/versions/create.js
@@ -3,8 +3,8 @@ const semver = require('semver');
 const { prompt } = require('enquirer');
 const promptOpts = require('../../lib/prompts');
 const { cleanHeaders } = require('../../lib/cleanHeaders');
-const { handleRes } = require('../../lib/handleRes');
-const fetch = require('node-fetch');
+const fetch = require('../../lib/fetch');
+const { handleRes } = require('../../lib/fetch');
 
 module.exports = class CreateVersionCommand {
   constructor() {
diff --git a/src/cmds/versions/delete.js b/src/cmds/versions/delete.js
index 599e4f01f..1f938e9d3 100644
--- a/src/cmds/versions/delete.js
+++ b/src/cmds/versions/delete.js
@@ -1,8 +1,8 @@
 const config = require('config');
 const { getProjectVersion } = require('../../lib/versionSelect');
 const { cleanHeaders } = require('../../lib/cleanHeaders');
-const { handleRes } = require('../../lib/handleRes');
-const fetch = require('node-fetch');
+const fetch = require('../../lib/fetch');
+const { handleRes } = require('../../lib/fetch');
 
 module.exports = class DeleteVersionCommand {
   constructor() {
diff --git a/src/cmds/versions/index.js b/src/cmds/versions/index.js
index 22cbbb9f2..9308389b3 100644
--- a/src/cmds/versions/index.js
+++ b/src/cmds/versions/index.js
@@ -3,8 +3,8 @@ const Table = require('cli-table');
 const config = require('config');
 const CreateVersionCmd = require('./create');
 const { cleanHeaders } = require('../../lib/cleanHeaders');
-const fetch = require('node-fetch');
-const { handleRes } = require('../../lib/handleRes');
+const fetch = require('../../lib/fetch');
+const { handleRes } = require('../../lib/fetch');
 
 module.exports = class VersionsCommand {
   constructor() {
diff --git a/src/cmds/versions/update.js b/src/cmds/versions/update.js
index 71b44223c..dffece2f1 100644
--- a/src/cmds/versions/update.js
+++ b/src/cmds/versions/update.js
@@ -3,8 +3,8 @@ const { prompt } = require('enquirer');
 const promptOpts = require('../../lib/prompts');
 const { cleanHeaders } = require('../../lib/cleanHeaders');
 const { getProjectVersion } = require('../../lib/versionSelect');
-const fetch = require('node-fetch');
-const { handleRes } = require('../../lib/handleRes');
+const fetch = require('../../lib/fetch');
+const { handleRes } = require('../../lib/fetch');
 
 module.exports = class UpdateVersionCommand {
   constructor() {
diff --git a/src/lib/fetch.js b/src/lib/fetch.js
new file mode 100644
index 000000000..00d8aa165
--- /dev/null
+++ b/src/lib/fetch.js
@@ -0,0 +1,30 @@
+/* eslint-disable no-param-reassign */
+const fetch = require('node-fetch');
+const pkg = require('../../package.json');
+const APIError = require('./apiError');
+
+/**
+ * Wrapper for the `fetch` API so we can add an rdme user agent to all API requests.
+ *
+ */
+module.exports = (url, options = {}) => {
+  const userAgent = `rdme/${pkg.version}`;
+
+  if (!options.headers) {
+    options.headers = {
+      'User-Agent': userAgent,
+    };
+  } else {
+    options.headers['User-Agent'] = userAgent;
+  }
+
+  return fetch(url, options);
+};
+
+module.exports.handleRes = async function handleRes(res) {
+  const body = await res.json();
+  if (body.error) {
+    return Promise.reject(new APIError(body));
+  }
+  return body;
+};
diff --git a/src/lib/handleRes.js b/src/lib/handleRes.js
deleted file mode 100644
index e29ede3fd..000000000
--- a/src/lib/handleRes.js
+++ /dev/null
@@ -1,11 +0,0 @@
-const APIError = require('./apiError');
-
-async function handleRes(res) {
-  const body = await res.json();
-  if (body.error) {
-    return Promise.reject(new APIError(body));
-  }
-  return body;
-}
-
-module.exports = { handleRes };
diff --git a/src/lib/versionSelect.js b/src/lib/versionSelect.js
index b1c5c2d31..5d5043d81 100644
--- a/src/lib/versionSelect.js
+++ b/src/lib/versionSelect.js
@@ -1,10 +1,10 @@
 const { prompt } = require('enquirer');
 const promptOpts = require('./prompts');
 const { cleanHeaders } = require('./cleanHeaders');
-const fetch = require('node-fetch');
 const config = require('config');
 const APIError = require('./apiError');
-const { handleRes } = require('./handleRes');
+const fetch = require('./fetch');
+const { handleRes } = require('./fetch');
 
 async function getProjectVersion(versionFlag, key, allowNewVersion) {
   try {

From 4ed4598329033419a03e6c2c43cd22bc48df9960 Mon Sep 17 00:00:00 2001
From: Jon Ursenbach <jon@ursenba.ch>
Date: Tue, 1 Feb 2022 18:01:56 -0800
Subject: [PATCH 2/4] feat: consolidating the cleanHeaders library into the new
 fetch lib

---
 __tests__/lib/cleanHeaders.test.js | 26 ------------
 __tests__/lib/fetch.test.js        | 63 ++++++++++++++++++++++++++++++
 src/cmds/docs/edit.js              |  3 +-
 src/cmds/docs/index.js             |  3 +-
 src/cmds/openapi.js                |  2 +-
 src/cmds/versions/create.js        |  3 +-
 src/cmds/versions/delete.js        |  3 +-
 src/cmds/versions/index.js         |  3 +-
 src/cmds/versions/update.js        |  3 +-
 src/lib/cleanHeaders.js            | 23 -----------
 src/lib/fetch.js                   | 23 +++++++++++
 src/lib/versionSelect.js           |  3 +-
 12 files changed, 94 insertions(+), 64 deletions(-)
 delete mode 100644 __tests__/lib/cleanHeaders.test.js
 create mode 100644 __tests__/lib/fetch.test.js
 delete mode 100644 src/lib/cleanHeaders.js

diff --git a/__tests__/lib/cleanHeaders.test.js b/__tests__/lib/cleanHeaders.test.js
deleted file mode 100644
index 45ac48659..000000000
--- a/__tests__/lib/cleanHeaders.test.js
+++ /dev/null
@@ -1,26 +0,0 @@
-const { cleanHeaders } = require('../../src/lib/cleanHeaders');
-
-describe('cleanHeaders', () => {
-  it('should b64-encode key in ReadMe-friendly format', () => {
-    expect(cleanHeaders('test')).toStrictEqual({ Authorization: 'Basic dGVzdDo=' });
-  });
-
-  it('should filter out undefined headers', () => {
-    expect(cleanHeaders('test', { 'x-readme-version': undefined })).toStrictEqual({ Authorization: 'Basic dGVzdDo=' });
-  });
-
-  it('should filter out null headers', () => {
-    expect(cleanHeaders('test', { 'x-readme-version': undefined, Accept: null })).toStrictEqual({
-      Authorization: 'Basic dGVzdDo=',
-    });
-  });
-
-  it('should pass in properly defined headers', () => {
-    expect(
-      cleanHeaders('test', { 'x-readme-version': undefined, Accept: null, 'Content-Type': 'application/json' })
-    ).toStrictEqual({
-      Authorization: 'Basic dGVzdDo=',
-      'Content-Type': 'application/json',
-    });
-  });
-});
diff --git a/__tests__/lib/fetch.test.js b/__tests__/lib/fetch.test.js
new file mode 100644
index 000000000..3df480fbe
--- /dev/null
+++ b/__tests__/lib/fetch.test.js
@@ -0,0 +1,63 @@
+const config = require('config');
+const fetch = require('../../src/lib/fetch');
+const { cleanHeaders, handleRes } = require('../../src/lib/fetch');
+const getApiNock = require('../get-api-nock');
+
+describe('#fetch()', () => {
+  it('should wrap all requests with a rdme User-Agent', async () => {
+    const key = 'API_KEY';
+
+    const mock = getApiNock()
+      .get('/api/v1')
+      .basicAuth({ user: key })
+      .reply(200, function () {
+        return this.req.headers['user-agent'];
+      });
+
+    const userAgent = await fetch(`${config.get('host')}/api/v1`, {
+      method: 'get',
+      headers: cleanHeaders(key),
+    }).then(handleRes);
+
+    expect(userAgent.shift()).toMatch(/rdme\/\d+.\d+.\d+/);
+    mock.done();
+  });
+
+  it('should support if we dont supply any other options with the request', async () => {
+    const mock = getApiNock()
+      .get('/api/v1/doesnt-need-auth')
+      .reply(200, function () {
+        return this.req.headers['user-agent'];
+      });
+
+    const userAgent = await fetch(`${config.get('host')}/api/v1/doesnt-need-auth`).then(handleRes);
+
+    expect(userAgent.shift()).toMatch(/rdme\/\d+.\d+.\d+/);
+    mock.done();
+  });
+});
+
+describe('#cleanHeaders()', () => {
+  it('should base64-encode key in ReadMe-friendly format', () => {
+    expect(cleanHeaders('test')).toStrictEqual({ Authorization: 'Basic dGVzdDo=' });
+  });
+
+  it('should filter out undefined headers', () => {
+    expect(cleanHeaders('test', { 'x-readme-version': undefined })).toStrictEqual({ Authorization: 'Basic dGVzdDo=' });
+  });
+
+  it('should filter out null headers', () => {
+    expect(cleanHeaders('test', { 'x-readme-version': undefined, Accept: null })).toStrictEqual({
+      Authorization: 'Basic dGVzdDo=',
+    });
+  });
+
+  it('should pass in properly defined headers', () => {
+    expect(
+      cleanHeaders('test', { 'x-readme-version': undefined, Accept: null, 'Content-Type': 'application/json' })
+    ).toStrictEqual({
+      Authorization: 'Basic dGVzdDo=',
+      'Content-Type': 'application/json',
+    });
+  });
+});
diff --git a/src/cmds/docs/edit.js b/src/cmds/docs/edit.js
index 72f9f485a..a42ab78e9 100644
--- a/src/cmds/docs/edit.js
+++ b/src/cmds/docs/edit.js
@@ -3,10 +3,9 @@ const fs = require('fs');
 const editor = require('editor');
 const { promisify } = require('util');
 const APIError = require('../../lib/apiError');
-const { cleanHeaders } = require('../../lib/cleanHeaders');
 const { getProjectVersion } = require('../../lib/versionSelect');
 const fetch = require('../../lib/fetch');
-const { handleRes } = require('../../lib/fetch');
+const { cleanHeaders, handleRes } = require('../../lib/fetch');
 
 const writeFile = promisify(fs.writeFile);
 const readFile = promisify(fs.readFile);
diff --git a/src/cmds/docs/index.js b/src/cmds/docs/index.js
index ebc5a963a..729811a8a 100644
--- a/src/cmds/docs/index.js
+++ b/src/cmds/docs/index.js
@@ -5,10 +5,9 @@ const config = require('config');
 const crypto = require('crypto');
 const frontMatter = require('gray-matter');
 const { promisify } = require('util');
-const { cleanHeaders } = require('../../lib/cleanHeaders');
 const { getProjectVersion } = require('../../lib/versionSelect');
 const fetch = require('../../lib/fetch');
-const { handleRes } = require('../../lib/fetch');
+const { cleanHeaders, handleRes } = require('../../lib/fetch');
 
 const readFile = promisify(fs.readFile);
 
diff --git a/src/cmds/openapi.js b/src/cmds/openapi.js
index 496fe04cc..3db39a003 100644
--- a/src/cmds/openapi.js
+++ b/src/cmds/openapi.js
@@ -5,9 +5,9 @@ const { prompt } = require('enquirer');
 const OASNormalize = require('oas-normalize');
 const promptOpts = require('../lib/prompts');
 const APIError = require('../lib/apiError');
-const { cleanHeaders } = require('../lib/cleanHeaders');
 const { getProjectVersion } = require('../lib/versionSelect');
 const fetch = require('../lib/fetch');
+const { cleanHeaders } = require('../lib/fetch');
 const FormData = require('form-data');
 const parse = require('parse-link-header');
 const { file: tmpFile } = require('tmp-promise');
diff --git a/src/cmds/versions/create.js b/src/cmds/versions/create.js
index 9b9f845bf..e9b7b6c6d 100644
--- a/src/cmds/versions/create.js
+++ b/src/cmds/versions/create.js
@@ -2,9 +2,8 @@ const config = require('config');
 const semver = require('semver');
 const { prompt } = require('enquirer');
 const promptOpts = require('../../lib/prompts');
-const { cleanHeaders } = require('../../lib/cleanHeaders');
 const fetch = require('../../lib/fetch');
-const { handleRes } = require('../../lib/fetch');
+const { cleanHeaders, handleRes } = require('../../lib/fetch');
 
 module.exports = class CreateVersionCommand {
   constructor() {
diff --git a/src/cmds/versions/delete.js b/src/cmds/versions/delete.js
index 1f938e9d3..b899247d4 100644
--- a/src/cmds/versions/delete.js
+++ b/src/cmds/versions/delete.js
@@ -1,8 +1,7 @@
 const config = require('config');
 const { getProjectVersion } = require('../../lib/versionSelect');
-const { cleanHeaders } = require('../../lib/cleanHeaders');
 const fetch = require('../../lib/fetch');
-const { handleRes } = require('../../lib/fetch');
+const { cleanHeaders, handleRes } = require('../../lib/fetch');
 
 module.exports = class DeleteVersionCommand {
   constructor() {
diff --git a/src/cmds/versions/index.js b/src/cmds/versions/index.js
index 9308389b3..dc626c682 100644
--- a/src/cmds/versions/index.js
+++ b/src/cmds/versions/index.js
@@ -2,9 +2,8 @@ const chalk = require('chalk');
 const Table = require('cli-table');
 const config = require('config');
 const CreateVersionCmd = require('./create');
-const { cleanHeaders } = require('../../lib/cleanHeaders');
 const fetch = require('../../lib/fetch');
-const { handleRes } = require('../../lib/fetch');
+const { cleanHeaders, handleRes } = require('../../lib/fetch');
 
 module.exports = class VersionsCommand {
   constructor() {
diff --git a/src/cmds/versions/update.js b/src/cmds/versions/update.js
index dffece2f1..c1fc92a0b 100644
--- a/src/cmds/versions/update.js
+++ b/src/cmds/versions/update.js
@@ -1,10 +1,9 @@
 const config = require('config');
 const { prompt } = require('enquirer');
 const promptOpts = require('../../lib/prompts');
-const { cleanHeaders } = require('../../lib/cleanHeaders');
 const { getProjectVersion } = require('../../lib/versionSelect');
 const fetch = require('../../lib/fetch');
-const { handleRes } = require('../../lib/fetch');
+const { cleanHeaders, handleRes } = require('../../lib/fetch');
 
 module.exports = class UpdateVersionCommand {
   constructor() {
diff --git a/src/lib/cleanHeaders.js b/src/lib/cleanHeaders.js
deleted file mode 100644
index 5f1b0786a..000000000
--- a/src/lib/cleanHeaders.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/**
- * Returns the basic auth header and any other defined headers for use in node-fetch API calls.
- * @param {string} key The ReadMe project API key
- * @param {Object} inputHeaders Any additional headers to be cleaned
- * @returns An object with cleaned request headers for usage in the node-fetch requests to the ReadMe API.
- */
-function cleanHeaders(key, inputHeaders = {}) {
-  const encodedKey = Buffer.from(`${key}:`).toString('base64');
-  const headers = {
-    Authorization: `Basic ${encodedKey}`,
-  };
-
-  Object.keys(inputHeaders).forEach(header => {
-    // For some reason, node-fetch will send in the string 'undefined'
-    // if you pass in an undefined value for a header,
-    // so that's why headers are added incrementally.
-    if (typeof inputHeaders[header] === 'string') headers[header] = inputHeaders[header];
-  });
-
-  return headers;
-}
-
-module.exports = { cleanHeaders };
diff --git a/src/lib/fetch.js b/src/lib/fetch.js
index 00d8aa165..f27dc836d 100644
--- a/src/lib/fetch.js
+++ b/src/lib/fetch.js
@@ -28,3 +28,26 @@ module.exports.handleRes = async function handleRes(res) {
   }
   return body;
 };
+
+/**
+ * Returns the basic auth header and any other defined headers for use in node-fetch API calls.
+ *
+ * @param {string} key The ReadMe project API key
+ * @param {Object} inputHeaders Any additional headers to be cleaned
+ * @returns An object with cleaned request headers for usage in the node-fetch requests to the ReadMe API.
+ */
+module.exports.cleanHeaders = function cleanHeaders(key, inputHeaders = {}) {
+  const encodedKey = Buffer.from(`${key}:`).toString('base64');
+  const headers = {
+    Authorization: `Basic ${encodedKey}`,
+  };
+
+  Object.keys(inputHeaders).forEach(header => {
+    // For some reason, node-fetch will send in the string 'undefined'
+    // if you pass in an undefined value for a header,
+    // so that's why headers are added incrementally.
+    if (typeof inputHeaders[header] === 'string') headers[header] = inputHeaders[header];
+  });
+
+  return headers;
+};
diff --git a/src/lib/versionSelect.js b/src/lib/versionSelect.js
index 5d5043d81..f33edcf61 100644
--- a/src/lib/versionSelect.js
+++ b/src/lib/versionSelect.js
@@ -1,10 +1,9 @@
 const { prompt } = require('enquirer');
 const promptOpts = require('./prompts');
-const { cleanHeaders } = require('./cleanHeaders');
 const config = require('config');
 const APIError = require('./apiError');
 const fetch = require('./fetch');
-const { handleRes } = require('./fetch');
+const { cleanHeaders, handleRes } = require('./fetch');
 
 async function getProjectVersion(versionFlag, key, allowNewVersion) {
   try {

From cbc042cff3770cd070e3167e9bd2600dc0cc7f16 Mon Sep 17 00:00:00 2001
From: Jon Ursenbach <jon@ursenba.ch>
Date: Tue, 1 Feb 2022 18:08:36 -0800
Subject: [PATCH 3/4] fix: standardizing a variable name

---
 __tests__/cmds/login.test.js    | 10 +++++-----
 __tests__/cmds/versions.test.js | 22 +++++++++++-----------
 2 files changed, 16 insertions(+), 16 deletions(-)

diff --git a/__tests__/cmds/login.test.js b/__tests__/cmds/login.test.js
index b3034b990..ac3067d18 100644
--- a/__tests__/cmds/login.test.js
+++ b/__tests__/cmds/login.test.js
@@ -2,7 +2,7 @@ const nock = require('nock');
 const configStore = require('../../src/lib/configstore');
 const Command = require('../../src/cmds/login');
 const APIError = require('../../src/lib/apiError');
-const apiNock = require('../get-api-nock');
+const getApiNock = require('../get-api-nock');
 
 const cmd = new Command();
 
@@ -32,7 +32,7 @@ describe('rdme login', () => {
   it('should post to /login on the API', async () => {
     const apiKey = 'abcdefg';
 
-    const mock = apiNock().post('/api/v1/login', { email, password, project }).reply(200, { apiKey });
+    const mock = getApiNock().post('/api/v1/login', { email, password, project }).reply(200, { apiKey });
 
     await expect(cmd.run({ email, password, project })).resolves.toMatchSnapshot();
 
@@ -52,7 +52,7 @@ describe('rdme login', () => {
       help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".',
     };
 
-    const mock = apiNock().post('/api/v1/login', { email, password, project }).reply(401, errorResponse);
+    const mock = getApiNock().post('/api/v1/login', { email, password, project }).reply(401, errorResponse);
 
     await expect(cmd.run({ email, password, project })).rejects.toStrictEqual(new APIError(errorResponse));
     mock.done();
@@ -66,7 +66,7 @@ describe('rdme login', () => {
       help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".',
     };
 
-    const mock = apiNock().post('/api/v1/login', { email, password, project }).reply(401, errorResponse);
+    const mock = getApiNock().post('/api/v1/login', { email, password, project }).reply(401, errorResponse);
 
     await expect(cmd.run({ email, password, project })).rejects.toStrictEqual(new APIError(errorResponse));
     mock.done();
@@ -75,7 +75,7 @@ describe('rdme login', () => {
   it('should send 2fa token if provided', async () => {
     const token = '123456';
 
-    const mock = apiNock().post('/api/v1/login', { email, password, project, token }).reply(200, { apiKey: '123' });
+    const mock = getApiNock().post('/api/v1/login', { email, password, project, token }).reply(200, { apiKey: '123' });
 
     await expect(cmd.run({ email, password, project, token })).resolves.toMatchSnapshot();
     mock.done();
diff --git a/__tests__/cmds/versions.test.js b/__tests__/cmds/versions.test.js
index c0bcbe5ab..791864725 100644
--- a/__tests__/cmds/versions.test.js
+++ b/__tests__/cmds/versions.test.js
@@ -1,7 +1,7 @@
 const nock = require('nock');
 const promptHandler = require('../../src/lib/prompts');
 const APIError = require('../../src/lib/apiError');
-const apiNock = require('../get-api-nock');
+const getApiNock = require('../get-api-nock');
 
 const VersionsCommand = require('../../src/cmds/versions');
 const CreateVersionCommand = require('../../src/cmds/versions/create');
@@ -49,7 +49,7 @@ describe('rdme versions*', () => {
     });
 
     it('should make a request to get a list of existing versions', async () => {
-      const mockRequest = apiNock()
+      const mockRequest = getApiNock()
         .get('/api/v1/version')
         .basicAuth({ user: key })
         .reply(200, [versionPayload, version2Payload]);
@@ -61,7 +61,7 @@ describe('rdme versions*', () => {
     });
 
     it('should make a request to get a list of existing versions and return them in a raw format', async () => {
-      const mockRequest = apiNock()
+      const mockRequest = getApiNock()
         .get('/api/v1/version')
         .basicAuth({ user: key })
         .reply(200, [versionPayload, version2Payload]);
@@ -72,7 +72,7 @@ describe('rdme versions*', () => {
     });
 
     it('should get a specific version object if version flag provided', async () => {
-      const mockRequest = apiNock()
+      const mockRequest = getApiNock()
         .get(`/api/v1/version/${version}`)
         .basicAuth({ user: key })
         .reply(200, versionPayload);
@@ -84,7 +84,7 @@ describe('rdme versions*', () => {
     });
 
     it('should get a specific version object if version flag provided and return it in a raw format', async () => {
-      const mockRequest = apiNock()
+      const mockRequest = getApiNock()
         .get(`/api/v1/version/${version}`)
         .basicAuth({ user: key })
         .reply(200, versionPayload);
@@ -109,7 +109,7 @@ describe('rdme versions*', () => {
         from: '1.0.0',
       });
 
-      const mockRequest = apiNock()
+      const mockRequest = getApiNock()
         .get('/api/v1/version')
         .basicAuth({ user: key })
         .reply(200, [{ version }, { version }])
@@ -135,7 +135,7 @@ describe('rdme versions*', () => {
         help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".',
       };
 
-      const mockRequest = apiNock().post('/api/v1/version').basicAuth({ user: key }).reply(400, errorResponse);
+      const mockRequest = getApiNock().post('/api/v1/version').basicAuth({ user: key }).reply(400, errorResponse);
 
       await expect(createVersion.run({ key, version, fork: '0.0.5' })).rejects.toStrictEqual(
         new APIError(errorResponse)
@@ -154,7 +154,7 @@ describe('rdme versions*', () => {
     });
 
     it('should delete a specific version', async () => {
-      const mockRequest = apiNock()
+      const mockRequest = getApiNock()
         .delete(`/api/v1/version/${version}`)
         .basicAuth({ user: key })
         .reply(200, { removed: true })
@@ -175,7 +175,7 @@ describe('rdme versions*', () => {
         help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".',
       };
 
-      const mockRequest = apiNock()
+      const mockRequest = getApiNock()
         .delete(`/api/v1/version/${version}`)
         .basicAuth({ user: key })
         .reply(404, errorResponse)
@@ -204,7 +204,7 @@ describe('rdme versions*', () => {
         is_deprecated: true,
       });
 
-      const mockRequest = apiNock()
+      const mockRequest = getApiNock()
         .get(`/api/v1/version/${version}`)
         .basicAuth({ user: key })
         .reply(200, { version })
@@ -232,7 +232,7 @@ describe('rdme versions*', () => {
         help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".',
       };
 
-      const mockRequest = apiNock()
+      const mockRequest = getApiNock()
         .get(`/api/v1/version/${version}`)
         .basicAuth({ user: key })
         .reply(200, { version })

From e992e07d0f810534e4fa5b7f06ced2b1e8c3f2a4 Mon Sep 17 00:00:00 2001
From: Jon Ursenbach <jon@ursenba.ch>
Date: Wed, 2 Feb 2022 09:36:39 -0800
Subject: [PATCH 4/4] fix: pr feedback

---
 src/lib/fetch.js | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/src/lib/fetch.js b/src/lib/fetch.js
index f27dc836d..332cce133 100644
--- a/src/lib/fetch.js
+++ b/src/lib/fetch.js
@@ -21,6 +21,12 @@ module.exports = (url, options = {}) => {
   return fetch(url, options);
 };
 
+/**
+ * Small handler for transforming responses from our API into JSON and if there's errors, throwing
+ * an APIError exception.
+ *
+ * @param {Response} res
+ */
 module.exports.handleRes = async function handleRes(res) {
   const body = await res.json();
   if (body.error) {