From 1be83d06a9be53eea66c51029e0609b72830fbcf Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Thu, 31 Aug 2023 12:13:39 -0500 Subject: [PATCH 1/2] feat: Allow custom server connection fail message --- integration/test/ParseServerTest.js | 16 ++++++++++ src/CoreManager.js | 1 + src/EventuallyQueue.js | 4 +-- src/ParseObject.js | 4 +-- src/RESTController.js | 7 ++--- src/__tests__/EventuallyQueue-test.js | 43 +++++++++++++++++++++++++++ src/__tests__/ParseObject-test.js | 32 ++++++++++++++++++++ src/__tests__/RESTController-test.js | 21 +++++++++++-- 8 files changed, 117 insertions(+), 11 deletions(-) diff --git a/integration/test/ParseServerTest.js b/integration/test/ParseServerTest.js index 97489e598..04753a3a1 100644 --- a/integration/test/ParseServerTest.js +++ b/integration/test/ParseServerTest.js @@ -1,6 +1,7 @@ 'use strict'; const assert = require('assert'); +const Parse = require('../../node'); describe('ParseServer', () => { it('can reconfigure server', async () => { @@ -20,4 +21,19 @@ describe('ParseServer', () => { await object.save(); assert(object.id); }); + + it('can shutdown with custom message', async () => { + const defaultMessage = Parse.CoreManager.get('CONNECTION_FAILED_MESSAGE'); + const message = 'Server is down!'; + Parse.CoreManager.set('CONNECTION_FAILED_MESSAGE', message); + const parseServer = await reconfigureServer(); + const object = new TestObject({ foo: 'bar' }); + await parseServer.handleShutdown(); + await new Promise((resolve) => parseServer.server.close(resolve)); + await expectAsync(object.save()).toBeRejectedWithError(message); + await reconfigureServer({}); + await object.save(); + assert(object.id); + Parse.CoreManager.set('CONNECTION_FAILED_MESSAGE', defaultMessage); + }); }); diff --git a/src/CoreManager.js b/src/CoreManager.js index 73228c6d3..f14ab162d 100644 --- a/src/CoreManager.js +++ b/src/CoreManager.js @@ -177,6 +177,7 @@ const config: Config & { [key: string]: mixed } = { !!process.versions.node && !process.versions.electron, REQUEST_ATTEMPT_LIMIT: 5, + CONNECTION_FAILED_MESSAGE: 'XMLHttpRequest failed: "Unable to connect to the Parse API"', REQUEST_BATCH_SIZE: 20, REQUEST_HEADERS: {}, SERVER_URL: 'https://api.parse.com/1', diff --git a/src/EventuallyQueue.js b/src/EventuallyQueue.js index 03dc82ce8..40b8c809b 100644 --- a/src/EventuallyQueue.js +++ b/src/EventuallyQueue.js @@ -275,7 +275,7 @@ const EventuallyQueue = { await object.save(queueObject.object, queueObject.serverOptions); await this.remove(queueObject.queueId); } catch (e) { - if (e.message !== 'XMLHttpRequest failed: "Unable to connect to the Parse API"') { + if (e.message !== CoreManager.get('CONNECTION_FAILED_MESSAGE')) { await this.remove(queueObject.queueId); } } @@ -285,7 +285,7 @@ const EventuallyQueue = { await object.destroy(queueObject.serverOptions); await this.remove(queueObject.queueId); } catch (e) { - if (e.message !== 'XMLHttpRequest failed: "Unable to connect to the Parse API"') { + if (e.message !== CoreManager.get('CONNECTION_FAILED_MESSAGE')) { await this.remove(queueObject.queueId); } } diff --git a/src/ParseObject.js b/src/ParseObject.js index 94c30b9c1..aaa95488c 100644 --- a/src/ParseObject.js +++ b/src/ParseObject.js @@ -1219,7 +1219,7 @@ class ParseObject { try { await this.save(null, options); } catch (e) { - if (e.message === 'XMLHttpRequest failed: "Unable to connect to the Parse API"') { + if (e.message === CoreManager.get('CONNECTION_FAILED_MESSAGE')) { await EventuallyQueue.save(this, options); EventuallyQueue.poll(); } @@ -1363,7 +1363,7 @@ class ParseObject { try { await this.destroy(options); } catch (e) { - if (e.message === 'XMLHttpRequest failed: "Unable to connect to the Parse API"') { + if (e.message === CoreManager.get('CONNECTION_FAILED_MESSAGE')) { await EventuallyQueue.destroy(this, options); EventuallyQueue.poll(); } diff --git a/src/RESTController.js b/src/RESTController.js index 5e0c42d0f..9fb875dad 100644 --- a/src/RESTController.js +++ b/src/RESTController.js @@ -132,7 +132,7 @@ const RESTController = { const delay = Math.round(Math.random() * 125 * Math.pow(2, attempts)); setTimeout(dispatch, delay); } else if (xhr.status === 0) { - promise.reject('Unable to connect to the Parse API'); + promise.reject(new ParseError(ParseError.CONNECTION_FAILED, CoreManager.get('CONNECTION_FAILED_MESSAGE'))); } else { // After the retry limit is reached, fail promise.reject(xhr); @@ -316,10 +316,7 @@ const RESTController = { } } else { const message = response.message ? response.message : response; - error = new ParseError( - ParseError.CONNECTION_FAILED, - 'XMLHttpRequest failed: ' + JSON.stringify(message) - ); + error = new ParseError(ParseError.CONNECTION_FAILED, message); } return Promise.reject(error); }, diff --git a/src/__tests__/EventuallyQueue-test.js b/src/__tests__/EventuallyQueue-test.js index 3b98d5022..ce5a9450a 100644 --- a/src/__tests__/EventuallyQueue-test.js +++ b/src/__tests__/EventuallyQueue-test.js @@ -256,6 +256,27 @@ describe('EventuallyQueue', () => { expect(EventuallyQueue.remove).toHaveBeenCalledTimes(0); }); + it('should not remove if queue destroy callback network fails with custom connection message', async () => { + const defaultMessage = CoreManager.get('CONNECTION_FAILED_MESSAGE'); + const message = 'Server is down!'; + CoreManager.set('CONNECTION_FAILED_MESSAGE', message); + const object = new ParseObject('TestObject'); + jest.spyOn(object, 'destroy').mockImplementationOnce(() => { + throw new ParseError(ParseError.CONNECTION_FAILED, message); + }); + jest.spyOn(EventuallyQueue, 'remove').mockImplementationOnce(() => {}); + const queueObject = { + action: 'destroy', + queueId: 'queue1', + serverOptions: {}, + }; + await EventuallyQueue.sendQueueCallback(object, queueObject); + expect(object.destroy).toHaveBeenCalledTimes(1); + expect(object.destroy).toHaveBeenCalledWith({}); + expect(EventuallyQueue.remove).toHaveBeenCalledTimes(0); + CoreManager.set('CONNECTION_FAILED_MESSAGE', defaultMessage); + }); + it('can handle send queue save callback with no object', async () => { jest.spyOn(EventuallyQueue, 'remove').mockImplementationOnce(() => {}); const object = null; @@ -327,6 +348,28 @@ describe('EventuallyQueue', () => { expect(EventuallyQueue.remove).toHaveBeenCalledTimes(0); }); + it('should not remove if queue save callback network fails with custom connection message', async () => { + const defaultMessage = CoreManager.get('CONNECTION_FAILED_MESSAGE'); + const message = 'Server is down!'; + CoreManager.set('CONNECTION_FAILED_MESSAGE', message); + const object = new ParseObject('TestObject'); + jest.spyOn(object, 'save').mockImplementationOnce(() => { + throw new ParseError(ParseError.CONNECTION_FAILED, message); + }); + jest.spyOn(EventuallyQueue, 'remove').mockImplementationOnce(() => {}); + const queueObject = { + action: 'save', + queueId: 'queue2', + object: { foo: 'bar' }, + serverOptions: {}, + }; + await EventuallyQueue.sendQueueCallback(object, queueObject); + expect(object.save).toHaveBeenCalledTimes(1); + expect(object.save).toHaveBeenCalledWith({ foo: 'bar' }, {}); + expect(EventuallyQueue.remove).toHaveBeenCalledTimes(0); + CoreManager.set('CONNECTION_FAILED_MESSAGE', defaultMessage); + }); + it('can handle send queue save callback if queue is old', async () => { jest.spyOn(EventuallyQueue, 'remove').mockImplementationOnce(() => {}); const today = new Date(); diff --git a/src/__tests__/ParseObject-test.js b/src/__tests__/ParseObject-test.js index c67c4a3e1..4813f61fd 100644 --- a/src/__tests__/ParseObject-test.js +++ b/src/__tests__/ParseObject-test.js @@ -1585,6 +1585,22 @@ describe('ParseObject', () => { expect(EventuallyQueue.poll).toHaveBeenCalledTimes(1); }); + it('can save the object eventually on network failure with custom connection message', async () => { + const defaultMessage = CoreManager.get('CONNECTION_FAILED_MESSAGE'); + const message = 'Server is down!'; + CoreManager.set('CONNECTION_FAILED_MESSAGE', message); + const p = new ParseObject('Person'); + jest.spyOn(EventuallyQueue, 'save').mockImplementationOnce(() => Promise.resolve()); + jest.spyOn(EventuallyQueue, 'poll').mockImplementationOnce(() => {}); + jest.spyOn(p, 'save').mockImplementationOnce(() => { + throw new ParseError(ParseError.CONNECTION_FAILED, message); + }); + await p.saveEventually(); + expect(EventuallyQueue.save).toHaveBeenCalledTimes(1); + expect(EventuallyQueue.poll).toHaveBeenCalledTimes(1); + CoreManager.set('CONNECTION_FAILED_MESSAGE', defaultMessage); + }); + it('should not save the object eventually on error', async () => { const p = new ParseObject('Person'); jest.spyOn(EventuallyQueue, 'save').mockImplementationOnce(() => Promise.resolve()); @@ -2924,6 +2940,22 @@ describe('ObjectController', () => { expect(EventuallyQueue.poll).toHaveBeenCalledTimes(1); }); + it('can destroy the object eventually on network failure with custom connection message', async () => { + const defaultMessage = CoreManager.get('CONNECTION_FAILED_MESSAGE'); + const message = 'Server is down!'; + CoreManager.set('CONNECTION_FAILED_MESSAGE', message); + const p = new ParseObject('Person'); + jest.spyOn(EventuallyQueue, 'destroy').mockImplementationOnce(() => Promise.resolve()); + jest.spyOn(EventuallyQueue, 'poll').mockImplementationOnce(() => {}); + jest.spyOn(p, 'destroy').mockImplementationOnce(() => { + throw new ParseError(ParseError.CONNECTION_FAILED, message); + }); + await p.destroyEventually(); + expect(EventuallyQueue.destroy).toHaveBeenCalledTimes(1); + expect(EventuallyQueue.poll).toHaveBeenCalledTimes(1); + CoreManager.set('CONNECTION_FAILED_MESSAGE', defaultMessage); + }); + it('should not destroy object eventually on error', async () => { const p = new ParseObject('Person'); jest.spyOn(EventuallyQueue, 'destroy').mockImplementationOnce(() => Promise.resolve()); diff --git a/src/__tests__/RESTController-test.js b/src/__tests__/RESTController-test.js index 7beb64f3d..d62982875 100644 --- a/src/__tests__/RESTController-test.js +++ b/src/__tests__/RESTController-test.js @@ -72,12 +72,29 @@ describe('RESTController', () => { mockXHR([{ status: 0 }, { status: 0 }, { status: 0 }, { status: 0 }, { status: 0 }]) ); RESTController.ajax('POST', 'users', {}).then(null, err => { - expect(err).toBe('Unable to connect to the Parse API'); + expect(err.code).toBe(100); + expect(err.message).toBe('XMLHttpRequest failed: "Unable to connect to the Parse API"'); done(); }); jest.runAllTimers(); }); + it('retries on connection failure custom message', done => { + const defaultMessage = CoreManager.get('CONNECTION_FAILED_MESSAGE'); + const message = 'Server is down!'; + CoreManager.set('CONNECTION_FAILED_MESSAGE', message); + RESTController._setXHR( + mockXHR([{ status: 0 }, { status: 0 }, { status: 0 }, { status: 0 }, { status: 0 }]) + ); + RESTController.ajax('POST', 'users', {}).then(null, err => { + expect(err.code).toBe(100); + expect(err.message).toBe(message); + done(); + }); + jest.runAllTimers(); + CoreManager.set('CONNECTION_FAILED_MESSAGE', defaultMessage); + }); + it('returns a connection error on network failure', async () => { expect.assertions(2); RESTController._setXHR( @@ -182,7 +199,7 @@ describe('RESTController', () => { RESTController._setXHR(XHR); RESTController.request('GET', 'classes/MyObject', {}, {}).then(null, error => { expect(error.code).toBe(100); - expect(error.message.indexOf('XMLHttpRequest failed')).toBe(0); + expect(error.message.indexOf('SyntaxError')).toBe(0); done(); }); }); From 8e576ee1e7c16bd7bd30f5790bef24643a4c935e Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Fri, 1 Sep 2023 10:29:48 -0500 Subject: [PATCH 2/2] Change default server fail message --- integration/test/ParseServerTest.js | 2 +- src/CoreManager.js | 2 +- src/__tests__/EventuallyQueue-test.js | 4 ++-- src/__tests__/ParseObject-test.js | 4 ++-- src/__tests__/RESTController-test.js | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/integration/test/ParseServerTest.js b/integration/test/ParseServerTest.js index 04753a3a1..323aac605 100644 --- a/integration/test/ParseServerTest.js +++ b/integration/test/ParseServerTest.js @@ -16,7 +16,7 @@ describe('ParseServer', () => { const object = new TestObject({ foo: 'bar' }); await parseServer.handleShutdown(); await new Promise((resolve) => parseServer.server.close(resolve)); - await expectAsync(object.save()).toBeRejectedWithError('XMLHttpRequest failed: "Unable to connect to the Parse API"'); + await expectAsync(object.save()).toBeRejectedWithError('The connection to the Parse servers failed.'); await reconfigureServer({}); await object.save(); assert(object.id); diff --git a/src/CoreManager.js b/src/CoreManager.js index f14ab162d..6c4a292a1 100644 --- a/src/CoreManager.js +++ b/src/CoreManager.js @@ -177,7 +177,7 @@ const config: Config & { [key: string]: mixed } = { !!process.versions.node && !process.versions.electron, REQUEST_ATTEMPT_LIMIT: 5, - CONNECTION_FAILED_MESSAGE: 'XMLHttpRequest failed: "Unable to connect to the Parse API"', + CONNECTION_FAILED_MESSAGE: 'The connection to the Parse servers failed.', REQUEST_BATCH_SIZE: 20, REQUEST_HEADERS: {}, SERVER_URL: 'https://api.parse.com/1', diff --git a/src/__tests__/EventuallyQueue-test.js b/src/__tests__/EventuallyQueue-test.js index ce5a9450a..4246c640f 100644 --- a/src/__tests__/EventuallyQueue-test.js +++ b/src/__tests__/EventuallyQueue-test.js @@ -241,7 +241,7 @@ describe('EventuallyQueue', () => { jest.spyOn(object, 'destroy').mockImplementationOnce(() => { throw new ParseError( ParseError.CONNECTION_FAILED, - 'XMLHttpRequest failed: "Unable to connect to the Parse API"' + 'The connection to the Parse servers failed.' ); }); jest.spyOn(EventuallyQueue, 'remove').mockImplementationOnce(() => {}); @@ -332,7 +332,7 @@ describe('EventuallyQueue', () => { jest.spyOn(object, 'save').mockImplementationOnce(() => { throw new ParseError( ParseError.CONNECTION_FAILED, - 'XMLHttpRequest failed: "Unable to connect to the Parse API"' + 'The connection to the Parse servers failed.' ); }); jest.spyOn(EventuallyQueue, 'remove').mockImplementationOnce(() => {}); diff --git a/src/__tests__/ParseObject-test.js b/src/__tests__/ParseObject-test.js index 4813f61fd..81a03f899 100644 --- a/src/__tests__/ParseObject-test.js +++ b/src/__tests__/ParseObject-test.js @@ -1577,7 +1577,7 @@ describe('ParseObject', () => { jest.spyOn(p, 'save').mockImplementationOnce(() => { throw new ParseError( ParseError.CONNECTION_FAILED, - 'XMLHttpRequest failed: "Unable to connect to the Parse API"' + 'The connection to the Parse servers failed.' ); }); await p.saveEventually(); @@ -2932,7 +2932,7 @@ describe('ObjectController', () => { jest.spyOn(p, 'destroy').mockImplementationOnce(() => { throw new ParseError( ParseError.CONNECTION_FAILED, - 'XMLHttpRequest failed: "Unable to connect to the Parse API"' + 'The connection to the Parse servers failed.' ); }); await p.destroyEventually(); diff --git a/src/__tests__/RESTController-test.js b/src/__tests__/RESTController-test.js index d62982875..c3551a8b6 100644 --- a/src/__tests__/RESTController-test.js +++ b/src/__tests__/RESTController-test.js @@ -73,7 +73,7 @@ describe('RESTController', () => { ); RESTController.ajax('POST', 'users', {}).then(null, err => { expect(err.code).toBe(100); - expect(err.message).toBe('XMLHttpRequest failed: "Unable to connect to the Parse API"'); + expect(err.message).toBe('The connection to the Parse servers failed.'); done(); }); jest.runAllTimers(); @@ -104,7 +104,7 @@ describe('RESTController', () => { null, err => { expect(err.code).toBe(100); - expect(err.message).toBe('XMLHttpRequest failed: "Unable to connect to the Parse API"'); + expect(err.message).toBe('The connection to the Parse servers failed.'); } ); await flushPromises();