Files
2026-07-13 13:12:33 +08:00

230 lines
7.4 KiB
Python

"""tz must be reachable from user surfaces, not just internal APIs.
tz is wired through ops/engine/persistence and must also be exposed on the cron
tool and RPC layer. These tests assert it is reachable through both surfaces and
round-trips on the RPC wire payload.
"""
from __future__ import annotations
import json
from typing import Any
from opensquilla.gateway.rpc import RpcContext
from opensquilla.gateway.rpc_cron import _handle_cron_add, _handle_cron_update, _job_to_wire
from opensquilla.scheduler.payloads import AGENT_TURN_KIND
from opensquilla.scheduler.types import CronJob, DeliveryConfig, SessionTarget
class _FakeScheduler:
def __init__(self, job: CronJob | None = None) -> None:
self.added_kwargs: dict[str, Any] | None = None
self.updated_patch: dict[str, Any] | None = None
self.job = job
async def add_job(self, **kwargs):
self.added_kwargs = kwargs
return CronJob(
id="job-1",
name=kwargs["name"],
cron_expr=kwargs.get("schedule_value") or kwargs.get("schedule_raw", ""),
schedule_raw=kwargs.get("schedule_value") or kwargs.get("schedule_raw", ""),
tz=kwargs.get("tz", ""),
handler_key=kwargs["handler_key"],
payload=kwargs["payload"],
session_target=kwargs["session_target"],
session_key=kwargs["session_key"],
origin_session_key=kwargs["origin_session_key"],
delivery=kwargs.get("delivery") or DeliveryConfig(),
)
async def update_job(self, job_id, **patch):
self.updated_patch = patch
if self.job is None:
return CronJob(id=job_id, **patch)
for key, value in patch.items():
setattr(self.job, key, value)
return self.job
async def get_job(self, job_id):
if self.job is not None and self.job.id == job_id:
return self.job
return None
# --- RPC: cron.add accepts tz --------------------------------------------
async def test_rpc_cron_add_forwards_tz_to_scheduler() -> None:
scheduler = _FakeScheduler()
await _handle_cron_add(
{
"name": "Morning brief",
"expression": "0 9 * * *",
"payloadKind": AGENT_TURN_KIND,
"text": "Summarize overnight updates",
"agentId": "main",
"tz": "America/Los_Angeles",
},
RpcContext(conn_id="test", cron_scheduler=scheduler),
)
assert scheduler.added_kwargs is not None
assert scheduler.added_kwargs["tz"] == "America/Los_Angeles"
async def test_rpc_cron_add_accepts_timezone_alias() -> None:
scheduler = _FakeScheduler()
await _handle_cron_add(
{
"name": "X",
"expression": "0 9 * * *",
"payloadKind": AGENT_TURN_KIND,
"text": "x",
"timezone": "Asia/Shanghai",
},
RpcContext(conn_id="test", cron_scheduler=scheduler),
)
assert scheduler.added_kwargs["tz"] == "Asia/Shanghai"
async def test_rpc_cron_add_defaults_tz_to_empty() -> None:
scheduler = _FakeScheduler()
await _handle_cron_add(
{
"name": "X",
"expression": "0 9 * * *",
"payloadKind": AGENT_TURN_KIND,
"text": "x",
},
RpcContext(conn_id="test", cron_scheduler=scheduler),
)
assert scheduler.added_kwargs["tz"] == ""
async def test_rpc_cron_update_patches_tz() -> None:
current = CronJob(
id="job-1",
cron_expr="0 9 * * *",
handler_key="agent_run",
payload={"kind": AGENT_TURN_KIND, "task": "x", "agent_id": "main"},
session_target=SessionTarget.ISOLATED,
)
scheduler = _FakeScheduler(job=current)
await _handle_cron_update(
{"id": "job-1", "tz": "America/New_York"},
RpcContext(conn_id="test", cron_scheduler=scheduler),
)
assert scheduler.updated_patch is not None
assert scheduler.updated_patch["tz"] == "America/New_York"
def test_job_to_wire_emits_tz() -> None:
job = CronJob(
id="job-1",
name="Briefing",
cron_expr="0 9 * * *",
schedule_raw="0 9 * * *",
tz="America/Los_Angeles",
handler_key="agent_run",
payload={"kind": AGENT_TURN_KIND, "task": "brief", "agent_id": "main"},
session_target=SessionTarget.ISOLATED,
)
wire = _job_to_wire(job)
assert wire["tz"] == "America/Los_Angeles"
def test_job_to_wire_emits_empty_tz_when_unset() -> None:
job = CronJob(
id="job-1",
name="Briefing",
cron_expr="0 9 * * *",
schedule_raw="0 9 * * *",
handler_key="agent_run",
payload={"kind": AGENT_TURN_KIND, "task": "brief", "agent_id": "main"},
session_target=SessionTarget.ISOLATED,
)
wire = _job_to_wire(job)
assert wire["tz"] == ""
# --- Cron tool (model-facing): accepts tz --------------------------------
class _ToolFakeScheduler:
def __init__(self) -> None:
self.added_kwargs: dict[str, Any] | None = None
async def list_jobs(self):
return []
async def add_job(self, **kwargs):
self.added_kwargs = kwargs
return CronJob(
id="job-tool",
name=kwargs["name"],
cron_expr=kwargs.get("schedule_value") or kwargs.get("schedule_raw", ""),
schedule_raw=kwargs.get("schedule_value") or kwargs.get("schedule_raw", ""),
tz=kwargs.get("tz", ""),
handler_key=kwargs["handler_key"],
payload=kwargs["payload"],
session_target=kwargs["session_target"],
session_key=kwargs["session_key"],
origin_session_key=kwargs["origin_session_key"],
delivery=kwargs.get("delivery") or DeliveryConfig(),
)
async def update_job(self, job_id, **patch):
return None
async def remove_job(self, job_id):
return True
async def run_job_now(self, job_id):
return None
async def test_cron_tool_accepts_tz_param() -> None:
"""The model-facing `cron` tool must let callers set tz at add time."""
import opensquilla.tools.builtin.admin as admin_mod
from opensquilla.tools.builtin.admin import cron as cron_tool
fake = _ToolFakeScheduler()
admin_mod.set_scheduler(fake)
try:
raw = await cron_tool(
action="add",
schedule={"kind": "cron", "expr": "0 9 * * *"},
task="Morning briefing",
job_kind="agent_turn",
session_target="isolated",
tz="America/Los_Angeles",
)
finally:
admin_mod.set_scheduler(None) # type: ignore[arg-type]
assert fake.added_kwargs is not None
assert fake.added_kwargs["tz"] == "America/Los_Angeles"
response = json.loads(raw)
assert response["tz"] == "America/Los_Angeles"
async def test_cron_tool_defaults_tz_to_empty_string() -> None:
import opensquilla.tools.builtin.admin as admin_mod
from opensquilla.tools.builtin.admin import cron as cron_tool
fake = _ToolFakeScheduler()
admin_mod.set_scheduler(fake)
try:
raw = await cron_tool(
action="add",
schedule={"kind": "cron", "expr": "0 9 * * *"},
task="x",
job_kind="agent_turn",
session_target="isolated",
)
finally:
admin_mod.set_scheduler(None) # type: ignore[arg-type]
assert fake.added_kwargs["tz"] == ""
assert json.loads(raw)["tz"] == ""