mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-31 19:16:29 +00:00
Systematic prune per AGENTS.md test policy, one pass over every major test tree (gateway, hermes_cli, tools, agent, run_agent, plugins, cli, cron, tui_gateway, honcho/openviking, root-level): - DELETE: source-reading tests (read_text/getsource on prod files), change-detector tests (exact catalog counts, model-name snapshots, config version literals), mock-echo tests (assert a mock returns what it was told), assertion-free/trivial tests, near-duplicate parametrizations (boundaries + one representative kept), async/sync twin duplicates, cosmetic within-file variations. - KEEP (mandatory): security/redaction/approval guards, message-role alternation invariants, prompt-caching/deterministic-call-id invariants, issue-number regression tests (deduped), E2E tests. - 6 test files deleted outright (script-style/no-assert or fully redundant); conftest.py, fakes/, fixtures/ untouched. - tests/acp/conftest.py added: autouse fixture stubs the live models.dev/GitHub/Copilot/Anthropic inventory fetches that ACP server tests performed on every session create — test_server.py 147s → 3.4s, and the tests are now genuinely hermetic. - Sleep-based slowness shrunk where safe (codex_ttfb_watchdog, compression_concurrent_fork, etc.); no wall-clock assertion tightened. Verification: full hermetic suite via scripts/run_tests.sh — 2439 files, 31,130 tests passed, 0 failed, 0 flaky retries, 315s wall (baseline: 583s wall, 13,564s subprocess CPU).
154 lines
5 KiB
Python
154 lines
5 KiB
Python
"""Tests for kanban worker/runs read endpoints.
|
|
|
|
Covers:
|
|
GET /workers/active
|
|
GET /runs/{run_id}
|
|
GET /runs/{run_id}/inspect
|
|
POST /runs/{run_id}/terminate
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import importlib.util
|
|
import secrets
|
|
import sys
|
|
import time
|
|
from pathlib import Path
|
|
from unittest.mock import MagicMock
|
|
|
|
import pytest
|
|
from fastapi import FastAPI
|
|
from fastapi.testclient import TestClient
|
|
|
|
from hermes_cli import kanban_db as kb
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Fixtures
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def _load_plugin_router():
|
|
"""Dynamically load plugins/kanban/dashboard/plugin_api.py and return its router."""
|
|
repo_root = Path(__file__).resolve().parents[2]
|
|
plugin_file = repo_root / "plugins" / "kanban" / "dashboard" / "plugin_api.py"
|
|
assert plugin_file.exists(), f"plugin file missing: {plugin_file}"
|
|
|
|
mod_name = "hermes_dashboard_plugin_kanban_worker_runs_test"
|
|
# Re-use a cached module if already loaded to avoid duplicate-router issues.
|
|
if mod_name in sys.modules:
|
|
return sys.modules[mod_name].router
|
|
|
|
spec = importlib.util.spec_from_file_location(mod_name, plugin_file)
|
|
assert spec is not None and spec.loader is not None
|
|
mod = importlib.util.module_from_spec(spec)
|
|
sys.modules[mod_name] = mod
|
|
spec.loader.exec_module(mod)
|
|
return mod.router
|
|
|
|
|
|
@pytest.fixture
|
|
def kanban_home(tmp_path, monkeypatch):
|
|
"""Isolated HERMES_HOME with an empty kanban DB."""
|
|
home = tmp_path / ".hermes"
|
|
home.mkdir()
|
|
monkeypatch.setenv("HERMES_HOME", str(home))
|
|
monkeypatch.setattr(Path, "home", lambda: tmp_path)
|
|
kb.init_db()
|
|
return home
|
|
|
|
|
|
@pytest.fixture
|
|
def client(kanban_home):
|
|
app = FastAPI()
|
|
app.include_router(_load_plugin_router(), prefix="/api/plugins/kanban")
|
|
return TestClient(app)
|
|
|
|
|
|
def _insert_run(conn, task_id, *, worker_pid=None, ended_at=None):
|
|
"""Insert a task_runs row directly (bypassing claim machinery) and return run_id."""
|
|
lock = secrets.token_hex(8)
|
|
future = int(time.time()) + 3600
|
|
cur = conn.execute(
|
|
"INSERT INTO task_runs "
|
|
"(task_id, status, claim_lock, claim_expires, worker_pid, started_at, ended_at) "
|
|
"VALUES (?, 'running', ?, ?, ?, ?, ?)",
|
|
(task_id, lock, future, worker_pid, int(time.time()), ended_at),
|
|
)
|
|
conn.commit()
|
|
return cur.lastrowid
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# GET /workers/active
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def test_workers_active_empty_board(client):
|
|
"""Board with no running tasks returns an empty workers list."""
|
|
r = client.get("/api/plugins/kanban/workers/active")
|
|
assert r.status_code == 200
|
|
body = r.json()
|
|
assert body["workers"] == []
|
|
assert body["count"] == 0
|
|
assert "checked_at" in body
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# GET /runs/{run_id}
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def test_get_run_404_unknown_id(client):
|
|
"""Non-existent run_id returns 404."""
|
|
r = client.get("/api/plugins/kanban/runs/999999")
|
|
assert r.status_code == 404
|
|
assert "999999" in r.json()["detail"]
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# GET /runs/{run_id}/inspect
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def test_inspect_run_404(client):
|
|
"""Non-existent run_id returns 404."""
|
|
r = client.get("/api/plugins/kanban/runs/888888/inspect")
|
|
assert r.status_code == 404
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# POST /runs/{run_id}/terminate
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def _setup_running_task_with_run(conn, *, title, assignee, worker_pid):
|
|
"""Create a task in 'running' state with a matching open task_runs row.
|
|
|
|
Mirrors what dispatcher_claim does: stamps tasks.status='running',
|
|
tasks.claim_lock, tasks.worker_pid; inserts task_runs row with the
|
|
same claim_lock so reclaim_task's preconditions are satisfied.
|
|
"""
|
|
task_id = kb.create_task(conn, title=title, assignee=assignee)
|
|
lock = secrets.token_hex(8)
|
|
future = int(time.time()) + 3600
|
|
conn.execute(
|
|
"UPDATE tasks SET status='running', claim_lock=?, "
|
|
"claim_expires=?, worker_pid=? WHERE id=?",
|
|
(lock, future, worker_pid, task_id),
|
|
)
|
|
cur = conn.execute(
|
|
"INSERT INTO task_runs "
|
|
"(task_id, status, claim_lock, claim_expires, worker_pid, started_at) "
|
|
"VALUES (?, 'running', ?, ?, ?, ?)",
|
|
(task_id, lock, future, worker_pid, int(time.time())),
|
|
)
|
|
conn.commit()
|
|
return task_id, cur.lastrowid
|
|
|
|
|
|
def test_terminate_run_404_unknown_id(client):
|
|
"""POST to unknown run_id returns 404."""
|
|
r = client.post(
|
|
"/api/plugins/kanban/runs/777777/terminate",
|
|
json={"reason": "test"},
|
|
)
|
|
assert r.status_code == 404
|
|
assert "777777" in r.json()["detail"]
|
|
|
|
|