0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-01-20 22:42:53 -05:00

🐛 Fixed returning HTTP 500 response when recommendations check fails

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
This commit is contained in:
Daniel Lockyer 2024-03-12 11:49:24 +01:00 committed by Daniel Lockyer
parent 5fa4496d52
commit dea639e3f6
2 changed files with 31 additions and 17 deletions

View file

@ -40,25 +40,31 @@ export class RecommendationMetadataService {
} }
async #fetchJSON(url: URL, options?: {timeout?: number}) { async #fetchJSON(url: URL, options?: {timeout?: number}) {
// default content type is application/x-www-form-encoded which is what we need for the webmentions spec // Even though we have throwHttpErrors: false, we still need to catch DNS errors
const response = await this.#externalRequest.get(url.toString(), { // that can arise from externalRequest, otherwise we'll return a HTTP 500 to the user
throwHttpErrors: false, try {
maxRedirects: 10, // default content type is application/x-www-form-encoded which is what we need for the webmentions spec
followRedirect: true, const response = await this.#externalRequest.get(url.toString(), {
timeout: 15000, throwHttpErrors: false,
retry: { maxRedirects: 10,
// Only retry on network issues, or specific HTTP status codes followRedirect: true,
limit: 3 timeout: 15000,
}, retry: {
...options // Only retry on network issues, or specific HTTP status codes
}); limit: 3
},
...options
});
if (response.statusCode >= 200 && response.statusCode < 300) { if (response.statusCode >= 200 && response.statusCode < 300) {
try { try {
return JSON.parse(response.body); return JSON.parse(response.body);
} catch (e) { } catch (e) {
return undefined; return undefined;
}
} }
} catch (e) {
return undefined;
} }
} }

View file

@ -32,6 +32,7 @@ describe('RecommendationMetadataService', function () {
afterEach(function () { afterEach(function () {
nock.cleanAll(); nock.cleanAll();
sinon.restore();
}); });
it('Returns metadata from the Ghost site', async function () { it('Returns metadata from the Ghost site', async function () {
@ -181,6 +182,13 @@ describe('RecommendationMetadataService', function () {
}); });
}); });
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 () { it('Nullifies empty oembed data', async function () {
nock('https://exampleghostsite.com') nock('https://exampleghostsite.com')
.get('/subdirectory/members/api/site') .get('/subdirectory/members/api/site')