diff --git a/src/cli/cron-cli/shared.test.ts b/src/cli/cron-cli/shared.test.ts index 4693ccb44de8..228542b2eb8a 100644 --- a/src/cli/cron-cli/shared.test.ts +++ b/src/cli/cron-cli/shared.test.ts @@ -394,6 +394,70 @@ describe("printCronList", () => { expect(singleLine).not.toContain("(1x)"); }); + it.each([ + { label: "required", bestEffort: false }, + { label: "best-effort", bestEffort: true }, + { label: "default", bestEffort: undefined }, + ])( + "makes $label undelivered automation output visible without changing JSON status", + ({ bestEffort }) => { + const job = createBaseJob({ + id: "undelivered-job", + delivery: { + mode: "announce", + ...(bestEffort === undefined ? {} : { bestEffort }), + }, + state: { + lastRunStatus: "ok", + lastDeliveryStatus: "not-delivered", + lastDeliveryError: "primary route rejected", + }, + }); + + const list = createRuntimeLogCapture(); + printCronList([job], list.runtime); + expectLogsToInclude(list.logs, "ok (not delivered)"); + + const show = createRuntimeLogCapture(); + printCronShow(job, show.runtime); + expectLogsToInclude(show.logs, "status: ok (not delivered)"); + expectLogsToInclude(show.logs, "last delivery error: primary route rejected"); + + expect(enrichCronJsonWithStatus(job)).toMatchObject({ + status: "ok", + state: { lastRunStatus: "ok", lastDeliveryStatus: "not-delivered" }, + }); + expect(enrichCronJsonWithStatus({ jobs: [job] })).toMatchObject({ + jobs: [{ status: "ok" }], + }); + }, + ); + + it.each([ + { label: "disabled", enabled: false, runStatus: "ok" as const, expectedStatus: "disabled" }, + { label: "running", enabled: true, runStatus: "ok" as const, expectedStatus: "running" }, + { label: "failed", enabled: true, runStatus: "error" as const, expectedStatus: "error" }, + ])( + "does not let prior non-delivery override a $label automation", + ({ enabled, runStatus, expectedStatus }) => { + const job = createBaseJob({ + enabled, + state: { + lastRunStatus: runStatus, + lastDeliveryStatus: "not-delivered", + ...(expectedStatus === "running" ? { runningAtMs: Date.now() } : {}), + }, + }); + + const show = createRuntimeLogCapture(); + printCronShow(job, show.runtime); + + expectLogsToInclude(show.logs, `status: ${expectedStatus}`); + expect(show.logs.join("\n")).not.toContain("ok (not delivered)"); + expect(enrichCronJsonWithStatus(job)).toMatchObject({ status: expectedStatus }); + }, + ); + it("shows why the scheduler auto-disabled a job without changing JSON status", () => { const runFailures = createBaseJob({ id: "auto-disabled-runs", diff --git a/src/cli/cron-cli/shared.ts b/src/cli/cron-cli/shared.ts index 16543b8d98cb..8b1480d999c1 100644 --- a/src/cli/cron-cli/shared.ts +++ b/src/cli/cron-cli/shared.ts @@ -198,12 +198,16 @@ function decorateStatusWithFailures(status: string, consecutiveErrors: number | function formatCronStatusForDisplay(job: CronJob): string { const state = job.state ?? {}; - if (computeStatus(job) === "disabled" && state.autoDisabled) { + const status = computeStatus(job); + if (status === "disabled" && state.autoDisabled) { return state.autoDisabled.reason === "schedule-errors" ? "disabled (schedule)" : `disabled (${state.autoDisabled.consecutiveErrors}x)`; } - return decorateStatusWithFailures(computeStatus(job), state.consecutiveErrors); + if (status === "ok" && state.lastDeliveryStatus === "not-delivered") { + return "ok (not delivered)"; + } + return decorateStatusWithFailures(status, state.consecutiveErrors); } export function handleCronCliError(err: unknown) { @@ -513,13 +517,13 @@ export function printCronList( ); const coloredStatus = (() => { - if (statusRaw === "ok") { + if (statusRaw === "ok" && state.lastDeliveryStatus !== "not-delivered") { return colorize(rich, theme.success, statusLabel); } if (statusRaw === "error") { return colorize(rich, theme.error, statusLabel); } - if (statusRaw === "running") { + if (statusRaw === "running" || statusRaw === "ok") { return colorize(rich, theme.warn, statusLabel); } if (statusRaw === "skipped") {