From 051a97ea2c3d7540e41ad119d32f9ab4e8c289f9 Mon Sep 17 00:00:00 2001 From: "viktor.liablin" Date: Tue, 2 Sep 2025 19:04:14 +0300 Subject: [PATCH 1/7] FR-1800 - Update SubFlow name by basic PUT rout for subFlows --- src/automation.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/automation.js b/src/automation.js index 4dd60b9..e1596d3 100644 --- a/src/automation.js +++ b/src/automation.js @@ -60,8 +60,10 @@ const routes = prepareRoutes({ installFlowProduct : '/api/app/:appId/automation/flow/marketplace/install/:productId', uninstallFlowProduct: '/api/app/:appId/automation/flow/marketplace/uninstall/:productId', - createSubFlow : '/api/app/:appId/automation/version/:versionId/subflow', - getSubFlowByID : '/api/app/:appId/automation/version/:versionId/subflow/:subFlowId', + createSubFlow : '/api/app/:appId/automation/version/:versionId/subflow', + getSubFlowByID: '/api/app/:appId/automation/version/:versionId/subflow/:subFlowId', + + updateSubFlowName : '/api/app/:appId/automation/version/:versionId/subflow/:subFlowId/name', updateSubFlowByID : '/api/app/:appId/automation/version/:versionId/subflow/:subFlowId', deleteSubFlowByID : '/api/app/:appId/automation/version/:versionId/subflow/:subFlowId', getSubFlows : '/api/app/:appId/automation/version/:versionId/subflow', @@ -343,6 +345,10 @@ export default req => ({ return req.automation.put(routes.updateSubFlowByID(appId, versionId, subFlowVersionId), data) }, + updateSubFlowName(appId, versionId, subFlowVersionId, name) { + return req.automation.put(routes.updateSubFlowByID(appId, versionId, subFlowVersionId), { name }) + }, + deleteSubFlow(appId, versionId, subFlowId) { return req.automation.delete(routes.deleteSubFlowByID(appId, versionId, subFlowId)) }, From 71d6458d7c64343faf88c06d661413e34220ca65 Mon Sep 17 00:00:00 2001 From: "viktor.liablin" Date: Tue, 2 Sep 2025 19:14:55 +0300 Subject: [PATCH 2/7] FR-1800 - Update SubFlow name by basic PUT rout for subFlows --- src/automation.js | 2 +- tests/specs/automation.test.js | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/automation.js b/src/automation.js index e1596d3..5275382 100644 --- a/src/automation.js +++ b/src/automation.js @@ -346,7 +346,7 @@ export default req => ({ }, updateSubFlowName(appId, versionId, subFlowVersionId, name) { - return req.automation.put(routes.updateSubFlowByID(appId, versionId, subFlowVersionId), { name }) + return req.automation.put(routes.updateSubFlowName(appId, versionId, subFlowVersionId), { name }) }, deleteSubFlow(appId, versionId, subFlowId) { diff --git a/tests/specs/automation.test.js b/tests/specs/automation.test.js index 77213c7..de04823 100644 --- a/tests/specs/automation.test.js +++ b/tests/specs/automation.test.js @@ -1352,6 +1352,28 @@ describe('apiClient.automation', () => { }) }) + describe('updateSubFlowName', () => { + it('should make PUT request to update subflow name', async () => { + mockSuccessAPIRequest(successResult) + + const name = 'Updated SubFlow Name' + const result = await automationAPI.updateSubFlowName(appId, versionId, subFlowId, name) + + expect(result).toEqual(successResult) + expect(apiRequestCalls()).toEqual([ + { + path: `http://test-host:3000/api/app/${appId}/automation/version/${versionId}/subflow/${subFlowId}/name`, + body: JSON.stringify({ name }), + method: 'PUT', + encoding: 'utf8', + headers: { 'Content-Type': 'application/json' }, + timeout: 0, + withCredentials: false + } + ]) + }) + }) + describe('deleteSubFlow', () => { it('should make DELETE request to delete subflow', async () => { mockSuccessAPIRequest(successResult) From a2270d5d471eaa9a167d6334a528714addf36b21 Mon Sep 17 00:00:00 2001 From: "viktor.liablin" Date: Wed, 3 Sep 2025 17:54:27 +0300 Subject: [PATCH 3/7] FR-1800 - Update SubFlow name by basic PUT rout for subFlows --- src/automation.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/automation.js b/src/automation.js index 5275382..df26a8e 100644 --- a/src/automation.js +++ b/src/automation.js @@ -345,8 +345,8 @@ export default req => ({ return req.automation.put(routes.updateSubFlowByID(appId, versionId, subFlowVersionId), data) }, - updateSubFlowName(appId, versionId, subFlowVersionId, name) { - return req.automation.put(routes.updateSubFlowName(appId, versionId, subFlowVersionId), { name }) + updateSubFlowName(appId, versionId, subFlowId, name) { + return req.automation.put(routes.updateSubFlowName(appId, versionId, subFlowId), { name }) }, deleteSubFlow(appId, versionId, subFlowId) { From 3918d4ac9a1e54c7681f4ad682a792b87ef81aa0 Mon Sep 17 00:00:00 2001 From: Vladimir Upirov <4648606+Valodya@users.noreply.github.com> Date: Thu, 11 Sep 2025 11:32:28 +0300 Subject: [PATCH 4/7] - remove API to get allowed AI models for specific FR blocks --- src/automation.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/automation.js b/src/automation.js index df26a8e..97f628f 100644 --- a/src/automation.js +++ b/src/automation.js @@ -38,7 +38,6 @@ const routes = prepareRoutes({ debugExecutionContext: '/api/app/:appId/automation/flow/:flowId/version/:versionId/debug/test-monitor/execution-context', runElementInDebugMode: '/api/app/:appId/automation/flow/:flowId/version/:versionId/debug/run/element/:elementId', - allowedAIModels : '/api/app/:appId/automation/ai/assistants/allowed-models', registerAIAssistant: '/api/app/:appId/automation/ai/assistants/register', aiAssistants : '/api/app/:appId/automation/ai/assistants', aiAssistant : '/api/app/:appId/automation/ai/assistants/:id', @@ -249,10 +248,6 @@ export default req => ({ return req.automation.delete(routes.SLACalendar(appId, id)) }, - getAllowedAIModels(appId) { - return req.automation.get(routes.allowedAIModels(appId)) - }, - registerAIAssistant(appId, openAiAssistantId) { return req.automation.post(routes.registerAIAssistant(appId), { openAiAssistantId }) }, From d83a33555e6e21a8fd619eba5d97ef95d87dd6ca Mon Sep 17 00:00:00 2001 From: "viktor.liablin" Date: Wed, 17 Sep 2025 16:08:27 +0300 Subject: [PATCH 5/7] FR-1815 - Add the ability to stop the instance manually --- src/automation.js | 5 ++++ tests/specs/automation.test.js | 54 ++++++++++++++++++++-------------- 2 files changed, 37 insertions(+), 22 deletions(-) diff --git a/src/automation.js b/src/automation.js index 97f628f..659ba31 100644 --- a/src/automation.js +++ b/src/automation.js @@ -20,6 +20,7 @@ const routes = prepareRoutes({ flowInstances : '/api/app/:appId/automation/flow/:flowId/version/:versionId/analytics/instances/find', countInstances : '/api/app/:appId/automation/flow/:flowId/version/:versionId/analytics/instances/count', flowInstance : '/api/app/:appId/automation/flow/:flowId/version/:versionId/analytics/instances/:executionId', + stopInstanceExecution : '/api/app/:appId/automation/flow/:flowId/version/:versionId/instances/:executionId/stop', elementExecutionInfo: '/api/app/:appId/automation/flow/:flowId/version/:versionId/analytics/instances/:executionId/element/:elementId', flowSlA : '/api/app/:appId/automation/flow/:flowId/version/:versionId/sla/goals', @@ -155,6 +156,10 @@ export default req => ({ return req.automation.get(routes.flowInstance(appId, flowId, versionId, executionId)) }, + stopFlowInstanceExecution(appId, flowId, versionId, executionId) { + return req.automation.post(routes.stopInstanceExecution(appId, flowId, versionId, executionId)) + }, + cleanFlowVersionAnalytics(appId, flowId, versionId) { return req.automation.delete(routes.flowVersionAnalytics(appId, flowId, versionId)) }, diff --git a/tests/specs/automation.test.js b/tests/specs/automation.test.js index de04823..25c6bbb 100644 --- a/tests/specs/automation.test.js +++ b/tests/specs/automation.test.js @@ -430,6 +430,27 @@ describe('apiClient.automation', () => { }) }) + describe('stopFlowInstanceExecution', () => { + it('should make POST request to stop flow instance execution', async () => { + mockSuccessAPIRequest(successResult) + + const result = await automationAPI.stopFlowInstanceExecution(appId, flowId, versionId, executionId) + + expect(result).toEqual(successResult) + expect(apiRequestCalls()).toEqual([ + { + path: `http://test-host:3000/api/app/${appId}/automation/flow/${flowId}/version/${versionId}/instances/${executionId}/stop`, + body: undefined, + method: 'POST', + encoding: 'utf8', + headers: {}, + timeout: 0, + withCredentials: false + } + ]) + }) + }) + describe('cleanFlowVersionAnalytics', () => { it('should make DELETE request to clean flow version analytics', async () => { mockSuccessAPIRequest(successResult) @@ -868,27 +889,6 @@ describe('apiClient.automation', () => { }) describe('AI Assistant Methods', () => { - describe('getAllowedAIModels', () => { - it('should make GET request to get allowed AI models', async () => { - mockSuccessAPIRequest(successResult) - - const result = await automationAPI.getAllowedAIModels(appId) - - expect(result).toEqual(successResult) - expect(apiRequestCalls()).toEqual([ - { - path: `http://test-host:3000/api/app/${appId}/automation/ai/assistants/allowed-models`, - body: undefined, - method: 'GET', - encoding: 'utf8', - headers: {}, - timeout: 0, - withCredentials: false - } - ]) - }) - }) - describe('registerAIAssistant', () => { it('should make POST request to register AI assistant', async () => { mockSuccessAPIRequest(successResult) @@ -1602,6 +1602,16 @@ describe('apiClient.automation', () => { expect(error.status).toBe(500) expect(error.message).toBe('Internal server error') }) + + it('stopFlowInstanceExecution fails with not found error', async () => { + mockFailedAPIRequest('Flow instance not found', 404) + + const error = await automationAPI.stopFlowInstanceExecution(appId, flowId, versionId, executionId).catch(e => e) + + expect(error).toBeInstanceOf(Error) + expect(error.status).toBe(404) + expect(error.message).toBe('Flow instance not found') + }) }) describe('Debug Session Errors', () => { @@ -1654,4 +1664,4 @@ describe('apiClient.automation', () => { }) }) }) -}) \ No newline at end of file +}) From 6bcf22a467c0ffc818e08c8fa6457cec72d4ca00 Mon Sep 17 00:00:00 2001 From: karyna Date: Tue, 23 Sep 2025 15:36:40 +0300 Subject: [PATCH 6/7] FR-1928 - For Webhook use Strings instead of Number to specify events --- src/index.js | 2 ++ src/integrations.js | 5 ++--- src/webhooks.js | 29 +++++++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 src/webhooks.js diff --git a/src/index.js b/src/index.js index c0dcf65..f7d98d7 100644 --- a/src/index.js +++ b/src/index.js @@ -47,6 +47,7 @@ import consolePreview from './console-preview' import quickApps from './quick-apps' import frExtensions from './fr-extensions' import mcpServices from './mcp-services' +import webhooks from './webhooks' import { community } from './community' import { marketplace } from './marketplace' @@ -222,6 +223,7 @@ const createClient = (serverUrl, authKey, options) => { consolePreview : consolePreview(request), quickApps : quickApps(request), integrations : integrations(request), + webhooks : webhooks(request), pdf : pdf(request), frExtensions : frExtensions(request), mcpServices : mcpServices(request) diff --git a/src/integrations.js b/src/integrations.js index 8dc034e..4f48dbe 100644 --- a/src/integrations.js +++ b/src/integrations.js @@ -1,8 +1,8 @@ import { prepareRoutes } from './utils/routes' const routes = prepareRoutes({ - integrations : '/:appId/console/integrations', - integration : '/:appId/console/integrations/:name', + integrations: '/:appId/console/integrations', + integration : '/:appId/console/integrations/:name', }) export default req => ({ @@ -21,5 +21,4 @@ export default req => ({ deleteIntegration(appId, name) { return req.delete(routes.integration(appId, name)) }, - }) diff --git a/src/webhooks.js b/src/webhooks.js new file mode 100644 index 0000000..312ba6f --- /dev/null +++ b/src/webhooks.js @@ -0,0 +1,29 @@ +import { prepareRoutes } from './utils/routes' + +const routes = prepareRoutes({ + webhooks : '/:appId/console/webhook', + operations: '/:appId/console/webhook/operations', + webhook : '/:appId/console/webhook/:webhookId', +}) + +export default req => ({ + getWebhooks(appId) { + return req.get(routes.webhooks(appId)) + }, + + getWebhookOperations(appId) { + return req.get(routes.operations(appId)) + }, + + saveWebhook(appId, configData) { + return req.post(routes.webhooks(appId), configData) + }, + + updateWebhook(appId, webhookId, configData) { + return req.put(routes.webhook(appId, webhookId), configData) + }, + + deleteWebhook(appId, webhookId) { + return req.delete(routes.webhook(appId, webhookId)) + }, +}) From 57dfe4009d73c1e69f3f54212b5c32f59fd1b2d9 Mon Sep 17 00:00:00 2001 From: Vladimir Upirov <4648606+Valodya@users.noreply.github.com> Date: Wed, 24 Sep 2025 14:52:27 +0300 Subject: [PATCH 7/7] - add tests for Webhooks API --- tests/specs/webhooks.test.js | 426 +++++++++++++++++++++++++++++++++++ 1 file changed, 426 insertions(+) create mode 100644 tests/specs/webhooks.test.js diff --git a/tests/specs/webhooks.test.js b/tests/specs/webhooks.test.js new file mode 100644 index 0000000..7d93a4c --- /dev/null +++ b/tests/specs/webhooks.test.js @@ -0,0 +1,426 @@ +describe('apiClient.webhooks', () => { + let apiClient + let webhooksAPI + + const appId = 'test-app-id' + const webhookId = 'test-webhook-id' + const successResult = { foo: 'bar' } + + beforeAll(() => { + apiClient = createAPIClient('http://test-host:3000') + webhooksAPI = apiClient.webhooks + }) + + describe('getWebhooks()', () => { + it('makes a GET request to retrieve webhooks for the app', async () => { + mockSuccessAPIRequest(successResult) + + const result = await webhooksAPI.getWebhooks(appId) + + expect(result).toEqual(successResult) + + expect(apiRequestCalls()).toEqual([{ + path : `http://test-host:3000/${appId}/console/webhook`, + method : 'GET', + body : undefined, + encoding : 'utf8', + headers : {}, + timeout : 0, + withCredentials: false + }]) + }) + + it('throws an error on failed request', async () => { + const errorMessage = 'Failed to fetch webhooks' + mockFailedAPIRequest(errorMessage, 500) + + const error = await webhooksAPI.getWebhooks(appId).catch(e => e) + + expect(error).toBeInstanceOf(Error) + expect({ ...error }).toEqual({ + body : { message: errorMessage }, + message: errorMessage, + status : 500, + }) + + expect(apiRequestCalls()).toEqual([{ + path : `http://test-host:3000/${appId}/console/webhook`, + method : 'GET', + body : undefined, + encoding : 'utf8', + headers : {}, + timeout : 0, + withCredentials: false + }]) + }) + }) + + describe('getWebhookOperations()', () => { + it('makes a GET request to retrieve webhook operations for the app', async () => { + const operations = ['CREATE', 'UPDATE', 'DELETE', 'FIND'] + mockSuccessAPIRequest(operations) + + const result = await webhooksAPI.getWebhookOperations(appId) + + expect(result).toEqual(operations) + + expect(apiRequestCalls()).toEqual([{ + path : `http://test-host:3000/${appId}/console/webhook/operations`, + method : 'GET', + body : undefined, + encoding : 'utf8', + headers : {}, + timeout : 0, + withCredentials: false + }]) + }) + + it('throws an error on failed request', async () => { + const errorMessage = 'Failed to fetch webhook operations' + mockFailedAPIRequest(errorMessage, 404) + + const error = await webhooksAPI.getWebhookOperations(appId).catch(e => e) + + expect(error).toBeInstanceOf(Error) + expect({ ...error }).toEqual({ + body : { message: errorMessage }, + message: errorMessage, + status : 404, + }) + + expect(apiRequestCalls()).toEqual([{ + path : `http://test-host:3000/${appId}/console/webhook/operations`, + method : 'GET', + body : undefined, + encoding : 'utf8', + headers : {}, + timeout : 0, + withCredentials: false + }]) + }) + }) + + describe('saveWebhook()', () => { + it('makes a POST request to create a new webhook', async () => { + const configData = { + name : 'Test Webhook', + url : 'https://example.com/webhook', + operations: ['CREATE', 'UPDATE'], + table : 'Users', + enabled : true + } + + const createdWebhook = { + id : 'new-webhook-id', + ...configData, + createdAt : Date.now(), + updatedAt : Date.now() + } + + mockSuccessAPIRequest(createdWebhook, 201) + + const result = await webhooksAPI.saveWebhook(appId, configData) + + expect(result).toEqual(createdWebhook) + + expect(apiRequestCalls()).toEqual([{ + path : `http://test-host:3000/${appId}/console/webhook`, + method : 'POST', + body : JSON.stringify(configData), + encoding : 'utf8', + headers : { 'Content-Type': 'application/json' }, + timeout : 0, + withCredentials: false + }]) + }) + + it('makes a POST request with minimal configuration', async () => { + const minimalConfig = { + name: 'Minimal Webhook', + url : 'https://example.com/minimal' + } + + mockSuccessAPIRequest(successResult) + + const result = await webhooksAPI.saveWebhook(appId, minimalConfig) + + expect(result).toEqual(successResult) + + expect(apiRequestCalls()).toEqual([{ + path : `http://test-host:3000/${appId}/console/webhook`, + method : 'POST', + body : JSON.stringify(minimalConfig), + encoding : 'utf8', + headers : { 'Content-Type': 'application/json' }, + timeout : 0, + withCredentials: false + }]) + }) + + it('throws an error when validation fails', async () => { + const invalidConfig = { name: 'Invalid' } + const errorMessage = 'Webhook URL is required' + mockFailedAPIRequest(errorMessage, 400) + + const error = await webhooksAPI.saveWebhook(appId, invalidConfig).catch(e => e) + + expect(error).toBeInstanceOf(Error) + expect({ ...error }).toEqual({ + body : { message: errorMessage }, + message: errorMessage, + status : 400, + }) + + expect(apiRequestCalls()).toEqual([{ + path : `http://test-host:3000/${appId}/console/webhook`, + method : 'POST', + body : JSON.stringify(invalidConfig), + encoding : 'utf8', + headers : { 'Content-Type': 'application/json' }, + timeout : 0, + withCredentials: false + }]) + }) + + it('throws an error on server failure', async () => { + const configData = { name: 'Test', url: 'https://example.com' } + const errorMessage = 'Internal server error' + mockFailedAPIRequest(errorMessage, 500) + + const error = await webhooksAPI.saveWebhook(appId, configData).catch(e => e) + + expect(error).toBeInstanceOf(Error) + expect({ ...error }).toEqual({ + body : { message: errorMessage }, + message: errorMessage, + status : 500, + }) + + expect(apiRequestCalls()).toEqual([{ + path : `http://test-host:3000/${appId}/console/webhook`, + method : 'POST', + body : JSON.stringify(configData), + encoding : 'utf8', + headers : { 'Content-Type': 'application/json' }, + timeout : 0, + withCredentials: false + }]) + }) + }) + + describe('updateWebhook()', () => { + it('makes a PUT request to update an existing webhook', async () => { + const updateData = { + name : 'Updated Webhook', + url : 'https://example.com/updated', + operations: ['DELETE'], + enabled : false + } + + const updatedWebhook = { + id : webhookId, + ...updateData, + updatedAt : Date.now() + } + + mockSuccessAPIRequest(updatedWebhook) + + const result = await webhooksAPI.updateWebhook(appId, webhookId, updateData) + + expect(result).toEqual(updatedWebhook) + + expect(apiRequestCalls()).toEqual([{ + path : `http://test-host:3000/${appId}/console/webhook/${webhookId}`, + method : 'PUT', + body : JSON.stringify(updateData), + encoding : 'utf8', + headers : { 'Content-Type': 'application/json' }, + timeout : 0, + withCredentials: false + }]) + }) + + it('makes a PUT request to partially update a webhook', async () => { + const partialUpdate = { + enabled: true + } + + mockSuccessAPIRequest(successResult) + + const result = await webhooksAPI.updateWebhook(appId, webhookId, partialUpdate) + + expect(result).toEqual(successResult) + + expect(apiRequestCalls()).toEqual([{ + path : `http://test-host:3000/${appId}/console/webhook/${webhookId}`, + method : 'PUT', + body : JSON.stringify(partialUpdate), + encoding : 'utf8', + headers : { 'Content-Type': 'application/json' }, + timeout : 0, + withCredentials: false + }]) + }) + + it('throws an error when webhook not found', async () => { + const updateData = { name: 'Updated' } + const errorMessage = 'Webhook not found' + mockFailedAPIRequest(errorMessage, 404) + + const error = await webhooksAPI.updateWebhook(appId, webhookId, updateData).catch(e => e) + + expect(error).toBeInstanceOf(Error) + expect({ ...error }).toEqual({ + body : { message: errorMessage }, + message: errorMessage, + status : 404, + }) + + expect(apiRequestCalls()).toEqual([{ + path : `http://test-host:3000/${appId}/console/webhook/${webhookId}`, + method : 'PUT', + body : JSON.stringify(updateData), + encoding : 'utf8', + headers : { 'Content-Type': 'application/json' }, + timeout : 0, + withCredentials: false + }]) + }) + + it('throws an error on validation failure', async () => { + const invalidUpdate = { url: 'invalid-url' } + const errorMessage = 'Invalid webhook URL format' + mockFailedAPIRequest(errorMessage, 422) + + const error = await webhooksAPI.updateWebhook(appId, webhookId, invalidUpdate).catch(e => e) + + expect(error).toBeInstanceOf(Error) + expect({ ...error }).toEqual({ + body : { message: errorMessage }, + message: errorMessage, + status : 422, + }) + + expect(apiRequestCalls()).toEqual([{ + path : `http://test-host:3000/${appId}/console/webhook/${webhookId}`, + method : 'PUT', + body : JSON.stringify(invalidUpdate), + encoding : 'utf8', + headers : { 'Content-Type': 'application/json' }, + timeout : 0, + withCredentials: false + }]) + }) + }) + + describe('deleteWebhook()', () => { + it('makes a DELETE request to remove a webhook', async () => { + const deleteResult = { deleted: true, webhookId } + mockSuccessAPIRequest(deleteResult) + + const result = await webhooksAPI.deleteWebhook(appId, webhookId) + + expect(result).toEqual(deleteResult) + + expect(apiRequestCalls()).toEqual([{ + path : `http://test-host:3000/${appId}/console/webhook/${webhookId}`, + method : 'DELETE', + body : undefined, + encoding : 'utf8', + headers : {}, + timeout : 0, + withCredentials: false + }]) + }) + + it('makes a DELETE request with successful 204 response', async () => { + mockSuccessAPIRequest(undefined, 204) + + const result = await webhooksAPI.deleteWebhook(appId, webhookId) + + expect(result).toBeUndefined() + + expect(apiRequestCalls()).toEqual([{ + path : `http://test-host:3000/${appId}/console/webhook/${webhookId}`, + method : 'DELETE', + body : undefined, + encoding : 'utf8', + headers : {}, + timeout : 0, + withCredentials: false + }]) + }) + + it('throws an error when webhook not found', async () => { + const errorMessage = 'Webhook not found' + mockFailedAPIRequest(errorMessage, 404) + + const error = await webhooksAPI.deleteWebhook(appId, webhookId).catch(e => e) + + expect(error).toBeInstanceOf(Error) + expect({ ...error }).toEqual({ + body : { message: errorMessage }, + message: errorMessage, + status : 404, + }) + + expect(apiRequestCalls()).toEqual([{ + path : `http://test-host:3000/${appId}/console/webhook/${webhookId}`, + method : 'DELETE', + body : undefined, + encoding : 'utf8', + headers : {}, + timeout : 0, + withCredentials: false + }]) + }) + + it('throws an error when deletion is forbidden', async () => { + const errorMessage = 'Cannot delete system webhook' + mockFailedAPIRequest(errorMessage, 403) + + const error = await webhooksAPI.deleteWebhook(appId, webhookId).catch(e => e) + + expect(error).toBeInstanceOf(Error) + expect({ ...error }).toEqual({ + body : { message: errorMessage }, + message: errorMessage, + status : 403, + }) + + expect(apiRequestCalls()).toEqual([{ + path : `http://test-host:3000/${appId}/console/webhook/${webhookId}`, + method : 'DELETE', + body : undefined, + encoding : 'utf8', + headers : {}, + timeout : 0, + withCredentials: false + }]) + }) + + it('throws an error on server failure', async () => { + const errorMessage = 'Internal server error' + mockFailedAPIRequest(errorMessage, 500) + + const error = await webhooksAPI.deleteWebhook(appId, webhookId).catch(e => e) + + expect(error).toBeInstanceOf(Error) + expect({ ...error }).toEqual({ + body : { message: errorMessage }, + message: errorMessage, + status : 500, + }) + + expect(apiRequestCalls()).toEqual([{ + path : `http://test-host:3000/${appId}/console/webhook/${webhookId}`, + method : 'DELETE', + body : undefined, + encoding : 'utf8', + headers : {}, + timeout : 0, + withCredentials: false + }]) + }) + }) +}) \ No newline at end of file