update dependencies and devDependencies
[squeep-indieauth-helper] / test / lib / communication.js
index f544d80f67304ce8e47468623368b635e487e1a7..332aa64921cd383d37cf73b155f2d6e754272949 100644 (file)
@@ -13,16 +13,15 @@ const dns = require('dns');
 const stubLogger = require('../stub-logger');
 const testData = require('../test-data/communication');
 
-const noExpectedException = 'did not get expected exception';
-
 describe('Communication', function () {
   let communication, options;
 
-  beforeEach(function () {
+  beforeEach(async function () {
     options = {};
     communication = new Communication(stubLogger, options);
+    await communication._init();
     stubLogger._reset();
-    sinon.stub(communication, 'axios');
+    sinon.stub(communication, 'got');
   });
   afterEach(function () {
     sinon.restore();
@@ -36,20 +35,19 @@ describe('Communication', function () {
     communication = new Communication(stubLogger);
   });
 
-  describe('Axios timing coverage', function () {
-    const request = {};
-    const response = {
-      config: request,
-    };
-    it('tags request', function () {
-      communication.axios.interceptors.request.handlers[0].fulfilled(request);
-      assert(request.startTimestampMs);
+  describe('_init', function () {
+    it('covers first use', async function () {
+      await communication._init({});
+      assert(communication.got.called);
     });
-    it('tags response', function () {
-      communication.axios.interceptors.response.handlers[0].fulfilled(response);
-      assert(response.elapsedTimeMs);
+  }); // _init
+
+  describe('_onRetry', function () {
+    it('covers', function () {
+      communication._onRetry(new Error('oh no'), 1);
+      assert(communication.logger.debug.called);
     });
-  }); // Axios timing coverage
+  }); // _onRetry
 
   describe('_challengeFromVerifier', function () {
     it('covers', function () {
@@ -69,12 +67,7 @@ describe('Communication', function () {
       assert.strictEqual(result.codeChallengeMethod, 'S256');
     });
     it('covers error', async function () {
-      try {
-        await Communication.generatePKCE(1);
-        assert.fail(noExpectedException);
-      } catch (e) {
-        assert(e instanceof RangeError);
-      }
+      await assert.rejects(() => Communication.generatePKCE(1));
     });
   }); // generatePKCE
 
@@ -104,12 +97,7 @@ describe('Communication', function () {
       const method = 'MD5';
       const challenge = 'xkfP7DUYDsnu07Kg6ogc8A';
       const verifier = 'VGhpcyBpcyBhIHNlY3JldC4u';
-      try {
-        Communication.verifyChallenge(challenge, verifier, method);
-        assert.fail(noExpectedException);
-      } catch (e) {
-        assert(e.message.includes('unsupported'));
-      }
+      assert.throws(() => Communication.verifyChallenge(challenge, verifier, method));
     });
   }); // verifyChallenge
 
@@ -139,76 +127,6 @@ describe('Communication', function () {
     });
   }); // userAgentString
 
-  describe('Axios Configurations', function () {
-    let requestUrl, expectedUrl;
-    beforeEach(function () {
-      requestUrl = 'https://example.com/client_id';
-      expectedUrl = 'https://example.com/client_id';
-    });
-    it('_axiosConfig', function () {
-      const method = 'GET';
-      const contentType = 'text/plain';
-      const body = undefined;
-      const params = {
-        'extra_parameter': 'foobar',
-      };
-      const urlObj = new URL(requestUrl);
-      const expectedUrlObj = new URL(`${requestUrl}?extra_parameter=foobar`);
-      const expected = {
-        method,
-        url: 'https://example.com/client_id',
-        headers: {
-          'Content-Type': 'text/plain',
-        },
-        params: expectedUrlObj.searchParams,
-        responseType: 'text',
-      };
-      const result = Communication._axiosConfig(method, urlObj, body, params, {
-        'Content-Type': contentType,
-      });
-      delete result.transformResponse;
-      assert.deepStrictEqual(result, expected);
-    });
-    it('_axiosConfig covers defaults', function () {
-      const method = 'OPTIONS';
-      const urlObj = new URL(requestUrl);
-      const expectedUrlObj = new URL(requestUrl);
-      const expected = {
-        method,
-        url: expectedUrl,
-        headers: {},
-        params: expectedUrlObj.searchParams,
-        responseType: 'text',
-      };
-      const result = Communication._axiosConfig(method, urlObj);
-      delete result.transformResponse;
-      assert.deepStrictEqual(result, expected);
-    });
-    it('covers data', function () {
-      const method = 'POST';
-      const body = Buffer.from('some data');
-      const params = {};
-      const urlObj = new URL(requestUrl);
-      const expected = {
-        method,
-        url: 'https://example.com/client_id',
-        data: body,
-        headers: {},
-        params: urlObj.searchParams,
-        responseType: 'text',
-      };
-      const result = Communication._axiosConfig(method, urlObj, body, params, {});
-      delete result.transformResponse;
-      assert.deepStrictEqual(result, expected);
-
-    });
-    it('covers null response transform', function () {
-      const urlObj = new URL(requestUrl);
-      const result = Communication._axiosConfig('GET', urlObj, undefined, {}, {});
-      result.transformResponse[0]();
-    });
-  }); // Axios Configurations
-
   describe('_baseUrlString', function () {
     it('covers no path', function () {
       const urlObj = new URL('https://example.com');
@@ -266,7 +184,7 @@ describe('Communication', function () {
         headers: {
           link: '<https://example.com/>; rel="self", <https://hub.example.com/>;rel="hub"',
         },
-        data: {},
+        body: {},
       }
     });
     it('covers', function () {
@@ -353,12 +271,12 @@ describe('Communication', function () {
       urlObj = new URL('https://thuza.ratfeathers.com/');
       response = {
         headers: Object.assign({}, testData.linkHeaders),
-        data: testData.hCardHtml,
+        body: Buffer.from(testData.hCardHtml),
       };
     });
     it('covers', async function () {
-      response.data = testData.hCardHtml;
-      communication.axios.resolves(response);
+      response.body = testData.hCardHtml;
+      communication.got.resolves(response);
       expected = {
         rels: {
           'authorization_endpoint': ['https://ia.squeep.com/auth'],
@@ -384,7 +302,7 @@ describe('Communication', function () {
             text: '',
           },
           'https://thuza.ratfeathers.com/': {
-            rels: ['self', 'canonical', 'author', 'me'],
+            rels: ['self', 'author', 'canonical', 'me'],
             text: 'Thuza',
           },
           'https://thuza.ratfeathers.com/image.png': {
@@ -405,8 +323,8 @@ describe('Communication', function () {
       result = await communication.fetchMicroformat(urlObj);
       assert.deepStrictEqual(result, expected);
     });
-    it('covers axios error', async function () {
-      communication.axios.rejects(new Error('blah'));
+    it('covers got error', async function () {
+      communication.got.rejects(new Error('blah'));
       expected = undefined;
 
       result = await communication.fetchMicroformat(urlObj);
@@ -414,9 +332,9 @@ describe('Communication', function () {
       assert.deepStrictEqual(result, expected);
     });
     it('covers non-parsable content', async function () {
-      response.data = 'some bare text';
+      response.body = 'some bare text';
       response.headers = {};
-      communication.axios.resolves(response);
+      communication.got.resolves(response);
       expected = {
         items: [],
         rels: {},
@@ -429,7 +347,7 @@ describe('Communication', function () {
     });
     it('covers non-utf8 content', async function () {
       response.headers['content-type'] = 'text/html; charset=ASCII';
-      communication.axios.resolves(response);
+      communication.got.resolves(response);
       expected = {
         rels: {
           'authorization_endpoint': ['https://ia.squeep.com/auth'],
@@ -455,7 +373,7 @@ describe('Communication', function () {
             text: '',
           },
           'https://thuza.ratfeathers.com/': {
-            rels: ['self', 'canonical', 'author', 'me'],
+            rels: ['self', 'author', 'canonical', 'me'],
             text: 'Thuza',
           },
           'https://thuza.ratfeathers.com/image.png': {
@@ -487,19 +405,19 @@ describe('Communication', function () {
       urlObj = new URL('https://thuza.ratfeathers.com/');
       response = {
         headers: Object.assign({}, testData.linkHeaders),
-        data: testData.hCardHtml,
+        body: testData.hCardHtml,
       };
     });
     it('covers', async function () {
-      communication.axios.resolves(response);
+      communication.got.resolves(response);
       expected = { foo: 'bar', baz: 123 };
-      response.data = JSON.stringify(expected);
+      response.body = expected;
 
       result = await communication.fetchJSON(urlObj);
       assert.deepStrictEqual(result, expected);
     });
-    it('covers axios error', async function () {
-      communication.axios.rejects(new Error('blah'));
+    it('covers got error', async function () {
+      communication.got.rejects(new Error('blah'));
       expected = undefined;
 
       result = await communication.fetchJSON(urlObj);
@@ -507,9 +425,11 @@ describe('Communication', function () {
       assert.deepStrictEqual(result, expected);
     });
     it('covers non-parsable content', async function () {
-      response.data = 'some bare text';
+      response.body = 'some bare text';
       response.headers = {};
-      communication.axios.resolves(response);
+      const error = new Error('oh no');
+      response.request = { options: { url: new URL('https://example.com/') } };
+      communication.got.rejects(new communication.Got.ParseError(error, response));
       expected = undefined;
 
       result = await communication.fetchJSON(urlObj);
@@ -522,8 +442,8 @@ describe('Communication', function () {
     let url, validationOptions;
     beforeEach(function () {
       url = 'https://example.com/';
-      options = {};
-      sinon.stub(dns, 'lookupAsync').resolves([{ family: 4, address: '10.11.12.14' }]);
+      validationOptions = {};
+      sinon.stub(dns.promises, 'lookup').resolves([{ family: 4, address: '10.11.12.14' }]);
     });
     it('rejects invalid url', async function () {
       url = 'bad url';
@@ -544,79 +464,39 @@ describe('Communication', function () {
     let url, validationOptions;
     beforeEach(function () {
       url = 'https://example.com/';
-      options = {};
-      sinon.stub(dns, 'lookupAsync').resolves([{ family: 4, address: '10.11.12.13' }]);
+      validationOptions = {};
+      sinon.stub(dns.promises, 'lookup').resolves([{ family: 4, address: '10.11.12.13' }]);
     });
     it('rejects invalid url', async function () {
-      try {
-        await communication.validateClientIdentifier('bad url');
-        assert.fail(noExpectedException);
-      } catch (e) {
-        assert(e instanceof ValidationError);
-      }
+      await assert.rejects(() => communication.validateClientIdentifier('bad url'), ValidationError);
     });
     it('rejects invalid scheme', async function () {
       url = 'ftp://example.com/';
-      try {
-        await communication.validateClientIdentifier(url, validationOptions);
-        assert.fail(noExpectedException);
-      } catch (e) {
-        assert(e instanceof ValidationError);
-      }
+      await assert.rejects(() => communication.validateClientIdentifier(url, validationOptions), ValidationError);
     });
     it('rejects fragment', async function () {
       url = 'https://example.com/#foo';
-      try {
-        await communication.validateClientIdentifier(url, validationOptions);
-        assert.fail(noExpectedException);
-      } catch (e) {
-        assert(e instanceof ValidationError);
-      }
+      await assert.rejects(() => communication.validateClientIdentifier(url, validationOptions), ValidationError);
     });
     it('rejects username', async function () {
       url = 'https://user@example.com/';
-      try {
-        await communication.validateClientIdentifier(url, validationOptions);
-        assert.fail(noExpectedException);
-      } catch (e) {
-        assert(e instanceof ValidationError);
-      }
+      await assert.rejects(() => communication.validateClientIdentifier(url, validationOptions), ValidationError);
     });
     it('rejects password', async function () {
       url = 'https://:foo@example.com/';
-      try {
-        await communication.validateClientIdentifier(url, validationOptions);
-        assert.fail(noExpectedException);
-      } catch (e) {
-        assert(e instanceof ValidationError);
-      }
+      await assert.rejects(() => communication.validateClientIdentifier(url, validationOptions), ValidationError);
     });
     it('rejects relative path', async function () {
       url = 'https://example.com/client/../sneaky';
-      try {
-        await communication.validateClientIdentifier(url, validationOptions);
-        assert.fail(noExpectedException);
-      } catch (e) {
-        assert(e instanceof ValidationError);
-      }
+      await assert.rejects(() => communication.validateClientIdentifier(url, validationOptions), ValidationError);
     });
     it('rejects ipv4', async function () {
       url = 'https://10.11.12.13/';
-      try {
-        await communication.validateClientIdentifier(url, validationOptions);
-        assert.fail(noExpectedException);
-      } catch (e) {
-        assert(e instanceof ValidationError);
-      }
+      await assert.rejects(() => communication.validateClientIdentifier(url, validationOptions), ValidationError);
     });
     it('rejects ipv6', async function () {
       url = 'https://[fd64:defa:00e5:caf4:0dff::ad39]/';
-      try {
-        await communication.validateClientIdentifier(url, validationOptions);
-        assert.fail(noExpectedException);
-      } catch (e) {
-        assert(e instanceof ValidationError);
-      }
+      await assert.rejects(() => communication.validateClientIdentifier(url, validationOptions), ValidationError);
     });
     it('accepts ipv4 loopback', async function () {
       url = 'https://127.0.0.1/';
@@ -629,12 +509,12 @@ describe('Communication', function () {
       assert.strictEqual(result.isLoopback, true);
     });
     it('accepts resolved ipv4 loopback', async function () {
-      dns.lookupAsync.resolves([{ family: 4, address: '127.0.0.1' }]);
+      dns.promises.lookup.resolves([{ family: 4, address: '127.0.0.1' }]);
       const result = await communication.validateClientIdentifier(url, validationOptions);
       assert.strictEqual(result.isLoopback, true);
     });
     it('accepts resolved ipv6 loopback', async function () {
-      dns.lookupAsync.resolves([{ family: 6, address: '::1' }]);
+      dns.promises.lookup.resolves([{ family: 6, address: '::1' }]);
       const result = await communication.validateClientIdentifier(url, validationOptions);
       assert.strictEqual(result.isLoopback, true);
     });
@@ -643,25 +523,15 @@ describe('Communication', function () {
       assert.strictEqual(result.isLoopback, false);
     });
     it('rejects resolution failure', async function () {
-      dns.lookupAsync.rejects(new Error('oh no'));
-      try {
-        await communication.validateClientIdentifier(url, validationOptions);
-        assert.fail(noExpectedException);
-      } catch (e) {
-        assert(e instanceof ValidationError);
-      }
+      dns.promises.lookup.rejects(new Error('oh no'));
+      await assert.rejects(() => communication.validateClientIdentifier(url, validationOptions), ValidationError);
     });
     it('rejects mismatched resolutions', async function () {
-      dns.lookupAsync.onCall(1).resolves([{ family: 4, address: '10.9.8.7' }]);
-      try {
-        await communication.validateClientIdentifier(url, validationOptions);
-        assert.fail(noExpectedException);
-      } catch (e) {
-        assert(e instanceof ValidationError);
-      }
+      dns.promises.lookup.onCall(1).resolves([{ family: 4, address: '10.9.8.7' }]);
+      await assert.rejects(() => communication.validateClientIdentifier(url, validationOptions), ValidationError);
     });
     it('ignores unknown dns family', async function () {
-      dns.lookupAsync.resolves([{ family: 5, address: '10.9.8.7' }]);
+      dns.promises.lookup.resolves([{ family: 5, address: '10.9.8.7' }]);
       const result = await communication.validateClientIdentifier(url, validationOptions);
       assert.strictEqual(result.isLoopback, false);
     });
@@ -671,7 +541,7 @@ describe('Communication', function () {
       assert.strictEqual(result.isLoopback, false);
     });
     it('covers unresolved', async function () {
-      dns.lookupAsync.resolves();
+      dns.promises.lookup.resolves();
       const result = await communication.validateClientIdentifier(url, validationOptions);
       assert.strictEqual(result.isLoopback, false);
     });
@@ -685,11 +555,11 @@ describe('Communication', function () {
       urlObj = new URL('https://thuza.ratfeathers.com/');
       response = {
         headers: {},
-        data: testData.multiMF2Html,
+        body: testData.multiMF2Html,
       };
     });
     it('covers', async function () {
-      communication.axios.resolves(response);
+      communication.got.resolves(response);
       expected = {
         items: [{
           properties: {
@@ -710,14 +580,14 @@ describe('Communication', function () {
       assert.deepStrictEqual(result, expected);
     });
     it('covers failed fetch', async function () {
-      communication.axios.rejects();
+      communication.got.rejects();
       expected = undefined;
       result = await communication.fetchClientIdentifier(urlObj);
       assert.deepStrictEqual(result, expected);
     });
     it('covers no h-app data', async function () {
-      response.data = testData.noneMF2Html;
-      communication.axios.resolves(response);
+      response.body = testData.noneMF2Html;
+      communication.got.resolves(response);
       expected = {
         items: [],
         rels: {},
@@ -774,13 +644,13 @@ describe('Communication', function () {
       urlObj = new URL('https://thuza.ratfeathers.com/');
       response = {
         headers: {},
-        data: testData.hCardHtml,
+        body: testData.hCardHtml,
       };
       sinon.stub(communication, 'fetchJSON');
     });
     describe('legacy without indieauth-metadata', function () {
       it('covers', async function () {
-        communication.axios.resolves(response);
+        communication.got.resolves(response);
         expected = {
           name: 'Thuza',
           photo: 'https://thuza.ratfeathers.com/image.png',
@@ -797,8 +667,8 @@ describe('Communication', function () {
         assert.deepStrictEqual(result, expected);
       });
       it('covers multiple hCards', async function () {
-        response.data = testData.multiMF2Html;
-        communication.axios.resolves(response);
+        response.body = testData.multiMF2Html;
+        communication.got.resolves(response);
         expected = {
           email: undefined,
           name: 'Thuza',
@@ -815,7 +685,7 @@ describe('Communication', function () {
         assert.deepStrictEqual(result, expected);
       });
       it('covers failed fetch', async function () {
-        communication.axios.rejects();
+        communication.got.rejects();
         expected = {
           email: undefined,
           name: undefined,
@@ -828,8 +698,8 @@ describe('Communication', function () {
       });
     });
     it('covers', async function () {
-      response.data = testData.hCardMetadataHtml;
-      communication.axios.resolves(response);
+      response.body = testData.hCardMetadataHtml;
+      communication.got.resolves(response);
       communication.fetchJSON.resolves({
         'issuer': 'https://ia.squeep.com/',
         'authorization_endpoint': 'https://ia.squeep.com/auth',
@@ -873,8 +743,8 @@ describe('Communication', function () {
       assert.deepStrictEqual(result, expected);
     });
     it('covers metadata missing fields', async function () {
-      response.data = testData.hCardMetadataHtml;
-      communication.axios.resolves(response);
+      response.body = testData.hCardMetadataHtml;
+      communication.got.resolves(response);
       communication.fetchJSON.resolves({
         'issuer': 'https://ia.squeep.com/',
       });
@@ -895,8 +765,8 @@ describe('Communication', function () {
     });
     it('covers metadata response failure', async function () {
       const jsonError = new Error('oh no');
-      response.data = testData.hCardMetadataHtml;
-      communication.axios
+      response.body = testData.hCardMetadataHtml;
+      communication.got
         .onCall(0).resolves(response)
         .onCall(1).rejects(jsonError);
       communication.fetchJSON.restore();
@@ -915,18 +785,34 @@ describe('Communication', function () {
     });
   }); // fetchProfile
 
-  describe('redeemProfileCode', function () {
+  describe('redeemCode', function () {
     let expected, urlObj, code, codeVerifier, clientId, redirectURI;
-    this.beforeEach(function () {
+    beforeEach(function () {
       urlObj = new URL('https://example.com/auth');
-      code = Buffer.allocUnsafe(42).toString('base64').replace('/', '_').replace('+', '-');
-      codeVerifier = Buffer.allocUnsafe(42).toString('base64').replace('/', '_').replace('+', '-');
+      code = Buffer.allocUnsafe(42).toString('base64url');
+      codeVerifier = Buffer.allocUnsafe(42).toString('base64url');
       clientId = 'https://example.com/';
       redirectURI = 'https://example.com/_ia';
     });
     it('covers', async function () {
-      communication.axios.resolves({
-        data: '{"me":"https://profile.example.com/"}',
+      communication.got.resolves({
+        body: {
+          me: 'https://profile.example.com/',
+        },
+      });
+      expected = {
+        me: 'https://profile.example.com/',
+      };
+
+      const result = await communication.redeemCode(urlObj, code, codeVerifier, clientId, redirectURI);
+
+      assert.deepStrictEqual(result, expected);
+    });
+    it('covers deprecated method name', async function () {
+      communication.got.resolves({
+        body: {
+          me: 'https://profile.example.com/',
+        },
       });
       expected = {
         me: 'https://profile.example.com/',
@@ -937,11 +823,79 @@ describe('Communication', function () {
       assert.deepStrictEqual(result, expected);
     });
     it('covers failure', async function () {
-      communication.axios.resolves('Not a JSON payload.');
+      const error = new Error('oh no');
+      const response = {
+        request: {
+          options: {
+            url: new URL('https://example.com'),
+          },
+        },
+      };
+      const parseError = new communication.Got.ParseError(error, response);
+      communication.got.rejects(parseError);
 
-      const result = await communication.redeemProfileCode(urlObj, code, codeVerifier, clientId, redirectURI);
+      const result = await communication.redeemCode(urlObj, code, codeVerifier, clientId, redirectURI);
 
       assert.strictEqual(result, undefined);
     });
-  });
-}); // Communication
\ No newline at end of file
+  }); // redeemCode
+
+  describe('introspectToken', function () {
+    let introspectionUrlObj, authenticationHeader, token;
+    beforeEach(function () {
+      introspectionUrlObj = new URL('https://ia.example.com/introspect');
+      authenticationHeader = 'Bearer XXX';
+      token = 'xxx';
+    });
+    it('covers success active', async function () {
+      const nowEpoch = Math.ceil(Date.now() / 1000);
+      communication.got.resolves({
+        body: {
+          active: true,
+          me: 'https://profile.example.com/',
+          'client_id': 'https://app.example.com/',
+          scope: 'create profile email',
+          exp: nowEpoch + 86400,
+          iat: nowEpoch,
+        },
+      });
+      const result = await communication.introspectToken(introspectionUrlObj, authenticationHeader, token);
+      assert.strictEqual(result.active, true);
+    });
+    it('covers success inactive', async function () {
+      communication.got.resolves({
+        body: {
+          active: false,
+        },
+      });
+      const result = await communication.introspectToken(introspectionUrlObj, authenticationHeader, token);
+      assert.strictEqual(result.active, false);
+    });
+    it('covers failure', async function () {
+      communication.got.resolves({ body: 'what kind of response is this?' });
+      await assert.rejects(() => communication.introspectToken(introspectionUrlObj, authenticationHeader, token));
+    });
+  }); // introspectToken
+
+  describe('deliverTicket', function () {
+    let ticketEndpointUrlObj, resourceUrlObj, subjectUrlObj, ticket;
+    beforeEach(function () {
+      ticketEndpointUrlObj = new URL('https://ticket.example.com/');
+      resourceUrlObj = new URL('https://resource.example.com/');
+      subjectUrlObj = new URL('https://subject.example.com/');
+      ticket = 'XXXThisIsATicketXXX';
+    });
+    it('covers success', async function () {
+      const expected = { body: 'blah', statusCode: 200 };
+      communication.got.resolves(expected);
+      const result = await communication.deliverTicket(ticketEndpointUrlObj, resourceUrlObj, subjectUrlObj, ticket);
+      assert.deepStrictEqual(result, expected);
+    });
+    it('covers failure', async function () {
+      const expectedException = new Error('oh no');
+      communication.got.rejects(expectedException);
+      await assert.rejects(() => communication.deliverTicket(ticketEndpointUrlObj, resourceUrlObj, subjectUrlObj, ticket), expectedException);
+    });
+  }); // deliverTicket
+
+}); // Communication