add support for redeeming ticket, and sending ticket with issuer
[squeep-indieauth-helper] / test / lib / communication.js
index 332aa64921cd383d37cf73b155f2d6e754272949..42b5116f7d7043c594d8b12ac6e3335e8a4fb172 100644 (file)
@@ -785,6 +785,52 @@ describe('Communication', function () {
     });
   }); // fetchProfile
 
+  describe('fetchMetadata', function () {
+    let metadataUrl;
+    beforeEach(function () {
+      metadataUrl = new URL('https://thuza.ratfeathers.com/');
+      sinon.stub(communication, 'fetchJSON');
+    });
+    it('covers success', async function () {
+      communication.fetchJSON.resolves({
+        'issuer': 'https://ia.squeep.com/',
+        'authorization_endpoint': 'https://ia.squeep.com/auth',
+        'token_endpoint': 'https://ia.squeep.com/token',
+        'introspection_endpoint': 'https://ia.squeep.com/introspect',
+        'introspection_endpoint_auth_methods_supported': [ '' ],
+        'revocation_endpoint': 'https://ia.squeep.com/revoke',
+        'revocation_endpoint_auth_methods_supported': [ 'none' ],
+        'scopes_supported': [ 'profile', 'email' ],
+        'service_documentation': 'https://indieauth.spec.indieweb.org/',
+        'code_challenge_methods_supported': [ 'S256', 'SHA256' ],
+        'authorization_response_iss_parameter_supported': true,
+        'userinfo_endpoint': 'https://ia.squeep.com/userinfo',
+      });
+      const expected = {
+        authorizationEndpoint: 'https://ia.squeep.com/auth',
+        tokenEndpoint: 'https://ia.squeep.com/token',
+        issuer: 'https://ia.squeep.com/',
+        introspectionEndpoint: 'https://ia.squeep.com/introspect',
+        introspectionEndpointAuthMethodsSupported: [ '' ],
+        revocationEndpoint: 'https://ia.squeep.com/revoke',
+        revocationEndpointAuthMethodsSupported: [ 'none' ],
+        scopesSupported: [ 'profile', 'email' ],
+        serviceDocumentation: 'https://indieauth.spec.indieweb.org/',
+        codeChallengeMethodsSupported: [ 'S256', 'SHA256' ],
+        authorizationResponseIssParameterSupported: true,
+        userinfoEndpoint: 'https://ia.squeep.com/userinfo',
+      };
+      const result = await communication.fetchMetadata(metadataUrl);
+      assert.deepStrictEqual(result, expected);
+    });
+    it('covers failure', async function () {
+      communication.fetchJSON.resolves(undefined);
+      const expected = {};
+      const result = await communication.fetchMetadata(metadataUrl);
+      assert.deepStrictEqual(result, expected);
+    });
+  }); // fetchMetadata
+
   describe('redeemCode', function () {
     let expected, urlObj, code, codeVerifier, clientId, redirectURI;
     beforeEach(function () {
@@ -878,14 +924,21 @@ describe('Communication', function () {
   }); // introspectToken
 
   describe('deliverTicket', function () {
-    let ticketEndpointUrlObj, resourceUrlObj, subjectUrlObj, ticket;
+    let ticketEndpointUrlObj, resourceUrlObj, subjectUrlObj, ticket, issuerUrlObj;
     beforeEach(function () {
       ticketEndpointUrlObj = new URL('https://ticket.example.com/');
       resourceUrlObj = new URL('https://resource.example.com/');
       subjectUrlObj = new URL('https://subject.example.com/');
+      issuerUrlObj = new URL('https://idp.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, issuerUrlObj);
+      assert.deepStrictEqual(result, expected);
+    });
+    it('covers success, no issuer', async function () {
       const expected = { body: 'blah', statusCode: 200 };
       communication.got.resolves(expected);
       const result = await communication.deliverTicket(ticketEndpointUrlObj, resourceUrlObj, subjectUrlObj, ticket);
@@ -898,4 +951,118 @@ describe('Communication', function () {
     });
   }); // deliverTicket
 
+  describe('_fetchMetadataOrTokenEndpoint', function () {
+    let urlObj, metadataUrl, tokenUrl;
+    beforeEach(function () {
+      urlObj = new URL('https://idp.example.com/');
+      metadataUrl = new URL('https://idp.example.com/meta');
+      tokenUrl = new URL('https://idp.example.com/token');
+      sinon.stub(communication, 'fetchMicroformat').resolves({
+        rels: {
+          'indieauth-metadata': [ metadataUrl.href ],
+          'token_endpoint': [ tokenUrl.href ],
+        },
+      });
+    });
+    it('covers success', async function () {
+      const expected = {
+        metadataUrl,
+        tokenUrl: undefined,
+      };
+      const result = await communication._fetchMetadataOrTokenEndpoint(urlObj);
+      assert.deepStrictEqual(result, expected);
+    });
+    it('covers bad metadata url', async function () {
+      communication.fetchMicroformat.resolves({
+        rels: {
+          'indieauth-metadata': [ 'not a url' ],
+          'token_endpoint': [ tokenUrl.href ],
+        },
+      });
+      const expected = {
+        metadataUrl: undefined,
+        tokenUrl,
+      };
+      const result = await communication._fetchMetadataOrTokenEndpoint(urlObj);
+      assert.deepStrictEqual(result, expected);
+    });
+    it('covers bad token url', async function () {
+      communication.fetchMicroformat.resolves({
+        rels: {
+          'indieauth-metadata': [],
+          'token_endpoint': [ 'not a url' ],
+        },
+      });
+      const expected = {
+        metadataUrl: undefined,
+        tokenUrl: undefined,
+      };
+      const result = await communication._fetchMetadataOrTokenEndpoint(urlObj);
+      assert.deepStrictEqual(result, expected);
+    });
+    it('covers no rels', async function () {
+      communication.fetchMicroformat.resolves({
+        rels: {
+          'indieauth-metadata': [],
+          'token_endpoint': [],
+        },
+      });
+      const expected = {
+        metadataUrl: undefined,
+        tokenUrl: undefined,
+      };
+      const result = await communication._fetchMetadataOrTokenEndpoint(urlObj);
+      assert.deepStrictEqual(result, expected);
+    });
+    it('covers no url', async function () {
+      const expected = {
+        metadataUrl: undefined,
+        tokenUrl: undefined,
+      };
+      const result = await communication._fetchMetadataOrTokenEndpoint();
+      assert.deepStrictEqual(result, expected);
+    });
+  }); // _fetchMetadataOrTokenEndpoint
+
+  describe('redeemTicket', function () {
+    let ticket, resourceUrlObj, issuerUrlObj;
+    beforeEach(function () {
+      resourceUrlObj = new URL('https://resource.example.com/');
+      issuerUrlObj = new URL('https://idp.example.com/');
+      ticket = 'XXXThisIsATicketXXX';
+      sinon.stub(communication, '_fetchMetadataOrTokenEndpoint').resolves({
+        metadataUrl: new URL('https://example.com'),
+        tokenUrl: undefined,
+      });
+      sinon.stub(communication, 'fetchMetadata').resolves({ tokenEndpoint: 'https://idp.example.com/' });
+    });
+    it('covers success', async function () {
+      const expected = { 'access_token': 'XXXThisIsAnAccessTokenXXX' };
+      const response = { body: expected, headers: {}, statusCode: 200 };
+      communication.got.resolves(response);
+      const result = await communication.redeemTicket(ticket, resourceUrlObj, issuerUrlObj);
+      assert.deepStrictEqual(result, expected);
+    });
+    it('covers success without issuer', async function () {
+      const expected = { 'access_token': 'XXXThisIsAnAccessTokenXXX' };
+      const response = { body: expected, headers: {}, statusCode: 200 };
+      communication.got.resolves(response);
+      const result = await communication.redeemTicket(ticket, resourceUrlObj);
+      assert.deepStrictEqual(result, expected);
+    });
+    it('covers got failure', async function () {
+      const expectedException = new Error('oh no');
+      communication.got.rejects(expectedException);
+      await assert.rejects(() => communication.redeemTicket(ticket, resourceUrlObj, issuerUrlObj), expectedException);
+    });
+    it('covers no metadata url', async function () {
+      communication._fetchMetadataOrTokenEndpoint.resolves({ metadataUrl: undefined, tokenUrl: undefined });
+      await assert.rejects(() => communication.redeemTicket(ticket, resourceUrlObj, issuerUrlObj), ValidationError);
+    });
+    it('covers bad token url', async function () {
+      communication.fetchMetadata.resolves({ tokenEndpoint: 'not a url' });
+      await assert.rejects(() => communication.redeemTicket(ticket, resourceUrlObj, issuerUrlObj), ValidationError);
+    });
+  }); // redeemTicket
+
 }); // Communication