230 lines
7.4 KiB
Python
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"] == ""
|