Ghost/ghost/core/test/e2e-api/webmentions/webmentions.test.js
Hannah Wolfe 6161f94910
Updated to use assert/strict everywhere (#17047)
refs: https://github.com/TryGhost/Toolbox/issues/595

We're rolling out new rules around the node assert library, the first of which is enforcing the use of assert/strict. This means we don't need to use the strict version of methods, as the standard version will work that way by default.

This caught some gotchas in our existing usage of assert where the lack of strict mode had unexpected results:
- Url matching needs to be done on `url.href` see aa58b354a4
- Null and undefined are not the same thing,  there were a few cases of this being confused
- Particularly questionable changes in [PostExporter tests](c1a468744b) tracked [here](https://github.com/TryGhost/Team/issues/3505).
- A typo see eaac9c293a

Moving forward, using assert strict should help us to catch unexpected behaviour, particularly around nulls and undefineds during implementation.
2023-06-21 09:56:59 +01:00

480 lines
17 KiB
JavaScript

const {
agentProvider,
fixtureManager,
mockManager,
dbUtils,
configUtils
} = require('../../utils/e2e-framework');
const models = require('../../../core/server/models');
const assert = require('assert/strict');
const urlUtils = require('../../../core/shared/url-utils');
const nock = require('nock');
const jobsService = require('../../../core/server/services/mentions-jobs');
const DomainEvents = require('@tryghost/domain-events');
async function allSettled() {
await jobsService.allSettled();
await DomainEvents.allSettled();
}
describe('Webmentions (receiving)', function () {
let agent;
before(async function () {
agent = await agentProvider.getWebmentionsAPIAgent();
await fixtureManager.init('posts');
});
beforeEach(async function () {
await allSettled();
mockManager.disableNetwork();
mockManager.mockLabsEnabled('webmentions');
});
afterEach(async function () {
await allSettled();
mockManager.restore();
await dbUtils.truncate('brute');
});
it('can receive a webmention', async function () {
const targetUrl = new URL('integrations/', urlUtils.getSiteUrl());
const sourceUrl = new URL('http://testpage.com/external-article/');
const html = `
<html><head><title>Test Page</title><meta name="description" content="Test description"><meta name="author" content="John Doe"></head><body></body></html>
`;
nock(targetUrl.origin)
.persist()
.head(targetUrl.pathname)
.reply(200);
nock(sourceUrl.origin)
.persist()
.get(sourceUrl.pathname)
.reply(200, html, {'Content-Type': 'text/html'});
await agent.post('/receive')
.body({
source: sourceUrl.href,
target: targetUrl.href,
withExtension: true // test payload recorded
})
.expectStatus(202);
await allSettled();
const mention = await models.Mention.findOne({source: 'http://testpage.com/external-article/'});
assert(mention);
assert.equal(mention.get('target'), urlUtils.getSiteUrl() + 'integrations/');
assert.ok(mention.get('resource_id'));
assert.equal(mention.get('resource_type'), 'post');
assert.equal(mention.get('source_title'), 'Test Page');
assert.equal(mention.get('source_excerpt'), 'Test description');
assert.equal(mention.get('source_author'), 'John Doe');
assert.equal(mention.get('payload'), JSON.stringify({
withExtension: true
}));
});
it('will update a mentions source metadata', async function () {
const targetUrl = new URL(urlUtils.getSiteUrl());
const sourceUrl = new URL('http://testpage.com/update-mention-test-1/');
testCreatingTheMention: {
const html = `
<html><head><title>Test Page</title><meta name="description" content="Test description"><meta name="author" content="John Doe"></head><body></body></html>
`;
nock(targetUrl.origin)
.persist()
.head(targetUrl.pathname)
.reply(200);
nock(sourceUrl.origin)
.persist()
.get(sourceUrl.pathname)
.reply(200, html, {'Content-Type': 'text/html'});
await agent.post('/receive')
.body({
source: sourceUrl.href,
target: targetUrl.href
})
.expectStatus(202);
await allSettled();
const mention = await models.Mention.findOne({source: 'http://testpage.com/update-mention-test-1/'});
assert(mention);
assert.equal(mention.get('source_title'), 'Test Page');
assert.equal(mention.get('source_excerpt'), 'Test description');
assert.equal(mention.get('source_author'), 'John Doe');
break testCreatingTheMention;
}
nock.cleanAll();
testUpdatingTheMention: {
const html = `
<html><head><title>New Title</title><meta name="description" content="New Description"><meta name="author" content="big man with a beard"></head><body></body></html>
`;
nock(targetUrl.origin)
.persist()
.head(targetUrl.pathname)
.reply(200);
nock(sourceUrl.origin)
.persist()
.get(sourceUrl.pathname)
.reply(200, html, {'Content-Type': 'text/html'});
await agent.post('/receive')
.body({
source: sourceUrl.href,
target: targetUrl.href
})
.expectStatus(202);
await allSettled();
const mention = await models.Mention.findOne({source: 'http://testpage.com/update-mention-test-1/'});
assert(mention);
assert.equal(mention.get('source_title'), 'New Title');
assert.equal(mention.get('source_excerpt'), 'New Description');
assert.equal(mention.get('source_author'), 'big man with a beard');
break testUpdatingTheMention;
}
});
it('will delete a mention when the target in Ghost was deleted', async function () {
const post = await models.Post.findOne({id: fixtureManager.get('posts', 0).id});
const targetUrl = new URL(urlUtils.getSiteUrl() + post.get('slug') + '/');
const sourceUrl = new URL('http://testpage.com/update-mention-test-2/');
const html = `
<html><head><title>Test Page</title><meta name="description" content="Test description"><meta name="author" content="John Doe"></head><body></body></html>
`;
nock(sourceUrl.origin)
.persist()
.get(sourceUrl.pathname)
.reply(200, html, {'Content-Type': 'text/html'});
testCreatingTheMention: {
await agent.post('/receive')
.body({
source: sourceUrl.href,
target: targetUrl.href
})
.expectStatus(202);
await allSettled();
const mention = await models.Mention.findOne({source: 'http://testpage.com/update-mention-test-2/'});
assert(mention);
assert.equal(mention.get('resource_id'), post.id);
assert.equal(mention.get('source_title'), 'Test Page');
assert.equal(mention.get('source_excerpt'), 'Test description');
assert.equal(mention.get('source_author'), 'John Doe');
break testCreatingTheMention;
}
// Move post to draft and mark page as 404
await models.Post.edit({status: 'draft'}, {id: post.id});
nock(targetUrl.origin)
.persist()
.head(targetUrl.pathname)
.reply(404);
testUpdatingTheMention: {
await agent.post('/receive')
.body({
source: sourceUrl.href,
target: targetUrl.href
})
.expectStatus(202);
await allSettled();
const mention = await models.Mention.findOne({source: 'http://testpage.com/update-mention-test-2/'});
assert(mention);
// Check resource id was not cleared
assert.equal(mention.get('resource_id'), post.id);
// Check deleted
assert.equal(mention.get('deleted'), true);
break testUpdatingTheMention;
}
});
it('can receive a webmention to homepage', async function () {
const targetUrl = new URL(urlUtils.getSiteUrl());
const sourceUrl = new URL('http://testpage.com/external-article-2/');
const html = `
<html><head><title>Test Page</title><meta name="description" content="Test description"><meta name="author" content="John Doe"></head><body></body></html>
`;
nock(targetUrl.origin)
.head(targetUrl.pathname)
.reply(200);
nock(sourceUrl.origin)
.get(sourceUrl.pathname)
.reply(200, html, {'Content-Type': 'text/html'});
await agent.post('/receive')
.body({
source: sourceUrl.href,
target: targetUrl.href
})
.expectStatus(202);
await allSettled();
const mention = await models.Mention.findOne({source: 'http://testpage.com/external-article-2/'});
assert(mention);
assert.equal(mention.get('target'), urlUtils.getSiteUrl());
assert.ok(!mention.get('resource_id'));
assert.equal(mention.get('resource_type'), null);
assert.equal(mention.get('source_title'), 'Test Page');
assert.equal(mention.get('source_excerpt'), 'Test description');
assert.equal(mention.get('source_author'), 'John Doe');
assert.equal(mention.get('payload'), JSON.stringify({}));
});
it('can verify a webmention <a> link', async function () {
const targetUrl = new URL(urlUtils.getSiteUrl());
const sourceUrl = new URL('http://testpage.com/external-article-2/');
const html = `
<html><head><title>Test Page</title><meta name="description" content="Test description"><meta name="author" content="John Doe"></head><body><a href="${urlUtils.getSiteUrl()}">your cool website mentioned</a></body></html>
`;
nock(targetUrl.origin)
.persist()
.head(targetUrl.pathname)
.reply(200);
nock(sourceUrl.origin)
.persist()
.get(sourceUrl.pathname)
.reply(200, html, {'Content-Type': 'text/html'});
await agent.post('/receive')
.body({
source: sourceUrl.href,
target: targetUrl.href
})
.expectStatus(202);
await allSettled();
const mention = await models.Mention.findOne({source: 'http://testpage.com/external-article-2/'});
assert(mention);
assert.equal(mention.get('verified'), true);
});
it('can verify a webmention <a> link to post', async function () {
const targetUrl = new URL('integrations/', urlUtils.getSiteUrl());
const sourceUrl = new URL('http://testpage.com/external-article-3/');
const html = `
<html><head><title>Test Page</title><meta name="description" content="Test description"><meta name="author" content="John Doe"></head><body><a href="${targetUrl.toString()}">your cool website mentioned</a></body></html>
`;
nock(targetUrl.origin)
.persist()
.head(targetUrl.pathname)
.reply(200);
nock(sourceUrl.origin)
.persist()
.get(sourceUrl.pathname)
.reply(200, html, {'Content-Type': 'text/html'});
await agent.post('/receive')
.body({
source: sourceUrl.href,
target: targetUrl.href
})
.expectStatus(202);
await allSettled();
const mention = await models.Mention.findOne({source: sourceUrl.href});
assert(mention);
assert.equal(mention.get('verified'), true);
});
it('can verify a webmention <a> link to post with tracking parameters', async function () {
const targetUrl = new URL('integrations/', urlUtils.getSiteUrl());
const sourceUrl = new URL('http://testpage.com/external-article-4/');
const html = `
<html><head><title>Test Page</title><meta name="description" content="Test description"><meta name="author" content="John Doe"></head><body><a href="${targetUrl.toString()}?ref=1234-working">your cool website mentioned</a></body></html>
`;
nock(targetUrl.origin)
.persist()
.head(targetUrl.pathname)
.reply(200);
nock(sourceUrl.origin)
.persist()
.get(sourceUrl.pathname)
.reply(200, html, {'Content-Type': 'text/html'});
await agent.post('/receive')
.body({
source: sourceUrl.href,
target: targetUrl.href
})
.expectStatus(202);
await allSettled();
const mention = await models.Mention.findOne({source: sourceUrl.href});
assert(mention);
assert.equal(mention.get('verified'), true);
});
it('marks as unverified if url not present on source', async function () {
const targetUrl = new URL('html-ipsum', urlUtils.getSiteUrl());
const sourceUrl = new URL('http://testpage.com/external-article-not-present/');
const html = `
<html><head><title>Test Page</title><meta name="description" content="Test description"><meta name="author" content="John Doe"></head><body><a href="${urlUtils.getSiteUrl()}">your cool website mentioned</a></body></html>
`;
nock(targetUrl.origin).persist()
.head(targetUrl.pathname)
.reply(200);
nock(sourceUrl.origin)
.persist()
.get(sourceUrl.pathname)
.reply(200, html, {'Content-Type': 'text/html'});
await agent.post('/receive')
.body({
source: sourceUrl.href,
target: targetUrl.href
})
.expectStatus(202);
await allSettled();
const mention = await models.Mention.findOne({source: sourceUrl.toString()});
assert(mention);
assert.equal(mention.get('verified'), false);
});
it('can verify a webmention <img> link', async function () {
const targetUrl = new URL(urlUtils.getSiteUrl());
const sourceUrl = new URL('http://testpage.com/external-article-2/');
const html = `
<html><head><title>Test Page</title><meta name="description" content="Test description"><meta name="author" content="John Doe"></head><body><img src="${urlUtils.getSiteUrl()}"></body></html>
`;
nock(targetUrl.origin).persist()
.head(targetUrl.pathname)
.reply(200);
nock(sourceUrl.origin)
.persist()
.get(sourceUrl.pathname)
.reply(200, html, {'Content-Type': 'text/html'});
await agent.post('/receive')
.body({
source: sourceUrl.href,
target: targetUrl.href
})
.expectStatus(202);
await allSettled();
const mention = await models.Mention.findOne({source: 'http://testpage.com/external-article-2/'});
assert(mention);
assert.equal(mention.get('verified'), true);
});
it('can verify a webmention <video> link', async function () {
const targetUrl = new URL(urlUtils.getSiteUrl());
const sourceUrl = new URL('http://testpage.com/external-article-2/');
const html = `
<html><head><title>Test Page</title><meta name="description" content="Test description"><meta name="author" content="John Doe"></head><body><video src="${urlUtils.getSiteUrl()}"></body></html>
`;
nock(targetUrl.origin).persist()
.head(targetUrl.pathname)
.reply(200);
nock(sourceUrl.origin)
.persist()
.get(sourceUrl.pathname)
.reply(200, html, {'Content-Type': 'text/html'});
await agent.post('/receive')
.body({
source: sourceUrl.href,
target: targetUrl.href
})
.expectStatus(202);
await allSettled();
const mention = await models.Mention.findOne({source: 'http://testpage.com/external-article-2/'});
assert(mention);
assert.equal(mention.get('verified'), true);
});
// NOTE: this test needs to be last; it will disrupt other tests based on the fact we can't
// await the jobService completion for multiple concurrent requests
it('is rate limited against spamming mention requests', async function () {
await dbUtils.truncate('brute');
const webmentionBlock = configUtils.config.get('spam').webmentions_block;
const targetUrl = new URL(urlUtils.getSiteUrl());
const sourceUrl = new URL('http://testpage.com/external-article-brute-test/');
const html = `
<html><head><title>Test Page</title><meta name="description" content="Test description"><meta name="author" content="John Doe"></head><body></body></html>
`;
nock(targetUrl.origin)
.persist()
.head(targetUrl.pathname)
.reply(200);
nock(sourceUrl.origin)
.persist()
.get(sourceUrl.pathname)
.reply(200, html, {'Content-Type': 'text/html'});
const requests = [];
for (let i = 0; i < webmentionBlock.freeRetries + 1; i++) {
const req = await agent.post('/receive/')
.body({
source: sourceUrl.href,
target: targetUrl.href,
payload: {}
})
.expectStatus(202);
requests.push(req);
}
await Promise.all(requests);
await agent
.post('/receive/')
.body({
source: sourceUrl.href,
target: targetUrl.href,
payload: {}
})
.expectStatus(429);
await allSettled();
});
// NOTE: do not list other tests after the spam prevention test
});