const MentionDiscoveryService = require('../lib/MentionDiscoveryService'); const sinon = require('sinon'); // non-standard to use externalRequest here, but this is required for the overrides in the library, which we want to test for security reasons in combination with the package const externalRequest = require('../../core/core/server/lib/request-external.js'); const dnsPromises = require('dns').promises; const assert = require('assert'); const nock = require('nock'); describe('MentionDiscoveryService', function () { const service = new MentionDiscoveryService({externalRequest}); beforeEach(function () { nock.disableNetConnect(); // externalRequest does dns lookup; stub to make sure we don't fail with fake domain names sinon.stub(dnsPromises, 'lookup').callsFake(function () { return Promise.resolve({address: '123.123.123.123'}); }); }); afterEach(function () { sinon.restore(); nock.cleanAll(); }); after(function () { nock.cleanAll(); nock.enableNetConnect(); }); it('Returns null from a bad URL', async function () { const url = new URL('http://www.notarealsite.com/'); nock(url.href) .get('/') .reply(404); let endpoint = await service.getEndpoint(url); assert.equal(endpoint, null); }); it('Follows redirects', async function () { let url = new URL('http://redirector.io/'); let nextUrl = new URL('http://testpage.com/'); nock(url.href) .intercept('/', 'HEAD') .reply(301, undefined, {location: nextUrl.href}) .get('/') .reply(200, 'Very cool site', {'content-type': 'text/html'}); let endpoint = await service.getEndpoint(url); assert(endpoint instanceof URL); }); describe('Can parse headers', function () { it('Returns null for a valid non-html site', async function () { const url = new URL('http://www.veryrealsite.com'); nock(url.href) .get('/') .reply(200, {}, {'content-type': 'application/json'}); const endpoint = await service.getEndpoint(url); assert.equal(endpoint, null); }); it('Returns an endpoint from a site with a webmentions Link in the header', async function () { const url = new URL('http://testpage.com/'); nock(url.href) .get('/') .reply(200, {}, {Link: '; rel="webmention"'}); const endpoint = await service.getEndpoint(url); assert(endpoint instanceof URL); assert.equal(endpoint, 'http://webmentions.endpoint.io/'); }); it('Returns null with Links in the header that are not for webmentions', async function () { const url = new URL('http://testpage.com/'); nock(url.href) .get('/') .reply(200, {}, {Link: '; rel="preconnect"'}); const endpoint = await service.getEndpoint(url); assert.equal(endpoint, null); }); it('Returns with multiple Links in the header, one of which is for webmentions', async function () { const url = new URL('http://testpage.com/'); nock(url.href) .get('/') .reply(200, {}, {Link: '; rel="preconnect",; rel="webmention"'}); const endpoint = await service.getEndpoint(url); assert(endpoint instanceof URL); assert.equal(endpoint, 'http://webmentions.endpoint.io/'); }); }); describe('Can parse html', function () { it('Returns endpoint for valid html site with tag in body', async function () { const url = new URL('http://testpage.com/'); nock(url.href) .get('/') .reply(200, '', {'content-type': 'text/html'}); const endpoint = await service.getEndpoint(url); assert(endpoint instanceof URL); assert.equal(endpoint, 'http://webmentions.endpoint.io/'); }); it('Returns endpoint for valid html site with tag in body', async function () { const url = new URL('http://testpage.com/'); nock(url.href) .get('/') .reply(200, 'webmention', {'content-type': 'text/html'}); const endpoint = await service.getEndpoint(url); assert(endpoint instanceof URL); assert.equal(endpoint, 'http://valid.site.org/'); }); it('Returns first endpoint for valid html site with multiple tags in body', async function () { const url = new URL('http://testpage.com/'); const html = ` kewl link 1 kewl link 2 kewl link 3 `; nock(url.href) .get('/') .reply(200, html, {'content-type': 'text/html'}); const endpoint = await service.getEndpoint(url); assert(endpoint instanceof URL); assert.equal(endpoint.href, 'http://first.webmention.endpoint/'); }); it('Returns first endpoint for valid html site with multiple tags in the header', async function () { const url = new URL('http://testpage.com/'); const html = ` `; nock(url.href) .get('/') .reply(200, html, {'content-type': 'text/html'}); const endpoint = await service.getEndpoint(url); assert(endpoint instanceof URL); assert.equal(endpoint.href, 'http://first.webmention.endpoint/'); }); it('Ignores link without href', async function () { const url = new URL('http://testpage.com/'); const html = ` `; nock(url.href) .get('/') .reply(200, html, {'content-type': 'text/html'}); const endpoint = await service.getEndpoint(url); assert.equal(endpoint, null); }); it('Returns first endpoint for valid html site with multiple and tags', async function () { // note - link tags are in the header and should come first const url = new URL('http://testpage.com/'); const html = ` kewl link 1 kewl link 2 `; nock(url.href) .get('/') .reply(200, html, {'content-type': 'text/html'}); const endpoint = await service.getEndpoint(url); assert(endpoint instanceof URL); assert.equal(endpoint.href, 'http://first.link.endpoint/'); }); it('Returns null for a valid html site with no endpoint', async function () { const url = new URL('http://www.veryrealsite.com'); nock(url.href) .get('/') .reply(200, {}, {'content-type': 'text/html'}); const endpoint = await service.getEndpoint(url); assert.equal(endpoint, null); }); }); });