From 054288e48855b5aa6372be2a4bf1864c9401f6c1 Mon Sep 17 00:00:00 2001 From: Checo <104541981+sergioperezcheco@users.noreply.github.com> Date: Thu, 15 Oct 2026 12:53:34 +0000 Subject: [PATCH] fix(GitlabTrigger): use NodeApiError.httpCode and safe description access in webhook checkExists (#35486) Signed-off-by: sergioperezcheco --- .../nodes/Gitlab/GitlabTrigger.node.ts | 4 +- .../Gitlab/__tests__/GitlabTrigger.test.ts | 83 +++++++++++++++++++ 2 files changed, 85 insertions(+), 2 deletions(-) diff --git a/packages/nodes-base/nodes/Gitlab/GitlabTrigger.node.ts b/packages/nodes-base/nodes/Gitlab/GitlabTrigger.node.ts index 313ed90c835..00a16dbefae 100644 --- a/packages/nodes-base/nodes/Gitlab/GitlabTrigger.node.ts +++ b/packages/nodes-base/nodes/Gitlab/GitlabTrigger.node.ts @@ -196,7 +196,7 @@ export class GitlabTrigger implements INodeType { try { await gitlabApiRequest.call(this, 'GET', endpoint, {}); } catch (error) { - if (error.cause.httpCode === '404' || error.description.includes('404')) { + if (error.httpCode === '404' || error.description?.includes('404')) { // Webhook does not exist delete webhookData.webhookId; delete webhookData.webhookEvents; @@ -205,7 +205,7 @@ export class GitlabTrigger implements INodeType { return false; } - // Some error occured + // Some error occurred throw error; } diff --git a/packages/nodes-base/nodes/Gitlab/__tests__/GitlabTrigger.test.ts b/packages/nodes-base/nodes/Gitlab/__tests__/GitlabTrigger.test.ts index bd860f93230..2350eb1fd6d 100644 --- a/packages/nodes-base/nodes/Gitlab/__tests__/GitlabTrigger.test.ts +++ b/packages/nodes-base/nodes/Gitlab/__tests__/GitlabTrigger.test.ts @@ -7,7 +7,13 @@ vi.mock('../GitlabTriggerHelpers', () => ({ verifySignature: vi.fn(), })); +vi.mock('../GenericFunctions', () => ({ + gitlabApiRequest: vi.fn(), +})); + +import { gitlabApiRequest } from '../GenericFunctions'; import { verifySignature } from '../GitlabTriggerHelpers'; +import { NodeApiError } from 'n8n-workflow'; import type { Mock } from 'vitest'; describe('GitlabTrigger', () => { @@ -85,6 +91,83 @@ describe('GitlabTrigger', () => { }); }); + describe('webhookMethods.default.checkExists', () => { + let mockHookFunctions: Partial; + + const staticData = (webhookId: unknown) => ({ + getWorkflowStaticData: vi.fn().mockReturnValue({ webhookId }), + getNodeParameter: vi.fn((name: string) => (name === 'owner' ? 'some-owner' : 'some-repo')), + }); + + beforeEach(() => { + (gitlabApiRequest as Mock).mockReset(); + }); + + it('should return false when no webhook id is stored', async () => { + mockHookFunctions = staticData(undefined) as unknown as Partial; + + const result = await trigger.webhookMethods!.default.checkExists.call( + mockHookFunctions as never, + ); + + expect(result).toBe(false); + expect(gitlabApiRequest).not.toHaveBeenCalled(); + }); + + it('should return true when the API confirms the webhook', async () => { + mockHookFunctions = staticData(42) as unknown as Partial; + (gitlabApiRequest as Mock).mockResolvedValue({ id: 42 }); + + const result = await trigger.webhookMethods!.default.checkExists.call( + mockHookFunctions as never, + ); + + expect(result).toBe(true); + expect(gitlabApiRequest).toHaveBeenCalledWith( + 'GET', + '/projects/some-owner%2Fsome-repo/hooks/42', + {}, + ); + }); + + it('should return false and clear static data when the webhook is gone (404 via httpCode)', async () => { + mockHookFunctions = staticData(42) as unknown as Partial; + const error = new NodeApiError({} as never, {}, { httpCode: '404' }); + (gitlabApiRequest as Mock).mockRejectedValue(error); + + const result = await trigger.webhookMethods!.default.checkExists.call( + mockHookFunctions as never, + ); + + expect(result).toBe(false); + const data = mockHookFunctions.getWorkflowStaticData!('node') as IDataObject; + expect(data.webhookId).toBeUndefined(); + expect(data.webhookEvents).toBeUndefined(); + expect(data.webhookSecret).toBeUndefined(); + }); + + it('should rethrow the original error when it has no http code and no description', async () => { + mockHookFunctions = staticData(42) as unknown as Partial; + const error = new NodeApiError({} as never, {}, { message: 'socket hang up' }); + error.description = undefined; + (gitlabApiRequest as Mock).mockRejectedValue(error); + + await expect( + trigger.webhookMethods!.default.checkExists.call(mockHookFunctions as never), + ).rejects.toBe(error); + }); + + it('should rethrow non-404 errors', async () => { + mockHookFunctions = staticData(42) as unknown as Partial; + const error = new NodeApiError({} as never, {}, { httpCode: '500' }); + (gitlabApiRequest as Mock).mockRejectedValue(error); + + await expect( + trigger.webhookMethods!.default.checkExists.call(mockHookFunctions as never), + ).rejects.toThrow(error); + }); + }); + describe('description', () => { it('should have correct node metadata', () => { expect(trigger.description.displayName).toBe('GitLab Trigger');