diff --git a/src/index.ts b/src/index.ts index 8c9adb2..cb50364 100644 --- a/src/index.ts +++ b/src/index.ts @@ -243,12 +243,20 @@ const run = async (): Promise => { // Execute the test command and capture the upload ID let uploadId: string | null = null; let testOutput = ''; + // The CLI's exit code is the primary verdict: it is computed by the process + // that actually watched the run. 0 = every test passed, 1 = CLI/infra error, + // 2 = the run itself failed (a failed test, or a cancelled one). This used to + // be discarded for anything but 1, leaving the separate `dcd status` call as + // the only gate — so one wrong branch in the API's status rollup turned + // cancelled runs green. + let cloudExitCode = 0; try { const { output, exitCode } = await executeCommand( `npx --yes "${dcdVersionString}" cloud ${paramsString} --quiet` ); testOutput = output; + cloudExitCode = exitCode; if (exitCode === 1) { throw new Error( @@ -308,11 +316,26 @@ const run = async (): Promise => { JSON.stringify(flowResults, null, 2) ); - if (result.status === 'PASSED') { + // Fail on either signal. The exit code is authoritative for a run that + // finished badly; the status call can only add failures the CLI could not + // see. A non-terminal status (PENDING/RUNNING) alongside a clean exit is a + // racy or degraded status call, not a failure — the CLI watched the run to + // completion, so warn rather than turn the build red. + if (cloudExitCode !== 0) { + setFailed( + `Test run failed (dcd exited ${cloudExitCode}, status ${result.status}). ` + + `Check flow results for details: ${result.consoleUrl}` + ); + } else if (result.status === 'PASSED') { console.info('Successfully completed test run.'); - } else if (result.status === 'FAILED') { + } else if (result.status === 'FAILED' || result.status === 'CANCELLED') { setFailed( - `Test run failed. Check flow results for details: ${result.consoleUrl}` + `Test run ${result.status}. Check flow results for details: ${result.consoleUrl}` + ); + } else { + warning( + `dcd reported success but the upload status is ${result.status}. ` + + `Treating the run as passed: ${result.consoleUrl}` ); } } else {