Import AITURK IDE 1.0.0-beta.1 from Hermes 63279301; preserve MIT license
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
"""Cross-surface approval mode/timeout parity invariant.
|
||||
|
||||
The approval mode (``approvals.mode``) and timeout (``approvals.timeout``)
|
||||
must resolve identically on every surface that consults them:
|
||||
|
||||
- the canonical core: ``tools.approval._get_approval_mode`` /
|
||||
``tools.approval._get_approval_timeout``
|
||||
- the TUI gateway: ``tui_gateway.server._load_approval_mode`` (delegates
|
||||
to the core as of the decision-core migration)
|
||||
- the codex app-server surface: ``agent/codex_runtime.py`` feeds
|
||||
``auto_approve_*`` from ``tools.approval.is_approval_bypass_active()``,
|
||||
which itself reads the core resolver — so parity there reduces to
|
||||
``is_approval_bypass_active() == (mode == "off")`` when no yolo
|
||||
source is active.
|
||||
|
||||
Historic drift class: tui_gateway re-read config raw and normalized
|
||||
locally (see commits f9cd577915, 1e652cca7a, bd246db10d — repeated parity
|
||||
re-alignments). This test pins the invariant so drift regressions fail
|
||||
loudly instead of silently disagreeing per surface.
|
||||
|
||||
There is no per-platform ``approvals.mode`` override in the config schema;
|
||||
mode/timeout are global, so the synthetic configs below cover global-set,
|
||||
unset (defaults), and malformed values.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def hermes_home(tmp_path, monkeypatch):
|
||||
home = tmp_path / ".hermes"
|
||||
home.mkdir()
|
||||
monkeypatch.setenv("HERMES_HOME", str(home))
|
||||
return home
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def tui_server():
|
||||
with patch.dict(
|
||||
"sys.modules",
|
||||
{
|
||||
"hermes_cli.env_loader": MagicMock(),
|
||||
"hermes_cli.banner": MagicMock(),
|
||||
},
|
||||
):
|
||||
yield importlib.import_module("tui_gateway.server")
|
||||
|
||||
|
||||
def _write_config(home, yaml_text: str | None) -> None:
|
||||
cfg = home / "config.yaml"
|
||||
if yaml_text is None:
|
||||
if cfg.exists():
|
||||
cfg.unlink()
|
||||
else:
|
||||
cfg.write_text(yaml_text, encoding="utf-8")
|
||||
|
||||
|
||||
# (config yaml, expected mode, expected timeout)
|
||||
CASES = [
|
||||
pytest.param(None, "smart", 300, id="unset-defaults"),
|
||||
pytest.param(
|
||||
"approvals:\n mode: manual\n", "manual", 300, id="global-manual"
|
||||
),
|
||||
pytest.param(
|
||||
"approvals:\n mode: smart\n timeout: 120\n",
|
||||
"smart",
|
||||
120,
|
||||
id="global-smart-timeout",
|
||||
),
|
||||
pytest.param(
|
||||
# YAML 1.1 parses bare OFF as boolean False; the normalizer maps
|
||||
# False -> "off". Both surfaces must agree on that quirk.
|
||||
"approvals:\n mode: OFF\n timeout: 45\n",
|
||||
"off",
|
||||
45,
|
||||
id="yaml-bool-off",
|
||||
),
|
||||
pytest.param(
|
||||
"approvals:\n mode: bogus-value\n timeout: not-a-number\n",
|
||||
"manual",
|
||||
300,
|
||||
id="malformed-values",
|
||||
),
|
||||
pytest.param(
|
||||
"approvals:\n mode: ' Smart '\n", "smart", 300, id="whitespace-case"
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
def _approval_module():
|
||||
"""Resolve tools.approval via sys.modules, not the package attribute.
|
||||
|
||||
The ``tui_server`` fixture's ``patch.dict("sys.modules", ...)`` purges
|
||||
modules imported during its block at teardown; ``from tools import
|
||||
approval`` can then hand back a stale attribute cached on the ``tools``
|
||||
package while the server re-imports a fresh module object. Going
|
||||
through ``importlib.import_module`` keeps the test and the server on
|
||||
the same sys.modules entry.
|
||||
"""
|
||||
return importlib.import_module("tools.approval")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("yaml_text,expected_mode,expected_timeout", CASES)
|
||||
def test_mode_and_timeout_parity_across_surfaces(
|
||||
hermes_home, tui_server, yaml_text, expected_mode, expected_timeout
|
||||
):
|
||||
approval_mod = _approval_module()
|
||||
|
||||
_write_config(hermes_home, yaml_text)
|
||||
|
||||
core_mode = approval_mod._get_approval_mode()
|
||||
core_timeout = approval_mod._get_approval_timeout()
|
||||
tui_mode = tui_server._load_approval_mode()
|
||||
|
||||
# Canonical resolver matches expectations.
|
||||
assert core_mode == expected_mode
|
||||
assert core_timeout == expected_timeout
|
||||
|
||||
# TUI surface returns the identical mode (delegation invariant).
|
||||
assert tui_mode == core_mode
|
||||
|
||||
# Codex surface: auto-approve routing is derived from
|
||||
# is_approval_bypass_active(), which must equal (mode == "off")
|
||||
# whenever no yolo source is active in this process.
|
||||
if not approval_mod._YOLO_MODE_FROZEN:
|
||||
with patch.object(
|
||||
approval_mod, "is_current_session_yolo_enabled", return_value=False
|
||||
):
|
||||
assert approval_mod.is_approval_bypass_active() == (
|
||||
core_mode == "off"
|
||||
)
|
||||
|
||||
|
||||
def test_tui_loader_delegates_to_core(hermes_home, tui_server):
|
||||
"""The TUI must not re-resolve mode itself — it delegates to the core.
|
||||
|
||||
Pin the delegation seam directly: patching the core resolver changes
|
||||
what the TUI reports, proving there is no independent config read left.
|
||||
"""
|
||||
approval_mod = _approval_module()
|
||||
|
||||
with patch.object(approval_mod, "_get_approval_mode", return_value="smart"):
|
||||
assert tui_server._load_approval_mode() == "smart"
|
||||
with patch.object(approval_mod, "_get_approval_mode", return_value="off"):
|
||||
assert tui_server._load_approval_mode() == "off"
|
||||
# Defensive clamp: an out-of-vocabulary value from the core is coerced
|
||||
# to manual rather than leaking an unknown mode to the TUI client.
|
||||
with patch.object(
|
||||
approval_mod, "_get_approval_mode", return_value="weird"
|
||||
):
|
||||
assert tui_server._load_approval_mode() == "manual"
|
||||
Reference in New Issue
Block a user