mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-30 21:40:39 +03:00
81df7ca05b
ref ENG-1372 ENG-447
350 lines
12 KiB
JavaScript
350 lines
12 KiB
JavaScript
const LinkClickTrackingService = require('../lib/LinkClickTrackingService');
|
|
const sinon = require('sinon');
|
|
const should = require('should');
|
|
const assert = require('assert/strict');
|
|
const ObjectID = require('bson-objectid').default;
|
|
const PostLink = require('../lib/PostLink');
|
|
const {RedirectEvent} = require('@tryghost/link-redirects');
|
|
const errors = require('@tryghost/errors');
|
|
|
|
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.deepEqual(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);
|
|
});
|
|
});
|
|
|
|
describe('bulkEdit', function () {
|
|
it('returns the result of updating links', async function () {
|
|
const service = new LinkClickTrackingService({
|
|
urlUtils: {
|
|
absoluteToTransformReady: (d) => {
|
|
return d;
|
|
},
|
|
isSiteUrl: sinon.stub().returns(true)
|
|
},
|
|
postLinkRepository: {
|
|
updateLinks: sinon.stub().resolves({
|
|
successful: 0,
|
|
unsuccessful: 0,
|
|
errors: [],
|
|
unsuccessfulData: []
|
|
})
|
|
},
|
|
linkRedirectService: {
|
|
getFilteredIds: sinon.stub().resolves([])
|
|
}
|
|
});
|
|
const options = {
|
|
filter: `post_id:1+to:'https://test.com'`
|
|
};
|
|
|
|
const result = await service.bulkEdit({
|
|
action: 'updateLink',
|
|
meta: {
|
|
link: {to: 'https://example.com'}
|
|
}
|
|
}, options);
|
|
should(result).eql({
|
|
successful: 0,
|
|
unsuccessful: 0,
|
|
errors: [],
|
|
unsuccessfulData: []
|
|
});
|
|
});
|
|
|
|
//test for #parseLinkFilter method
|
|
it('correctly decodes and parses the filter', async function () {
|
|
const urlUtilsStub = {
|
|
absoluteToTransformReady: sinon.stub().returnsArg(0),
|
|
isSiteUrl: sinon.stub().returns(true)
|
|
};
|
|
const postLinkRepositoryStub = {
|
|
updateLinks: sinon.stub().resolves({
|
|
successful: 0,
|
|
unsuccessful: 0,
|
|
errors: [],
|
|
unsuccessfulData: []
|
|
})
|
|
};
|
|
const linkRedirectServiceStub = {
|
|
getFilteredIds: sinon.stub().resolves([])
|
|
};
|
|
|
|
const service = new LinkClickTrackingService({
|
|
urlUtils: urlUtilsStub,
|
|
postLinkRepository: postLinkRepositoryStub,
|
|
linkRedirectService: linkRedirectServiceStub
|
|
});
|
|
|
|
const options = {
|
|
filter: 'post_id:1+to:\'https://example.com/path\''
|
|
};
|
|
|
|
const data = {
|
|
action: 'updateLink',
|
|
meta: {
|
|
link: {to: 'https://example.com/new-path'}
|
|
}
|
|
};
|
|
|
|
const result = await service.bulkEdit(data, options);
|
|
|
|
should(postLinkRepositoryStub.updateLinks.calledOnce).be.true();
|
|
should(result).eql({
|
|
successful: 0,
|
|
unsuccessful: 0,
|
|
errors: [],
|
|
unsuccessfulData: []
|
|
});
|
|
|
|
const [filterOptions] = linkRedirectServiceStub.getFilteredIds.firstCall.args;
|
|
should(filterOptions.filter).equal('post_id:\'1\'+to:\'https://example.com/path\'');
|
|
});
|
|
|
|
//test for #parseLinkFilter method
|
|
it('correctly decodes and parses the filter for encoded urls', async function () {
|
|
const urlUtilsStub = {
|
|
absoluteToTransformReady: sinon.stub().returnsArg(0),
|
|
isSiteUrl: sinon.stub().returns(true)
|
|
};
|
|
const postLinkRepositoryStub = {
|
|
updateLinks: sinon.stub().resolves({
|
|
successful: 0,
|
|
unsuccessful: 0,
|
|
errors: [],
|
|
unsuccessfulData: []
|
|
})
|
|
};
|
|
const linkRedirectServiceStub = {
|
|
getFilteredIds: sinon.stub().resolves([])
|
|
};
|
|
|
|
const service = new LinkClickTrackingService({
|
|
urlUtils: urlUtilsStub,
|
|
postLinkRepository: postLinkRepositoryStub,
|
|
linkRedirectService: linkRedirectServiceStub
|
|
});
|
|
|
|
const options = {
|
|
filter: 'post_id:1+to:\'https://example.com/path%2Ftestpath\''
|
|
};
|
|
|
|
const data = {
|
|
action: 'updateLink',
|
|
meta: {
|
|
link: {to: 'https://example.com/new-path'}
|
|
}
|
|
};
|
|
|
|
const result = await service.bulkEdit(data, options);
|
|
|
|
should(postLinkRepositoryStub.updateLinks.calledOnce).be.true();
|
|
should(result).eql({
|
|
successful: 0,
|
|
unsuccessful: 0,
|
|
errors: [],
|
|
unsuccessfulData: []
|
|
});
|
|
|
|
const [filterOptions] = linkRedirectServiceStub.getFilteredIds.firstCall.args;
|
|
should(filterOptions.filter).equal('post_id:\'1\'+to:\'https://example.com/path%2Ftestpath\'');
|
|
});
|
|
|
|
//test for #parseLinkFilter method
|
|
it('throws BadRequestError for invalid filter', async function () {
|
|
const urlUtilsStub = {
|
|
absoluteToTransformReady: sinon.stub().returnsArg(0),
|
|
isSiteUrl: sinon.stub().returns(true)
|
|
};
|
|
const postLinkRepositoryStub = {
|
|
updateLinks: sinon.stub().resolves({
|
|
successful: 0,
|
|
unsuccessful: 0,
|
|
errors: [],
|
|
unsuccessfulData: []
|
|
})
|
|
};
|
|
const linkRedirectServiceStub = {
|
|
getFilteredIds: sinon.stub().resolves([])
|
|
};
|
|
|
|
const service = new LinkClickTrackingService({
|
|
urlUtils: urlUtilsStub,
|
|
postLinkRepository: postLinkRepositoryStub,
|
|
linkRedirectService: linkRedirectServiceStub
|
|
});
|
|
|
|
const options = {
|
|
filter: 'invalid_filter'
|
|
};
|
|
|
|
const data = {
|
|
action: 'updateLink',
|
|
meta: {
|
|
link: {to: 'https://example.com/new-path'}
|
|
}
|
|
};
|
|
|
|
await should(service.bulkEdit(data, options)).be.rejectedWith(errors.BadRequestError);
|
|
});
|
|
});
|
|
});
|