mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-29 13:52:10 +03:00
dea639e3f6
ref ENG-737 ref https://linear.app/tryghost/issue/ENG-737/http-500-errors-from-recommendations-check-endpoint - it's still possible for `this.#externalRequest.get` to throw, like if DNS resolution fails - we want to try-catch this so we don't throw from this function and return a HTTP 500 to the user - instead, we can just return `undefined`, which is the fallback - adds a breaking test too
224 lines
7.8 KiB
TypeScript
224 lines
7.8 KiB
TypeScript
import assert from 'assert/strict';
|
|
import got from 'got';
|
|
import nock from 'nock';
|
|
import {RecommendationMetadataService} from '../src';
|
|
import sinon from 'sinon';
|
|
|
|
describe('RecommendationMetadataService', function () {
|
|
let service: RecommendationMetadataService;
|
|
let fetchOembedDataFromUrl: sinon.SinonStub;
|
|
|
|
beforeEach(function () {
|
|
nock.disableNetConnect();
|
|
fetchOembedDataFromUrl = sinon.stub().resolves({
|
|
version: '1.0',
|
|
type: 'webmention',
|
|
metadata: {
|
|
title: 'Oembed Site Title',
|
|
description: 'Oembed Site Description',
|
|
publisher: 'Oembed Site Publisher',
|
|
author: 'Oembed Site Author',
|
|
thumbnail: 'https://example.com/oembed/thumbnail.png',
|
|
icon: 'https://example.com/oembed/icon.png'
|
|
}
|
|
});
|
|
service = new RecommendationMetadataService({
|
|
externalRequest: got,
|
|
oembedService: {
|
|
fetchOembedDataFromUrl
|
|
}
|
|
});
|
|
});
|
|
|
|
afterEach(function () {
|
|
nock.cleanAll();
|
|
sinon.restore();
|
|
});
|
|
|
|
it('Returns metadata from the Ghost site', async function () {
|
|
nock('https://exampleghostsite.com')
|
|
.get('/subdirectory/members/api/site')
|
|
.reply(200, {
|
|
site: {
|
|
title: 'Example Ghost Site',
|
|
description: 'Example Ghost Site Description',
|
|
cover_image: 'https://exampleghostsite.com/cover.png',
|
|
icon: 'https://exampleghostsite.com/favicon.ico',
|
|
allow_external_signup: true
|
|
}
|
|
});
|
|
|
|
const metadata = await service.fetch(new URL('https://exampleghostsite.com/subdirectory'));
|
|
assert.deepEqual(metadata, {
|
|
title: 'Example Ghost Site',
|
|
excerpt: 'Example Ghost Site Description',
|
|
featuredImage: new URL('https://exampleghostsite.com/cover.png'),
|
|
favicon: new URL('https://exampleghostsite.com/favicon.ico'),
|
|
oneClickSubscribe: true
|
|
});
|
|
});
|
|
|
|
it('Nulifies empty data from Ghost site response', async function () {
|
|
nock('https://exampleghostsite.com')
|
|
.get('/subdirectory/members/api/site')
|
|
.reply(200, {
|
|
site: {
|
|
title: '',
|
|
description: '',
|
|
cover_image: '',
|
|
icon: '',
|
|
allow_external_signup: false
|
|
}
|
|
});
|
|
|
|
const metadata = await service.fetch(new URL('https://exampleghostsite.com/subdirectory'));
|
|
assert.deepEqual(metadata, {
|
|
title: null,
|
|
excerpt: null,
|
|
featuredImage: null,
|
|
favicon: null,
|
|
oneClickSubscribe: false
|
|
});
|
|
});
|
|
|
|
it('Ignores ghost site if allow_external_signup is missing', async function () {
|
|
nock('https://exampleghostsite.com')
|
|
.get('/members/api/site')
|
|
.reply(200, {
|
|
site: {
|
|
title: '',
|
|
description: '',
|
|
cover_image: '',
|
|
icon: ''
|
|
}
|
|
});
|
|
|
|
const metadata = await service.fetch(new URL('https://exampleghostsite.com'));
|
|
assert.deepEqual(metadata, {
|
|
// oembed
|
|
title: 'Oembed Site Title',
|
|
excerpt: 'Oembed Site Description',
|
|
featuredImage: new URL('https://example.com/oembed/thumbnail.png'),
|
|
favicon: new URL('https://example.com/oembed/icon.png'),
|
|
oneClickSubscribe: false
|
|
});
|
|
});
|
|
|
|
it('Returns metadata from the Ghost site root if not found on subdirectory', async function () {
|
|
nock('https://exampleghostsite.com')
|
|
.get('/subdirectory/members/api/site')
|
|
.reply(404, {});
|
|
|
|
nock('https://exampleghostsite.com')
|
|
.get('/members/api/site')
|
|
.reply(200, {
|
|
site: {
|
|
title: 'Example Ghost Site',
|
|
description: 'Example Ghost Site Description',
|
|
cover_image: 'https://exampleghostsite.com/cover.png',
|
|
icon: 'https://exampleghostsite.com/favicon.ico',
|
|
allow_external_signup: true
|
|
}
|
|
});
|
|
|
|
const metadata = await service.fetch(new URL('https://exampleghostsite.com/subdirectory'));
|
|
assert.deepEqual(metadata, {
|
|
title: 'Example Ghost Site',
|
|
excerpt: 'Example Ghost Site Description',
|
|
featuredImage: new URL('https://exampleghostsite.com/cover.png'),
|
|
favicon: new URL('https://exampleghostsite.com/favicon.ico'),
|
|
oneClickSubscribe: true
|
|
});
|
|
});
|
|
|
|
it('Skips ghost metadata if json is invalid', async function () {
|
|
nock('https://exampleghostsite.com')
|
|
.get('/subdirectory/members/api/site')
|
|
.reply(200, 'invalidjson');
|
|
|
|
nock('https://exampleghostsite.com')
|
|
.get('/members/api/site')
|
|
.reply(200, 'invalidjson');
|
|
|
|
const metadata = await service.fetch(new URL('https://exampleghostsite.com/subdirectory'));
|
|
assert.deepEqual(metadata, {
|
|
title: 'Oembed Site Title',
|
|
excerpt: 'Oembed Site Description',
|
|
featuredImage: new URL('https://example.com/oembed/thumbnail.png'),
|
|
favicon: new URL('https://example.com/oembed/icon.png'),
|
|
oneClickSubscribe: false
|
|
});
|
|
});
|
|
|
|
it('Ignores invalid urls', async function () {
|
|
nock('https://exampleghostsite.com')
|
|
.get('/subdirectory/members/api/site')
|
|
.reply(404, 'invalidjson');
|
|
|
|
nock('https://exampleghostsite.com')
|
|
.get('/members/api/site')
|
|
.reply(404, 'invalidjson');
|
|
|
|
fetchOembedDataFromUrl.resolves({
|
|
version: '1.0',
|
|
type: 'webmention',
|
|
metadata: {
|
|
title: 'Oembed Site Title',
|
|
description: 'Oembed Site Description',
|
|
publisher: 'Oembed Site Publisher',
|
|
author: 'Oembed Site Author',
|
|
thumbnail: 'invalid',
|
|
icon: 'invalid'
|
|
}
|
|
});
|
|
|
|
const metadata = await service.fetch(new URL('https://exampleghostsite.com/subdirectory'));
|
|
assert.deepEqual(metadata, {
|
|
title: 'Oembed Site Title',
|
|
excerpt: 'Oembed Site Description',
|
|
featuredImage: null,
|
|
favicon: null,
|
|
oneClickSubscribe: false
|
|
});
|
|
});
|
|
|
|
it('does not throw an error even if fetching throws an error', async function () {
|
|
// TODO: simulate DNS resolution failures if possible
|
|
sinon.stub(got, 'get').rejects(new Error('Failed to fetch'));
|
|
|
|
await service.fetch(new URL('https://exampleghostsite.com/subdirectory'));
|
|
});
|
|
|
|
it('Nullifies empty oembed data', async function () {
|
|
nock('https://exampleghostsite.com')
|
|
.get('/subdirectory/members/api/site')
|
|
.reply(404, 'invalidjson');
|
|
|
|
nock('https://exampleghostsite.com')
|
|
.get('/members/api/site')
|
|
.reply(404, 'invalidjson');
|
|
|
|
fetchOembedDataFromUrl.resolves({
|
|
version: '1.0',
|
|
type: 'webmention',
|
|
metadata: {
|
|
title: '',
|
|
description: '',
|
|
publisher: '',
|
|
author: '',
|
|
thumbnail: '',
|
|
icon: ''
|
|
}
|
|
});
|
|
|
|
const metadata = await service.fetch(new URL('https://exampleghostsite.com/subdirectory'));
|
|
assert.deepEqual(metadata, {
|
|
title: null,
|
|
excerpt: null,
|
|
featuredImage: null,
|
|
favicon: null,
|
|
oneClickSubscribe: false
|
|
});
|
|
});
|
|
});
|