Skip to content

feat: Allow custom server connection fail message #2011

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion integration/test/ParseServerTest.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict';

const assert = require('assert');
const Parse = require('../../node');

describe('ParseServer', () => {
it('can reconfigure server', async () => {
Expand All @@ -15,9 +16,24 @@ 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);
});

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);
});
});
1 change: 1 addition & 0 deletions src/CoreManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ const config: Config & { [key: string]: mixed } = {
!!process.versions.node &&
!process.versions.electron,
REQUEST_ATTEMPT_LIMIT: 5,
CONNECTION_FAILED_MESSAGE: 'The connection to the Parse servers failed.',
REQUEST_BATCH_SIZE: 20,
REQUEST_HEADERS: {},
SERVER_URL: 'https://api.parse.com/1',
Expand Down
4 changes: 2 additions & 2 deletions src/EventuallyQueue.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Expand All @@ -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);
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/ParseObject.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down Expand Up @@ -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();
}
Expand Down
7 changes: 2 additions & 5 deletions src/RESTController.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
},
Expand Down
47 changes: 45 additions & 2 deletions src/__tests__/EventuallyQueue-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(() => {});
Expand All @@ -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;
Expand Down Expand Up @@ -311,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(() => {});
Expand All @@ -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();
Expand Down
36 changes: 34 additions & 2 deletions src/__tests__/ParseObject-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1577,14 +1577,30 @@ 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();
expect(EventuallyQueue.save).toHaveBeenCalledTimes(1);
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());
Expand Down Expand Up @@ -2916,14 +2932,30 @@ 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();
expect(EventuallyQueue.destroy).toHaveBeenCalledTimes(1);
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());
Expand Down
23 changes: 20 additions & 3 deletions src/__tests__/RESTController-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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('The connection to the Parse servers failed.');
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(
Expand All @@ -87,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();
Expand Down Expand Up @@ -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();
});
});
Expand Down