602 lines
19 KiB
JavaScript
602 lines
19 KiB
JavaScript
const assert = require('assert/strict');
|
|
const ObjectID = require('bson-objectid');
|
|
const Mention = require('../lib/Mention');
|
|
const MentionsAPI = require('../lib/MentionsAPI');
|
|
const InMemoryMentionRepository = require('../lib/InMemoryMentionRepository');
|
|
const sinon = require('sinon');
|
|
const cheerio = require('cheerio');
|
|
|
|
const mockRoutingService = {
|
|
async pageExists() {
|
|
return true;
|
|
}
|
|
};
|
|
const mockResourceService = {
|
|
async getByURL() {
|
|
return {
|
|
type: null,
|
|
id: null
|
|
};
|
|
}
|
|
};
|
|
|
|
const mockWebmentionMetadata = {
|
|
async fetch() {
|
|
return {
|
|
siteTitle: 'Clickbait News',
|
|
title: 'This egg breakfast will make you cry',
|
|
excerpt: 'How many times have you woken up and almost cancelled your church plans? Well this breakfast is about to change everything, a hearty, faith restoring egg dish that will get your tastebuds in a twist.',
|
|
author: 'Dr Egg Man',
|
|
image: new URL('https://unsplash.com/photos/QAND9huzD04'),
|
|
favicon: new URL('https://ghost.org/favicon.ico'),
|
|
body: `<html><body><p>Some HTML and a <a href='https://target.com/'>mentioned url</a></p></body></html>`,
|
|
contentType: 'text/html'
|
|
};
|
|
}
|
|
};
|
|
|
|
function addMinutes(date, minutes) {
|
|
date.setMinutes(date.getMinutes() + minutes);
|
|
|
|
return date;
|
|
}
|
|
|
|
describe('MentionsAPI', function () {
|
|
beforeEach(function () {
|
|
sinon.restore();
|
|
});
|
|
|
|
it('Can generate a mentions report', async function () {
|
|
// this.retries(1);
|
|
const repository = new InMemoryMentionRepository();
|
|
const api = new MentionsAPI({
|
|
repository,
|
|
routingService: mockRoutingService,
|
|
resourceService: mockResourceService,
|
|
webmentionMetadata: mockWebmentionMetadata
|
|
});
|
|
|
|
const mention = await api.processWebmention({
|
|
source: new URL('https://source.com'),
|
|
target: new URL('https://target.com'),
|
|
payload: {}
|
|
});
|
|
|
|
assert(mention instanceof Mention);
|
|
|
|
const now = new Date();
|
|
|
|
const report = await api.getMentionReport(new Date(0), now);
|
|
|
|
assert.deepEqual(report.startDate, new Date(0));
|
|
assert.deepEqual(report.endDate, now);
|
|
});
|
|
|
|
it('Can list paginated mentions', async function () {
|
|
const repository = new InMemoryMentionRepository();
|
|
const api = new MentionsAPI({
|
|
repository,
|
|
routingService: mockRoutingService,
|
|
resourceService: mockResourceService,
|
|
webmentionMetadata: mockWebmentionMetadata
|
|
});
|
|
|
|
const mention = await api.processWebmention({
|
|
source: new URL('https://source.com'),
|
|
target: new URL('https://target.com'),
|
|
payload: {}
|
|
});
|
|
|
|
assert(mention instanceof Mention);
|
|
|
|
const page = await api.listMentions({
|
|
limit: 1,
|
|
page: 1
|
|
});
|
|
|
|
assert.equal(page.data[0].id, mention.id);
|
|
});
|
|
|
|
it('Can list all mentions', async function () {
|
|
const repository = new InMemoryMentionRepository();
|
|
const api = new MentionsAPI({
|
|
repository,
|
|
routingService: mockRoutingService,
|
|
resourceService: mockResourceService,
|
|
webmentionMetadata: mockWebmentionMetadata
|
|
});
|
|
|
|
const mention = await api.processWebmention({
|
|
source: new URL('https://source.com'),
|
|
target: new URL('https://target.com'),
|
|
payload: {}
|
|
});
|
|
|
|
assert(mention instanceof Mention);
|
|
|
|
const page = await api.listMentions({
|
|
limit: 'all'
|
|
});
|
|
|
|
assert.equal(page.data[0].id, mention.id);
|
|
});
|
|
|
|
it('Can list filtered mentions', async function () {
|
|
const repository = new InMemoryMentionRepository();
|
|
const api = new MentionsAPI({
|
|
repository,
|
|
routingService: mockRoutingService,
|
|
resourceService: mockResourceService,
|
|
webmentionMetadata: mockWebmentionMetadata
|
|
});
|
|
|
|
const mentionOne = await api.processWebmention({
|
|
source: new URL('https://diff-source.com'),
|
|
target: new URL('https://target.com'),
|
|
payload: {}
|
|
});
|
|
const mentionTwo = await api.processWebmention({
|
|
source: new URL('https://source.com'),
|
|
target: new URL('https://target.com'),
|
|
payload: {}
|
|
});
|
|
|
|
assert(mentionOne instanceof Mention);
|
|
assert(mentionTwo instanceof Mention);
|
|
|
|
const page = await api.listMentions({
|
|
filter: 'source.host:source.com',
|
|
limit: 'all'
|
|
});
|
|
|
|
assert(page.meta.pagination.total === 1);
|
|
assert(page.data[0].id === mentionTwo.id);
|
|
});
|
|
|
|
it('Can list mentions in descending order', async function () {
|
|
const repository = new InMemoryMentionRepository();
|
|
const api = new MentionsAPI({
|
|
repository,
|
|
routingService: mockRoutingService,
|
|
resourceService: mockResourceService,
|
|
webmentionMetadata: mockWebmentionMetadata
|
|
});
|
|
|
|
const mentionOne = await api.processWebmention({
|
|
source: new URL('https://source.com'),
|
|
target: new URL('https://target.com'),
|
|
payload: {}
|
|
});
|
|
|
|
sinon.useFakeTimers(addMinutes(new Date(), 10).getTime());
|
|
|
|
const mentionTwo = await api.processWebmention({
|
|
source: new URL('https://source2.com'),
|
|
target: new URL('https://target.com'),
|
|
payload: {}
|
|
});
|
|
|
|
assert(mentionOne instanceof Mention);
|
|
assert(mentionTwo instanceof Mention);
|
|
|
|
const page = await api.listMentions({
|
|
limit: 'all',
|
|
order: 'created_at desc'
|
|
});
|
|
|
|
assert(page.meta.pagination.total === 2);
|
|
assert(page.data[0].id === mentionTwo.id, 'First mention should be the second one in descending order');
|
|
assert(page.data[1].id === mentionOne.id, 'Second mention should be the first one in descending order');
|
|
});
|
|
|
|
it('Can list mentions in ascending order', async function () {
|
|
const repository = new InMemoryMentionRepository();
|
|
const api = new MentionsAPI({
|
|
repository,
|
|
routingService: mockRoutingService,
|
|
resourceService: mockResourceService,
|
|
webmentionMetadata: mockWebmentionMetadata
|
|
});
|
|
|
|
const mentionOne = await api.processWebmention({
|
|
source: new URL('https://source.com'),
|
|
target: new URL('https://target.com'),
|
|
payload: {}
|
|
});
|
|
|
|
sinon.useFakeTimers(addMinutes(new Date(), 10).getTime());
|
|
|
|
const mentionTwo = await api.processWebmention({
|
|
source: new URL('https://source2.com'),
|
|
target: new URL('https://target.com'),
|
|
payload: {}
|
|
});
|
|
|
|
assert(mentionOne instanceof Mention);
|
|
assert(mentionTwo instanceof Mention);
|
|
|
|
const page = await api.listMentions({
|
|
limit: 'all',
|
|
order: 'created_at asc'
|
|
});
|
|
|
|
assert(page.meta.pagination.total === 2);
|
|
assert(page.data[0].id === mentionOne.id, 'First mention should be the first one in ascending order');
|
|
assert(page.data[1].id === mentionTwo.id, 'Second mention should be the second one in ascending order');
|
|
});
|
|
|
|
it('Can update recommendations', async function () {
|
|
const repository = new InMemoryMentionRepository();
|
|
const api = new MentionsAPI({
|
|
repository,
|
|
routingService: mockRoutingService,
|
|
resourceService: mockResourceService,
|
|
webmentionMetadata: {
|
|
fetch: sinon.stub()
|
|
.onFirstCall().resolves(mockWebmentionMetadata.fetch())
|
|
.onSecondCall().resolves(mockWebmentionMetadata.fetch())
|
|
.onThirdCall().resolves(mockWebmentionMetadata.fetch())
|
|
.onCall(3).rejects()
|
|
}
|
|
});
|
|
|
|
await api.processWebmention({
|
|
source: new URL('https://source.com'),
|
|
target: new URL('https://target.com'),
|
|
payload: {}
|
|
});
|
|
|
|
sinon.useFakeTimers(addMinutes(new Date(), 10).getTime());
|
|
|
|
await api.processWebmention({
|
|
source: new URL('https://source2.com'),
|
|
target: new URL('https://target.com'),
|
|
payload: {}
|
|
});
|
|
|
|
let page = await api.listMentions({
|
|
limit: 'all'
|
|
});
|
|
assert(page.meta.pagination.total === 2);
|
|
|
|
// Now we invalidate the second mention
|
|
|
|
await api.refreshMentions({
|
|
limit: 'all'
|
|
});
|
|
|
|
page = await api.listMentions({
|
|
limit: 'all'
|
|
});
|
|
assert(page.meta.pagination.total === 1);
|
|
});
|
|
|
|
it('Can handle updating mentions', async function () {
|
|
const repository = new InMemoryMentionRepository();
|
|
const api = new MentionsAPI({
|
|
repository,
|
|
routingService: mockRoutingService,
|
|
resourceService: mockResourceService,
|
|
webmentionMetadata: mockWebmentionMetadata
|
|
});
|
|
|
|
const mentionOne = await api.processWebmention({
|
|
source: new URL('https://source.com'),
|
|
target: new URL('https://target.com'),
|
|
payload: {}
|
|
});
|
|
|
|
const mentionTwo = await api.processWebmention({
|
|
source: new URL('https://source.com'),
|
|
target: new URL('https://target.com'),
|
|
payload: {
|
|
new: 'info'
|
|
}
|
|
});
|
|
|
|
assert(mentionOne.id === mentionTwo.id);
|
|
|
|
const page = await api.listMentions({
|
|
limit: 'all'
|
|
});
|
|
|
|
assert(page.meta.pagination.total === 1);
|
|
assert(page.data[0].id === mentionOne.id);
|
|
});
|
|
|
|
it('Will error if the target page does not exist', async function () {
|
|
const repository = new InMemoryMentionRepository();
|
|
const api = new MentionsAPI({
|
|
repository,
|
|
routingService: {
|
|
async pageExists() {
|
|
return false;
|
|
}
|
|
},
|
|
resourceService: mockResourceService,
|
|
webmentionMetadata: mockWebmentionMetadata
|
|
});
|
|
|
|
let errored = false;
|
|
try {
|
|
await api.processWebmention({
|
|
source: new URL('https://source.com'),
|
|
target: new URL('https://target.com'),
|
|
payload: {}
|
|
});
|
|
} catch (err) {
|
|
errored = true;
|
|
} finally {
|
|
assert(errored);
|
|
}
|
|
});
|
|
|
|
it('Handles verify errors', async function () {
|
|
const repository = new InMemoryMentionRepository();
|
|
sinon.stub(cheerio, 'load').throws(new Error('Test error'));
|
|
|
|
const api = new MentionsAPI({
|
|
repository,
|
|
routingService: {
|
|
async pageExists() {
|
|
return true;
|
|
}
|
|
},
|
|
resourceService: mockResourceService,
|
|
webmentionMetadata: mockWebmentionMetadata
|
|
});
|
|
|
|
await api.processWebmention({
|
|
source: new URL('https://source.com'),
|
|
target: new URL('https://target.com'),
|
|
payload: {}
|
|
});
|
|
});
|
|
|
|
it('Will only store resource if if the resource type is post', async function () {
|
|
const repository = new InMemoryMentionRepository();
|
|
const api = new MentionsAPI({
|
|
repository,
|
|
routingService: mockRoutingService,
|
|
resourceService: {
|
|
async getByURL() {
|
|
return {
|
|
type: 'post',
|
|
id: new ObjectID
|
|
};
|
|
}
|
|
},
|
|
webmentionMetadata: mockWebmentionMetadata
|
|
});
|
|
|
|
const mention = await api.processWebmention({
|
|
source: new URL('https://source.com'),
|
|
target: new URL('https://target.com'),
|
|
payload: {}
|
|
});
|
|
|
|
assert(mention instanceof Mention);
|
|
|
|
const page = await api.listMentions({
|
|
limit: 'all'
|
|
});
|
|
|
|
assert.equal(page.data[0].id, mention.id);
|
|
});
|
|
|
|
it('Will delete an existing mention if the target page does not exist', async function () {
|
|
const repository = new InMemoryMentionRepository();
|
|
const api = new MentionsAPI({
|
|
repository,
|
|
routingService: {
|
|
pageExists: sinon.stub().onFirstCall().resolves(true).onSecondCall().resolves(false)
|
|
},
|
|
resourceService: {
|
|
async getByURL() {
|
|
return {
|
|
type: 'post',
|
|
id: new ObjectID
|
|
};
|
|
}
|
|
},
|
|
webmentionMetadata: mockWebmentionMetadata
|
|
});
|
|
|
|
checkFirstMention: {
|
|
const mention = await api.processWebmention({
|
|
source: new URL('https://source.com'),
|
|
target: new URL('https://target.com'),
|
|
payload: {}
|
|
});
|
|
|
|
const page = await api.listMentions({
|
|
limit: 'all'
|
|
});
|
|
|
|
assert.equal(page.data[0].id, mention.id);
|
|
break checkFirstMention;
|
|
}
|
|
|
|
checkMentionDeleted: {
|
|
await api.processWebmention({
|
|
source: new URL('https://source.com'),
|
|
target: new URL('https://target.com'),
|
|
payload: {}
|
|
});
|
|
|
|
const page = await api.listMentions({
|
|
limit: 'all'
|
|
});
|
|
|
|
assert.equal(page.data.length, 0);
|
|
break checkMentionDeleted;
|
|
}
|
|
});
|
|
|
|
it('Will delete and restore an existing mention if the source page does not exist', async function () {
|
|
const repository = new InMemoryMentionRepository();
|
|
const api = new MentionsAPI({
|
|
repository,
|
|
routingService: mockRoutingService,
|
|
resourceService: {
|
|
async getByURL() {
|
|
return {
|
|
type: 'post',
|
|
id: new ObjectID
|
|
};
|
|
}
|
|
},
|
|
webmentionMetadata: {
|
|
fetch: sinon.stub()
|
|
.onFirstCall().resolves(mockWebmentionMetadata.fetch())
|
|
.onSecondCall().rejects()
|
|
.onThirdCall().resolves(mockWebmentionMetadata.fetch())
|
|
}
|
|
});
|
|
|
|
checkFirstMention: {
|
|
const mention = await api.processWebmention({
|
|
source: new URL('https://source.com'),
|
|
target: new URL('https://target.com'),
|
|
payload: {}
|
|
});
|
|
|
|
const page = await api.listMentions({
|
|
limit: 'all'
|
|
});
|
|
|
|
assert.equal(page.data[0].id, mention.id);
|
|
break checkFirstMention;
|
|
}
|
|
|
|
checkMentionDeleted: {
|
|
await api.processWebmention({
|
|
source: new URL('https://source.com'),
|
|
target: new URL('https://target.com'),
|
|
payload: {}
|
|
});
|
|
|
|
const page = await api.listMentions({
|
|
limit: 'all'
|
|
});
|
|
|
|
assert.equal(page.data.length, 0);
|
|
break checkMentionDeleted;
|
|
}
|
|
|
|
checkRestored: {
|
|
const mention = await api.processWebmention({
|
|
source: new URL('https://source.com'),
|
|
target: new URL('https://target.com'),
|
|
payload: {}
|
|
});
|
|
|
|
const page = await api.listMentions({
|
|
limit: 'all'
|
|
});
|
|
|
|
assert.equal(page.data[0].id, mention.id);
|
|
break checkRestored;
|
|
}
|
|
});
|
|
|
|
it('Will delete and restore an existing mention if the target url is not present on the source page', async function () {
|
|
const repository = new InMemoryMentionRepository();
|
|
const api = new MentionsAPI({
|
|
repository,
|
|
routingService: mockRoutingService,
|
|
resourceService: {
|
|
async getByURL() {
|
|
return {
|
|
type: 'post',
|
|
id: new ObjectID
|
|
};
|
|
}
|
|
},
|
|
webmentionMetadata: {
|
|
fetch: sinon.stub()
|
|
.onFirstCall().resolves(mockWebmentionMetadata.fetch())
|
|
.onSecondCall().resolves({...(await mockWebmentionMetadata.fetch()), body: 'test'})
|
|
.onThirdCall().resolves(mockWebmentionMetadata.fetch())
|
|
}
|
|
});
|
|
|
|
checkFirstMention: {
|
|
const mention = await api.processWebmention({
|
|
source: new URL('https://source.com'),
|
|
target: new URL('https://target.com'),
|
|
payload: {}
|
|
});
|
|
|
|
const page = await api.listMentions({
|
|
limit: 'all'
|
|
});
|
|
|
|
assert.equal(page.data[0].id, mention.id);
|
|
break checkFirstMention;
|
|
}
|
|
|
|
checkMentionDeleted: {
|
|
await api.processWebmention({
|
|
source: new URL('https://source.com'),
|
|
target: new URL('https://target.com'),
|
|
payload: {}
|
|
});
|
|
|
|
const page = await api.listMentions({
|
|
limit: 'all'
|
|
});
|
|
|
|
assert.equal(page.data.length, 0);
|
|
break checkMentionDeleted;
|
|
}
|
|
|
|
checkRestored: {
|
|
const mention = await api.processWebmention({
|
|
source: new URL('https://source.com'),
|
|
target: new URL('https://target.com'),
|
|
payload: {}
|
|
});
|
|
|
|
const page = await api.listMentions({
|
|
limit: 'all'
|
|
});
|
|
|
|
assert.equal(page.data[0].id, mention.id);
|
|
break checkRestored;
|
|
}
|
|
});
|
|
|
|
it('Will throw for new mentions if the source page is not found', async function () {
|
|
const repository = new InMemoryMentionRepository();
|
|
const api = new MentionsAPI({
|
|
repository,
|
|
routingService: mockRoutingService,
|
|
resourceService: {
|
|
async getByURL() {
|
|
return {
|
|
type: 'post',
|
|
id: new ObjectID
|
|
};
|
|
}
|
|
},
|
|
webmentionMetadata: {
|
|
fetch: sinon.stub().rejects(new Error(''))
|
|
}
|
|
});
|
|
|
|
let error = null;
|
|
try {
|
|
await api.processWebmention({
|
|
source: new URL('https://source.com'),
|
|
target: new URL('https://target.com'),
|
|
payload: {}
|
|
});
|
|
} catch (err) {
|
|
error = err;
|
|
} finally {
|
|
assert(error);
|
|
}
|
|
});
|
|
});
|