fix(cron): preserve enabled-with-defaults failure alert through store roundtrip (fixes #96589) (AI-assisted) (#96615)

Summary:
- The PR preserves `failure_alert_disabled === 0` as the enabled-with-defaults failure-alert state and adds focused codec roundtrip tests.
- PR surface: Source +2, Tests +54. Total +56 across 2 files.
- Reproducibility: yes. At source level, current main encodes `failureAlert: {}` with `failure_alert_disabled = 0`, then decodes it as `undefined` when all explicit alert option columns are null.

Automerge notes:
- No ClawSweeper repair was needed after automerge opt-in.

Validation:
- ClawSweeper review passed for head bd9b2a1798.
- Required merge gates passed before the squash merge.

Prepared head SHA: bd9b2a1798
Review: https://github.com/openclaw/openclaw/pull/96615#issuecomment-4794949533

Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: liuhao1024 <11816344+liuhao1024@users.noreply.github.com>
Approved-by: takhoffman
This commit is contained in:
clawsweeper[bot]
2026-06-25 01:17:31 +00:00
committed by GitHub
parent 8a5cb85c31
commit a21144d8a6
2 changed files with 57 additions and 1 deletions
@@ -0,0 +1,54 @@
// Unit tests for failure-alert SQLite column codec roundtrip.
import { describe, expect, it } from "vitest";
import { bindFailureAlertColumns, failureAlertFromRow } from "./failure-alert-codec.js";
import type { CronJobRow } from "./schema.js";
function roundtrip(
input: Parameters<typeof bindFailureAlertColumns>[0],
): ReturnType<typeof failureAlertFromRow> {
const columns = bindFailureAlertColumns(input);
return failureAlertFromRow(columns as CronJobRow);
}
describe("failureAlertFromRow", () => {
it("round-trips disabled config (false)", () => {
expect(roundtrip(false)).toBe(false);
});
it("round-trips undefined (no alert config) as undefined", () => {
expect(roundtrip(undefined)).toBeUndefined();
});
it("round-trips enabled-with-defaults ({}) as {}", () => {
const result = roundtrip({});
expect(result).toEqual({});
});
it("round-trips populated config with all fields", () => {
const config = {
after: 3,
cooldownMs: 120_000,
channel: "telegram" as const,
to: "@user",
mode: "announce" as const,
accountId: "acc-1",
includeSkipped: true,
};
expect(roundtrip(config)).toEqual(config);
});
it("round-trips partial config (only after)", () => {
expect(roundtrip({ after: 5 })).toEqual({ after: 5 });
});
it("enabled-with-defaults does not collapse to undefined on read", () => {
const columns = bindFailureAlertColumns({});
const row = columns as CronJobRow;
expect(row.failure_alert_disabled).toBe(0);
expect(row.failure_alert_after).toBeNull();
const decoded = failureAlertFromRow(row);
expect(decoded).toEqual({});
expect(decoded).not.toBeUndefined();
expect(decoded).toBeTruthy();
});
});
+3 -1
View File
@@ -46,6 +46,7 @@ export function failureAlertFromRow(row: CronJobRow): CronFailureAlert | false |
if (row.failure_alert_disabled === 1) {
return false;
}
const failureAlertExplicitlyEnabled = row.failure_alert_disabled === 0;
if (
row.failure_alert_after == null &&
!row.failure_alert_channel &&
@@ -53,7 +54,8 @@ export function failureAlertFromRow(row: CronJobRow): CronFailureAlert | false |
row.failure_alert_cooldown_ms == null &&
row.failure_alert_include_skipped == null &&
!row.failure_alert_mode &&
!row.failure_alert_account_id
!row.failure_alert_account_id &&
!failureAlertExplicitlyEnabled
) {
return undefined;
}