fix(cron): treat null repeat.completed as 0 in update_job and job definitions
#122114 guarded mark_job_run/claim paths against a hand-edited "completed": null, but update_job and the job_definition merge still used .get("completed", 0), which returns None when the key is present, so the null was carried forward into the store. Use `or 0` there too, and pin the behaviour with one test covering mark_job_run and update_job. Fixes #123281 Co-authored-by: John Paul Soliva <soliva.johnpaul@icloud.com> Co-authored-by: JoaoMarcos44 <joaomarcosdias444@gmail.com> Co-authored-by: mochamgx <1114149@qq.com>
This commit is contained in:
@@ -26,7 +26,7 @@ def merge_job_definition(local: Dict[str, Any], authored: Dict[str, Any]) -> Dic
|
||||
merged = {key: value for key, value in local.items() if key not in JOB_DEFINITION_FIELDS}
|
||||
merged.update((key, authored[key]) for key in JOB_DEFINITION_FIELDS if key in authored)
|
||||
merged["repeat"] = {
|
||||
"completed": (local.get("repeat") or {}).get("completed", 0),
|
||||
"completed": (local.get("repeat") or {}).get("completed") or 0,
|
||||
"times": (authored.get("repeat") or {}).get("times"),
|
||||
}
|
||||
if isinstance(merged.get("schedule"), str):
|
||||
|
||||
@@ -1973,7 +1973,7 @@ def _normalize_job_updates(job: Dict[str, Any], updates: Dict[str, Any]) -> None
|
||||
updates[key] = norm(updates[key])
|
||||
if "repeat" in updates:
|
||||
_rp = updates["repeat"]
|
||||
completed = (job.get("repeat") or {}).get("completed", 0)
|
||||
completed = (job.get("repeat") or {}).get("completed") or 0
|
||||
if isinstance(_rp, dict):
|
||||
_rp = dict(_rp)
|
||||
_rp["times"] = normalize_repeat_value(_rp.get("times"))
|
||||
|
||||
@@ -438,6 +438,25 @@ class TestJobCRUD:
|
||||
with pytest.raises(ValueError, match="Invalid repeat"):
|
||||
update_job(job["id"], {"repeat": "banana"})
|
||||
|
||||
def test_null_repeat_completed_counts_as_zero(self, tmp_cron_dir):
|
||||
"""A hand-edited "completed": null must not kill mark_job_run (None += 1) or be
|
||||
carried forward by update_job."""
|
||||
import json
|
||||
from cron.jobs import JOBS_FILE, get_job, mark_job_run, update_job
|
||||
|
||||
job = create_job(prompt="t", schedule="every 1h", repeat=3)
|
||||
|
||||
def null_completed():
|
||||
payload = json.loads(JOBS_FILE.read_text(encoding="utf-8"))
|
||||
payload["jobs"][0]["repeat"]["completed"] = None
|
||||
JOBS_FILE.write_text(json.dumps(payload), encoding="utf-8")
|
||||
|
||||
null_completed()
|
||||
mark_job_run(job["id"], success=True)
|
||||
assert get_job(job["id"])["repeat"]["completed"] == 1
|
||||
null_completed()
|
||||
assert update_job(job["id"], {"repeat": {"times": 5}})["repeat"]["completed"] == 0
|
||||
|
||||
def test_oneshot_turned_recurring_becomes_forever(self, tmp_cron_dir):
|
||||
"""A one-shot budget must not survive a schedule change to a recurring kind.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user