Commit 53eb40d3 authored by Robert Knight's avatar Robert Knight

Convert `APIRoutesService` to use `fetchJSON`

parent a8fa207e
import { fetchJSON } from '../util/fetch';
import { retryPromiseOperation } from '../util/retry'; import { retryPromiseOperation } from '../util/retry';
/** /**
...@@ -11,12 +12,7 @@ function getJSON(url) { ...@@ -11,12 +12,7 @@ function getJSON(url) {
// any additional headers/config so that we can use `<link rel="preload">` in // any additional headers/config so that we can use `<link rel="preload">` in
// the `/app.html` response to fetch them early, while the client JS app // the `/app.html` response to fetch them early, while the client JS app
// is loading. // is loading.
fetch(url).then(response => { fetchJSON(url)
if (response.status !== 200) {
throw new Error(`Fetching ${url} failed`);
}
return response.json();
})
); );
} }
......
import { FetchError } from '../../util/fetch';
import { APIRoutesService, $imports } from '../api-routes'; import { APIRoutesService, $imports } from '../api-routes';
// Abridged version of the response returned by https://hypothes.is/api, // Abridged version of the response returned by https://hypothes.is/api,
...@@ -51,24 +52,18 @@ async function fakeRetryPromiseOperation(callback) { ...@@ -51,24 +52,18 @@ async function fakeRetryPromiseOperation(callback) {
describe('APIRoutesService', () => { describe('APIRoutesService', () => {
let apiRoutes; let apiRoutes;
let fakeSettings;
function httpResponse(status, data) { let fakeFetchJSON;
return Promise.resolve({ status, json: () => Promise.resolve(data) }); let fakeSettings;
}
beforeEach(() => { beforeEach(() => {
// We use a simple sinon stub of `fetch` here rather than `fetch-mock` fakeFetchJSON = sinon.stub().rejects(new FetchError(null));
// because this service's usage of fetch is very simple and it makes it fakeFetchJSON
// easier to mock the retry behavior.
const fetchStub = sinon.stub(window, 'fetch');
fetchStub
.withArgs('https://annotation.service/api/') .withArgs('https://annotation.service/api/')
.returns(httpResponse(200, apiIndexResponse)); .returns(apiIndexResponse);
fetchStub fakeFetchJSON
.withArgs('https://annotation.service/api/links') .withArgs('https://annotation.service/api/links')
.returns(httpResponse(200, linksResponse)); .returns(linksResponse);
fakeSettings = { fakeSettings = {
apiUrl: 'https://annotation.service/api/', apiUrl: 'https://annotation.service/api/',
...@@ -77,13 +72,13 @@ describe('APIRoutesService', () => { ...@@ -77,13 +72,13 @@ describe('APIRoutesService', () => {
apiRoutes = new APIRoutesService(fakeSettings); apiRoutes = new APIRoutesService(fakeSettings);
$imports.$mock({ $imports.$mock({
'../util/fetch': { fetchJSON: fakeFetchJSON },
'../util/retry': { retryPromiseOperation: fakeRetryPromiseOperation }, '../util/retry': { retryPromiseOperation: fakeRetryPromiseOperation },
}); });
}); });
afterEach(() => { afterEach(() => {
$imports.$restore(); $imports.$restore();
window.fetch.restore();
}); });
describe('#routes', () => { describe('#routes', () => {
...@@ -98,20 +93,20 @@ describe('APIRoutesService', () => { ...@@ -98,20 +93,20 @@ describe('APIRoutesService', () => {
return Promise.all([apiRoutes.routes(), apiRoutes.routes()]).then( return Promise.all([apiRoutes.routes(), apiRoutes.routes()]).then(
([routesA, routesB]) => { ([routesA, routesB]) => {
assert.equal(routesA, routesB); assert.equal(routesA, routesB);
assert.equal(window.fetch.callCount, 1); assert.equal(fakeFetchJSON.callCount, 1);
} }
); );
}); });
it('retries the route fetch until it succeeds', async () => { it('retries the route fetch until it succeeds', async () => {
window.fetch fakeFetchJSON
.withArgs('https://annotation.service/api/') .withArgs('https://annotation.service/api/')
.onFirstCall() .onFirstCall()
.returns(httpResponse(500, null)); .throws(new FetchError(null, 'Fetch failed'));
const routes = await apiRoutes.routes(); const routes = await apiRoutes.routes();
assert.calledTwice(window.fetch); assert.calledTwice(fakeFetchJSON);
assert.deepEqual(routes, apiIndexResponse.links); assert.deepEqual(routes, apiIndexResponse.links);
}); });
}); });
...@@ -129,20 +124,20 @@ describe('APIRoutesService', () => { ...@@ -129,20 +124,20 @@ describe('APIRoutesService', () => {
return Promise.all([apiRoutes.links(), apiRoutes.links()]).then( return Promise.all([apiRoutes.links(), apiRoutes.links()]).then(
([linksA, linksB]) => { ([linksA, linksB]) => {
assert.equal(linksA, linksB); assert.equal(linksA, linksB);
assert.deepEqual(window.fetch.callCount, 2); assert.deepEqual(fakeFetchJSON.callCount, 2);
} }
); );
}); });
it('retries the link fetch until it succeeds', async () => { it('retries the link fetch until it succeeds', async () => {
window.fetch fakeFetchJSON
.withArgs(apiIndexResponse.links.links.url) .withArgs(apiIndexResponse.links.links.url)
.onFirstCall() .onFirstCall()
.returns(httpResponse(500, null)); .throws(new FetchError(null));
const links = await apiRoutes.links(); const links = await apiRoutes.links();
assert.equal(window.fetch.callCount, 3); // One `/api` fetch, two `/api/links` fetches. assert.equal(fakeFetchJSON.callCount, 3); // One `/api` fetch, two `/api/links` fetches.
assert.deepEqual(links, linksResponse); assert.deepEqual(links, linksResponse);
}); });
}); });
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment