mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-21 03:39:54 +00:00
1a5172742e
Salvages #27568 by @SerenityTn. Dashboard cron page now lists cron jobs from all profiles, with profile-aware filter UI and storage routing. Includes test coverage for cross-profile listing, mutation, deletion, and validation. Also fixes orphan conflict markers in config.py left by an earlier salvage merge (kanban.dispatch_stale_timeout_seconds was double-nested in HEAD/PR markers from #28452 salvage of #23790).
173 lines
5.8 KiB
Python
173 lines
5.8 KiB
Python
"""Regression tests for dashboard cron job profile routing."""
|
|
|
|
import pytest
|
|
from fastapi import HTTPException
|
|
|
|
|
|
@pytest.fixture()
|
|
def isolated_profiles(tmp_path, monkeypatch):
|
|
"""Give profile discovery an isolated default home with one named profile."""
|
|
from hermes_cli import profiles
|
|
|
|
default_home = tmp_path / ".hermes"
|
|
profiles_root = default_home / "profiles"
|
|
worker_home = profiles_root / "worker_alpha"
|
|
|
|
for home in (default_home, worker_home):
|
|
(home / "cron").mkdir(parents=True, exist_ok=True)
|
|
(home / "config.yaml").write_text("model: test-model\n", encoding="utf-8")
|
|
|
|
monkeypatch.setattr(profiles, "_get_default_hermes_home", lambda: default_home)
|
|
monkeypatch.setattr(profiles, "_get_profiles_root", lambda: profiles_root)
|
|
return {"default": default_home, "worker_alpha": worker_home}
|
|
|
|
|
|
def test_call_cron_for_profile_routes_storage_and_restores_globals(isolated_profiles):
|
|
from cron import jobs as cron_jobs
|
|
from hermes_cli import web_server
|
|
|
|
old_cron_dir = cron_jobs.CRON_DIR
|
|
old_jobs_file = cron_jobs.JOBS_FILE
|
|
old_output_dir = cron_jobs.OUTPUT_DIR
|
|
|
|
job = web_server._call_cron_for_profile(
|
|
"worker_alpha",
|
|
"create_job",
|
|
prompt="run scheduled task",
|
|
schedule="every 1h",
|
|
name="worker-alpha-scan",
|
|
)
|
|
|
|
assert job["profile"] == "worker_alpha"
|
|
assert job["profile_name"] == "worker_alpha"
|
|
assert job["hermes_home"] == str(isolated_profiles["worker_alpha"])
|
|
assert job["is_default_profile"] is False
|
|
assert (isolated_profiles["worker_alpha"] / "cron" / "jobs.json").exists()
|
|
assert not (isolated_profiles["default"] / "cron" / "jobs.json").exists()
|
|
|
|
assert cron_jobs.CRON_DIR == old_cron_dir
|
|
assert cron_jobs.JOBS_FILE == old_jobs_file
|
|
assert cron_jobs.OUTPUT_DIR == old_output_dir
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_list_cron_jobs_all_includes_default_and_named_profiles(isolated_profiles):
|
|
from hermes_cli import web_server
|
|
|
|
default_job = web_server._call_cron_for_profile(
|
|
"default",
|
|
"create_job",
|
|
prompt="default heartbeat",
|
|
schedule="every 2h",
|
|
name="default-heartbeat",
|
|
)
|
|
worker_job = web_server._call_cron_for_profile(
|
|
"worker_alpha",
|
|
"create_job",
|
|
prompt="worker heartbeat",
|
|
schedule="every 3h",
|
|
name="worker-alpha-heartbeat",
|
|
)
|
|
|
|
jobs = await web_server.list_cron_jobs(profile="all")
|
|
by_id = {job["id"]: job for job in jobs}
|
|
|
|
assert set(by_id) >= {default_job["id"], worker_job["id"]}
|
|
assert by_id[default_job["id"]]["profile"] == "default"
|
|
assert by_id[default_job["id"]]["is_default_profile"] is True
|
|
assert by_id[default_job["id"]]["hermes_home"] == str(isolated_profiles["default"])
|
|
assert by_id[worker_job["id"]]["profile"] == "worker_alpha"
|
|
assert by_id[worker_job["id"]]["is_default_profile"] is False
|
|
assert by_id[worker_job["id"]]["hermes_home"] == str(isolated_profiles["worker_alpha"])
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_list_cron_jobs_specific_profile_filters_results(isolated_profiles):
|
|
from hermes_cli import web_server
|
|
|
|
web_server._call_cron_for_profile(
|
|
"default",
|
|
"create_job",
|
|
prompt="default only",
|
|
schedule="every 2h",
|
|
name="default-only",
|
|
)
|
|
worker_job = web_server._call_cron_for_profile(
|
|
"worker_alpha",
|
|
"create_job",
|
|
prompt="worker only",
|
|
schedule="every 3h",
|
|
name="worker-only",
|
|
)
|
|
|
|
jobs = await web_server.list_cron_jobs(profile="worker_alpha")
|
|
|
|
assert [job["id"] for job in jobs] == [worker_job["id"]]
|
|
assert jobs[0]["profile"] == "worker_alpha"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_cron_mutation_without_profile_finds_named_profile_job(isolated_profiles):
|
|
from hermes_cli import web_server
|
|
|
|
worker_job = web_server._call_cron_for_profile(
|
|
"worker_alpha",
|
|
"create_job",
|
|
prompt="managed by named profile",
|
|
schedule="every 1h",
|
|
name="named-profile-job",
|
|
)
|
|
|
|
paused = await web_server.pause_cron_job(worker_job["id"])
|
|
assert paused["profile"] == "worker_alpha"
|
|
assert paused["enabled"] is False
|
|
|
|
default_jobs = await web_server.list_cron_jobs(profile="default")
|
|
worker_jobs = await web_server.list_cron_jobs(profile="worker_alpha")
|
|
|
|
assert default_jobs == []
|
|
assert len(worker_jobs) == 1
|
|
assert worker_jobs[0]["id"] == worker_job["id"]
|
|
assert worker_jobs[0]["enabled"] is False
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_cron_delete_with_profile_deletes_only_target_profile(isolated_profiles):
|
|
from hermes_cli import web_server
|
|
|
|
default_job = web_server._call_cron_for_profile(
|
|
"default",
|
|
"create_job",
|
|
prompt="same-ish default",
|
|
schedule="every 1h",
|
|
name="shared-name",
|
|
)
|
|
worker_job = web_server._call_cron_for_profile(
|
|
"worker_alpha",
|
|
"create_job",
|
|
prompt="same-ish worker",
|
|
schedule="every 1h",
|
|
name="shared-name-worker",
|
|
)
|
|
|
|
deleted = await web_server.delete_cron_job(worker_job["id"], profile="worker_alpha")
|
|
assert deleted == {"ok": True}
|
|
|
|
remaining_default = await web_server.list_cron_jobs(profile="default")
|
|
remaining_worker = await web_server.list_cron_jobs(profile="worker_alpha")
|
|
assert [job["id"] for job in remaining_default] == [default_job["id"]]
|
|
assert remaining_worker == []
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_cron_profile_validation_errors(isolated_profiles):
|
|
from hermes_cli import web_server
|
|
|
|
with pytest.raises(HTTPException) as bad_name:
|
|
await web_server.list_cron_jobs(profile="../bad")
|
|
assert bad_name.value.status_code == 400
|
|
|
|
with pytest.raises(HTTPException) as missing:
|
|
await web_server.list_cron_jobs(profile="missing_profile")
|
|
assert missing.value.status_code == 404
|