X-Git-Url: https://git.squeep.com/?a=blobdiff_plain;f=test%2Flib%2Fcommunication.js;h=42b5116f7d7043c594d8b12ac6e3335e8a4fb172;hb=45d151c0587750c9a8ed3e3180ca79139a6ccb5e;hp=9781cd65b05088664a1d1dee04282d12ff31bf05;hpb=dca6a761e6fe437a3fbeb24540bb6fa5d0b8eb12;p=squeep-indieauth-helper diff --git a/test/lib/communication.js b/test/lib/communication.js index 9781cd6..42b5116 100644 --- a/test/lib/communication.js +++ b/test/lib/communication.js @@ -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,93 +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', - validateStatus: Communication._validateStatus, - }; - 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', - validateStatus: Communication._validateStatus, - }; - 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', - validateStatus: Communication._validateStatus, - }; - 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](); - }); - - describe('_validateStatus', function () { - it('allows normal valid', function () { - const result = Communication._validateStatus(200); - assert.strictEqual(result, true); - }); - it('allows unauthorized', function () { - const result = Communication._validateStatus(401); - assert.strictEqual(result, true); - }); - it('rejects invalid', function () { - const result = Communication._validateStatus(400); - assert.strictEqual(result, false); - }); - }); // _validateStatus - }); // Axios Configurations - describe('_baseUrlString', function () { it('covers no path', function () { const urlObj = new URL('https://example.com'); @@ -283,7 +184,7 @@ describe('Communication', function () { headers: { link: '; rel="self", ;rel="hub"', }, - data: {}, + body: {}, } }); it('covers', function () { @@ -370,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'], @@ -401,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': { @@ -422,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); @@ -431,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: {}, @@ -446,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'], @@ -472,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': { @@ -504,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); @@ -524,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); @@ -539,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'; @@ -561,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/'; @@ -646,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); }); @@ -660,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); }); @@ -688,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); }); @@ -702,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: { @@ -727,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: {}, @@ -791,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', @@ -814,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', @@ -832,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, @@ -845,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', @@ -890,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/', }); @@ -912,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(); @@ -932,18 +785,80 @@ describe('Communication', function () { }); }); // fetchProfile - describe('redeemProfileCode', function () { + 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; - 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/', @@ -954,11 +869,200 @@ 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, 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); + 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 + + 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