Skip to content

Commit

Permalink
fix: Parse.Hooks requests have double forward slash in URL (#2441)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: Internal REST requests for `Parse.Hooks` use a URL that contains a double forward slash, for example `http://localhost/parse//hooks/functions`. This release changes the double forward slash to a single forward slash.
  • Loading branch information
dplewis authored Feb 16, 2025
1 parent dc03103 commit 1fc520c
Show file tree
Hide file tree
Showing 2 changed files with 107 additions and 42 deletions.
14 changes: 7 additions & 7 deletions src/ParseHooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export function remove(hook: HookDeleteArg) {

const DefaultController = {
get(type: string, functionName?: string, triggerName?: string) {
let url = '/hooks/' + type;
let url = 'hooks/' + type;
if (functionName) {
url += '/' + functionName;
if (triggerName) {
Expand All @@ -74,9 +74,9 @@ const DefaultController = {
create(hook) {
let url;
if (hook.functionName && hook.url) {
url = '/hooks/functions';
url = 'hooks/functions';
} else if (hook.className && hook.triggerName && hook.url) {
url = '/hooks/triggers';
url = 'hooks/triggers';
} else {
return Promise.reject({ error: 'invalid hook declaration', code: 143 });
}
Expand All @@ -86,10 +86,10 @@ const DefaultController = {
remove(hook) {
let url;
if (hook.functionName) {
url = '/hooks/functions/' + hook.functionName;
url = 'hooks/functions/' + hook.functionName;
delete hook.functionName;
} else if (hook.className && hook.triggerName) {
url = '/hooks/triggers/' + hook.className + '/' + hook.triggerName;
url = 'hooks/triggers/' + hook.className + '/' + hook.triggerName;
delete hook.className;
delete hook.triggerName;
} else {
Expand All @@ -101,10 +101,10 @@ const DefaultController = {
update(hook) {
let url;
if (hook.functionName && hook.url) {
url = '/hooks/functions/' + hook.functionName;
url = 'hooks/functions/' + hook.functionName;
delete hook.functionName;
} else if (hook.className && hook.triggerName && hook.url) {
url = '/hooks/triggers/' + hook.className + '/' + hook.triggerName;
url = 'hooks/triggers/' + hook.className + '/' + hook.triggerName;
delete hook.className;
delete hook.triggerName;
} else {
Expand Down
135 changes: 100 additions & 35 deletions src/__tests__/Hooks-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,27 @@ jest.dontMock('../decode');
jest.dontMock('../encode');
jest.dontMock('../ParseError');
jest.dontMock('../ParseObject');
jest.dontMock('../RESTController');

const Hooks = require('../ParseHooks');
const CoreManager = require('../CoreManager');
const RESTController = require('../RESTController');

const defaultController = CoreManager.getHooksController();
const { sendRequest } = defaultController;

CoreManager.setInstallationController({
currentInstallationId() {
return Promise.resolve('iid');
},
currentInstallation() {},
updateInstallationOnDisk() {},
});
CoreManager.set('APPLICATION_ID', 'A');
CoreManager.set('JAVASCRIPT_KEY', 'B');
CoreManager.set('MASTER_KEY', 'C');
CoreManager.set('VERSION', 'V');

describe('Hooks', () => {
beforeEach(() => {
const run = jest.fn();
Expand All @@ -19,125 +33,176 @@ describe('Hooks', () => {
result: {},
})
);
const ajax = jest.fn();
ajax.mockReturnValue(
Promise.resolve({
response: {},
})
);
RESTController.ajax = ajax;
defaultController.sendRequest = run;
CoreManager.setHooksController(defaultController);
CoreManager.setRESTController(RESTController);
});

it('shoud properly build GET functions', () => {
it('should properly build GET functions', async () => {
Hooks.getFunctions();

expect(CoreManager.getHooksController().sendRequest.mock.calls[0]).toEqual([
'GET',
'/hooks/functions',
'hooks/functions',
]);
defaultController.sendRequest = sendRequest;
await Hooks.getFunctions();
expect(RESTController.ajax.mock.calls[0][1]).toBe('https://api.parse.com/1/hooks/functions');
});

it('shoud properly build GET triggers', () => {
it('should properly build GET triggers', async () => {
Hooks.getTriggers();

expect(CoreManager.getHooksController().sendRequest.mock.calls[0]).toEqual([
'GET',
'/hooks/triggers',
'hooks/triggers',
]);
defaultController.sendRequest = sendRequest;
await Hooks.getTriggers();
expect(RESTController.ajax.mock.calls[0][1]).toBe('https://api.parse.com/1/hooks/triggers');
});

it('shoud properly build GET function', () => {
it('should properly build GET function', async () => {
Hooks.getFunction('functionName');

expect(CoreManager.getHooksController().sendRequest.mock.calls[0]).toEqual([
'GET',
'/hooks/functions/functionName',
'hooks/functions/functionName',
]);
defaultController.sendRequest = sendRequest;
await Hooks.getFunction('functionName');
expect(RESTController.ajax.mock.calls[0][1]).toBe(
'https://api.parse.com/1/hooks/functions/functionName'
);
});

it('shoud properly build GET trigger', () => {
it('should properly build GET trigger', async () => {
Hooks.getTrigger('MyClass', 'beforeSave');

expect(CoreManager.getHooksController().sendRequest.mock.calls[0]).toEqual([
'GET',
'/hooks/triggers/MyClass/beforeSave',
'hooks/triggers/MyClass/beforeSave',
]);
defaultController.sendRequest = sendRequest;
await Hooks.getTrigger('MyClass', 'beforeSave');
expect(RESTController.ajax.mock.calls[0][1]).toBe(
'https://api.parse.com/1/hooks/triggers/MyClass/beforeSave'
);
});

it('shoud properly build POST function', () => {
Hooks.createFunction('myFunction', 'https://dummy.com');
it('should properly build POST function', async () => {
Hooks.createFunction('myFunction', 'https://example.com');

expect(CoreManager.getHooksController().sendRequest.mock.calls[0]).toEqual([
'POST',
'/hooks/functions',
'hooks/functions',
{
functionName: 'myFunction',
url: 'https://dummy.com',
url: 'https://example.com',
},
]);
defaultController.sendRequest = sendRequest;
await Hooks.createFunction('myFunction', 'https://example.com');
expect(RESTController.ajax.mock.calls[0][1]).toBe('https://api.parse.com/1/hooks/functions');
});

it('shoud properly build POST trigger', () => {
Hooks.createTrigger('MyClass', 'beforeSave', 'https://dummy.com');
it('should properly build POST trigger', async () => {
Hooks.createTrigger('MyClass', 'beforeSave', 'https://example.com');

expect(CoreManager.getHooksController().sendRequest.mock.calls[0]).toEqual([
'POST',
'/hooks/triggers',
'hooks/triggers',
{
className: 'MyClass',
triggerName: 'beforeSave',
url: 'https://dummy.com',
url: 'https://example.com',
},
]);
defaultController.sendRequest = sendRequest;
await Hooks.createTrigger('MyClass', 'beforeSave', 'https://example.com');
expect(RESTController.ajax.mock.calls[0][1]).toBe('https://api.parse.com/1/hooks/triggers');
});

it('shoud properly build PUT function', () => {
Hooks.updateFunction('myFunction', 'https://dummy.com');
it('should properly build PUT function', async () => {
Hooks.updateFunction('myFunction', 'https://example.com');

expect(CoreManager.getHooksController().sendRequest.mock.calls[0]).toEqual([
'PUT',
'/hooks/functions/myFunction',
'hooks/functions/myFunction',
{
url: 'https://dummy.com',
url: 'https://example.com',
},
]);
defaultController.sendRequest = sendRequest;
await Hooks.updateFunction('myFunction', 'https://example.com');
expect(RESTController.ajax.mock.calls[0][1]).toBe(
'https://api.parse.com/1/hooks/functions/myFunction'
);
});

it('shoud properly build PUT trigger', () => {
Hooks.updateTrigger('MyClass', 'beforeSave', 'https://dummy.com');
it('should properly build PUT trigger', async () => {
Hooks.updateTrigger('MyClass', 'beforeSave', 'https://example.com');

expect(CoreManager.getHooksController().sendRequest.mock.calls[0]).toEqual([
'PUT',
'/hooks/triggers/MyClass/beforeSave',
'hooks/triggers/MyClass/beforeSave',
{
url: 'https://dummy.com',
url: 'https://example.com',
},
]);
defaultController.sendRequest = sendRequest;
await Hooks.updateTrigger('MyClass', 'beforeSave', 'https://example.com');
expect(RESTController.ajax.mock.calls[0][1]).toBe(
'https://api.parse.com/1/hooks/triggers/MyClass/beforeSave'
);
});

it('shoud properly build removeFunction', () => {
it('should properly build removeFunction', async () => {
Hooks.removeFunction('myFunction');

expect(CoreManager.getHooksController().sendRequest.mock.calls[0]).toEqual([
'PUT',
'/hooks/functions/myFunction',
'hooks/functions/myFunction',
{ __op: 'Delete' },
]);

defaultController.sendRequest = sendRequest;
await Hooks.removeFunction('myFunction');
expect(RESTController.ajax.mock.calls[0][1]).toBe(
'https://api.parse.com/1/hooks/functions/myFunction'
);
});

it('shoud properly build removeTrigger', () => {
it('should properly build removeTrigger', async () => {
Hooks.removeTrigger('MyClass', 'beforeSave');

expect(CoreManager.getHooksController().sendRequest.mock.calls[0]).toEqual([
'PUT',
'/hooks/triggers/MyClass/beforeSave',
'hooks/triggers/MyClass/beforeSave',
{ __op: 'Delete' },
]);
defaultController.sendRequest = sendRequest;
await Hooks.removeTrigger('MyClass', 'beforeSave');
expect(RESTController.ajax.mock.calls[0][1]).toBe(
'https://api.parse.com/1/hooks/triggers/MyClass/beforeSave'
);
});

it('shoud throw invalid create', async () => {
it('should throw invalid create', async () => {
expect.assertions(10);
const p1 = Hooks.create({ functionName: 'myFunction' }).catch(err => {
expect(err.code).toBe(143);
expect(err.error).toBe('invalid hook declaration');
});

const p2 = Hooks.create({ url: 'http://dummy.com' }).catch(err => {
const p2 = Hooks.create({ url: 'http://example.com' }).catch(err => {
expect(err.code).toBe(143);
expect(err.error).toBe('invalid hook declaration');
});
Expand All @@ -147,7 +212,7 @@ describe('Hooks', () => {
expect(err.error).toBe('invalid hook declaration');
});

const p4 = Hooks.create({ className: 'MyClass', url: 'http://dummy.com' }).catch(err => {
const p4 = Hooks.create({ className: 'MyClass', url: 'http://example.com' }).catch(err => {
expect(err.code).toBe(143);
expect(err.error).toBe('invalid hook declaration');
});
Expand All @@ -160,7 +225,7 @@ describe('Hooks', () => {
await Promise.all([p1, p2, p3, p4, p5]);
});

it('shoud throw invalid update', async () => {
it('should throw invalid update', async () => {
expect.assertions(6);
const p1 = Hooks.update({ functionssName: 'myFunction' }).catch(err => {
expect(err.code).toBe(143);
Expand All @@ -172,14 +237,14 @@ describe('Hooks', () => {
expect(err.error).toBe('invalid hook declaration');
});

const p3 = Hooks.update({ className: 'MyClass', url: 'http://dummy.com' }).catch(err => {
const p3 = Hooks.update({ className: 'MyClass', url: 'http://example.com' }).catch(err => {
expect(err.code).toBe(143);
expect(err.error).toBe('invalid hook declaration');
});
await Promise.all([p1, p2, p3]);
});

it('shoud throw invalid remove', async () => {
it('should throw invalid remove', async () => {
expect.assertions(6);
const p1 = Hooks.remove({ functionssName: 'myFunction' }).catch(err => {
expect(err.code).toBe(143);
Expand All @@ -191,7 +256,7 @@ describe('Hooks', () => {
expect(err.error).toBe('invalid hook declaration');
});

const p3 = Hooks.remove({ className: 'MyClass', url: 'http://dummy.com' }).catch(err => {
const p3 = Hooks.remove({ className: 'MyClass', url: 'http://example.com' }).catch(err => {
expect(err.code).toBe(143);
expect(err.error).toBe('invalid hook declaration');
});
Expand Down

0 comments on commit 1fc520c

Please # to comment.