fee402a340
fixes https://github.com/TryGhost/Product/issues/4102 E.g. you recommend myghostsite.com, while that site redirects all traffic to [www.myghostsite.com](#): The redirect causes CORS issues, which means we cannot detect one-click-subscribe support. - This is fixed by moving the whole detection to the backend, which has the additional benefit that we can update it in the background without the frontend, and update it on every recommendation change. - This change also fixes existing recommendations by doing a check on boot (we can move this to a background job in the future).
216 lines
7.5 KiB
TypeScript
216 lines
7.5 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();
|
|
});
|
|
|
|
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('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
|
|
});
|
|
});
|
|
});
|