UNPKG

react-relay-network-layer

Version:

Network Layer for React Relay and Express (Batch Queries, AuthToken, Logging, Retry)

546 lines (544 loc) 14.9 kB
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } } function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; } import fetchMock from 'fetch-mock'; import FormData from 'form-data'; import { RelayNetworkLayer, batchMiddleware } from '../..'; import { mockReq, mockReqWithSize, mockReqWithFiles } from '../../__mocks__/mockReq'; global.FormData = FormData; describe('batchMiddleware', () => { const rnl = new RelayNetworkLayer([batchMiddleware()]); beforeEach(() => { fetchMock.restore(); }); it('should make a successfull single request', /*#__PURE__*/ _asyncToGenerator(function* () { fetchMock.post('/graphql', { data: { ok: 1 } }); const req = mockReq(); yield rnl.sendQueries([req]); expect(req.payload).toEqual({ response: { ok: 1 } }); })); it('should make a successfully batch request', /*#__PURE__*/ _asyncToGenerator(function* () { fetchMock.mock({ matcher: '/graphql/batch', response: { status: 200, body: [{ id: 1, data: { ok: 1 } }, { id: 2, data: { ok: 2 } }] }, method: 'POST' }); const req1 = mockReq(1); const req2 = mockReq(2); yield rnl.sendQueries([req1, req2]); expect(req1.payload).toEqual({ response: { ok: 1 } }); expect(req2.payload).toEqual({ response: { ok: 2 } }); })); it('should make a successfully batch request without server IDs', /*#__PURE__*/ _asyncToGenerator(function* () { fetchMock.mock({ matcher: '/graphql/batch', response: { status: 200, body: [{ data: { ok: 1 } }, { data: { ok: 2 } }] }, method: 'POST' }); const req1 = mockReq(1); const req2 = mockReq(2); yield rnl.sendQueries([req1, req2]); expect(req1.payload).toEqual({ response: { ok: 1 } }); expect(req2.payload).toEqual({ response: { ok: 2 } }); })); it('should reject if server returns a different number of responses than requests', /*#__PURE__*/ _asyncToGenerator(function* () { fetchMock.mock({ matcher: '/graphql/batch', response: { status: 200, body: [{ data: { ok: 2 } }] }, method: 'POST' }); const req1 = mockReq(1); const req2 = mockReq(2); yield rnl.sendQueries([req1, req2]).catch(() => {}); expect(req1.error.toString()).toMatch('Server returned a different number of responses than requested.'); expect(req2.error.toString()).toMatch('Server returned a different number of responses than requested.'); })); it('should make a successfully batch request with duplicate request ids', /*#__PURE__*/ _asyncToGenerator(function* () { fetchMock.mock({ matcher: '/graphql/batch', response: { status: 200, body: [{ id: 1, data: { ok: 1 } }, { id: 2, data: { ok: 2 } }] }, method: 'POST' }); const req1 = mockReq(1); const req2 = mockReq(2); const req3 = mockReq(2); yield rnl.sendQueries([req1, req2, req3]); expect(req1.payload).toEqual({ response: { ok: 1 } }); expect(req2.payload).toEqual({ response: { ok: 2 } }); expect(req3.payload).toEqual({ response: { ok: 2 } }); })); it('should reject if server does not return response for request', /*#__PURE__*/ _asyncToGenerator(function* () { fetchMock.mock({ matcher: '/graphql/batch', response: { status: 200, body: [{ id: 2, data: { ok: 2 } }] }, method: 'POST' }); const req1 = mockReq(1); const req2 = mockReq(2); yield rnl.sendQueries([req1, req2]).catch(() => {}); expect(req1.error).toBeInstanceOf(Error); expect(req1.error.toString()).toMatch('Server does not return response for request'); expect(req2.payload).toEqual({ response: { ok: 2 } }); })); it('should reject if server does not return response for duplicate request ids', /*#__PURE__*/ _asyncToGenerator(function* () { fetchMock.mock({ matcher: '/graphql/batch', response: { status: 200, body: [{ id: 2, data: { ok: 2 } }] }, method: 'POST' }); const req1 = mockReq(1); const req2 = mockReq(2); const req3 = mockReq(1); yield rnl.sendQueries([req1, req2, req3]).catch(() => {}); expect(req1.error).toBeInstanceOf(Error); expect(req1.error.toString()).toMatch('Server does not return response for request'); expect(req2.payload).toEqual({ response: { ok: 2 } }); expect(req3.error).toBeInstanceOf(Error); expect(req3.error.toString()).toMatch('Server does not return response for request'); })); it('should handle network failure', /*#__PURE__*/ _asyncToGenerator(function* () { fetchMock.mock({ matcher: '/graphql/batch', response: { throws: new Error('Network connection error') }, method: 'POST' }); const req1 = mockReq(1); const req2 = mockReq(2); yield rnl.sendQueries([req1, req2]).catch(() => {}); expect(req1.error).toBeInstanceOf(Error); expect(req1.error.toString()).toMatch('Network connection error'); expect(req2.error).toBeInstanceOf(Error); expect(req2.error.toString()).toMatch('Network connection error'); })); it('should handle server errors for one request', /*#__PURE__*/ _asyncToGenerator(function* () { fetchMock.mock({ matcher: '/graphql/batch', response: { status: 200, body: [{ id: 1, payload: { errors: [{ location: 1, message: 'major error' }] } }, { id: 2, payload: { data: { ok: 2 } } }] }, method: 'POST' }); const req1 = mockReq(1); const req2 = mockReq(2); yield rnl.sendQueries([req1, req2]).catch(() => {}); expect(req1.error).toBeInstanceOf(Error); expect(req1.error.toString()).toMatch('major error'); expect(req2.payload).toEqual({ response: { ok: 2 } }); expect(req2.error).toBeUndefined(); })); it('should handle server errors for all requests', /*#__PURE__*/ _asyncToGenerator(function* () { fetchMock.mock({ matcher: '/graphql/batch', response: { status: 200, body: { errors: [{ location: 1, message: 'major error' }] } }, method: 'POST' }); const req1 = mockReq(1); const req2 = mockReq(2); const req3 = mockReq(3); yield rnl.sendQueries([req1, req2, req3]).catch(() => {}); expect(req1.error.toString()).toMatch('Wrong response'); expect(req2.error.toString()).toMatch('Wrong response'); expect(req3.error.toString()).toMatch('Wrong response'); })); it('should handle responses without payload wrapper', /*#__PURE__*/ _asyncToGenerator(function* () { fetchMock.mock({ matcher: '/graphql/batch', response: { status: 200, body: [{ id: 1, errors: [{ location: 1, message: 'major error' }] }, { id: 2, data: { ok: 2 } }] }, method: 'POST' }); const req1 = mockReq(1); const req2 = mockReq(2); yield rnl.sendQueries([req1, req2]).catch(() => {}); expect(req1.error).toBeInstanceOf(Error); expect(req1.error.toString()).toMatch('major error'); expect(req2.payload).toEqual({ response: { ok: 2 } }); expect(req2.error).toBeUndefined(); })); describe('option `batchTimeout`', () => { beforeEach(() => { fetchMock.restore(); }); it('should gather different requests into one batch request', /*#__PURE__*/ _asyncToGenerator(function* () { fetchMock.mock({ matcher: '/graphql/batch', response: { status: 200, body: [{ id: 1, data: {} }, { id: 2, data: {} }, { id: 3, data: {} }] }, method: 'POST' }); const rnl2 = new RelayNetworkLayer([batchMiddleware({ batchTimeout: 50 })]); rnl2.sendQueries([mockReq(1)]); setTimeout(() => rnl2.sendQueries([mockReq(2)]), 30); yield rnl2.sendQueries([mockReq(3)]); const reqs = fetchMock.calls('/graphql/batch'); expect(reqs).toHaveLength(1); expect(reqs[0][1].body).toEqual('[{"id":"1","query":"{}","variables":{}},{"id":"2","query":"{}","variables":{}},{"id":"3","query":"{}","variables":{}}]'); })); it('should gather different requests into two batch request', /*#__PURE__*/ _asyncToGenerator(function* () { fetchMock.mock({ matcher: '/graphql/batch', response: { status: 200, body: [{ id: 1, data: {} }, { id: 2, data: {} }, { id: 3, data: {} }, { id: 4, data: {} }] }, method: 'POST' }); const rnl2 = new RelayNetworkLayer([batchMiddleware({ batchTimeout: 50 })]); rnl2.sendQueries([mockReq(1)]); setTimeout(() => rnl2.sendQueries([mockReq(2)]), 60); setTimeout(() => rnl2.sendQueries([mockReq(3)]), 70); rnl2.sendQueries([mockReq(4)]); yield new Promise((resolve, reject) => { setTimeout(() => { try { const reqs = fetchMock.calls('/graphql/batch'); expect(reqs).toHaveLength(2); expect(reqs[0][1].body).toBe('[{"id":"1","query":"{}","variables":{}},{"id":"4","query":"{}","variables":{}}]'); expect(reqs[1][1].body).toBe('[{"id":"2","query":"{}","variables":{}},{"id":"3","query":"{}","variables":{}}]'); resolve(); } catch (e) { reject(e); } }, 200); }); })); }); describe('option `maxBatchSize`', () => { const rnl3 = new RelayNetworkLayer([batchMiddleware({ maxBatchSize: 1024 * 10 })]); beforeEach(() => { fetchMock.restore(); }); it('should split large batched requests into multiple requests', /*#__PURE__*/ _asyncToGenerator(function* () { fetchMock.mock({ matcher: '/graphql', response: { status: 200, body: { id: 5, data: {} } }, method: 'POST' }); fetchMock.mock({ matcher: '/graphql/batch', response: { status: 200, body: [{ id: 1, data: {} }, { id: 2, data: {} }, { id: 3, data: {} }, { id: 4, data: {} }] }, method: 'POST' }); const req1 = mockReqWithSize(1, 1024 * 7); const req2 = mockReqWithSize(2, 1024 * 2); const req3 = mockReqWithSize(3, 1024 * 5); const req4 = mockReqWithSize(4, 1024 * 4); const req5 = mockReqWithSize(5, 1024 * 11); yield rnl3.sendQueries([req1, req2, req3, req4, req5]); const batchReqs = fetchMock.calls('/graphql/batch'); const singleReqs = fetchMock.calls('/graphql'); expect(batchReqs).toHaveLength(2); expect(singleReqs).toHaveLength(1); })); }); describe('option `allowMutations`', () => { beforeEach(() => { fetchMock.restore(); }); it('should not batch mutations by default', /*#__PURE__*/ _asyncToGenerator(function* () { const rnlTimeout20 = new RelayNetworkLayer([batchMiddleware({ batchTimeout: 20 })]); fetchMock.mock({ matcher: '/graphql', response: { status: 200, body: { id: 1, data: {} } }, method: 'POST' }); const req1 = mockReqWithFiles(1); rnlTimeout20.sendMutation(req1); const req2 = mockReqWithFiles(1); yield rnlTimeout20.sendMutation(req2); const singleReqs = fetchMock.calls('/graphql'); expect(singleReqs).toHaveLength(2); })); it('should batch mutations if `allowMutations=true`', /*#__PURE__*/ _asyncToGenerator(function* () { const rnlAllowMutations = new RelayNetworkLayer([batchMiddleware({ batchTimeout: 20, allowMutations: true })]); fetchMock.mock({ matcher: '/graphql/batch', response: { status: 200, body: [{ id: 1, data: {} }, { id: 2, data: {} }] }, method: 'POST' }); const req1 = mockReq(1); rnlAllowMutations.sendMutation(req1); const req2 = mockReq(2); yield rnlAllowMutations.sendMutation(req2); const batchReqs = fetchMock.calls('/graphql/batch'); expect(batchReqs).toHaveLength(1); })); it('should not batch mutations with files if `allowMutations=true`', /*#__PURE__*/ _asyncToGenerator(function* () { const rnlAllowMutations = new RelayNetworkLayer([batchMiddleware({ batchTimeout: 20, allowMutations: true })]); fetchMock.mock({ matcher: '/graphql', response: { status: 200, body: { id: 1, data: {} } }, method: 'POST' }); const req1 = mockReqWithFiles(1); rnlAllowMutations.sendMutation(req1); const req2 = mockReqWithFiles(1); yield rnlAllowMutations.sendMutation(req2); const singleReqs = fetchMock.calls('/graphql'); expect(singleReqs).toHaveLength(2); })); }); });