fix(core): "Respond to Webhook" should work with workflows with waiting nodes (#12806)

This commit is contained in:
कारतोफ्फेलस्क्रिप्ट™ 2025-02-03 12:24:11 +01:00 committed by GitHub
parent 18b6867785
commit e8635f2574
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 43 additions and 9 deletions

View file

@ -41,7 +41,7 @@ describe('ActiveExecutions', () => {
}); });
test('Should initialize activeExecutions with empty list', () => { test('Should initialize activeExecutions with empty list', () => {
expect(activeExecutions.getActiveExecutions().length).toBe(0); expect(activeExecutions.getActiveExecutions()).toHaveLength(0);
}); });
test('Should add execution to active execution list', async () => { test('Should add execution to active execution list', async () => {
@ -49,7 +49,7 @@ describe('ActiveExecutions', () => {
const executionId = await activeExecutions.add(newExecution); const executionId = await activeExecutions.add(newExecution);
expect(executionId).toBe(FAKE_EXECUTION_ID); expect(executionId).toBe(FAKE_EXECUTION_ID);
expect(activeExecutions.getActiveExecutions().length).toBe(1); expect(activeExecutions.getActiveExecutions()).toHaveLength(1);
expect(createNewExecution).toHaveBeenCalledTimes(1); expect(createNewExecution).toHaveBeenCalledTimes(1);
expect(updateExistingExecution).toHaveBeenCalledTimes(0); expect(updateExistingExecution).toHaveBeenCalledTimes(0);
}); });
@ -59,7 +59,7 @@ describe('ActiveExecutions', () => {
const executionId = await activeExecutions.add(newExecution, FAKE_SECOND_EXECUTION_ID); const executionId = await activeExecutions.add(newExecution, FAKE_SECOND_EXECUTION_ID);
expect(executionId).toBe(FAKE_SECOND_EXECUTION_ID); expect(executionId).toBe(FAKE_SECOND_EXECUTION_ID);
expect(activeExecutions.getActiveExecutions().length).toBe(1); expect(activeExecutions.getActiveExecutions()).toHaveLength(1);
expect(createNewExecution).toHaveBeenCalledTimes(0); expect(createNewExecution).toHaveBeenCalledTimes(0);
expect(updateExistingExecution).toHaveBeenCalledTimes(1); expect(updateExistingExecution).toHaveBeenCalledTimes(1);
}); });
@ -93,6 +93,37 @@ describe('ActiveExecutions', () => {
await expect(deferredPromise.promise).resolves.toEqual(fakeResponse); await expect(deferredPromise.promise).resolves.toEqual(fakeResponse);
}); });
test('Should copy over startedAt and responsePromise when resuming a waiting execution', async () => {
const newExecution = mockExecutionData();
const executionId = await activeExecutions.add(newExecution);
activeExecutions.setStatus(executionId, 'waiting');
activeExecutions.attachResponsePromise(executionId, mockDeferredPromise());
const waitingExecution = activeExecutions.getExecution(executionId);
expect(waitingExecution.responsePromise).toBeDefined();
// Resume the execution
await activeExecutions.add(newExecution, executionId);
const resumedExecution = activeExecutions.getExecution(executionId);
expect(resumedExecution.startedAt).toBe(waitingExecution.startedAt);
expect(resumedExecution.responsePromise).toBe(waitingExecution.responsePromise);
});
test('Should not remove a waiting execution', async () => {
const newExecution = mockExecutionData();
const executionId = await activeExecutions.add(newExecution);
activeExecutions.setStatus(executionId, 'waiting');
activeExecutions.finalizeExecution(executionId);
// Wait until the next tick to ensure that the post-execution promise has settled
await new Promise(setImmediate);
// Execution should still be in activeExecutions
expect(activeExecutions.getActiveExecutions()).toHaveLength(1);
expect(activeExecutions.getStatus(executionId)).toBe('waiting');
});
test('Should remove an existing execution', async () => { test('Should remove an existing execution', async () => {
// ARRANGE // ARRANGE
const newExecution = mockExecutionData(); const newExecution = mockExecutionData();
@ -105,11 +136,10 @@ describe('ActiveExecutions', () => {
await new Promise(setImmediate); await new Promise(setImmediate);
// ASSERT // ASSERT
expect(activeExecutions.getActiveExecutions().length).toBe(0); expect(activeExecutions.getActiveExecutions()).toHaveLength(0);
}); });
test('Should not try to resolve a post-execute promise for an inactive execution', async () => { test('Should not try to resolve a post-execute promise for an inactive execution', async () => {
// @ts-expect-error Private method
const getExecutionSpy = jest.spyOn(activeExecutions, 'getExecution'); const getExecutionSpy = jest.spyOn(activeExecutions, 'getExecution');
activeExecutions.finalizeExecution('inactive-execution-id', mockFullRunData()); activeExecutions.finalizeExecution('inactive-execution-id', mockFullRunData());

View file

@ -94,13 +94,15 @@ export class ActiveExecutions {
await this.executionRepository.updateExistingExecution(executionId, execution); await this.executionRepository.updateExistingExecution(executionId, execution);
} }
const resumingExecution = this.activeExecutions[executionId];
const postExecutePromise = createDeferredPromise<IRun | undefined>(); const postExecutePromise = createDeferredPromise<IRun | undefined>();
this.activeExecutions[executionId] = { this.activeExecutions[executionId] = {
executionData, executionData,
startedAt: new Date(), startedAt: resumingExecution?.startedAt ?? new Date(),
postExecutePromise, postExecutePromise,
status: executionStatus, status: executionStatus,
responsePromise: resumingExecution?.responsePromise,
}; };
// Automatically remove execution once the postExecutePromise settles // Automatically remove execution once the postExecutePromise settles
@ -111,8 +113,10 @@ export class ActiveExecutions {
}) })
.finally(() => { .finally(() => {
this.concurrencyControl.release({ mode: executionData.executionMode }); this.concurrencyControl.release({ mode: executionData.executionMode });
delete this.activeExecutions[executionId]; if (this.activeExecutions[executionId]?.status !== 'waiting') {
this.logger.debug('Execution removed', { executionId }); delete this.activeExecutions[executionId];
this.logger.debug('Execution removed', { executionId });
}
}); });
this.logger.debug('Execution added', { executionId }); this.logger.debug('Execution added', { executionId });
@ -227,7 +231,7 @@ export class ActiveExecutions {
} }
} }
private getExecution(executionId: string): IExecutingWorkflowData { getExecution(executionId: string): IExecutingWorkflowData {
const execution = this.activeExecutions[executionId]; const execution = this.activeExecutions[executionId];
if (!execution) { if (!execution) {
throw new ExecutionNotFoundError(executionId); throw new ExecutionNotFoundError(executionId);