feat(engine): Answer lastNode and responseNode webhooks with a remote engine v2 data plane (#39636)

This commit is contained in:
Tomi Turtiainen 2026-09-28 10:44:31 +00:00 committed by GitHub
parent 3584d15ddd
commit ce3d50b8f9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 7 additions and 24 deletions

View File

@ -1,7 +1,6 @@
import type { StepSlots } from '@n8n/engine';
import type { EngineConfig } from '@n8n/config';
import type { INode, WebhookResponseMode } from 'n8n-workflow';
import { UserError, WorkflowOperationError } from 'n8n-workflow';
import { WorkflowOperationError } from 'n8n-workflow';
import { mock } from 'vitest-mock-extended';
import { EngineDataPlaneProxyService } from '@/services/engine-data-plane-proxy.service';
@ -13,18 +12,16 @@ const engineV2Webhooks = new EngineV2Webhooks(
mock<EngineV2Dispatcher>(),
mock<EngineV2PayloadGuard>(),
mock<EngineDataPlaneProxyService>(),
mock<EngineConfig>(),
);
describe('EngineV2Webhooks.assertSupported', () => {
const assertSupported = (mode: EngineConfig['mode'], responseMode: WebhookResponseMode) => {
const assertSupported = (responseMode: WebhookResponseMode) => {
const proxy = mock<EngineDataPlaneProxyService>();
proxy.isAvailable.mockReturnValue(true);
const webhooks = new EngineV2Webhooks(
mock<EngineV2Dispatcher>(),
mock<EngineV2PayloadGuard>(),
proxy,
mock<EngineConfig>({ mode }),
);
webhooks.assertSupported({
@ -38,21 +35,14 @@ describe('EngineV2Webhooks.assertSupported', () => {
});
};
it.each(['lastNode', 'responseNode'] as const)(
'rejects %s responses with a remote data plane',
// The engine mode no longer reaches this check, so these modes pass with a
// remote data plane and in-process alike.
it.each(['onReceived', 'lastNode', 'responseNode'] as const)(
'allows %s responses in every engine mode',
(responseMode) => {
expect(() => assertSupported('remote', responseMode)).toThrow(UserError);
expect(() => assertSupported('remote', responseMode)).toThrow('remote data plane');
expect(() => assertSupported(responseMode)).not.toThrow();
},
);
it('allows onReceived with a remote data plane', () => {
expect(() => assertSupported('remote', 'onReceived')).not.toThrow();
});
it.each(['lastNode', 'responseNode'] as const)('allows %s in-process', (responseMode) => {
expect(() => assertSupported('in-process', responseMode)).not.toThrow();
});
});
describe('EngineV2Webhooks.toRun', () => {

View File

@ -1,4 +1,3 @@
import { EngineConfig } from '@n8n/config';
import { Service } from '@n8n/di';
import type {
INode,
@ -73,7 +72,6 @@ export class EngineV2Webhooks {
private readonly dispatcher: EngineV2Dispatcher,
private readonly payloadGuard: EngineV2PayloadGuard,
private readonly proxy: EngineDataPlaneProxyService,
private readonly engineConfig: EngineConfig,
) {}
/** Whether this webhook run starts on the engine v2 data plane. */
@ -133,11 +131,6 @@ export class EngineV2Webhooks {
`Engine v2 does not support the '${responseMode}' response mode yet. Respond immediately instead.`,
);
}
if (this.engineConfig.mode === 'remote' && responseMode !== 'onReceived') {
throw new UserError(
`Engine v2 does not support the '${responseMode}' response mode with a remote data plane yet. Respond immediately instead.`,
);
}
}
/** Converts the data plane's answer to the shape the v1 response path reads. */