import { RuleTester } from '@typescript-eslint/rule-tester'; import { WebhookLifecycleCompleteRule } from './webhook-lifecycle-complete.js'; const ruleTester = new RuleTester(); function createTriggerNode(options: { group?: string; hasWebhooks?: boolean; webhookMethods?: string | null; }): string { const { group = 'trigger', hasWebhooks = true, webhookMethods } = options; const webhooksProp = hasWebhooks ? "webhooks: [{ name: 'default', httpMethod: 'POST', responseMode: 'onReceived', path: 'webhook' }]," : ''; const methodsProp = webhookMethods === null ? '' : `\n\twebhookMethods = ${webhookMethods};`; return ` import type { INodeType, INodeTypeDescription, IHookFunctions } from 'n8n-workflow'; export class TestTrigger implements INodeType { description: INodeTypeDescription = { displayName: 'Test Trigger', name: 'testTrigger', group: ['${group}'], version: 1, description: 'A test trigger', defaults: { name: 'Test Trigger' }, inputs: [], outputs: ['main'], ${webhooksProp} properties: [], };${methodsProp} }`; } const completeWebhookMethods = `{ default: { async checkExists(this: IHookFunctions): Promise { return true; }, async create(this: IHookFunctions): Promise { return true; }, async delete(this: IHookFunctions): Promise { return true; }, }, }`; ruleTester.run('webhook-lifecycle-complete', WebhookLifecycleCompleteRule, { valid: [ { name: 'trigger node with all three webhook lifecycle methods', code: createTriggerNode({ webhookMethods: completeWebhookMethods }), }, { name: 'non-trigger node without webhookMethods', code: createTriggerNode({ group: 'transform', hasWebhooks: false, webhookMethods: null, }), }, { name: 'polling trigger without webhooks array and without webhookMethods', code: createTriggerNode({ hasWebhooks: false, webhookMethods: null }), }, { name: 'non-INodeType class with webhookMethods (should be ignored)', code: ` export class RegularClass { webhookMethods = { default: { async checkExists() { return true; }, }, }; }`, }, { name: 'trigger with multiple webhook groups, all complete', code: createTriggerNode({ webhookMethods: `{ default: { async checkExists() { return true; }, async create() { return true; }, async delete() { return true; }, }, setup: { async checkExists() { return true; }, async create() { return true; }, async delete() { return true; }, }, }`, }), }, { name: 'lifecycle methods written with computed literal keys', code: createTriggerNode({ webhookMethods: `{ default: { ['checkExists']: async () => true, ['create']: async () => true, [\`delete\`]: async () => true, }, }`, }), }, { name: 'lifecycle group and methods written with quoted keys', code: createTriggerNode({ webhookMethods: `{ 'default': { 'checkExists': async () => true, 'create': async () => true, 'delete': async () => true, }, }`, }), }, { name: 'webhook lifecycle defined via arrow functions', code: createTriggerNode({ webhookMethods: `{ default: { checkExists: async () => true, create: async () => true, delete: async () => true, }, }`, }), }, ], invalid: [ { name: 'trigger node missing webhookMethods entirely', code: createTriggerNode({ webhookMethods: null }), errors: [{ messageId: 'missingWebhookMethods' }], }, { name: 'trigger node with empty webhookMethods object (no lifecycle groups)', code: createTriggerNode({ webhookMethods: '{}' }), errors: [{ messageId: 'emptyWebhookMethods' }], }, { name: 'trigger node with empty webhookMethods group (all three missing)', code: createTriggerNode({ webhookMethods: `{ default: {}, }`, }), errors: [ { messageId: 'missingLifecycleMethod', data: { group: 'default', missing: '`checkExists`, `create`, `delete`', }, }, ], }, { name: 'trigger node missing only delete', code: createTriggerNode({ webhookMethods: `{ default: { async checkExists() { return true; }, async create() { return true; }, }, }`, }), errors: [ { messageId: 'missingLifecycleMethod', data: { group: 'default', missing: '`delete`' }, }, ], }, { name: 'trigger node missing checkExists and create', code: createTriggerNode({ webhookMethods: `{ default: { async delete() { return true; }, }, }`, }), errors: [ { messageId: 'missingLifecycleMethod', data: { group: 'default', missing: '`checkExists`, `create`' }, }, ], }, { name: 'non-trigger node with incomplete webhookMethods (still flagged)', code: createTriggerNode({ group: 'transform', hasWebhooks: false, webhookMethods: `{ default: { async create() { return true; }, }, }`, }), errors: [ { messageId: 'missingLifecycleMethod', data: { group: 'default', missing: '`checkExists`, `delete`' }, }, ], }, { name: 'webhook trigger (detected via webhooks array) missing webhookMethods', code: createTriggerNode({ group: 'transform', hasWebhooks: true, webhookMethods: null, }), errors: [{ messageId: 'missingWebhookMethods' }], }, { name: 'lifecycle method behind a key only known at runtime', code: createTriggerNode({ webhookMethods: `{ default: { async checkExists() { return true; }, async create() { return true; }, [methodName]: async () => true, }, }`, }), errors: [ { messageId: 'missingLifecycleMethod', data: { group: 'default', missing: '`delete`' }, }, ], }, { name: 'multiple webhook groups each missing methods', code: createTriggerNode({ webhookMethods: `{ default: { async checkExists() { return true; }, }, setup: { async create() { return true; }, async delete() { return true; }, }, }`, }), errors: [ { messageId: 'missingLifecycleMethod', data: { group: 'default', missing: '`create`, `delete`' }, }, { messageId: 'missingLifecycleMethod', data: { group: 'setup', missing: '`checkExists`' }, }, ], }, ], });