Added tests for click events in the activity feed

fixes https://github.com/TryGhost/Team/issues/2018

- Includes new test fixtures for redirects and click events
- Tests if post, and links are returned in the click events
This commit is contained in:
Simon Backx 2022-10-06 11:43:39 +02:00
parent 08da18e324
commit d4540012dc
5 changed files with 281 additions and 4 deletions

View File

@ -358,13 +358,17 @@ const PostEmailSerializer = {
// Now replace the links in the HTML version
if (!options.isBrowserPreview && !options.isTestEmail && settingsCache.get('email_track_clicks')) {
result.html = await linkReplacer.replace(result.html, async (url) => {
// Add newsletter source attribution
url = memberAttribution.service.addEmailSourceAttributionTracking(url, newsletter);
const isSite = urlUtils.isSiteUrl(url);
if (isSite) {
url = memberAttribution.service.addEmailSourceAttributionTracking(url, newsletter);
// Only add post attribution to our own site (because external sites could/should not process this information)
url = memberAttribution.service.addPostAttributionTracking(url, post);
} else {
// For external sites, add ref to the site URL instead of newsletter
const siteUrl = new URL(urlUtils.urlFor('home', true));
url.searchParams.append('ref', siteUrl.hostname);
}
// Add link click tracking

View File

@ -4042,6 +4042,209 @@ Object {
}
`;
exports[`Members API Returns click events in activity feed 1: [body] 1`] = `
Object {
"events": Array [
Object {
"data": Object {
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}/,
"link": Object {
"from": "/r/0",
"to": "https:://ghost.org",
},
"member": Object {
"avatar_image": null,
"email": "member1@test.com",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"name": "Mr Egg",
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
},
"post": Object {
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"title": "HTML Ipsum",
"url": Any<String>,
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
},
},
"type": Any<String>,
},
Object {
"data": Object {
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}/,
"link": Object {
"from": "/r/1",
"to": "https:://ghost.org",
},
"member": Object {
"avatar_image": null,
"email": "member2@test.com",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"name": null,
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
},
"post": Object {
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"title": "Ghostly Kitchen Sink",
"url": Any<String>,
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
},
},
"type": Any<String>,
},
Object {
"data": Object {
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}/,
"link": Object {
"from": "/r/2",
"to": "https:://ghost.org",
},
"member": Object {
"avatar_image": null,
"email": "paid@test.com",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"name": "Egon Spengler",
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
},
"post": Object {
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"title": "Short and Sweet",
"url": Any<String>,
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
},
},
"type": Any<String>,
},
Object {
"data": Object {
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}/,
"link": Object {
"from": "/r/3",
"to": "https:://ghost.org",
},
"member": Object {
"avatar_image": null,
"email": "trialing@test.com",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"name": "Ray Stantz",
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
},
"post": Object {
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"title": "Not finished yet",
"url": Any<String>,
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
},
},
"type": Any<String>,
},
Object {
"data": Object {
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}/,
"link": Object {
"from": "/r/4",
"to": "https:://ghost.org",
},
"member": Object {
"avatar_image": null,
"email": "comped@test.com",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"name": "Vinz Clortho",
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
},
"post": Object {
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"title": "Not so short, bit complex",
"url": Any<String>,
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
},
},
"type": Any<String>,
},
Object {
"data": Object {
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}/,
"link": Object {
"from": "/r/5",
"to": "https:://ghost.org",
},
"member": Object {
"avatar_image": null,
"email": "vip@test.com",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"name": "Winston Zeddemore",
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
},
"post": Object {
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"title": "This is a static page",
"url": Any<String>,
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
},
},
"type": Any<String>,
},
Object {
"data": Object {
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}/,
"link": Object {
"from": "/r/6",
"to": "https:://ghost.org",
},
"member": Object {
"avatar_image": null,
"email": "vip-paid@test.com",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"name": "Peter Venkman",
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
},
"post": Object {
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"title": "This is a draft static page",
"url": Any<String>,
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
},
},
"type": Any<String>,
},
Object {
"data": Object {
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}/,
"link": Object {
"from": "/r/7",
"to": "https:://ghost.org",
},
"member": Object {
"avatar_image": null,
"email": "with-product@test.com",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"name": "Dana Barrett",
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
},
"post": Object {
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"title": "This is a scheduled post!!",
"url": Any<String>,
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
},
},
"type": Any<String>,
},
],
}
`;
exports[`Members API Returns click events in activity feed 2: [headers] 1`] = `
Object {
"access-control-allow-origin": "http://127.0.0.1:2369",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "3631",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"vary": "Accept-Version, Origin, Accept-Encoding",
"x-powered-by": "Express",
}
`;
exports[`Members API Returns comments in activity feed 1: [body] 1`] = `
Object {
"events": Array [

View File

@ -416,7 +416,7 @@ describe('Members API', function () {
before(async function () {
agent = await agentProvider.getAdminAPIAgent();
await fixtureManager.init('posts', 'newsletters', 'members:newsletters', 'comments');
await fixtureManager.init('posts', 'newsletters', 'members:newsletters', 'comments', 'redirects', 'clicks');
await agent.loginAsOwner();
newsletters = await getNewsletters();
@ -451,6 +451,36 @@ describe('Members API', function () {
});
});
it('Returns click events in activity feed', async function () {
// Check activity feed
await agent
.get(`/members/events?filter=type:click_event`)
.expectStatus(200)
.matchHeaderSnapshot({
etag: anyEtag
})
.matchBodySnapshot({
events: new Array(8).fill({
type: anyString,
data: {
created_at: anyISODate,
member: {
id: anyObjectId,
uuid: anyUuid
},
post: {
id: anyObjectId,
uuid: anyUuid,
url: anyString
}
}
})
})
.expect(({body}) => {
should(body.events.find(e => e.type === 'click_event')).not.be.undefined();
});
});
// List Members
it('Can browse', async function () {

View File

@ -643,6 +643,18 @@ const fixtures = {
}));
},
insertRedirects: async function insertClicks() {
await Promise.all(DataGenerator.forKnex.redirects.map((click) => {
return models.Redirect.add(click, context.internal);
}));
},
insertClicks: async function insertClicks() {
await Promise.all(DataGenerator.forKnex.members_click_events.map((click) => {
return models.MemberClickEvent.add(click, context.internal);
}));
},
insertSnippets: function insertSnippets() {
return Promise.map(DataGenerator.forKnex.snippets, function (snippet) {
return models.Snippet.add(snippet, context.internal);
@ -776,6 +788,12 @@ const toDoList = {
},
comments: function insertComments() {
return fixtures.insertComments();
},
redirects: function insertRedirects() {
return fixtures.insertRedirects();
},
clicks: function insertClicks() {
return fixtures.insertClicks();
}
};

View File

@ -1656,6 +1656,26 @@ DataGenerator.forKnex = (function () {
createBasic(DataGenerator.Content.members_paid_subscription_events[2])
];
const redirects = posts.map((post, index) => {
return {
id: ObjectId().toHexString(),
from: '/r/' + index,
to: 'https:://ghost.org',
post_id: post.id,
created_at: new Date(),
updated_at: new Date()
};
});
const members_click_events = redirects.map((redirect, index) => {
return {
id: ObjectId().toHexString(),
member_id: members[index].id,
redirect_id: redirect.id,
created_at: new Date()
};
});
const snippets = [
createBasic(DataGenerator.Content.snippets[0])
];
@ -1730,10 +1750,12 @@ DataGenerator.forKnex = (function () {
snippets,
custom_theme_settings,
comments,
redirects,
members_paid_subscription_events,
members_created_events,
members_subscription_created_events
members_subscription_created_events,
members_click_events
};
}());