Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/child-workflow-failure-stops-parent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@redocly/respect-core': patch
'@redocly/cli': patch
---

Fixed an issue in `respect` where the execution of parent workflow's steps didn't halt after a step that referenced another workflow had failed.
Original file line number Diff line number Diff line change
Expand Up @@ -2613,6 +2613,11 @@ describe('runStep', () => {
} as unknown as TestContext;

vi.mocked(resolveWorkflowContext).mockResolvedValueOnce(localCTX);
vi.mocked(runWorkflow).mockResolvedValueOnce({
type: 'workflow',
workflowId: 'reusable-workflow',
executedSteps: [],
} as unknown as WorkflowExecutionResult);

await runStep({
step,
Expand All @@ -2625,6 +2630,72 @@ describe('runStep', () => {
expect(localCTX.$steps['get-bird'].outputs).toEqual({ stepOutput: 'Hello, world!' });
});

it('should end parent workflow when child workflow step has failed steps', async () => {
const step: Step = {
stepId: 'call-child-workflow',
workflowId: 'child-workflow',
checks: [],
response: {} as any,
};
const localCTX = {
executedSteps: [],
$steps: {},
workflows: [
{
workflowId: 'parent-workflow',
steps: [step],
},
{
workflowId: 'child-workflow',
steps: [
{
stepId: 'child-step',
'x-operation': {
url: 'http://localhost:3000/delete-mock',
method: 'delete',
},
successCriteria: [{ condition: '$statusCode == 204' }],
checks: [],
},
],
},
],
options: {
filePath: 'runStepTest.yml',
logger: logger,
},
severity: DEFAULT_SEVERITY_CONFIGURATION,
} as unknown as TestContext;

vi.mocked(resolveWorkflowContext).mockResolvedValueOnce(localCTX);
vi.mocked(runWorkflow).mockResolvedValueOnce({
type: 'workflow',
workflowId: 'child-workflow',
executedSteps: [
{
stepId: 'child-step',
checks: [
{
name: CHECKS.SUCCESS_CRITERIA_CHECK,
message: 'Checking simple criteria: {"condition":"$statusCode == 204"}',
passed: false,
severity: 'error',
},
],
},
],
} as unknown as WorkflowExecutionResult);

const result = await runStep({
step,
ctx: localCTX,
workflowId: 'parent-workflow',
executedStepsCount: { value: 0 },
});

expect(result).toEqual({ shouldEnd: true });
});

it('should run step with workflowId from external workflowSpec', async () => {
const step: Step = {
stepId: 'get-bird',
Expand Down Expand Up @@ -3016,6 +3087,12 @@ describe('runStep', () => {
$outputs: {},
} as unknown as TestContext;

vi.mocked(runWorkflow).mockResolvedValueOnce({
type: 'workflow',
workflowId: 'reusable-external-workflow',
executedSteps: [],
} as unknown as WorkflowExecutionResult);

await runStep({
step,
ctx: localCTX,
Expand Down Expand Up @@ -3462,6 +3539,11 @@ describe('runStep', () => {
vi.mocked(resolveWorkflowContext).mockImplementation((): any => {
return { ...localCTX };
});
vi.mocked(runWorkflow).mockResolvedValueOnce({
type: 'workflow',
workflowId: 'reusable-external-workflow',
executedSteps: [],
} as unknown as WorkflowExecutionResult);

await runStep({
step,
Expand Down
20 changes: 20 additions & 0 deletions packages/respect-core/src/modules/flow-runner/run-step.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
printActionsSeparator,
printUnknownStep,
} from '../logger-output/helpers.js';
import { calculateTotals } from '../logger-output/index.js';
import { evaluateRuntimeExpressionPayload } from '../runtime-expressions/index.js';
import { Timer } from '../timeout-timer/timer.js';
import { callAPIAndAnalyzeResults } from './call-api-and-analyze-results.js';
Expand Down Expand Up @@ -166,6 +167,25 @@ export async function runStep({
}
}

const childWorkflowFailed = calculateTotals([stepWorkflowResult]).steps.failed > 0;

if (childWorkflowFailed) {
const result = await runActions(failureActionsToRun, 'failure', executedStepsCount);
if (result?.retriesLeft && result.retriesLeft > 0) {
return result.stepResult;
}
if (result?.shouldEnd) {
return { shouldEnd: true };
}
}

if (successActionsToRun.length && !childWorkflowFailed) {
const result = await runActions(successActionsToRun, 'success', executedStepsCount);
if (result?.shouldEnd) {
return { shouldEnd: true };
}
}

return { shouldEnd: false };
}
ctx.executedSteps.push(step);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ exports[`should quit an infinite loop on REDOCLY_CLI_RESPECT_MAX_STEPS 1`] = `



Running failure action continue for the step with-nested

✗ GET /ping - step ping

    Request URL: https://bad-api-url.com/api/ping
Expand Down Expand Up @@ -96,6 +98,7 @@ exports[`should quit an infinite loop on REDOCLY_CLI_RESPECT_MAX_STEPS 1`] = `





  Failed tests info:

Expand Down
4 changes: 4 additions & 0 deletions tests/e2e/respect/max-steps/arazzo.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ workflows:
- stepId: with-nested
description: With nested workflow
workflowId: nested-workflow
onFailure:
- name: continue
type: goto
stepId: ping

- stepId: ping
description: Ping the API
Expand Down
Loading