Added missing unit tests to the Recommendations package (#18489)

refs https://github.com/TryGhost/Product/issues/3954
This commit is contained in:
Sag 2023-10-04 18:52:22 -03:00 committed by GitHub
parent 2391ccc3db
commit 380f3b5ca2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 249 additions and 27 deletions

View File

@ -11,7 +11,7 @@
"build": "tsc",
"build:ts": "yarn build",
"prepare": "tsc",
"test:unit": "NODE_ENV=testing c8 --src src --all --reporter text --reporter cobertura mocha -r ts-node/register './test/**/*.test.ts'",
"test:unit": "NODE_ENV=testing c8 --src src --all --check-coverage --100 --reporter text --reporter cobertura mocha -r ts-node/register './test/**/*.test.ts'",
"test": "yarn test:types && yarn test:unit",
"test:types": "tsc --noEmit",
"lint:code": "eslint src/ --ext .ts --cache",

View File

@ -1,9 +0,0 @@
export type IncomingRecommendation = {
id: string;
title: string;
url: URL;
excerpt: string|null;
favicon: URL|null;
featuredImage: URL|null;
recommendingBack: boolean;
}

View File

@ -1,5 +1,5 @@
import {IncomingRecommendationService} from './IncomingRecommendationService';
import {IncomingRecommendation} from './IncomingRecommendation';
import {IncomingRecommendation} from './IncomingRecommendationService';
import {UnsafeData} from './UnsafeData';
type Frame = {
@ -23,6 +23,7 @@ export class IncomingRecommendationController {
const page = options.optionalKey('page')?.integer ?? 1;
const limit = options.optionalKey('limit')?.integer ?? 5;
const {incomingRecommendations, meta} = await this.service.listIncomingRecommendations({page, limit});
return this.#serialize(

View File

@ -1,5 +1,5 @@
import {EmailRecipient} from './IncomingRecommendationService';
import {IncomingRecommendation} from './IncomingRecommendation';
import {IncomingRecommendation} from './IncomingRecommendationService';
type StaffService = {
api: {

View File

@ -1,8 +1,17 @@
import {IncomingRecommendation} from './IncomingRecommendation';
import {IncomingRecommendationEmailRenderer} from './IncomingRecommendationEmailRenderer';
import {RecommendationService} from './RecommendationService';
import logging from '@tryghost/logging';
export type IncomingRecommendation = {
id: string;
title: string;
url: URL;
excerpt: string|null;
favicon: URL|null;
featuredImage: URL|null;
recommendingBack: boolean;
}
export type Report = {
startDate: Date,
endDate: Date,
@ -21,7 +30,14 @@ type Mention = {
}
type MentionMeta = {
pagination: object,
pagination: {
page: number;
limit: number;
pages: number;
total: number;
next: null | number;
prev: null | number;
}
}
type MentionsAPI = {
@ -75,11 +91,7 @@ export class IncomingRecommendationService {
}
#getMentionFilter() {
const base = `source:~$'/.well-known/recommendations.json'`;
// if (verified) {
// return `${base}+verified:true`;
// }
return base;
return `source:~$'/.well-known/recommendations.json'`;
}
async #updateIncomingRecommendations() {

View File

@ -0,0 +1,157 @@
import assert from 'assert/strict';
import {IncomingRecommendationController, IncomingRecommendationService} from '../src';
import sinon, {SinonSpy} from 'sinon';
describe('IncomingRecommendationController', function () {
let service: Partial<IncomingRecommendationService>;
let controller: IncomingRecommendationController;
beforeEach(function () {
service = {};
controller = new IncomingRecommendationController({service: service as IncomingRecommendationService});
});
describe('browse', function () {
beforeEach(function () {
service.listIncomingRecommendations = async () => {
return {
incomingRecommendations: [
{
id: '1',
title: 'Test 1',
url: new URL('https://test1.com'),
excerpt: 'Excerpt 1',
favicon: new URL('https://test1.com/favicon.ico'),
featuredImage: new URL('https://test1.com/image.png'),
recommendingBack: true
},
{
id: '2',
title: 'Test 2',
url: new URL('https://test2.com'),
excerpt: 'Excerpt 2',
favicon: null,
featuredImage: null,
recommendingBack: false
}
],
meta: {
pagination: {
page: 1,
limit: 5,
pages: 1,
total: 2,
next: null,
prev: null
}
}
};
};
});
it('without options', async function () {
const result = await controller.browse({
data: {},
options: {}
});
assert.deepEqual(result, {
data: [{
id: '1',
title: 'Test 1',
excerpt: 'Excerpt 1',
featured_image: 'https://test1.com/image.png',
favicon: 'https://test1.com/favicon.ico',
url: 'https://test1.com/',
recommending_back: true
},
{
id: '2',
title: 'Test 2',
excerpt: 'Excerpt 2',
featured_image: null,
favicon: null,
url: 'https://test2.com/',
recommending_back: false
}],
meta: {
pagination: {
page: 1,
limit: 5,
pages: 1,
total: 2,
next: null,
prev: null
}
}
});
});
describe('with options', function () {
let listSpy: SinonSpy;
beforeEach(function () {
listSpy = sinon.spy(service, 'listIncomingRecommendations');
});
it('limit is set to 5 by default', async function () {
await controller.browse({
data: {},
options: {}
});
assert(listSpy.calledOnce);
const args = listSpy.getCall(0).args[0];
assert.deepEqual(args.limit, 5);
});
it('limit can be set to 100', async function () {
await controller.browse({
data: {},
options: {
limit: 100
}
});
assert(listSpy.calledOnce);
const args = listSpy.getCall(0).args[0];
assert.deepEqual(args.limit, 100);
});
it('limit cannot be set to "all"', async function () {
await assert.rejects(
controller.browse({
data: {},
options: {
limit: 'all'
}
}),
{
message: 'limit must be an integer'
}
);
});
it('page is set to 1 by default', async function () {
await controller.browse({
data: {},
options: {
}
});
assert(listSpy.calledOnce);
const args = listSpy.getCall(0).args[0];
assert.deepEqual(args.page, 1);
});
it('page can be set to 2', async function () {
await controller.browse({
data: {},
options: {
page: 2
}
});
assert(listSpy.calledOnce);
const args = listSpy.getCall(0).args[0];
assert.deepEqual(args.page, 2);
});
});
});
});

View File

@ -103,4 +103,73 @@ describe('IncomingRecommendationService', function () {
assert(!send.calledOnce);
});
});
describe('listIncomingRecommendations', function () {
beforeEach(function () {
refreshMentions = sinon.stub().resolves();
send = sinon.stub().resolves();
readRecommendationByUrl = sinon.stub().resolves(null);
service = new IncomingRecommendationService({
recommendationService: {
readRecommendationByUrl
} as any as RecommendationService,
mentionsApi: {
refreshMentions,
listMentions: () => Promise.resolve({data: [
{
id: 'Incoming recommendation',
source: new URL('https://incoming-rec.com/.well-known/recommendations.json'),
sourceTitle: 'Incoming recommendation title',
sourceSiteTitle: null,
sourceAuthor: null,
sourceExcerpt: 'Incoming recommendation excerpt',
sourceFavicon: new URL('https://incoming-rec.com/favicon.ico'),
sourceFeaturedImage: new URL('https://incoming-rec.com/image.png')
}
], meta: {
pagination: {
page: 1,
limit: 5,
pages: 1,
total: 1,
next: null,
prev: null
}
}})
},
emailService: {
send
},
emailRenderer: {
renderSubject: () => Promise.resolve(''),
renderHTML: () => Promise.resolve(''),
renderText: () => Promise.resolve('')
} as any as IncomingRecommendationEmailRenderer,
getEmailRecipients: () => Promise.resolve([
{
email: 'example@example.com'
}
])
});
});
it('returns a list of incoming recommendations and pagination', async function () {
const list = await service.listIncomingRecommendations({});
assert.equal(list.incomingRecommendations.length, 1);
assert.equal(list.incomingRecommendations[0].id, 'Incoming recommendation');
assert.equal(list.incomingRecommendations[0].title, 'Incoming recommendation title');
assert.equal(list.incomingRecommendations[0].excerpt, 'Incoming recommendation excerpt');
assert.equal(list.incomingRecommendations[0].url.toString(), 'https://incoming-rec.com/');
assert.equal(list.incomingRecommendations[0].favicon?.toString(), 'https://incoming-rec.com/favicon.ico');
assert.equal(list.incomingRecommendations[0].featuredImage?.toString(), 'https://incoming-rec.com/image.png');
assert.equal(list.meta?.pagination.page, 1);
assert.equal(list.meta?.pagination.limit, 5);
assert.equal(list.meta?.pagination.pages, 1);
assert.equal(list.meta?.pagination.total, 1);
assert.equal(list.meta?.pagination.prev, null);
assert.equal(list.meta?.pagination.next, null);
});
});
});

View File

@ -1,8 +0,0 @@
import assert from 'assert/strict';
describe('Hello world', function () {
it('Runs a test', function () {
// TODO: Write me!
assert.ok(require('../'));
});
});