hermes-agent/tests/gateway/test_pairing_allowlist_bypass.py
Teknium 39975613b1
test: prune wave 2 + speed fixes — 28,106 → 19,757 test functions, suite wall 315s → 294s
Second, deeper pass over tools/gateway/hermes_cli plus first pass over
the trees wave 1 missed (acp, acp_adapter, skills, computer_use, docker,
dashboard, conformance, monitoring, secret_sources, hermes_state,
providers). Same rubric as wave 1 (AGENTS.md test policy); security,
alternation/caching invariants, issue-number regressions, and E2E kept.

Real test-quality fixes found and rooted out along the way:
- tests/tools/test_command_guards.py made real auxiliary-LLM HTTPS calls
  (DEFAULT_CONFIG smart-approval leaked in) — pinned approval
  mode=manual via autouse fixture: 17.4s → 0.4s.
- test_model_switch_custom_providers.py / test_user_providers_model_switch.py
  silently probed live provider catalogs (~2s/test) — stubbed
  cached_provider_model_ids/provider_model_ids/fetch_api_models.
- test_telegram_noise_filter.py: 15-platform copy-paste matrix over
  shared gateway.run logic → 3 representative platforms (55s → 3.9s).
- test_gateway_shutdown.py: stop()'s 5s interrupt-deadline loop spun on
  MagicMock agents — interrupt.side_effect now clears _running_agents
  (22s → 1.0s).
- test_gateway_inactivity_timeout.py poll-harness timings shrunk 3-5x
  (24s → 1.1s); test_mcp_stability.py backoff/SIGTERM-grace sleeps
  patched (15.4s → 2.5s); test_async_delegation.py negative-drain wait
  5s → 0.5s.
- test_telegram_init_deadline.py: loop-block margin restored to 1.0s
  with rationale comment — the watchdog-dump assertion needs the loop
  blocked well past deadline+grace under parallel load (flaked once in
  the 40-worker verification run at a 0.2s margin).

Verification: full hermetic suite via scripts/run_tests.sh —
2,438 files, 21,718 tests passed, 0 failed, 293.9s wall.
Suite totals vs original baseline: 46,820 → 19,757 test functions
(−57.8%), wall 583.5s → 293.9s (−50%), subprocess CPU 13,564s → 11,623s.
2026-07-29 13:39:40 -07:00

129 lines
4.6 KiB
Python

"""Pairing store <-> allowlist consolidation (#23778).
Design (union + option-i mirror):
* A pairing-store entry is a first-class authorization grant. A paired user
is authorized regardless of any configured allowlist (union), because
``approve_code`` is reachable only by the trusted operator (CLI/dashboard),
never by an inbound sender.
* When an allowlist IS already configured for the platform, approving a
pairing code ALSO writes the user into that allowlist env var (and revoking
removes them), so the two stay a single operator-visible source of truth.
* On an open gateway (no allowlist configured) approval does NOT create an
allowlist — that would silently lock an open gateway. The pairing store
remains the grant record, honored by the authz union.
"""
import os
from types import SimpleNamespace
import pytest
from gateway.session import Platform, SessionSource
@pytest.fixture(autouse=True)
def _isolate_env(monkeypatch):
for var in (
"TELEGRAM_ALLOWED_USERS",
"TELEGRAM_ALLOW_ALL_USERS",
"TELEGRAM_GROUP_ALLOWED_USERS",
"TELEGRAM_GROUP_ALLOWED_CHATS",
"GATEWAY_ALLOW_ALL_USERS",
"GATEWAY_ALLOWED_USERS",
):
monkeypatch.delenv(var, raising=False)
# --------------------------------------------------------------------------
# authz union: a paired user is authorized regardless of the allowlist
# --------------------------------------------------------------------------
def _make_runner(*, paired: bool):
from gateway.run import GatewayRunner
runner = object.__new__(GatewayRunner)
runner.pairing_store = SimpleNamespace(is_approved=lambda *_a, **_kw: paired)
return runner
def _make_source(user_id: str = "pairme", chat_type: str = "dm"):
return SessionSource(
platform=Platform.TELEGRAM,
chat_id="123",
chat_type=chat_type,
user_id=user_id,
user_name="SomeHuman",
is_bot=False,
)
def test_paired_user_authorized_even_when_not_in_allowlist(monkeypatch):
"""Union semantics: pairing is a grant, honored alongside the allowlist."""
runner = _make_runner(paired=True)
monkeypatch.setenv("TELEGRAM_ALLOWED_USERS", "owner1,owner2")
assert runner._is_user_authorized(_make_source("pairme")) is True
def test_unpaired_user_in_allowlist_still_authorized(monkeypatch):
runner = _make_runner(paired=False)
monkeypatch.setenv("TELEGRAM_ALLOWED_USERS", "owner1")
assert runner._is_user_authorized(_make_source("owner1")) is True
# --------------------------------------------------------------------------
# B2 mirror: approval writes into the allowlist iff one is configured
# --------------------------------------------------------------------------
@pytest.fixture
def store(tmp_path, monkeypatch):
"""A real PairingStore backed by a temp pairing dir."""
monkeypatch.setenv("HERMES_HOME", str(tmp_path / ".hermes"))
(tmp_path / ".hermes").mkdir(parents=True, exist_ok=True)
import importlib
import gateway.pairing as pairing_mod
importlib.reload(pairing_mod)
return pairing_mod.PairingStore()
def _approve_new_user(store, platform, user_id, user_name=""):
code = store.generate_code(platform, user_id, user_name)
assert code is not None
return store.approve_code(platform, code)
def test_approval_adds_to_configured_allowlist(store, monkeypatch):
"""When an allowlist exists, approval appends the user to it (option i)."""
monkeypatch.setenv("TELEGRAM_ALLOWED_USERS", "owner1")
# save_env_value writes to .env under HERMES_HOME; patch it to capture.
captured = {}
import hermes_cli.config as cfg
monkeypatch.setattr(cfg, "save_env_value",
lambda k, v: (captured.__setitem__(k, v),
os.environ.__setitem__(k, v)))
_approve_new_user(store, "telegram", "newuser99")
assert captured.get("TELEGRAM_ALLOWED_USERS") == "owner1,newuser99"
def test_revoke_removes_from_allowlist(store, monkeypatch):
monkeypatch.setenv("TELEGRAM_ALLOWED_USERS", "owner1,newuser99")
saved = {}
removed = []
import hermes_cli.config as cfg
monkeypatch.setattr(cfg, "save_env_value",
lambda k, v: (saved.__setitem__(k, v),
os.environ.__setitem__(k, v)))
monkeypatch.setattr(cfg, "remove_env_value", lambda k: removed.append(k))
# Seed the approved list directly so revoke has something to remove.
store._approve_user("telegram", "newuser99", "")
assert store.revoke("telegram", "newuser99") is True
assert saved.get("TELEGRAM_ALLOWED_USERS") == "owner1"