stage-8: cron scheduler with APScheduler

Scheduler class, cron/jobs.json, Discord /cron commands, CLI cron subcommand, job lifecycle management. 88 new tests (281 total).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
MoltBot Service
2026-02-13 16:12:56 +00:00
parent 09d3de003a
commit 24a4d87f8c
8 changed files with 1640 additions and 1 deletions

View File

@@ -364,3 +364,124 @@ class TestSend:
with pytest.raises(SystemExit):
cli.cmd_send(_args(alias="nope", message=["hi"]))
assert "unknown channel" in capsys.readouterr().out.lower()
# ---------------------------------------------------------------------------
# cron list
# ---------------------------------------------------------------------------
class TestCronList:
def test_list_empty(self, iso, capsys):
mock_sched = MagicMock()
mock_sched._load_jobs.return_value = []
mock_sched.list_jobs.return_value = []
with patch("src.scheduler.Scheduler", return_value=mock_sched):
cli._cron_list()
assert "No scheduled jobs" in capsys.readouterr().out
def test_list_shows_table(self, iso, capsys):
mock_sched = MagicMock()
mock_sched._load_jobs.return_value = [
{
"name": "daily-run",
"cron": "30 6 * * *",
"channel": "work",
"model": "sonnet",
"enabled": True,
"last_status": "ok",
"next_run": None,
}
]
mock_sched.list_jobs.return_value = mock_sched._load_jobs.return_value
with patch("src.scheduler.Scheduler", return_value=mock_sched):
cli._cron_list()
out = capsys.readouterr().out
assert "daily-run" in out
assert "30 6 * * *" in out
assert "sonnet" in out
# ---------------------------------------------------------------------------
# cron add / remove / enable / disable
# ---------------------------------------------------------------------------
class TestCronAdd:
def test_add_success(self, iso, capsys):
mock_sched = MagicMock()
mock_sched._load_jobs.return_value = []
mock_sched.add_job.return_value = {
"name": "new-job",
"cron": "0 * * * *",
"channel": "ch",
"model": "sonnet",
}
with patch("src.scheduler.Scheduler", return_value=mock_sched):
cli._cron_add("new-job", "0 * * * *", "ch", "hello prompt", "sonnet", [])
out = capsys.readouterr().out
assert "new-job" in out
assert "added" in out.lower()
def test_add_error(self, iso, capsys):
mock_sched = MagicMock()
mock_sched._load_jobs.return_value = []
mock_sched.add_job.side_effect = ValueError("duplicate name")
with patch("src.scheduler.Scheduler", return_value=mock_sched):
with pytest.raises(SystemExit):
cli._cron_add("dup", "0 * * * *", "ch", "prompt", "sonnet", [])
assert "duplicate name" in capsys.readouterr().out
class TestCronRemove:
def test_remove_found(self, iso, capsys):
mock_sched = MagicMock()
mock_sched._load_jobs.return_value = []
mock_sched.remove_job.return_value = True
with patch("src.scheduler.Scheduler", return_value=mock_sched):
cli._cron_remove("old-job")
assert "removed" in capsys.readouterr().out.lower()
def test_remove_not_found(self, iso, capsys):
mock_sched = MagicMock()
mock_sched._load_jobs.return_value = []
mock_sched.remove_job.return_value = False
with patch("src.scheduler.Scheduler", return_value=mock_sched):
cli._cron_remove("ghost")
assert "not found" in capsys.readouterr().out.lower()
class TestCronEnable:
def test_enable_found(self, iso, capsys):
mock_sched = MagicMock()
mock_sched._load_jobs.return_value = []
mock_sched.enable_job.return_value = True
with patch("src.scheduler.Scheduler", return_value=mock_sched):
cli._cron_enable("my-job")
assert "enabled" in capsys.readouterr().out.lower()
def test_enable_not_found(self, iso, capsys):
mock_sched = MagicMock()
mock_sched._load_jobs.return_value = []
mock_sched.enable_job.return_value = False
with patch("src.scheduler.Scheduler", return_value=mock_sched):
cli._cron_enable("nope")
assert "not found" in capsys.readouterr().out.lower()
class TestCronDisable:
def test_disable_found(self, iso, capsys):
mock_sched = MagicMock()
mock_sched._load_jobs.return_value = []
mock_sched.disable_job.return_value = True
with patch("src.scheduler.Scheduler", return_value=mock_sched):
cli._cron_disable("my-job")
assert "disabled" in capsys.readouterr().out.lower()
def test_disable_not_found(self, iso, capsys):
mock_sched = MagicMock()
mock_sched._load_jobs.return_value = []
mock_sched.disable_job.return_value = False
with patch("src.scheduler.Scheduler", return_value=mock_sched):
cli._cron_disable("nope")
assert "not found" in capsys.readouterr().out.lower()