rein-aharness/tests/test_ops_run_client.py
tegwick 0f01c3e421 feat: route profiled ops runs through Glas
Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a02b6f-7db1-7222-918b-e813a6bda38d
2026-08-22 23:55:29 +02:00

178 lines
5.7 KiB
Python

"""Tests for activity-core ops_run client (REIN-A-0002-T01)."""
from __future__ import annotations
import json
from pathlib import Path
from unittest.mock import MagicMock, patch
import httpx
import pytest
from rein_aharness.ops_run_client import (
ActivityCoreOpsClient,
OpsRun,
OpsRunConfig,
OpsRunError,
ops_run_to_taskspec,
)
from rein_aharness.taskspec import TaskSpecError
def test_ops_run_from_api() -> None:
row = OpsRun.from_api(
{
"id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
"activity_definition_id": "11111111-2222-3333-4444-555555555555",
"idempotency_key": "k",
"target_repo": "freedom-intelligence",
"title": "FI daily",
"description": "d",
"labels": ["automated", "research-brief"],
"state": "open",
"attempt": 0,
"harness_profile_ref": "harness.agent-dev-local@1.0.0",
"execution_refs": {
"correlation_id": "corr-1",
"goal_refs": ["goal:42@1"],
},
}
)
assert row.target_repo == "freedom-intelligence"
assert "research-brief" in row.labels
assert row.harness_profile_ref == "harness.agent-dev-local@1.0.0"
assert row.execution_refs == {
"correlation_id": "corr-1",
"goal_refs": ["goal:42@1"],
}
def test_config_from_env(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("ACTIVITY_CORE_URL", "http://actcore:8010/")
monkeypatch.setenv("ACTIVITY_CORE_WORKER_TOKEN", "tok")
monkeypatch.setenv("AGENT_HARNESS_WORKER_ID", "w@host")
monkeypatch.setenv("AGENT_HARNESS_OPS_LABELS", "automated,research-brief")
monkeypatch.setenv(
"AGENT_HARNESS_REPO_MAP",
json.dumps({"freedom-intelligence": "/tmp/fi"}),
)
cfg = OpsRunConfig.from_env()
assert cfg.base_url == "http://actcore:8010"
assert cfg.worker_token == "tok"
assert cfg.worker_id == "w@host"
assert "research-brief" in cfg.claim_labels
assert cfg.repo_map["freedom-intelligence"] == "/tmp/fi"
def test_claim_posts_body() -> None:
cfg = OpsRunConfig(
base_url="http://example.test",
worker_token="secret",
worker_id="worker-1",
claim_labels=("automated",),
lease_seconds=120,
)
client = ActivityCoreOpsClient(cfg)
mock_resp = MagicMock()
mock_resp.raise_for_status = MagicMock()
mock_resp.json.return_value = {
"items": [
{
"id": "r1",
"activity_definition_id": "d1",
"idempotency_key": "k",
"target_repo": "freedom-intelligence",
"title": "t",
"description": "",
"labels": ["automated", "research-brief"],
"state": "claimed",
"claim_owner": "worker-1",
"attempt": 1,
}
]
}
with patch("rein_aharness.ops_run_client.httpx.post", return_value=mock_resp) as post:
claimed = client.claim(limit=1)
assert len(claimed) == 1
assert claimed[0].id == "r1"
assert claimed[0].state == "claimed"
kwargs = post.call_args.kwargs
assert kwargs["json"]["worker_id"] == "worker-1"
assert kwargs["json"]["labels"] == ["automated"]
assert kwargs["headers"]["X-Worker-Token"] == "secret"
def test_complete_and_fail() -> None:
cfg = OpsRunConfig(base_url="http://example.test", worker_id="w")
client = ActivityCoreOpsClient(cfg)
mock_resp = MagicMock()
mock_resp.raise_for_status = MagicMock()
mock_resp.json.return_value = {
"id": "r1",
"activity_definition_id": "d",
"idempotency_key": "k",
"title": "t",
"description": "",
"state": "succeeded",
"labels": [],
}
with patch("rein_aharness.ops_run_client.httpx.post", return_value=mock_resp):
out = client.complete("r1", result={"path": "x"})
assert out.state == "succeeded"
mock_resp.json.return_value = {**mock_resp.json.return_value, "state": "open"}
with patch("rein_aharness.ops_run_client.httpx.post", return_value=mock_resp) as post:
out = client.fail("r1", error="timeout", reopen=True)
assert post.call_args.kwargs["json"]["reopen"] is True
def test_claim_http_error() -> None:
cfg = OpsRunConfig(base_url="http://example.test", worker_id="w")
client = ActivityCoreOpsClient(cfg)
with patch(
"rein_aharness.ops_run_client.httpx.post",
side_effect=httpx.ConnectError("down"),
):
with pytest.raises(OpsRunError, match="claim failed"):
client.claim()
def test_ops_run_to_taskspec(tmp_path: Path) -> None:
import subprocess
repo = tmp_path / "freedom-intelligence"
repo.mkdir()
subprocess.run(["git", "init", "-q"], cwd=repo, check=True)
(repo / "README.md").write_text("x\n")
subprocess.run(["git", "add", "."], cwd=repo, check=True)
subprocess.run(
["git", "-c", "user.email=t@t", "-c", "user.name=t", "commit", "-qm", "i"],
cwd=repo,
check=True,
)
run = OpsRun(
id="r1",
activity_definition_id="d",
idempotency_key="k",
target_repo="freedom-intelligence",
title="FI",
description="do",
labels=["automated"],
)
cfg = OpsRunConfig(repo_roots=(str(tmp_path),))
spec = ops_run_to_taskspec(run, cfg)
assert spec.target_repo == repo.resolve()
assert spec.hub_task_id == "r1"
def test_ops_run_to_taskspec_missing_repo() -> None:
run = OpsRun(
id="r1",
activity_definition_id="d",
idempotency_key="k",
target_repo=None,
title="t",
description="",
)
with pytest.raises(TaskSpecError):
ops_run_to_taskspec(run, OpsRunConfig())