fix(core): Handle dynamic webhook edge cases (#16554)

This commit is contained in:
Iván Ovejero
2025-06-20 16:26:08 +02:00
committed by GitHub
parent 2d638023be
commit 1573ae6352
2 changed files with 125 additions and 1 deletions

View File

@@ -386,4 +386,116 @@ describe('WebhookService', () => {
expect(webhookRepository.findBy).toHaveBeenCalledTimes(2);
});
});
describe('isDynamicPath', () => {
test.each(['a', 'a/b'])('should treat static path (%s) as static', (path) => {
const workflow = new Workflow({
id: 'test-workflow',
nodes: [],
connections: {},
active: true,
nodeTypes,
});
const node = mock<INode>({
name: 'Webhook',
type: 'n8n-nodes-base.webhook',
});
const nodeType = mock<INodeType>({
description: {
webhooks: [
{
name: 'default',
httpMethod: 'GET',
path,
isFullPath: false,
restartWebhook: false,
},
],
},
});
nodeTypes.getByNameAndVersion.mockReturnValue(nodeType);
const webhooks = webhookService.getNodeWebhooks(workflow, node, additionalData);
expect(webhooks).toHaveLength(1);
expect(webhooks[0].webhookId).toBeUndefined();
});
test.each([':', '/:'])('should treat literal colon path (%s) as static', (path) => {
const workflow = new Workflow({
id: 'test-workflow',
nodes: [],
connections: {},
active: true,
nodeTypes,
});
const nodeWithWebhookId = mock<INode>({
name: 'Webhook',
type: 'n8n-nodes-base.webhook',
});
const nodeType = mock<INodeType>({
description: {
webhooks: [
{
name: 'default',
httpMethod: 'GET',
path,
isFullPath: false,
restartWebhook: false,
},
],
},
});
nodeTypes.getByNameAndVersion.mockReturnValue(nodeType);
const webhooks = webhookService.getNodeWebhooks(workflow, nodeWithWebhookId, additionalData);
expect(webhooks).toHaveLength(1);
expect(webhooks[0].webhookId).toBeUndefined();
});
test('should treat dynamic path (user/:id) as dynamic', () => {
const workflow = new Workflow({
id: 'test-workflow',
nodes: [],
connections: {},
active: true,
nodeTypes,
});
const nodeWithWebhookId = mock<INode>({
name: 'Webhook',
type: 'n8n-nodes-base.webhook',
disabled: false,
webhookId: 'test-webhook-id',
});
const nodeType = mock<INodeType>({
description: {
webhooks: [
{
name: 'default',
httpMethod: 'GET',
path: 'user/:id',
isFullPath: false,
restartWebhook: false,
},
],
},
});
nodeTypes.getByNameAndVersion.mockReturnValue(nodeType);
const webhooks = webhookService.getNodeWebhooks(workflow, nodeWithWebhookId, additionalData);
expect(webhooks).toHaveLength(1);
expect(webhooks[0].webhookId).toBe('test-webhook-id');
});
});
});

View File

@@ -139,6 +139,17 @@ export class WebhookService {
.then((rows) => rows.map((r) => r.method));
}
private isDynamicPath(rawPath: string) {
const firstSlashIndex = rawPath.indexOf('/');
const path = firstSlashIndex !== -1 ? rawPath.substring(firstSlashIndex + 1) : rawPath;
// if dynamic, first segment is webhook ID so disregard it
if (path === '' || path === ':' || path === '/:') return false;
return path.startsWith(':') || path.includes('/:');
}
/**
* Returns all the webhooks which should be created for the give node
*/
@@ -232,7 +243,8 @@ export class WebhookService {
}
let webhookId: string | undefined;
if ((path.startsWith(':') || path.includes('/:')) && node.webhookId) {
if (this.isDynamicPath(path) && node.webhookId) {
webhookId = node.webhookId;
}