mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-08-04 08:28:36 +00:00
fix(cron): coerce null runHistory ms fields from jobs.json
Explicit JSON null for runAtMs/durationMs bypassed the missing-key default and raised TypeError on load. Treat null/blank like missing.
This commit is contained in:
parent
dfc3919b52
commit
0b1b02f187
@ -8,6 +8,13 @@ from typing import Any, Literal
|
|||||||
from nanobot.utils.dict_keys import get_camel_snake
|
from nanobot.utils.dict_keys import get_camel_snake
|
||||||
|
|
||||||
|
|
||||||
|
def _required_store_int(value: Any, default: int = 0) -> int:
|
||||||
|
"""Coerce JSON numerics to int; treat null/blank like a missing key."""
|
||||||
|
if value is None or value == "":
|
||||||
|
return default
|
||||||
|
return int(value)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class CronSchedule:
|
class CronSchedule:
|
||||||
"""Schedule definition for a cron job."""
|
"""Schedule definition for a cron job."""
|
||||||
@ -78,9 +85,11 @@ class CronRunRecord:
|
|||||||
@classmethod
|
@classmethod
|
||||||
def from_store_dict(cls, data: dict[str, Any]) -> CronRunRecord:
|
def from_store_dict(cls, data: dict[str, Any]) -> CronRunRecord:
|
||||||
return cls(
|
return cls(
|
||||||
run_at_ms=int(get_camel_snake(data, "runAtMs", "run_at_ms", 0)),
|
run_at_ms=_required_store_int(get_camel_snake(data, "runAtMs", "run_at_ms", 0)),
|
||||||
status=data["status"],
|
status=data["status"],
|
||||||
duration_ms=int(get_camel_snake(data, "durationMs", "duration_ms", 0)),
|
duration_ms=_required_store_int(
|
||||||
|
get_camel_snake(data, "durationMs", "duration_ms", 0)
|
||||||
|
),
|
||||||
error=data.get("error"),
|
error=data.get("error"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -1007,3 +1007,42 @@ async def test_list_jobs_during_on_job_does_not_cause_stale_reload(tmp_path) ->
|
|||||||
next_run = j["state"]["nextRunAtMs"]
|
next_run = j["state"]["nextRunAtMs"]
|
||||||
assert next_run is not None
|
assert next_run is not None
|
||||||
assert next_run > now_ms, f"Job '{j['name']}' next_run should be in the future"
|
assert next_run > now_ms, f"Job '{j['name']}' next_run should be in the future"
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_jobs_accepts_null_run_history_ms(tmp_path) -> None:
|
||||||
|
store_path = tmp_path / "cron" / "jobs.json"
|
||||||
|
store_path.parent.mkdir(parents=True)
|
||||||
|
store_path.write_text(
|
||||||
|
json.dumps(
|
||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"jobs": [
|
||||||
|
{
|
||||||
|
"id": "j1",
|
||||||
|
"name": "t",
|
||||||
|
"enabled": True,
|
||||||
|
"schedule": {"kind": "every", "everyMs": 60_000},
|
||||||
|
"payload": {
|
||||||
|
"kind": "agent_turn",
|
||||||
|
"message": "hi",
|
||||||
|
"sessionKey": "websocket:chat-1",
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"runHistory": [
|
||||||
|
{"runAtMs": None, "status": "ok", "durationMs": None},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"createdAtMs": 0,
|
||||||
|
"updatedAtMs": 0,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
jobs, _version = CronService(store_path)._load_jobs()
|
||||||
|
assert jobs is not None
|
||||||
|
assert jobs[0].state.run_history[0].run_at_ms == 0
|
||||||
|
assert jobs[0].state.run_history[0].duration_ms == 0
|
||||||
|
assert jobs[0].state.run_history[0].status == "ok"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user