2023-10-03 18:11:47 +03:00
|
|
|
import assert from 'assert/strict';
|
2023-11-03 17:02:45 +03:00
|
|
|
import {ClickEvent, InMemoryRecommendationRepository, Recommendation, RecommendationService, SubscribeEvent, WellknownService, RecommendationMetadata, RecommendationMetadataService} from '../src';
|
2023-10-03 18:11:47 +03:00
|
|
|
import {InMemoryRepository} from '@tryghost/in-memory-repository';
|
|
|
|
import sinon from 'sinon';
|
|
|
|
|
|
|
|
class InMemoryClickEventRepository<T extends ClickEvent|SubscribeEvent> extends InMemoryRepository<string, T> {
|
|
|
|
toPrimitive(entity: T): object {
|
|
|
|
return entity;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
describe('RecommendationService', function () {
|
|
|
|
let service: RecommendationService;
|
|
|
|
let enabled = false;
|
2023-11-03 17:02:45 +03:00
|
|
|
let clock: sinon.SinonFakeTimers;
|
|
|
|
let fetchMetadataStub: sinon.SinonStub<any[], Promise<RecommendationMetadata>>;
|
2023-10-03 18:11:47 +03:00
|
|
|
|
|
|
|
beforeEach(function () {
|
|
|
|
enabled = false;
|
2023-11-03 17:02:45 +03:00
|
|
|
fetchMetadataStub = sinon.stub().resolves({
|
|
|
|
title: 'Test',
|
|
|
|
excerpt: null,
|
|
|
|
featuredImage: null,
|
|
|
|
favicon: null,
|
|
|
|
oneClickSubscribe: false
|
|
|
|
});
|
2023-10-03 18:11:47 +03:00
|
|
|
service = new RecommendationService({
|
|
|
|
repository: new InMemoryRecommendationRepository(),
|
|
|
|
clickEventRepository: new InMemoryClickEventRepository<ClickEvent>(),
|
|
|
|
subscribeEventRepository: new InMemoryClickEventRepository<SubscribeEvent>(),
|
|
|
|
wellknownService: {
|
|
|
|
getPath() {
|
|
|
|
return '';
|
|
|
|
},
|
|
|
|
getURL() {
|
|
|
|
return new URL('http://localhost/.well-known/recommendations.json');
|
|
|
|
},
|
|
|
|
set() {
|
|
|
|
return Promise.resolve();
|
|
|
|
}
|
|
|
|
} as unknown as WellknownService,
|
|
|
|
mentionSendingService: {
|
|
|
|
sendAll() {
|
|
|
|
return Promise.resolve();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
recommendationEnablerService: {
|
|
|
|
getSetting() {
|
|
|
|
return enabled.toString();
|
|
|
|
},
|
|
|
|
setSetting(e) {
|
|
|
|
enabled = e === 'true';
|
|
|
|
return Promise.resolve();
|
|
|
|
}
|
2023-11-03 17:02:45 +03:00
|
|
|
},
|
|
|
|
recommendationMetadataService: {
|
|
|
|
fetch: fetchMetadataStub
|
|
|
|
} as unknown as RecommendationMetadataService
|
2023-10-03 18:11:47 +03:00
|
|
|
});
|
2023-11-03 17:02:45 +03:00
|
|
|
clock = sinon.useFakeTimers();
|
|
|
|
});
|
|
|
|
|
|
|
|
afterEach(function () {
|
|
|
|
sinon.restore();
|
|
|
|
clock.restore();
|
2023-10-03 18:11:47 +03:00
|
|
|
});
|
|
|
|
|
|
|
|
describe('init', function () {
|
|
|
|
it('should update wellknown', async function () {
|
|
|
|
const updateWellknown = sinon.stub(service.wellknownService, 'set').resolves();
|
|
|
|
await service.init();
|
|
|
|
assert(updateWellknown.calledOnce);
|
|
|
|
});
|
2023-11-03 17:02:45 +03:00
|
|
|
|
|
|
|
it('should update recommendations on boot', async function () {
|
|
|
|
const recommendation = Recommendation.create({
|
|
|
|
id: '2',
|
|
|
|
url: 'http://localhost/1',
|
|
|
|
title: 'Test',
|
|
|
|
description: null,
|
|
|
|
excerpt: null,
|
|
|
|
featuredImage: null,
|
|
|
|
favicon: null,
|
|
|
|
oneClickSubscribe: false
|
|
|
|
});
|
|
|
|
await service.repository.save(recommendation);
|
|
|
|
|
|
|
|
// Sandbox time
|
|
|
|
const saved = process.env.NODE_ENV;
|
|
|
|
try {
|
|
|
|
process.env.NODE_ENV = 'development';
|
|
|
|
const spy = sinon.spy(service, 'updateAllRecommendationsMetadata');
|
|
|
|
await service.init();
|
|
|
|
await clock.tick(1000 * 60 * 60 * 24);
|
|
|
|
assert(spy.calledOnce);
|
|
|
|
} finally {
|
|
|
|
process.env.NODE_ENV = saved;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
it('ignores errors when update recommendations on boot', async function () {
|
|
|
|
// Sandbox time
|
|
|
|
const saved = process.env.NODE_ENV;
|
|
|
|
try {
|
|
|
|
process.env.NODE_ENV = 'development';
|
|
|
|
const spy = sinon.stub(service, 'updateAllRecommendationsMetadata');
|
|
|
|
spy.rejects(new Error('test'));
|
|
|
|
await service.init();
|
|
|
|
clock.tick(1000 * 60 * 60 * 24);
|
|
|
|
assert(spy.calledOnce);
|
|
|
|
} finally {
|
|
|
|
process.env.NODE_ENV = saved;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should errors when update recommendations on boot (invidiual)', async function () {
|
|
|
|
const recommendation = Recommendation.create({
|
|
|
|
id: '2',
|
|
|
|
url: 'http://localhost/1',
|
|
|
|
title: 'Test',
|
|
|
|
description: null,
|
|
|
|
excerpt: null,
|
|
|
|
featuredImage: null,
|
|
|
|
favicon: null,
|
|
|
|
oneClickSubscribe: false
|
|
|
|
});
|
|
|
|
await service.repository.save(recommendation);
|
|
|
|
|
|
|
|
// Sandbox time
|
|
|
|
const saved = process.env.NODE_ENV;
|
|
|
|
try {
|
|
|
|
process.env.NODE_ENV = 'development';
|
|
|
|
const spy = sinon.stub(service, '_updateRecommendationMetadata');
|
|
|
|
spy.rejects(new Error('This is a test'));
|
|
|
|
await service.init();
|
|
|
|
clock.tick(1000 * 60 * 60 * 24);
|
|
|
|
clock.restore();
|
|
|
|
// This assert doesn't work without a timeout because the timeout in boot is async
|
|
|
|
// eslint-disable-next-line no-promise-executor-return
|
|
|
|
await new Promise((resolve) => {
|
|
|
|
setTimeout(() => resolve(true), 50);
|
|
|
|
});
|
|
|
|
assert(!!spy.calledOnce);
|
|
|
|
} finally {
|
|
|
|
process.env.NODE_ENV = saved;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('checkRecommendation', function () {
|
|
|
|
it('Returns existing recommendation if found', async function () {
|
|
|
|
const recommendation = Recommendation.create({
|
|
|
|
id: '2',
|
|
|
|
url: 'http://localhost/existing',
|
|
|
|
title: 'Test',
|
|
|
|
description: null,
|
|
|
|
excerpt: null,
|
|
|
|
featuredImage: null,
|
|
|
|
favicon: null,
|
|
|
|
oneClickSubscribe: false
|
|
|
|
});
|
|
|
|
await service.repository.save(recommendation);
|
|
|
|
|
|
|
|
const response = await service.checkRecommendation(new URL('http://localhost/existing'));
|
|
|
|
assert.deepEqual(response, recommendation.plain);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('Returns updated recommendation if found', async function () {
|
|
|
|
const recommendation = Recommendation.create({
|
|
|
|
id: '2',
|
|
|
|
url: 'http://localhost/existing',
|
|
|
|
title: 'Test',
|
|
|
|
description: null,
|
|
|
|
excerpt: null,
|
|
|
|
featuredImage: null,
|
|
|
|
favicon: null,
|
|
|
|
oneClickSubscribe: false
|
|
|
|
});
|
|
|
|
// Force an empty title (shouldn't be possible)
|
|
|
|
recommendation.title = '';
|
|
|
|
await service.repository.save(recommendation);
|
|
|
|
|
|
|
|
fetchMetadataStub.resolves({
|
|
|
|
title: 'Test 2',
|
|
|
|
excerpt: 'Test excerpt',
|
|
|
|
featuredImage: new URL('https://example.com/image.png'),
|
|
|
|
favicon: new URL('https://example.com/favicon.ico'),
|
|
|
|
oneClickSubscribe: true
|
|
|
|
});
|
|
|
|
|
|
|
|
const response = await service.checkRecommendation(new URL('http://localhost/existing'));
|
|
|
|
assert.deepEqual(response, {
|
|
|
|
...recommendation.plain,
|
|
|
|
// Note: Title only changes if it was empty
|
|
|
|
title: 'Test 2',
|
|
|
|
description: null,
|
|
|
|
excerpt: 'Test excerpt',
|
|
|
|
featuredImage: new URL('https://example.com/image.png'),
|
|
|
|
favicon: new URL('https://example.com/favicon.ico'),
|
|
|
|
oneClickSubscribe: true
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('Returns updated recommendation if found but keeps empty title if no title found', async function () {
|
|
|
|
const recommendation = Recommendation.create({
|
|
|
|
id: '2',
|
|
|
|
url: 'http://localhost/existing',
|
|
|
|
title: 'Test',
|
|
|
|
description: null,
|
|
|
|
excerpt: null,
|
|
|
|
featuredImage: null,
|
|
|
|
favicon: null,
|
|
|
|
oneClickSubscribe: false
|
|
|
|
});
|
|
|
|
// Force an empty title (shouldn't be possible)
|
|
|
|
recommendation.title = '';
|
|
|
|
await service.repository.save(recommendation);
|
|
|
|
|
|
|
|
fetchMetadataStub.resolves({
|
|
|
|
title: null,
|
|
|
|
excerpt: 'Test excerpt',
|
|
|
|
featuredImage: new URL('https://example.com/image.png'),
|
|
|
|
favicon: new URL('https://example.com/favicon.ico'),
|
|
|
|
oneClickSubscribe: true
|
|
|
|
});
|
|
|
|
|
|
|
|
const response = await service.checkRecommendation(new URL('http://localhost/existing'));
|
|
|
|
|
|
|
|
// No changes here, because validation failed with an empty title
|
|
|
|
assert.deepEqual(response, {
|
|
|
|
...recommendation.plain
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('Returns existing recommendation if found and fetch failes', async function () {
|
|
|
|
const recommendation = Recommendation.create({
|
|
|
|
id: '2',
|
|
|
|
url: 'http://localhost/existing',
|
|
|
|
title: 'Outdated title',
|
|
|
|
description: null,
|
|
|
|
excerpt: null,
|
|
|
|
featuredImage: null,
|
|
|
|
favicon: null,
|
|
|
|
oneClickSubscribe: false
|
|
|
|
});
|
|
|
|
await service.repository.save(recommendation);
|
|
|
|
|
|
|
|
fetchMetadataStub.rejects(new Error('Test'));
|
|
|
|
const response = await service.checkRecommendation(new URL('http://localhost/existing'));
|
|
|
|
assert.deepEqual(response, recommendation.plain);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('Returns recommendation metadata if not found', async function () {
|
|
|
|
const response = await service.checkRecommendation(new URL('http://localhost/newone'));
|
|
|
|
assert.deepEqual(response, {
|
|
|
|
title: 'Test',
|
|
|
|
excerpt: undefined,
|
|
|
|
featuredImage: undefined,
|
|
|
|
favicon: undefined,
|
|
|
|
oneClickSubscribe: false,
|
|
|
|
url: new URL('http://localhost/newone')
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('Returns recommendation metadata if not found with all data except title', async function () {
|
|
|
|
fetchMetadataStub.resolves({
|
|
|
|
title: null,
|
|
|
|
excerpt: 'Test excerpt',
|
|
|
|
featuredImage: new URL('https://example.com/image.png'),
|
|
|
|
favicon: new URL('https://example.com/favicon.ico'),
|
|
|
|
oneClickSubscribe: true
|
|
|
|
});
|
|
|
|
const response = await service.checkRecommendation(new URL('http://localhost/newone'));
|
|
|
|
assert.deepEqual(response, {
|
|
|
|
title: undefined,
|
|
|
|
excerpt: 'Test excerpt',
|
|
|
|
featuredImage: new URL('https://example.com/image.png'),
|
|
|
|
favicon: new URL('https://example.com/favicon.ico'),
|
|
|
|
oneClickSubscribe: true,
|
|
|
|
url: new URL('http://localhost/newone')
|
|
|
|
});
|
|
|
|
});
|
2024-03-14 13:36:28 +03:00
|
|
|
|
|
|
|
it('Returns undefined recommendation metadata if metadata fails to fetch', async function () {
|
|
|
|
fetchMetadataStub.rejects(new Error('Metadata failed to fetch'));
|
|
|
|
|
|
|
|
const response = await service.checkRecommendation(new URL('http://localhost/newone'));
|
|
|
|
assert.deepEqual(response, {
|
|
|
|
title: undefined,
|
|
|
|
excerpt: undefined,
|
|
|
|
featuredImage: undefined,
|
|
|
|
favicon: undefined,
|
|
|
|
oneClickSubscribe: false,
|
|
|
|
url: new URL('http://localhost/newone')
|
|
|
|
});
|
|
|
|
});
|
2023-10-03 18:11:47 +03:00
|
|
|
});
|
|
|
|
|
|
|
|
describe('updateRecommendationsEnabledSetting', function () {
|
|
|
|
it('should set to true if more than one', async function () {
|
|
|
|
enabled = false;
|
|
|
|
await service.updateRecommendationsEnabledSetting([
|
|
|
|
Recommendation.create({
|
|
|
|
url: 'http://localhost/1',
|
|
|
|
title: 'Test',
|
2023-10-09 17:19:44 +03:00
|
|
|
description: null,
|
2023-10-03 18:11:47 +03:00
|
|
|
excerpt: null,
|
|
|
|
featuredImage: null,
|
|
|
|
favicon: null,
|
|
|
|
oneClickSubscribe: false
|
|
|
|
})
|
|
|
|
]);
|
|
|
|
assert(enabled);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should keep enabled true if already enabled', async function () {
|
|
|
|
enabled = true;
|
|
|
|
await service.updateRecommendationsEnabledSetting([
|
|
|
|
Recommendation.create({
|
|
|
|
url: 'http://localhost/1',
|
|
|
|
title: 'Test',
|
2023-10-09 17:19:44 +03:00
|
|
|
description: null,
|
2023-10-03 18:11:47 +03:00
|
|
|
excerpt: null,
|
|
|
|
featuredImage: null,
|
|
|
|
favicon: null,
|
|
|
|
oneClickSubscribe: false
|
|
|
|
})
|
|
|
|
]);
|
|
|
|
assert(enabled);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should set to false if none', async function () {
|
|
|
|
enabled = false;
|
|
|
|
await service.updateRecommendationsEnabledSetting([]);
|
|
|
|
assert.equal(enabled, false);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should set to false if none if currently enabled', async function () {
|
|
|
|
enabled = true;
|
|
|
|
await service.updateRecommendationsEnabledSetting([]);
|
|
|
|
assert.equal(enabled, false);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('readRecommendation', function () {
|
|
|
|
it('throws if not found', async function () {
|
|
|
|
await assert.rejects(() => service.readRecommendation('1'), {
|
|
|
|
name: 'NotFoundError',
|
|
|
|
message: 'Recommendation with id 1 not found'
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('returns plain if found', async function () {
|
|
|
|
const recommendation = Recommendation.create({
|
|
|
|
id: '2',
|
|
|
|
url: 'http://localhost/1',
|
|
|
|
title: 'Test',
|
2023-10-09 17:19:44 +03:00
|
|
|
description: null,
|
2023-10-03 18:11:47 +03:00
|
|
|
excerpt: null,
|
|
|
|
featuredImage: null,
|
|
|
|
favicon: null,
|
|
|
|
oneClickSubscribe: false
|
|
|
|
});
|
|
|
|
await service.repository.save(recommendation);
|
|
|
|
|
|
|
|
const response = await service.readRecommendation('2');
|
|
|
|
assert.deepEqual(response, recommendation.plain);
|
|
|
|
|
|
|
|
// Check not instance of Recommendation
|
|
|
|
assert.equal(response instanceof Recommendation, false);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('addRecommendation', function () {
|
|
|
|
it('throws if already exists', async function () {
|
|
|
|
const recommendation = Recommendation.create({
|
|
|
|
id: '2',
|
|
|
|
url: 'http://localhost/1',
|
|
|
|
title: 'Test',
|
2023-10-09 17:19:44 +03:00
|
|
|
description: null,
|
2023-10-03 18:11:47 +03:00
|
|
|
excerpt: null,
|
|
|
|
featuredImage: null,
|
|
|
|
favicon: null,
|
|
|
|
oneClickSubscribe: false
|
|
|
|
});
|
|
|
|
await service.repository.save(recommendation);
|
|
|
|
|
|
|
|
await assert.rejects(() => service.addRecommendation({
|
|
|
|
url: 'http://localhost/1',
|
|
|
|
title: 'Test 2',
|
2023-10-09 17:19:44 +03:00
|
|
|
description: null,
|
2023-10-03 18:11:47 +03:00
|
|
|
excerpt: null,
|
|
|
|
featuredImage: null,
|
|
|
|
favicon: null,
|
|
|
|
oneClickSubscribe: false
|
|
|
|
}), {
|
|
|
|
name: 'ValidationError',
|
|
|
|
message: 'A recommendation with this URL already exists.'
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('returns plain if sucessful', async function () {
|
|
|
|
const response = await service.addRecommendation({
|
|
|
|
url: 'http://localhost/1',
|
|
|
|
title: 'Test',
|
2023-10-09 17:19:44 +03:00
|
|
|
description: null,
|
2023-10-03 18:11:47 +03:00
|
|
|
excerpt: null,
|
|
|
|
featuredImage: null,
|
|
|
|
favicon: null,
|
|
|
|
oneClickSubscribe: false
|
|
|
|
});
|
|
|
|
assert.deepEqual(response, {
|
|
|
|
title: 'Test',
|
2023-10-09 17:19:44 +03:00
|
|
|
description: null,
|
2023-10-03 18:11:47 +03:00
|
|
|
excerpt: null,
|
|
|
|
featuredImage: null,
|
|
|
|
favicon: null,
|
|
|
|
oneClickSubscribe: false,
|
|
|
|
clickCount: undefined,
|
|
|
|
subscriberCount: undefined,
|
|
|
|
updatedAt: null,
|
|
|
|
|
|
|
|
// Ignored
|
|
|
|
url: response.url,
|
|
|
|
id: response.id,
|
|
|
|
createdAt: response.createdAt
|
|
|
|
});
|
|
|
|
|
|
|
|
assert(response.id);
|
|
|
|
assert(response.url);
|
|
|
|
assert(response.createdAt);
|
|
|
|
|
|
|
|
assert(response.url instanceof URL);
|
|
|
|
assert(response.createdAt instanceof Date);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('does not throw if sendMentionToRecommendation throws', async function () {
|
|
|
|
const recommendation = Recommendation.create({
|
|
|
|
id: '2',
|
|
|
|
url: 'http://localhost/1',
|
|
|
|
title: 'Test',
|
2023-10-09 17:19:44 +03:00
|
|
|
description: null,
|
2023-10-03 18:11:47 +03:00
|
|
|
excerpt: null,
|
|
|
|
featuredImage: null,
|
|
|
|
favicon: null,
|
|
|
|
oneClickSubscribe: false
|
|
|
|
});
|
|
|
|
|
|
|
|
const updateRecommendationsEnabledSetting = sinon.stub(service.mentionSendingService, 'sendAll').rejects(new Error('Test'));
|
|
|
|
await service.repository.save(recommendation);
|
|
|
|
|
|
|
|
await assert.doesNotReject(() => service.addRecommendation({
|
|
|
|
url: 'http://localhost/2',
|
|
|
|
title: 'Test 2',
|
2023-10-09 17:19:44 +03:00
|
|
|
description: null,
|
2023-10-03 18:11:47 +03:00
|
|
|
excerpt: null,
|
|
|
|
featuredImage: null,
|
|
|
|
favicon: null,
|
|
|
|
oneClickSubscribe: false
|
|
|
|
}));
|
|
|
|
|
|
|
|
assert(updateRecommendationsEnabledSetting.calledOnce);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('editRecommendation', function () {
|
|
|
|
it('throws if not found', async function () {
|
|
|
|
await assert.rejects(() => service.editRecommendation('1', {
|
|
|
|
title: 'Test 2'
|
|
|
|
}), {
|
|
|
|
name: 'NotFoundError',
|
|
|
|
message: 'Recommendation with id 1 not found'
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('returns plain if sucessful', async function () {
|
|
|
|
const recommendation = Recommendation.create({
|
|
|
|
id: '2',
|
|
|
|
url: 'http://localhost/1',
|
|
|
|
title: 'Test',
|
2023-10-09 17:19:44 +03:00
|
|
|
description: null,
|
2023-10-03 18:11:47 +03:00
|
|
|
excerpt: null,
|
|
|
|
featuredImage: null,
|
|
|
|
favicon: null,
|
|
|
|
oneClickSubscribe: false
|
|
|
|
});
|
|
|
|
await service.repository.save(recommendation);
|
|
|
|
|
|
|
|
const response = await service.editRecommendation('2', {
|
|
|
|
title: 'Test 2'
|
|
|
|
});
|
|
|
|
assert.deepEqual(response, {
|
|
|
|
title: 'Test 2',
|
2023-10-09 17:19:44 +03:00
|
|
|
description: null,
|
2023-10-03 18:11:47 +03:00
|
|
|
excerpt: null,
|
|
|
|
featuredImage: null,
|
|
|
|
favicon: null,
|
|
|
|
oneClickSubscribe: false,
|
|
|
|
clickCount: undefined,
|
|
|
|
subscriberCount: undefined,
|
|
|
|
|
|
|
|
// Ignored
|
|
|
|
url: response.url,
|
|
|
|
id: response.id,
|
|
|
|
createdAt: response.createdAt,
|
|
|
|
updatedAt: response.updatedAt
|
|
|
|
});
|
|
|
|
|
|
|
|
assert(response.id);
|
|
|
|
assert(response.url);
|
|
|
|
assert(response.createdAt);
|
|
|
|
assert(response.updatedAt);
|
|
|
|
|
|
|
|
assert(response.url instanceof URL);
|
|
|
|
assert(response.createdAt instanceof Date);
|
|
|
|
assert(response.updatedAt instanceof Date);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('deleteRecommendation', function () {
|
|
|
|
it('throws if not found', async function () {
|
|
|
|
await assert.rejects(() => service.deleteRecommendation('1'), {
|
|
|
|
name: 'NotFoundError',
|
|
|
|
message: 'Recommendation with id 1 not found'
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('deletes if found', async function () {
|
|
|
|
const recommendation = Recommendation.create({
|
|
|
|
id: '2',
|
|
|
|
url: 'http://localhost/1',
|
|
|
|
title: 'Test',
|
2023-10-09 17:19:44 +03:00
|
|
|
description: null,
|
2023-10-03 18:11:47 +03:00
|
|
|
excerpt: null,
|
|
|
|
featuredImage: null,
|
|
|
|
favicon: null,
|
|
|
|
oneClickSubscribe: false
|
|
|
|
});
|
|
|
|
await service.repository.save(recommendation);
|
|
|
|
|
|
|
|
assert.equal(await service.repository.getCount({}), 1);
|
|
|
|
await service.deleteRecommendation('2');
|
|
|
|
assert.equal(await service.repository.getCount({}), 0);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('listRecommendations', function () {
|
|
|
|
it('returns plain if sucessful', async function () {
|
|
|
|
const recommendation = Recommendation.create({
|
|
|
|
id: '2',
|
|
|
|
url: 'http://localhost/1',
|
|
|
|
title: 'Test',
|
2023-10-09 17:19:44 +03:00
|
|
|
description: null,
|
2023-10-03 18:11:47 +03:00
|
|
|
excerpt: null,
|
|
|
|
featuredImage: null,
|
|
|
|
favicon: null,
|
|
|
|
oneClickSubscribe: false
|
|
|
|
});
|
|
|
|
await service.repository.save(recommendation);
|
|
|
|
|
|
|
|
const response = await service.listRecommendations();
|
|
|
|
assert.equal(response.length, 1);
|
|
|
|
assert.equal(response[0] instanceof Recommendation, false);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('returns pages', async function () {
|
|
|
|
const recommendation = Recommendation.create({
|
|
|
|
id: '2',
|
|
|
|
url: 'http://localhost/1',
|
|
|
|
title: 'Test',
|
2023-10-09 17:19:44 +03:00
|
|
|
description: null,
|
2023-10-03 18:11:47 +03:00
|
|
|
excerpt: null,
|
|
|
|
featuredImage: null,
|
|
|
|
favicon: null,
|
|
|
|
oneClickSubscribe: false
|
|
|
|
});
|
|
|
|
await service.repository.save(recommendation);
|
|
|
|
|
|
|
|
const recommendation2 = Recommendation.create({
|
|
|
|
id: '3',
|
|
|
|
url: 'http://localhost/2',
|
|
|
|
title: 'Test 2',
|
2023-10-09 17:19:44 +03:00
|
|
|
description: null,
|
2023-10-03 18:11:47 +03:00
|
|
|
excerpt: null,
|
|
|
|
featuredImage: null,
|
|
|
|
favicon: null,
|
|
|
|
oneClickSubscribe: false
|
|
|
|
});
|
|
|
|
await service.repository.save(recommendation2);
|
|
|
|
|
|
|
|
const response = await service.listRecommendations({
|
|
|
|
limit: 1,
|
|
|
|
order: [
|
|
|
|
{
|
|
|
|
field: 'id',
|
|
|
|
direction: 'desc'
|
|
|
|
}
|
|
|
|
]
|
|
|
|
});
|
|
|
|
assert.equal(response.length, 1);
|
|
|
|
assert.equal(response[0].id, '3');
|
|
|
|
assert.equal(response[0] instanceof Recommendation, false);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('uses a default limit and page', async function () {
|
|
|
|
const recommendation = Recommendation.create({
|
|
|
|
id: '2',
|
|
|
|
url: 'http://localhost/1',
|
|
|
|
title: 'Test',
|
2023-10-09 17:19:44 +03:00
|
|
|
description: null,
|
2023-10-03 18:11:47 +03:00
|
|
|
excerpt: null,
|
|
|
|
featuredImage: null,
|
|
|
|
favicon: null,
|
|
|
|
oneClickSubscribe: false
|
|
|
|
});
|
|
|
|
await service.repository.save(recommendation);
|
|
|
|
|
|
|
|
const recommendation2 = Recommendation.create({
|
|
|
|
id: '3',
|
|
|
|
url: 'http://localhost/2',
|
|
|
|
title: 'Test 2',
|
2023-10-09 17:19:44 +03:00
|
|
|
description: null,
|
2023-10-03 18:11:47 +03:00
|
|
|
excerpt: null,
|
|
|
|
featuredImage: null,
|
|
|
|
favicon: null,
|
|
|
|
oneClickSubscribe: false
|
|
|
|
});
|
|
|
|
await service.repository.save(recommendation2);
|
|
|
|
|
|
|
|
const response = await service.listRecommendations({});
|
|
|
|
assert.equal(response.length, 2);
|
|
|
|
assert.equal(response[0] instanceof Recommendation, false);
|
|
|
|
assert.equal(response[1] instanceof Recommendation, false);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('countRecommendations', function () {
|
|
|
|
it('returns count', async function () {
|
|
|
|
const recommendation = Recommendation.create({
|
|
|
|
id: '2',
|
|
|
|
url: 'http://localhost/1',
|
|
|
|
title: 'Test',
|
2023-10-09 17:19:44 +03:00
|
|
|
description: null,
|
2023-10-03 18:11:47 +03:00
|
|
|
excerpt: null,
|
|
|
|
featuredImage: null,
|
|
|
|
favicon: null,
|
|
|
|
oneClickSubscribe: false
|
|
|
|
});
|
|
|
|
await service.repository.save(recommendation);
|
|
|
|
|
|
|
|
assert.equal(await service.countRecommendations({}), 1);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('trackClicked', function () {
|
|
|
|
it('adds click event', async function () {
|
|
|
|
await service.trackClicked({id: '1'});
|
|
|
|
assert.equal(await service.clickEventRepository.getCount({}), 1);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('trackSubscribed', function () {
|
|
|
|
it('adds subscribe event', async function () {
|
|
|
|
await service.trackSubscribed({id: '1', memberId: '1'});
|
|
|
|
assert.equal(await service.subscribeEventRepository.getCount({}), 1);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('readRecommendationByUrl', function () {
|
|
|
|
it('returns if found', async function () {
|
|
|
|
const recommendation = Recommendation.create({
|
|
|
|
id: '2',
|
|
|
|
url: 'http://localhost/1',
|
|
|
|
title: 'Test',
|
2023-10-09 17:19:44 +03:00
|
|
|
description: null,
|
2023-10-03 18:11:47 +03:00
|
|
|
excerpt: null,
|
|
|
|
featuredImage: null,
|
|
|
|
favicon: null,
|
|
|
|
oneClickSubscribe: false
|
|
|
|
});
|
|
|
|
await service.repository.save(recommendation);
|
|
|
|
|
|
|
|
const response = await service.readRecommendationByUrl(new URL('http://localhost/1'));
|
|
|
|
assert.deepEqual(response, recommendation.plain);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('returns null if not found', async function () {
|
|
|
|
const response = await service.readRecommendationByUrl(new URL('http://localhost/1'));
|
|
|
|
assert.equal(response, null);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|