diff --git a/integration/test/IdempotencyTest.js b/integration/test/IdempotencyTest.js index ed0147d4c..d7c23e05d 100644 --- a/integration/test/IdempotencyTest.js +++ b/integration/test/IdempotencyTest.js @@ -22,9 +22,14 @@ function DuplicateXHR(requestId) { describe('Idempotency', () => { beforeEach(() => { + Parse.idempotency = false; RESTController._setXHR(XHR); }); + afterEach(() => { + Parse.idempotency = true; + }); + it('handle duplicate cloud code function request', async () => { RESTController._setXHR(DuplicateXHR('1234')); await Parse.Cloud.run('CloudFunctionIdempotency'); diff --git a/src/CoreManager.ts b/src/CoreManager.ts index 8b619cf9f..06694eba1 100644 --- a/src/CoreManager.ts +++ b/src/CoreManager.ts @@ -325,7 +325,7 @@ const config: Config & { [key: string]: any } = { PERFORM_USER_REWRITE: true, FORCE_REVOCABLE_SESSION: false, ENCRYPTED_USER: false, - IDEMPOTENCY: false, + IDEMPOTENCY: true, ALLOW_CUSTOM_OBJECT_ID: false, PARSE_ERRORS: [], }; diff --git a/src/RESTController.ts b/src/RESTController.ts index 1c70b156e..1173cc91e 100644 --- a/src/RESTController.ts +++ b/src/RESTController.ts @@ -103,8 +103,7 @@ const RESTController = { return ajaxIE9(method, url, data, headers, options); } const promise = resolvingPromise(); - const isIdempotent = CoreManager.get('IDEMPOTENCY') && ['POST', 'PUT'].includes(method); - const requestId = isIdempotent ? uuidv4() : ''; + const requestId = CoreManager.get('IDEMPOTENCY') ? uuidv4() : ''; let attempts = 0; const dispatch = function () { @@ -171,7 +170,7 @@ const RESTController = { headers['User-Agent'] = 'Parse/' + CoreManager.get('VERSION') + ' (NodeJS ' + process.versions.node + ')'; } - if (isIdempotent) { + if (requestId) { headers['X-Parse-Request-Id'] = requestId; } if (CoreManager.get('SERVER_AUTH_TYPE') && CoreManager.get('SERVER_AUTH_TOKEN')) { diff --git a/src/__tests__/Parse-test.js b/src/__tests__/Parse-test.js index e68ea8253..7e2084749 100644 --- a/src/__tests__/Parse-test.js +++ b/src/__tests__/Parse-test.js @@ -75,12 +75,12 @@ describe('Parse module', () => { }); it('can set idempotency', () => { - expect(Parse.idempotency).toBe(false); - Parse.idempotency = true; - expect(CoreManager.get('IDEMPOTENCY')).toBe(true); expect(Parse.idempotency).toBe(true); Parse.idempotency = false; + expect(CoreManager.get('IDEMPOTENCY')).toBe(false); expect(Parse.idempotency).toBe(false); + Parse.idempotency = true; + expect(Parse.idempotency).toBe(true); }); it('can set LocalDatastoreController', () => { diff --git a/src/__tests__/RESTController-test.js b/src/__tests__/RESTController-test.js index 412d5f2e8..54571fa1e 100644 --- a/src/__tests__/RESTController-test.js +++ b/src/__tests__/RESTController-test.js @@ -352,7 +352,6 @@ describe('RESTController', () => { }); it('idempotency - sends requestId header', async () => { - CoreManager.set('IDEMPOTENCY', true); const requestIdHeader = header => 'X-Parse-Request-Id' === header[0]; const xhr = { setRequestHeader: jest.fn(), @@ -364,21 +363,19 @@ describe('RESTController', () => { }); RESTController.request('POST', 'classes/MyObject', {}, {}); await flushPromises(); - expect(xhr.setRequestHeader.mock.calls.filter(requestIdHeader)).toEqual([ - ['X-Parse-Request-Id', '1000'], - ]); + const [header, requestId] = xhr.setRequestHeader.mock.calls.filter(requestIdHeader)[0]; + expect(header).toBe('X-Parse-Request-Id'); + expect(requestId).toBeDefined(); xhr.setRequestHeader.mockClear(); RESTController.request('PUT', 'classes/MyObject', {}, {}); await flushPromises(); - expect(xhr.setRequestHeader.mock.calls.filter(requestIdHeader)).toEqual([ - ['X-Parse-Request-Id', '1001'], - ]); - CoreManager.set('IDEMPOTENCY', false); + const [nextHeader, nextRequestId] = xhr.setRequestHeader.mock.calls.filter(requestIdHeader)[0]; + expect(nextHeader).toBe('X-Parse-Request-Id'); + expect(nextRequestId).toBe((Number(requestId) + 1).toString()); }); it('idempotency - handle requestId on network retries', done => { - CoreManager.set('IDEMPOTENCY', true); RESTController._setXHR( mockXHR([{ status: 500 }, { status: 500 }, { status: 200, response: { success: true } }]) ); @@ -394,25 +391,6 @@ describe('RESTController', () => { done(); }); jest.runAllTimers(); - CoreManager.set('IDEMPOTENCY', false); - }); - - it('idempotency - should properly handle url method not POST / PUT', () => { - CoreManager.set('IDEMPOTENCY', true); - const xhr = { - setRequestHeader: jest.fn(), - open: jest.fn(), - send: jest.fn(), - }; - RESTController._setXHR(function () { - return xhr; - }); - RESTController.ajax('GET', 'users/me', {}, {}); - const requestIdHeaders = xhr.setRequestHeader.mock.calls.filter( - header => 'X-Parse-Request-Id' === header[0] - ); - expect(requestIdHeaders.length).toBe(0); - CoreManager.set('IDEMPOTENCY', false); }); it('handles aborted requests', done => { @@ -685,7 +663,10 @@ describe('RESTController', () => { return xhr; }); RESTController.ajax('GET', 'users/me', {}, { 'X-Parse-Session-Token': '123' }); - expect(xhr.setRequestHeader.mock.calls[3]).toEqual(['Cache-Control', 'max-age=3600']); + const cacheHeader = header => 'Cache-Control' === header[0]; + const [header, value] = xhr.setRequestHeader.mock.calls.filter(cacheHeader)[0]; + expect(header).toBe('Cache-Control'); + expect(value).toBe('max-age=3600'); expect(xhr.open.mock.calls[0]).toEqual(['GET', 'users/me', true]); expect(xhr.send.mock.calls[0][0]).toEqual({}); CoreManager.set('REQUEST_HEADERS', {});