From 1fc520ccdc3742c467dfaa9f58d249389b4d5c5a Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Sun, 16 Feb 2025 12:09:03 -0600 Subject: [PATCH] fix: `Parse.Hooks` requests have double forward slash in URL (#2441) 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. --- src/ParseHooks.ts | 14 ++-- src/__tests__/Hooks-test.js | 135 ++++++++++++++++++++++++++---------- 2 files changed, 107 insertions(+), 42 deletions(-) diff --git a/src/ParseHooks.ts b/src/ParseHooks.ts index 9ec21dec8..1e6a9ef3c 100644 --- a/src/ParseHooks.ts +++ b/src/ParseHooks.ts @@ -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) { @@ -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 }); } @@ -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 { @@ -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 { diff --git a/src/__tests__/Hooks-test.js b/src/__tests__/Hooks-test.js index 03d9b248a..2bac07f71 100644 --- a/src/__tests__/Hooks-test.js +++ b/src/__tests__/Hooks-test.js @@ -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(); @@ -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'); }); @@ -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'); }); @@ -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); @@ -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); @@ -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'); });