Files
AIPass/src/aipass/daemon/tests/test_run_module.py
T
AIOSAIandClaude Opus 4.8 cbe3ba66c6 feat(daemon): decentralized .daemon scheduler — branches own schedule.json (DPLAN-0204/FPLAN-0282)
Each branch owns .daemon/schedule.json; daemon does discovery + firing via
wake_branch() direct (path A). Owner IS the wake target, killing stale-target
bugs. Proven live: drone @daemon run -> discovered @commons/wake-test ->
wake_branch() fired -> @commons woke -> @devpulse received 'DAEMON TEST FIRED'.

- handlers/schedule/discovery.py  — scan branches for .daemon/schedule.json
- handlers/schedule/runstate.py   — per-job run-state tracking
- modules/run.py                  — run-tick entry (wired into 5 modules)
- scheduler_cron.py               — rewired process_actions -> run_tick
- old plugins (community_rotation, daily_audit, heartbeat) retired -> .archive
- 448 tests (+61)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 14:29:22 -07:00

104 lines
3.6 KiB
Python

"""Tests for the drone @daemon run module (decentralized scheduler tick)."""
from unittest.mock import patch
from aipass.daemon.apps.modules.run import (
run_tick,
handle_command,
HANDLED_COMMANDS,
)
class TestHandleCommand:
def test_handles_run(self):
assert "run" in HANDLED_COMMANDS
def test_rejects_unknown(self):
assert handle_command("unknown", []) is False
def test_help_flag(self, capsys):
result = handle_command("run", ["--help"])
assert result is True
class TestRunTick:
@patch("aipass.daemon.apps.modules.run.discover_jobs", return_value=[])
def test_no_jobs(self, mock_discover):
results = run_tick(dry_run=True)
assert results["discovered"] == 0
assert results["fired"] == 0
@patch("aipass.daemon.apps.modules.run.discover_jobs")
@patch("aipass.daemon.apps.modules.run.load_runstate", return_value={"jobs": {}})
def test_dry_run_does_not_fire(self, mock_rs, mock_discover):
mock_discover.return_value = [
{
"owner": "@commons",
"id": "test",
"enabled": True,
"schedule": {"type": "interval", "interval_minutes": 1},
"wake": {"fresh": True},
"prompt": "test prompt",
}
]
results = run_tick(dry_run=True)
assert results["due"] == 1
assert results["fired"] == 0
@patch("aipass.daemon.apps.modules.run.discover_jobs")
@patch("aipass.daemon.apps.modules.run.load_runstate", return_value={"jobs": {}})
def test_disabled_jobs_skipped(self, mock_rs, mock_discover):
mock_discover.return_value = [
{
"owner": "@commons",
"id": "off",
"enabled": False,
"schedule": {"type": "interval", "interval_minutes": 1},
"wake": {},
"prompt": "disabled",
}
]
results = run_tick(dry_run=True)
assert results["enabled"] == 0
assert results["due"] == 0
@patch("aipass.daemon.apps.modules.run.save_runstate")
@patch("aipass.daemon.apps.modules.run._fire_job", return_value=True)
@patch("aipass.daemon.apps.modules.run.discover_jobs")
@patch("aipass.daemon.apps.modules.run.load_runstate", return_value={"jobs": {}})
def test_fires_due_job(self, mock_rs, mock_discover, mock_fire, mock_save):
mock_discover.return_value = [
{
"owner": "@commons",
"id": "test",
"enabled": True,
"schedule": {"type": "interval", "interval_minutes": 1},
"wake": {"fresh": True},
"prompt": "test",
}
]
results = run_tick()
assert results["fired"] == 1
assert results["failed"] == 0
mock_fire.assert_called_once()
mock_save.assert_called()
@patch("aipass.daemon.apps.modules.run.save_runstate")
@patch("aipass.daemon.apps.modules.run._fire_job", return_value=False)
@patch("aipass.daemon.apps.modules.run.discover_jobs")
@patch("aipass.daemon.apps.modules.run.load_runstate", return_value={"jobs": {}})
def test_failed_fire_counted(self, mock_rs, mock_discover, mock_fire, mock_save):
mock_discover.return_value = [
{
"owner": "@commons",
"id": "test",
"enabled": True,
"schedule": {"type": "interval", "interval_minutes": 1},
"wake": {},
"prompt": "test",
}
]
results = run_tick()
assert results["failed"] == 1
assert results["fired"] == 0