2 /* eslint-disable capitalized-comments, sonarjs/no-duplicate-string */
6 const assert
= require('assert');
7 const sinon
= require('sinon'); // eslint-disable-line node/no-unpublished-require
9 const Communication
= require('../../lib/communication');
10 const { ValidationError
} = require('../../lib/errors');
11 const dns
= require('dns');
13 const stubLogger
= require('../stub-logger');
14 const testData
= require('../test-data/communication');
16 describe('Communication', function () {
17 let communication
, options
;
19 beforeEach(async
function () {
21 communication
= new Communication(stubLogger
, options
);
22 await communication
._init();
24 sinon
.stub(communication
, 'got');
26 afterEach(function () {
30 it('instantiates', function () {
31 assert(communication
);
34 it('covers no config', function () {
35 communication
= new Communication(stubLogger
);
38 describe('_init', function () {
39 it('covers first use', async
function () {
40 await communication
._init({});
41 assert(communication
.got
.called
);
45 describe('_onRetry', function () {
46 it('covers', function () {
47 communication
._onRetry(new Error('oh no'), 1);
48 assert(communication
.logger
.debug
.called
);
52 describe('_challengeFromVerifier', function () {
53 it('covers', function () {
54 const verifier
= 'VGhpcyBpcyBhIHNlY3JldC4u';
55 const expected
= 'O5W5A-1CAnrNGp2yHZtEql6rfHere4wJmzsyow7LLiY';
56 const result
= Communication
._challengeFromVerifier(verifier
);
57 assert
.strictEqual(result
, expected
);
59 }); // _challengeFromVerifier
61 describe('generatePKCE', function () {
62 it('covers', async
function () {
63 const result
= await Communication
.generatePKCE();
64 assert(result
.codeVerifier
);
65 assert(result
.codeChallenge
);
66 assert(result
.codeChallengeMethod
);
67 assert
.strictEqual(result
.codeChallengeMethod
, 'S256');
69 it('covers error', async
function () {
70 await assert
.rejects(() => Communication
.generatePKCE(1));
74 describe('verifyChallenge', function () {
75 it('covers success', function () {
76 const method
= 'S256';
77 const challenge
= 'O5W5A-1CAnrNGp2yHZtEql6rfHere4wJmzsyow7LLiY';
78 const verifier
= 'VGhpcyBpcyBhIHNlY3JldC4u';
79 const result
= Communication
.verifyChallenge(challenge
, verifier
, method
);
80 assert
.strictEqual(result
, true);
82 it('also covers success', function () {
83 const method
= 'SHA256';
84 const challenge
= 'O5W5A-1CAnrNGp2yHZtEql6rfHere4wJmzsyow7LLiY';
85 const verifier
= 'VGhpcyBpcyBhIHNlY3JldC4u';
86 const result
= Communication
.verifyChallenge(challenge
, verifier
, method
);
87 assert
.strictEqual(result
, true);
89 it('covers failure', function () {
90 const method
= 'S256';
91 const challenge
= 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
92 const verifier
= 'VGhpcyBpcyBhIHNlY3JldC4u';
93 const result
= Communication
.verifyChallenge(challenge
, verifier
, method
);
94 assert
.strictEqual(result
, false);
96 it('covers unhandled method', function () {
98 const challenge
= 'xkfP7DUYDsnu07Kg6ogc8A';
99 const verifier
= 'VGhpcyBpcyBhIHNlY3JldC4u';
100 assert
.throws(() => Communication
.verifyChallenge(challenge
, verifier
, method
));
102 }); // verifyChallenge
104 describe('_userAgentString', function () {
105 it('has default behavior', function () {
106 const result
= Communication
._userAgentString();
108 assert(result
.length
> 30);
110 it('is settable', function () {
111 const result
= Communication
._userAgentString({
114 implementation: 'custom',
117 assert
.strictEqual(result
, 'myClient/9.9.9 (custom)');
119 it('covers branches', function () {
120 const result
= Communication
._userAgentString({
126 assert
.strictEqual(result
, 'myClient/9.9.9');
128 }); // userAgentString
130 describe('_baseUrlString', function () {
131 it('covers no path', function () {
132 const urlObj
= new URL('https://example.com');
133 const expected
= 'https://example.com/';
134 const result
= Communication
._baseUrlString(urlObj
);
135 assert
.strictEqual(result
, expected
);
137 it('covers paths', function () {
138 const urlObj
= new URL('https://example.com/path/blah');
139 const expected
= 'https://example.com/path/';
140 const result
= Communication
._baseUrlString(urlObj
);
141 assert
.strictEqual(result
, expected
);
143 }); // _baseUrlString
145 describe('_parseContentType', function () {
146 let contentTypeHeader
, expected
, result
;
147 it('covers undefined', function () {
148 contentTypeHeader
= undefined;
150 mediaType: 'application/octet-stream',
153 result
= Communication
._parseContentType(contentTypeHeader
);
154 assert
.deepStrictEqual(result
, expected
);
156 it('covers empty', function () {
157 contentTypeHeader
= '';
159 mediaType: 'application/octet-stream',
162 result
= Communication
._parseContentType(contentTypeHeader
);
163 assert
.deepStrictEqual(result
, expected
);
165 it('covers extra parameters', function () {
166 contentTypeHeader
= 'text/plain; CharSet="UTF-8"; WeirdParam';
168 mediaType: 'text/plain',
171 'weirdparam': undefined,
174 result
= Communication
._parseContentType(contentTypeHeader
);
175 assert
.deepStrictEqual(result
, expected
);
177 }); // parseContentType
179 describe('_mergeLinkHeader', function () {
180 let microformat
, response
, expected
;
181 beforeEach(function () {
185 link: '<https://example.com/>; rel="self", <https://hub.example.com/>;rel="hub"',
190 it('covers', function () {
194 'hub': ['https://hub.example.com/'],
195 'self': ['https://example.com/'],
198 'https://example.com/': {
202 'https://hub.example.com/': {
208 communication
._mergeLinkHeader(microformat
, response
);
209 assert
.deepStrictEqual(microformat
, expected
);
211 it('covers existing', function () {
215 'preload': ['https://example.com/style'],
216 'hub': ['https://hub.example.com/'],
219 'https://hub.example.com/': {
223 'https://example.com/style': {
232 'preload': ['https://example.com/style'],
233 'hub': ['https://hub.example.com/', 'https://hub.example.com/'],
234 'self': ['https://example.com/'],
237 'https://example.com/': {
241 'https://hub.example.com/': {
242 rels: ['hub', 'hub'],
245 'https://example.com/style': {
251 communication
._mergeLinkHeader(microformat
, response
);
252 assert
.deepStrictEqual(microformat
, expected
);
254 it('ignores bad header', function () {
255 response
.headers
.link
= 'not really a link header';
261 communication
._mergeLinkHeader(microformat
, response
);
262 assert
.deepStrictEqual(microformat
, expected
);
264 }); // _mergeLinkHeader
266 describe('fetchMicroformat', function () {
267 let expected
, response
, result
, urlObj
;
268 beforeEach(function () {
269 expected
= undefined;
271 urlObj
= new URL('https://thuza.ratfeathers.com/');
273 headers: Object
.assign({}, testData
.linkHeaders
),
274 body: Buffer
.from(testData
.hCardHtml
),
277 it('covers', async
function () {
278 response
.body
= testData
.hCardHtml
;
279 communication
.got
.resolves(response
);
282 'authorization_endpoint': ['https://ia.squeep.com/auth'],
283 'token_endpoint': ['https://ia.squeep.com/token'],
284 'canonical': ['https://thuza.ratfeathers.com/'],
285 'author': ['https://thuza.ratfeathers.com/'],
286 'me': ['https://thuza.ratfeathers.com/'],
287 'self': ['https://thuza.ratfeathers.com/'],
288 'hub': ['https://hub.squeep.com/'],
289 'preload': ['https://thuza.ratfeathers.com/image.png'],
292 'https://hub.squeep.com/': {
296 'https://ia.squeep.com/auth': {
297 rels: ['authorization_endpoint'],
300 'https://ia.squeep.com/token': {
301 rels: ['token_endpoint'],
304 'https://thuza.ratfeathers.com/': {
305 rels: ['self', 'canonical', 'author', 'me'],
308 'https://thuza.ratfeathers.com/image.png': {
316 photo: ['https://thuza.ratfeathers.com/image.png'],
317 url: ['https://thuza.ratfeathers.com/'],
323 result
= await communication
.fetchMicroformat(urlObj
);
324 assert
.deepStrictEqual(result
, expected
);
326 it('covers got error', async
function () {
327 communication
.got
.rejects(new Error('blah'));
328 expected
= undefined;
330 result
= await communication
.fetchMicroformat(urlObj
);
332 assert
.deepStrictEqual(result
, expected
);
334 it('covers non-parsable content', async
function () {
335 response
.body
= 'some bare text';
336 response
.headers
= {};
337 communication
.got
.resolves(response
);
344 result
= await communication
.fetchMicroformat(urlObj
);
346 assert
.deepStrictEqual(result
, expected
);
348 it('covers non-utf8 content', async
function () {
349 response
.headers
['content-type'] = 'text/html; charset=ASCII';
350 communication
.got
.resolves(response
);
353 'authorization_endpoint': ['https://ia.squeep.com/auth'],
354 'token_endpoint': ['https://ia.squeep.com/token'],
355 'canonical': ['https://thuza.ratfeathers.com/'],
356 'author': ['https://thuza.ratfeathers.com/'],
357 'me': ['https://thuza.ratfeathers.com/'],
358 'self': ['https://thuza.ratfeathers.com/'],
359 'hub': ['https://hub.squeep.com/'],
360 'preload': ['https://thuza.ratfeathers.com/image.png'],
363 'https://hub.squeep.com/': {
367 'https://ia.squeep.com/auth': {
368 rels: ['authorization_endpoint'],
371 'https://ia.squeep.com/token': {
372 rels: ['token_endpoint'],
375 'https://thuza.ratfeathers.com/': {
376 rels: ['self', 'canonical', 'author', 'me'],
379 'https://thuza.ratfeathers.com/image.png': {
387 photo: ['https://thuza.ratfeathers.com/image.png'],
388 url: ['https://thuza.ratfeathers.com/'],
394 result
= await communication
.fetchMicroformat(urlObj
);
396 assert
.deepStrictEqual(result
, expected
);
398 }); // fetchMicroformat
400 describe('fetchJSON', function () {
401 let expected
, response
, result
, urlObj
;
402 beforeEach(function () {
403 expected
= undefined;
405 urlObj
= new URL('https://thuza.ratfeathers.com/');
407 headers: Object
.assign({}, testData
.linkHeaders
),
408 body: testData
.hCardHtml
,
411 it('covers', async
function () {
412 communication
.got
.resolves(response
);
413 expected
= { foo: 'bar', baz: 123 };
414 response
.body
= expected
;
416 result
= await communication
.fetchJSON(urlObj
);
417 assert
.deepStrictEqual(result
, expected
);
419 it('covers got error', async
function () {
420 communication
.got
.rejects(new Error('blah'));
421 expected
= undefined;
423 result
= await communication
.fetchJSON(urlObj
);
425 assert
.deepStrictEqual(result
, expected
);
427 it('covers non-parsable content', async
function () {
428 response
.body
= 'some bare text';
429 response
.headers
= {};
430 const error
= new Error('oh no');
431 response
.request
= { options: { url: new URL('https://example.com/') } };
432 communication
.got
.rejects(new communication
.Got
.ParseError(error
, response
));
433 expected
= undefined;
435 result
= await communication
.fetchJSON(urlObj
);
437 assert
.deepStrictEqual(result
, expected
);
441 describe('validateProfile', function () {
442 let url
, validationOptions
;
443 beforeEach(function () {
444 url
= 'https://example.com/';
445 validationOptions
= {};
446 sinon
.stub(dns
.promises
, 'lookup').resolves([{ family: 4, address: '10.11.12.14' }]);
448 it('rejects invalid url', async
function () {
450 await assert
.rejects(() => communication
.validateProfile(url
, validationOptions
), ValidationError
);
452 it('covers success', async
function () {
453 const result
= await communication
.validateProfile(url
, validationOptions
);
454 assert
.strictEqual(result
.isLoopback
, false);
456 it('rejects invalid', async
function () {
457 url
= 'ftp://example.com/';
458 await assert
.rejects(() => communication
.validateProfile(url
, validationOptions
), ValidationError
);
461 }); // validateProfile
463 describe('validateClientIdentifier', function () {
464 let url
, validationOptions
;
465 beforeEach(function () {
466 url
= 'https://example.com/';
467 validationOptions
= {};
468 sinon
.stub(dns
.promises
, 'lookup').resolves([{ family: 4, address: '10.11.12.13' }]);
470 it('rejects invalid url', async
function () {
471 await assert
.rejects(() => communication
.validateClientIdentifier('bad url'), ValidationError
);
473 it('rejects invalid scheme', async
function () {
474 url
= 'ftp://example.com/';
475 await assert
.rejects(() => communication
.validateClientIdentifier(url
, validationOptions
), ValidationError
);
477 it('rejects fragment', async
function () {
478 url
= 'https://example.com/#foo';
479 await assert
.rejects(() => communication
.validateClientIdentifier(url
, validationOptions
), ValidationError
);
481 it('rejects username', async
function () {
482 url
= 'https://user@example.com/';
483 await assert
.rejects(() => communication
.validateClientIdentifier(url
, validationOptions
), ValidationError
);
485 it('rejects password', async
function () {
486 url
= 'https://:foo@example.com/';
487 await assert
.rejects(() => communication
.validateClientIdentifier(url
, validationOptions
), ValidationError
);
489 it('rejects relative path', async
function () {
490 url
= 'https://example.com/client/../sneaky';
491 await assert
.rejects(() => communication
.validateClientIdentifier(url
, validationOptions
), ValidationError
);
493 it('rejects ipv4', async
function () {
494 url
= 'https://10.11.12.13/';
495 await assert
.rejects(() => communication
.validateClientIdentifier(url
, validationOptions
), ValidationError
);
497 it('rejects ipv6', async
function () {
498 url
= 'https://[fd64:defa:00e5:caf4:0dff::ad39]/';
499 await assert
.rejects(() => communication
.validateClientIdentifier(url
, validationOptions
), ValidationError
);
501 it('accepts ipv4 loopback', async
function () {
502 url
= 'https://127.0.0.1/';
503 const result
= await communication
.validateClientIdentifier(url
, validationOptions
);
504 assert
.strictEqual(result
.isLoopback
, true);
506 it('accepts ipv6 loopback', async
function () {
507 url
= 'https://[::1]/';
508 const result
= await communication
.validateClientIdentifier(url
, validationOptions
);
509 assert
.strictEqual(result
.isLoopback
, true);
511 it('accepts resolved ipv4 loopback', async
function () {
512 dns
.promises
.lookup
.resolves([{ family: 4, address: '127.0.0.1' }]);
513 const result
= await communication
.validateClientIdentifier(url
, validationOptions
);
514 assert
.strictEqual(result
.isLoopback
, true);
516 it('accepts resolved ipv6 loopback', async
function () {
517 dns
.promises
.lookup
.resolves([{ family: 6, address: '::1' }]);
518 const result
= await communication
.validateClientIdentifier(url
, validationOptions
);
519 assert
.strictEqual(result
.isLoopback
, true);
521 it('covers success', async
function () {
522 const result
= await communication
.validateClientIdentifier(url
, validationOptions
);
523 assert
.strictEqual(result
.isLoopback
, false);
525 it('rejects resolution failure', async
function () {
526 dns
.promises
.lookup
.rejects(new Error('oh no'));
527 await assert
.rejects(() => communication
.validateClientIdentifier(url
, validationOptions
), ValidationError
);
529 it('rejects mismatched resolutions', async
function () {
530 dns
.promises
.lookup
.onCall(1).resolves([{ family: 4, address: '10.9.8.7' }]);
531 await assert
.rejects(() => communication
.validateClientIdentifier(url
, validationOptions
), ValidationError
);
533 it('ignores unknown dns family', async
function () {
534 dns
.promises
.lookup
.resolves([{ family: 5, address: '10.9.8.7' }]);
535 const result
= await communication
.validateClientIdentifier(url
, validationOptions
);
536 assert
.strictEqual(result
.isLoopback
, false);
538 it('covers rooted hostname', async
function() {
539 url
= 'https://example.com./';
540 const result
= await communication
.validateClientIdentifier(url
, validationOptions
);
541 assert
.strictEqual(result
.isLoopback
, false);
543 it('covers unresolved', async
function () {
544 dns
.promises
.lookup
.resolves();
545 const result
= await communication
.validateClientIdentifier(url
, validationOptions
);
546 assert
.strictEqual(result
.isLoopback
, false);
548 }); // validateClientIdentifier
550 describe('fetchClientIdentifier', function () {
551 let expected
, response
, result
, urlObj
;
552 beforeEach(function () {
553 expected
= undefined;
555 urlObj
= new URL('https://thuza.ratfeathers.com/');
558 body: testData
.multiMF2Html
,
561 it('covers', async
function () {
562 communication
.got
.resolves(response
);
566 name: ['Also Some Client'],
567 url: ['https://thuza.ratfeathers.com/'],
572 'author': ['https://thuza.ratfeathers.com/'],
573 'authorization_endpoint': ['https://ia.squeep.com/auth'],
574 'canonical': ['https://thuza.ratfeathers.com/'],
575 'me': ['https://thuza.ratfeathers.com/'],
576 'token_endpoint': ['https://ia.squeep.com/token'],
579 result
= await communication
.fetchClientIdentifier(urlObj
);
580 assert
.deepStrictEqual(result
, expected
);
582 it('covers failed fetch', async
function () {
583 communication
.got
.rejects();
584 expected
= undefined;
585 result
= await communication
.fetchClientIdentifier(urlObj
);
586 assert
.deepStrictEqual(result
, expected
);
588 it('covers no h-app data', async
function () {
589 response
.body
= testData
.noneMF2Html
;
590 communication
.got
.resolves(response
);
595 result
= await communication
.fetchClientIdentifier(urlObj
);
596 assert
.deepStrictEqual(result
, expected
);
598 it('covers missing fields', async
function () {
599 sinon
.stub(communication
, 'fetchMicroformat').resolves({});
604 result
= await communication
.fetchClientIdentifier(urlObj
);
605 assert
.deepStrictEqual(result
, expected
);
607 it('covers other missing fields', async
function () {
608 sinon
.stub(communication
, 'fetchMicroformat').resolves({
614 url: ['https://example.com'],
623 result
= await communication
.fetchClientIdentifier(urlObj
);
624 assert
.deepStrictEqual(result
, expected
);
626 it('covers loopback', async
function () {
627 sinon
.spy(communication
, 'fetchMicroformat');
628 urlObj
.isLoopback
= true;
633 result
= await communication
.fetchClientIdentifier(urlObj
);
634 assert
.deepStrictEqual(result
, expected
);
635 assert(communication
.fetchMicroformat
.notCalled
);
637 }); // fetchClientIdentifier
639 describe('fetchProfile', function () {
640 let expected
, response
, result
, urlObj
;
641 beforeEach(function () {
642 expected
= undefined;
644 urlObj
= new URL('https://thuza.ratfeathers.com/');
647 body: testData
.hCardHtml
,
649 sinon
.stub(communication
, 'fetchJSON');
651 describe('legacy without indieauth-metadata', function () {
652 it('covers', async
function () {
653 communication
.got
.resolves(response
);
656 photo: 'https://thuza.ratfeathers.com/image.png',
657 url: 'https://thuza.ratfeathers.com/',
659 authorizationEndpoint: 'https://ia.squeep.com/auth',
660 tokenEndpoint: 'https://ia.squeep.com/token',
662 authorizationEndpoint: 'https://ia.squeep.com/auth',
663 tokenEndpoint: 'https://ia.squeep.com/token',
666 result
= await communication
.fetchProfile(urlObj
);
667 assert
.deepStrictEqual(result
, expected
);
669 it('covers multiple hCards', async
function () {
670 response
.body
= testData
.multiMF2Html
;
671 communication
.got
.resolves(response
);
675 photo: 'https://thuza.ratfeathers.com/image.png',
676 url: 'https://thuza.ratfeathers.com/',
677 authorizationEndpoint: 'https://ia.squeep.com/auth',
678 tokenEndpoint: 'https://ia.squeep.com/token',
680 authorizationEndpoint: 'https://ia.squeep.com/auth',
681 tokenEndpoint: 'https://ia.squeep.com/token',
684 result
= await communication
.fetchProfile(urlObj
);
685 assert
.deepStrictEqual(result
, expected
);
687 it('covers failed fetch', async
function () {
688 communication
.got
.rejects();
696 result
= await communication
.fetchProfile(urlObj
);
697 assert
.deepStrictEqual(result
, expected
);
700 it('covers', async
function () {
701 response
.body
= testData
.hCardMetadataHtml
;
702 communication
.got
.resolves(response
);
703 communication
.fetchJSON
.resolves({
704 'issuer': 'https://ia.squeep.com/',
705 'authorization_endpoint': 'https://ia.squeep.com/auth',
706 'token_endpoint': 'https://ia.squeep.com/token',
707 'introspection_endpoint': 'https://ia.squeep.com/introspect',
708 'introspection_endpoint_auth_methods_supported': [ '' ],
709 'revocation_endpoint': 'https://ia.squeep.com/revoke',
710 'revocation_endpoint_auth_methods_supported': [ 'none' ],
711 'scopes_supported': [ 'profile', 'email' ],
712 'service_documentation': 'https://indieauth.spec.indieweb.org/',
713 'code_challenge_methods_supported': [ 'S256', 'SHA256' ],
714 'authorization_response_iss_parameter_supported': true,
715 'userinfo_endpoint': 'https://ia.squeep.com/userinfo',
719 photo: 'https://thuza.ratfeathers.com/image.png',
720 url: 'https://thuza.ratfeathers.com/',
723 authorizationEndpoint: 'https://ia.squeep.com/auth',
724 tokenEndpoint: 'https://ia.squeep.com/token',
725 issuer: 'https://ia.squeep.com/',
726 introspectionEndpoint: 'https://ia.squeep.com/introspect',
727 introspectionEndpointAuthMethodsSupported: [ '' ],
728 revocationEndpoint: 'https://ia.squeep.com/revoke',
729 revocationEndpointAuthMethodsSupported: [ 'none' ],
730 scopesSupported: [ 'profile', 'email' ],
731 serviceDocumentation: 'https://indieauth.spec.indieweb.org/',
732 codeChallengeMethodsSupported: [ 'S256', 'SHA256' ],
733 authorizationResponseIssParameterSupported: true,
734 userinfoEndpoint: 'https://ia.squeep.com/userinfo',
736 authorizationEndpoint: 'https://ia.squeep.com/auth',
737 tokenEndpoint: 'https://ia.squeep.com/token',
738 indieauthMetadata: 'https://ia.squeep.com/meta',
741 result
= await communication
.fetchProfile(urlObj
);
743 assert
.deepStrictEqual(result
, expected
);
745 it('covers metadata missing fields', async
function () {
746 response
.body
= testData
.hCardMetadataHtml
;
747 communication
.got
.resolves(response
);
748 communication
.fetchJSON
.resolves({
749 'issuer': 'https://ia.squeep.com/',
753 photo: 'https://thuza.ratfeathers.com/image.png',
754 url: 'https://thuza.ratfeathers.com/',
757 issuer: 'https://ia.squeep.com/',
759 indieauthMetadata: 'https://ia.squeep.com/meta',
762 result
= await communication
.fetchProfile(urlObj
);
764 assert
.deepStrictEqual(result
, expected
);
766 it('covers metadata response failure', async
function () {
767 const jsonError
= new Error('oh no');
768 response
.body
= testData
.hCardMetadataHtml
;
770 .onCall(0).resolves(response
)
771 .onCall(1).rejects(jsonError
);
772 communication
.fetchJSON
.restore();
775 photo: 'https://thuza.ratfeathers.com/image.png',
776 url: 'https://thuza.ratfeathers.com/',
779 indieauthMetadata: 'https://ia.squeep.com/meta',
782 result
= await communication
.fetchProfile(urlObj
);
784 assert
.deepStrictEqual(result
, expected
);
788 describe('redeemCode', function () {
789 let expected
, urlObj
, code
, codeVerifier
, clientId
, redirectURI
;
790 beforeEach(function () {
791 urlObj
= new URL('https://example.com/auth');
792 code
= Buffer
.allocUnsafe(42).toString('base64url');
793 codeVerifier
= Buffer
.allocUnsafe(42).toString('base64url');
794 clientId
= 'https://example.com/';
795 redirectURI
= 'https://example.com/_ia';
797 it('covers', async
function () {
798 communication
.got
.resolves({
800 me: 'https://profile.example.com/',
804 me: 'https://profile.example.com/',
807 const result
= await communication
.redeemCode(urlObj
, code
, codeVerifier
, clientId
, redirectURI
);
809 assert
.deepStrictEqual(result
, expected
);
811 it('covers deprecated method name', async
function () {
812 communication
.got
.resolves({
814 me: 'https://profile.example.com/',
818 me: 'https://profile.example.com/',
821 const result
= await communication
.redeemProfileCode(urlObj
, code
, codeVerifier
, clientId
, redirectURI
);
823 assert
.deepStrictEqual(result
, expected
);
825 it('covers failure', async
function () {
826 const error
= new Error('oh no');
830 url: new URL('https://example.com'),
834 const parseError
= new communication
.Got
.ParseError(error
, response
);
835 communication
.got
.rejects(parseError
);
837 const result
= await communication
.redeemCode(urlObj
, code
, codeVerifier
, clientId
, redirectURI
);
839 assert
.strictEqual(result
, undefined);
843 describe('introspectToken', function () {
844 let introspectionUrlObj
, authenticationHeader
, token
;
845 beforeEach(function () {
846 introspectionUrlObj
= new URL('https://ia.example.com/introspect');
847 authenticationHeader
= 'Bearer XXX';
850 it('covers success active', async
function () {
851 const nowEpoch
= Math
.ceil(Date
.now() / 1000);
852 communication
.got
.resolves({
855 me: 'https://profile.example.com/',
856 'client_id': 'https://app.example.com/',
857 scope: 'create profile email',
858 exp: nowEpoch
+ 86400,
862 const result
= await communication
.introspectToken(introspectionUrlObj
, authenticationHeader
, token
);
863 assert
.strictEqual(result
.active
, true);
865 it('covers success inactive', async
function () {
866 communication
.got
.resolves({
871 const result
= await communication
.introspectToken(introspectionUrlObj
, authenticationHeader
, token
);
872 assert
.strictEqual(result
.active
, false);
874 it('covers failure', async
function () {
875 communication
.got
.resolves({ body: 'what kind of response is this?' });
876 await assert
.rejects(() => communication
.introspectToken(introspectionUrlObj
, authenticationHeader
, token
));
878 }); // introspectToken
880 describe('deliverTicket', function () {
881 let ticketEndpointUrlObj
, resourceUrlObj
, subjectUrlObj
, ticket
;
882 beforeEach(function () {
883 ticketEndpointUrlObj
= new URL('https://ticket.example.com/');
884 resourceUrlObj
= new URL('https://resource.example.com/');
885 subjectUrlObj
= new URL('https://subject.example.com/');
886 ticket
= 'XXXThisIsATicketXXX';
888 it('covers success', async
function () {
889 const expected
= { body: 'blah', statusCode: 200 };
890 communication
.got
.resolves(expected
);
891 const result
= await communication
.deliverTicket(ticketEndpointUrlObj
, resourceUrlObj
, subjectUrlObj
, ticket
);
892 assert
.deepStrictEqual(result
, expected
);
894 it('covers failure', async
function () {
895 const expectedException
= new Error('oh no');
896 communication
.got
.rejects(expectedException
);
897 await assert
.rejects(() => communication
.deliverTicket(ticketEndpointUrlObj
, resourceUrlObj
, subjectUrlObj
, ticket
), expectedException
);