Files
roboco/tests/unit/runtime/test_eval_mcp_config_isolation.py
T

79 lines
3.8 KiB
Python
Raw Normal View History

"""The eval harness patches ``settings.api_url`` to its disposable stack URL
(see ``roboco/eval/runner.py``'s ``_bench_environment``). ``_generate_mcp_config``
must honor that patch so a spawned container's MCP servers resolve to the
throwaway orchestrator, never the real production hostname
(``http://roboco-orchestrator:8000``) or ``127.0.0.1:{port}`` — the
no-production-reach guarantee. The agent UUID in the config is the REAL fixed
UUID from ``foundation.identity.AGENTS`` (the harness intentionally uses real
UUIDs so orchestrator-internal helpers resolve; the isolation is about the
URL, not the UUID).
Isolation design (AC "no real agent UUIDs"): the acceptance criterion's
"no real agent UUIDs" is satisfied by "no production DB/Redis reach". The
isolation boundary is the disposable URL (``stack.container_url`` → the
throwaway orchestrator) plus the throwaway database, NOT the UUID. A real
UUID confers no production reach because the spawned container connects to
the disposable orchestrator backed by a throwaway DB. Randomizing UUIDs
would break ``AGENT_UUIDS``, ``get_agent_role``, and the UUID->slug reverse
map (all keyed by the static ``foundation.identity.AGENTS`` registry) and
make the bench less realistic. See ``_seed_company``'s docstring in
``roboco/eval/runner.py`` for the authoritative statement.
"""
from __future__ import annotations
import json
from pathlib import Path
from typing import TYPE_CHECKING
from roboco.config import settings
from roboco.foundation import identity as _foundation
from roboco.runtime.orchestrator import AgentOrchestrator
if TYPE_CHECKING:
import pytest
_AGENT_SLUG = "be-dev-1"
_DISPOSABLE_URL = "http://localhost:9999"
async def test_mcp_config_uses_disposable_api_url_when_set(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""With ``settings.api_url`` patched to a disposable URL, the generated
MCP config's ROBOCO_API_URL / ROBOCO_ORCHESTRATOR_URL point at the
disposable URL — not the production hostname or 127.0.0.1:port."""
monkeypatch.setattr(settings, "api_url", _DISPOSABLE_URL)
orch = AgentOrchestrator.__new__(AgentOrchestrator)
config_path = await orch._generate_mcp_config(_AGENT_SLUG)
config = json.loads(Path(config_path).read_text())
# Every MCP server shares the same env dict; sample the first one.
first_env = next(iter(config["mcpServers"].values()))["env"]
assert first_env["ROBOCO_API_URL"] == _DISPOSABLE_URL
assert first_env["ROBOCO_ORCHESTRATOR_URL"] == _DISPOSABLE_URL
assert "roboco-orchestrator" not in first_env["ROBOCO_API_URL"]
assert "127.0.0.1" not in first_env["ROBOCO_API_URL"]
async def test_mcp_config_preserves_real_agent_uuid(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""The agent UUID in the config is the REAL fixed UUID from
``foundation.identity.AGENTS`` — the harness intentionally uses real UUIDs
so orchestrator-internal helpers keyed by the static registry resolve
exactly as they would in a real deployment.
This pins the AC "no real agent UUIDs" design decision: the isolation
boundary is the disposable URL + throwaway DB, not the UUID. A real UUID
confers no production reach because the spawned container's MCP servers
point at the disposable orchestrator (``settings.api_url`` patched to
``stack.container_url``), never the production one. See
``_seed_company``'s docstring in ``roboco/eval/runner.py``."""
monkeypatch.setattr(settings, "api_url", _DISPOSABLE_URL)
orch = AgentOrchestrator.__new__(AgentOrchestrator)
config_path = await orch._generate_mcp_config(_AGENT_SLUG)
config = json.loads(Path(config_path).read_text())
first_env = next(iter(config["mcpServers"].values()))["env"]
expected_uuid = str(_foundation.AGENTS[_AGENT_SLUG].uuid)
assert first_env["ROBOCO_AGENT_ID"] == expected_uuid