diff --git a/packages/metro-cache/src/stores/HttpStore.js b/packages/metro-cache/src/stores/HttpStore.js index c8224fb500..f07adeb342 100644 --- a/packages/metro-cache/src/stores/HttpStore.js +++ b/packages/metro-cache/src/stores/HttpStore.js @@ -33,6 +33,7 @@ type EndpointOptions = { ca?: string | $ReadOnlyArray | Buffer | $ReadOnlyArray, params?: URLSearchParams, headers?: {[string]: string}, + additionalSuccessStatuses?: $ReadOnlyArray, }; type Endpoint = { @@ -44,6 +45,7 @@ type Endpoint = { params: URLSearchParams, headers?: {[string]: string}, timeout: number, + additionalSuccessStatuses: $ReadOnlySet, }; const ZLIB_OPTIONS = { @@ -109,6 +111,9 @@ class HttpStore { params: new URLSearchParams(options.params), timeout: options.timeout || 5000, module: uri.protocol === 'http:' ? http : https, + additionalSuccessStatuses: new Set( + options.additionalSuccessStatuses ?? [], + ), }; } @@ -142,7 +147,10 @@ class HttpStore { resolve(null); return; - } else if (code !== 200) { + } else if ( + code !== 200 && + !this._getEndpoint.additionalSuccessStatuses.has(code) + ) { res.resume(); reject(new HttpError('HTTP error: ' + code, code)); @@ -215,7 +223,10 @@ class HttpStore { const req = this._setEndpoint.module.request(options, res => { const code = res.statusCode; - if (code < 200 || code > 299) { + if ( + (code < 200 || code > 299) && + !this._setEndpoint.additionalSuccessStatuses.has(code) + ) { res.resume(); reject(new HttpError('HTTP error: ' + code, code)); diff --git a/packages/metro-cache/src/stores/__tests__/HttpStore-test.js b/packages/metro-cache/src/stores/__tests__/HttpStore-test.js index ae7d53facf..15bece1d61 100644 --- a/packages/metro-cache/src/stores/__tests__/HttpStore-test.js +++ b/packages/metro-cache/src/stores/__tests__/HttpStore-test.js @@ -17,9 +17,9 @@ describe('HttpStore', () => { let HttpStore; let httpPassThrough; - function responseHttpOk(data) { + function responseHttpOk(data, statusCode = 200) { const res = Object.assign(new PassThrough(), { - statusCode: 200, + statusCode, }); process.nextTick(() => { @@ -30,10 +30,16 @@ describe('HttpStore', () => { return res; } - function responseHttpError(code) { - return Object.assign(new PassThrough(), { - statusCode: code, + function responseHttpError(statusCode) { + const res = Object.assign(new PassThrough(), { + statusCode, + }); + + process.nextTick(() => { + res.end(); }); + + return res; } function responseError(err) { @@ -112,6 +118,25 @@ describe('HttpStore', () => { }); }); + it('get() resolves when the HTTP code is in additionalSuccessStatuses', async () => { + const store = new HttpStore({ + endpoint: 'http://www.example.com/endpoint', + additionalSuccessStatuses: [419], + }); + const promise = store.get(Buffer.from('key')); + const [opts, callback] = require('http').request.mock.calls[0]; + + expect(opts.method).toEqual('GET'); + expect(opts.host).toEqual('www.example.com'); + expect(opts.path).toEqual('/endpoint/6b6579'); + expect(opts.timeout).toEqual(5000); + + callback(responseHttpOk(JSON.stringify({foo: 42}), 419)); + jest.runAllTimers(); + + expect(await promise).toEqual({foo: 42}); + }); + it('rejects when it gets an invalid JSON response', done => { const store = new HttpStore({endpoint: 'http://example.com'}); const promise = store.get(Buffer.from('key')); @@ -187,6 +212,30 @@ describe('HttpStore', () => { }); }); + test('set() resolves when the HTTP code is in additionalSuccessStatuses', done => { + const store = new HttpStore({ + endpoint: 'http://www.example.com/endpoint', + additionalSuccessStatuses: [403], + }); + const promise = store.set(Buffer.from('key-set'), {foo: 42}); + const [opts, callback] = require('http').request.mock.calls[0]; + + expect(opts.method).toEqual('PUT'); + expect(opts.host).toEqual('www.example.com'); + expect(opts.path).toEqual('/endpoint/6b65792d736574'); + expect(opts.timeout).toEqual(5000); + + callback(responseHttpError(403)); + + httpPassThrough.on('data', () => {}); + + httpPassThrough.on('end', async () => { + await promise; // Ensure that the setting promise successfully finishes. + + done(); + }); + }); + it('rejects when setting and HTTP returns an error response', done => { const store = new HttpStore({endpoint: 'http://example.com'}); const promise = store.set(Buffer.from('key-set'), {foo: 42});