From ac22b6a7994cf4a3e968e7e33600b239ce36009b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97SO?= <142557582+Linxiushen@users.noreply.github.com> Date: Mon, 10 Aug 2026 19:01:04 +0800 Subject: [PATCH] fix(cron): unpack job scan results in management actions --- backend/super-magic/app/tools/manage_cron.py | 6 +- .../tools/test_manage_cron_scan_result.py | 97 +++++++++++++++++++ 2 files changed, 100 insertions(+), 3 deletions(-) create mode 100644 backend/super-magic/tests/tools/test_manage_cron_scan_result.py diff --git a/backend/super-magic/app/tools/manage_cron.py b/backend/super-magic/app/tools/manage_cron.py index a2af529652..b04e8080a5 100644 --- a/backend/super-magic/app/tools/manage_cron.py +++ b/backend/super-magic/app/tools/manage_cron.py @@ -243,7 +243,7 @@ async def execute(self, tool_context: ToolContext, params: ManageCronParams) -> async def _status(self) -> ToolResult: from app.service.cron.store import load_cron_state, scan_jobs cron_dir = PathManager.get_cron_dir() - jobs = await scan_jobs({}) + jobs, _ = await scan_jobs({}) state = await load_cron_state() lines = [f"Cron dir: {cron_dir}", f"Total jobs: {len(jobs)}"] for job in jobs: @@ -260,7 +260,7 @@ async def _status(self) -> ToolResult: async def _list(self, params: ManageCronParams) -> ToolResult: from app.service.cron.store import scan_jobs - jobs = await scan_jobs({}) + jobs, _ = await scan_jobs({}) include_disabled = bool(params.include_disabled) if not include_disabled: jobs = [j for j in jobs if j.enabled] @@ -373,7 +373,7 @@ async def _run_now(self, params: ManageCronParams) -> ToolResult: if not params.job_id: return ToolResult.error("job_id is required for action=run") from app.service.cron.store import scan_jobs - jobs = await scan_jobs({}) + jobs, _ = await scan_jobs({}) job = next((j for j in jobs if j.id == params.job_id), None) if job is None: return ToolResult.error(f"Job '{params.job_id}' not found or has parse errors") diff --git a/backend/super-magic/tests/tools/test_manage_cron_scan_result.py b/backend/super-magic/tests/tools/test_manage_cron_scan_result.py new file mode 100644 index 0000000000..3cb5b23e79 --- /dev/null +++ b/backend/super-magic/tests/tools/test_manage_cron_scan_result.py @@ -0,0 +1,97 @@ +from __future__ import annotations + +import asyncio +from unittest.mock import AsyncMock + +import pytest + +from app.service.cron.models import ( + CronJob, + CronJobState, + CronPayload, + CronSchedule, + CronState, + ScheduleKind, +) +from app.tools.manage_cron import ManageCron, ManageCronParams + + +def _job(job_id: str, *, enabled: bool = True) -> CronJob: + return CronJob( + id=job_id, + name=f"{job_id} name", + schedule=CronSchedule(kind=ScheduleKind.EVERY, every_ms=60_000), + payload=CronPayload(), + body="Run a scheduled task.", + enabled=enabled, + ) + + +@pytest.mark.asyncio +async def test_status_uses_jobs_from_scan_result(monkeypatch): + job = _job("daily-report") + scan_jobs = AsyncMock(return_value=([job], {"invalid-job": 123.0})) + load_cron_state = AsyncMock( + return_value=CronState( + jobs={ + job.id: CronJobState( + next_run_at_ms=1_700_000_000_000, + last_status="ok", + ) + } + ) + ) + monkeypatch.setattr("app.service.cron.store.scan_jobs", scan_jobs) + monkeypatch.setattr("app.service.cron.store.load_cron_state", load_cron_state) + + result = await ManageCron()._status() + + assert result.ok + assert "Total jobs: 1" in result.content + assert "[daily-report] enabled=True" in result.content + scan_jobs.assert_awaited_once_with({}) + + +@pytest.mark.asyncio +async def test_list_filters_disabled_jobs_from_scan_result(monkeypatch): + enabled_job = _job("enabled-job") + disabled_job = _job("disabled-job", enabled=False) + scan_jobs = AsyncMock(return_value=([enabled_job, disabled_job], {"invalid-job": 123.0})) + monkeypatch.setattr("app.service.cron.store.scan_jobs", scan_jobs) + + result = await ManageCron()._list(ManageCronParams(action="list")) + + assert result.ok + assert result.data == { + "jobs": [ + { + "id": enabled_job.id, + "name": enabled_job.name, + "enabled": True, + "schedule_kind": ScheduleKind.EVERY, + "payload_kind": enabled_job.payload.kind, + } + ] + } + assert disabled_job.id not in result.content + scan_jobs.assert_awaited_once_with({}) + + +@pytest.mark.asyncio +async def test_run_now_finds_job_from_scan_result(monkeypatch): + job = _job("manual-job") + scan_jobs = AsyncMock(return_value=([job], {"invalid-job": 123.0})) + execute_agent_turn = AsyncMock() + monkeypatch.setattr("app.service.cron.store.scan_jobs", scan_jobs) + monkeypatch.setattr( + "app.service.cron.executor.execute_agent_turn", + execute_agent_turn, + ) + + result = await ManageCron()._run_now(ManageCronParams(action="run", job_id=job.id)) + await asyncio.sleep(0) + + assert result.ok + assert f"Triggered job '{job.id}' immediately" in result.content + execute_agent_turn.assert_awaited_once_with(job) + scan_jobs.assert_awaited_once_with({})