Added LinkClickTrackingService unit tests and renamed wrapper (#15462)

refs https://github.com/TryGhost/Team/issues/1958

- Renamed wrapper service link-click-tracking to link-tracking to be consistent with the package name
- Added unit tests for LinkClickTrackingService
- Added DomainEvents dependency to LinkClickTrackingService
- Fixes dependencies in link-tracking package
This commit is contained in:
Simon Backx 2022-09-23 16:19:16 +02:00 committed by GitHub
parent 2bff2a22e0
commit e658f7622a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 187 additions and 17 deletions

View File

@ -287,7 +287,7 @@ async function initServices({config}) {
const staffService = require('./server/services/staff');
const memberAttribution = require('./server/services/member-attribution');
const membersEvents = require('./server/services/members-events');
const linkTracking = require('./server/services/link-click-tracking');
const linkTracking = require('./server/services/link-tracking');
const urlUtils = require('./shared/url-utils');

View File

@ -1,4 +1,4 @@
const linkTrackingService = require('../../services/link-click-tracking');
const linkTrackingService = require('../../services/link-tracking');
module.exports = {
docName: 'links',

View File

@ -19,7 +19,7 @@ class LinkTrackingServiceWrapper {
const {MemberLinkClickEvent} = require('@tryghost/member-events');
const DomainEvents = require('@tryghost/domain-events');
const {LinkTrackingService} = require('@tryghost/link-tracking');
const {LinkClickTrackingService} = require('@tryghost/link-tracking');
const postLinkRepository = new PostLinkRepository({
LinkRedirect: models.LinkRedirect,
@ -34,10 +34,11 @@ class LinkTrackingServiceWrapper {
});
// Expose the service
this.service = new LinkTrackingService({
this.service = new LinkClickTrackingService({
linkRedirectService: linkRedirection.service,
linkClickRepository,
postLinkRepository
postLinkRepository,
DomainEvents
});
await this.service.init();

View File

@ -15,7 +15,7 @@ const {textColorForBackgroundColor, darkenToContrastThreshold} = require('@trygh
const logging = require('@tryghost/logging');
const urlService = require('../../services/url');
const linkReplacer = require('@tryghost/link-replacer');
const linkTracking = require('../link-click-tracking');
const linkTracking = require('../link-tracking');
const memberAttribution = require('../member-attribution');
const ALLOWED_REPLACEMENTS = ['first_name', 'uuid'];

View File

@ -1,4 +1,3 @@
const DomainEvents = require('@tryghost/domain-events');
const {RedirectEvent} = require('@tryghost/link-redirects');
const LinkClick = require('./LinkClick');
const PostLink = require('./PostLink');
@ -43,17 +42,21 @@ class LinkClickTrackingService {
#linkRedirectService;
/** @type IPostLinkRepository */
#postLinkRepository;
/** @type DomainEvents */
#DomainEvents;
/**
* @param {object} deps
* @param {ILinkClickRepository} deps.linkClickRepository
* @param {ILinkRedirectService} deps.linkRedirectService
* @param {IPostLinkRepository} deps.postLinkRepository
* @param {DomainEvents} deps.DomainEvents
*/
constructor(deps) {
this.#linkClickRepository = deps.linkClickRepository;
this.#linkRedirectService = deps.linkRedirectService;
this.#postLinkRepository = deps.postLinkRepository;
this.#DomainEvents = deps.DomainEvents;
}
async init() {
@ -109,7 +112,7 @@ class LinkClickTrackingService {
}
subscribe() {
DomainEvents.subscribe(RedirectEvent, async (event) => {
this.#DomainEvents.subscribe(RedirectEvent, async (event) => {
const uuid = event.data.url.searchParams.get('m');
if (!uuid) {
return;

View File

@ -1,5 +1,5 @@
module.exports = {
LinkTrackingService: require('./LinkClickTrackingService'),
LinkClickTrackingService: require('./LinkClickTrackingService'),
LinkClick: require('./LinkClick'),
PostLink: require('./PostLink'),
FullPostLink: require('./FullPostLink')

View File

@ -7,7 +7,7 @@
"main": "index.js",
"scripts": {
"dev": "echo \"Implement me!\"",
"test:unit": "NODE_ENV=testing c8 --all --reporter text --reporter cobertura mocha './test/**/*.test.js'",
"test:unit": "NODE_ENV=testing c8 --all --check-coverage --reporter text --reporter cobertura mocha './test/**/*.test.js'",
"test": "yarn test:unit",
"lint:code": "eslint *.js lib/ --ext .js --cache",
"lint": "yarn lint:code && yarn lint:test",
@ -24,7 +24,7 @@
"sinon": "14.0.0"
},
"dependencies": {
"@tryghost/domain-events": "0.0.0",
"@tryghost/link-redirects": "0.0.0"
"@tryghost/link-redirects": "0.0.0",
"bson-objectid": "2.0.3"
}
}

View File

@ -0,0 +1,171 @@
const LinkClickTrackingService = require('../lib/LinkClickTrackingService');
const sinon = require('sinon');
const assert = require('assert');
const ObjectID = require('bson-objectid').default;
const PostLink = require('../lib/PostLink');
const {RedirectEvent} = require('@tryghost/link-redirects');
describe('LinkClickTrackingService', function () {
it('exists', function () {
require('../');
});
describe('init', function () {
it('initialises only once', function () {
const subscribe = sinon.stub();
const service = new LinkClickTrackingService({
DomainEvents: {
subscribe
}
});
service.init();
assert.ok(subscribe.calledOnce);
service.init();
assert.ok(subscribe.calledOnce);
});
});
describe('getLinks', function () {
it('passes call to postLinkRepository', async function () {
const getAll = sinon.stub().resolves(['test']);
const service = new LinkClickTrackingService({
postLinkRepository: {
getAll
}
});
const links = await service.getLinks({filter: 'post_id:1'});
// Check called with filter
assert.ok(getAll.calledOnceWithExactly({filter: 'post_id:1'}));
// Check returned value
assert.deepStrictEqual(links, ['test']);
});
});
describe('addRedirectToUrl', function () {
it('Creates a redirect', async function () {
const getSlugUrl = sinon.stub().resolves(new URL('https://example.com/r/uniqueslug'));
const save = sinon.stub().resolves();
const linkId = new ObjectID();
const addRedirect = sinon.stub().resolves({link_id: linkId, to: new URL('https://example.com/destination'), from: new URL('https://example.com/r/uniqueslug')});
const service = new LinkClickTrackingService({
linkRedirectService: {
getSlugUrl,
addRedirect
},
postLinkRepository: {
save
}
});
const postId = new ObjectID().toHexString();
const updatedUrl = await service.addRedirectToUrl(new URL('https://example.com/destination'), {id: postId});
assert.equal(updatedUrl.toString(), 'https://example.com/r/uniqueslug');
// Check getSlugUrl called
assert(getSlugUrl.calledOnce);
// Check save called
assert(
save.calledOnceWithExactly(
new PostLink({
post_id: postId,
link_id: linkId
})
)
);
});
});
describe('addTrackingToUrl', function () {
it('Creates a redirect', async function () {
const getSlugUrl = sinon.stub().resolves(new URL('https://example.com/r/uniqueslug'));
const save = sinon.stub().resolves();
const linkId = new ObjectID();
const addRedirect = sinon.stub().resolves({link_id: linkId, to: new URL('https://example.com/destination'), from: new URL('https://example.com/r/uniqueslug')});
const service = new LinkClickTrackingService({
linkRedirectService: {
getSlugUrl,
addRedirect
},
postLinkRepository: {
save
}
});
const postId = new ObjectID().toHexString();
const updatedUrl = await service.addTrackingToUrl(new URL('https://example.com/destination'), {id: postId}, '123');
assert.equal(updatedUrl.toString(), 'https://example.com/r/uniqueslug?m=123');
// Check getSlugUrl called
assert(getSlugUrl.calledOnce);
// Check save called
assert(
save.calledOnceWithExactly(
new PostLink({
post_id: postId,
link_id: linkId
})
)
);
});
});
describe('subscribe', function () {
it('Ignores redirects without a member id', async function () {
const event = RedirectEvent.create({
url: new URL('https://example.com/destination'),
link: {}
});
const save = sinon.stub().resolves();
const service = new LinkClickTrackingService({
DomainEvents: {
subscribe: (eventType, callback) => {
assert.equal(eventType, RedirectEvent);
callback(event);
}
},
linkClickRepository: {
save
}
});
service.subscribe();
assert(!save.called);
});
it('Tracks redirects with a member id', async function () {
const linkId = new ObjectID();
const event = RedirectEvent.create({
url: new URL('https://example.com/destination?m=memberId'),
link: {
link_id: linkId
}
});
const save = sinon.stub().resolves();
const service = new LinkClickTrackingService({
DomainEvents: {
subscribe: (eventType, callback) => {
assert.equal(eventType, RedirectEvent);
callback(event);
}
},
linkClickRepository: {
save
}
});
service.subscribe();
assert(save.calledOnce);
assert.equal(save.firstCall.args[0].member_uuid, 'memberId');
assert.equal(save.firstCall.args[0].link_id, linkId);
});
});
});

View File

@ -1,5 +0,0 @@
describe('LinkTrackingService', function () {
it('exists', function () {
require('../');
});
});