Files
John SmithandClaude 4e8477d08b feat(gateway): admin bootstrap allowlist, fail-closed secret, lobby membership gate (iteration 12)
Signup accepts only player/spectator — staff roles come from the
VTT_ADMIN_EMAILS allowlist (rotatable without restart). Gateway aborts
startup without AUTH_SECRET/VTT_ENGINE_SECRET, mirroring engine semantics.
GET /lobbies/{id} member-or-staff only with no existence oracle; invite
codes stay visible to members. 20 tests red-first; pytest 672 passed.

Co-Authored-By: Claude <[email protected]>
2026-08-24 21:14:10 -04:00

75 lines
3.0 KiB
Python

"""Shared pytest configuration for the orchestrator test suite.
Implements the opt-in gate for the LIVE LLM suite (``pytest.mark.live``):
A test marked ``@pytest.mark.live`` runs ONLY when BOTH hold:
1. ``LLM_KEY`` is set in the environment (a real external endpoint is
configured -- see ``LLMConfig`` in
``vtt_orchestrator/routing/llm_client.py``), AND
2. ``RUN_LIVE_LLM=1`` is exported explicitly.
CI never exports ``RUN_LIVE_LLM``, so live tests can never execute there;
locally they are skipped (not failed) unless the operator opts in. The skip
decision lives here so every test in the live module gets it for free and no
individual test has to duplicate the guard.
"""
import os
# The gateway now REFUSES to start without a signing secret (audit F6b: the
# old hardcoded dev fallback made session tokens forgeable). Production and
# every launcher script export AUTH_SECRET explicitly; the suite pins the
# well-known dev value here for exactly the same reason — deliberately, not
# by silent fallback inside server.py.
os.environ.setdefault("AUTH_SECRET", "aethertable-dev-secret")
import pytest
def _live_llm_enabled() -> bool:
"""True only when a key is configured AND the opt-in flag is exported."""
if not os.environ.get("LLM_KEY"):
return False
return os.environ.get("RUN_LIVE_LLM", "0").strip().lower() == "1"
def pytest_collection_modifyitems(config, items):
if _live_llm_enabled():
return
skip_live = pytest.mark.skip(
reason="live LLM test: requires LLM_KEY set AND RUN_LIVE_LLM=1 "
"(opt-in; never runs in CI)"
)
for item in items:
if "live" in item.keywords:
item.add_marker(skip_live)
@pytest.fixture(autouse=True)
def _isolate_rate_limiter_windows():
"""Reset the orchestrator's in-process sliding-window rate limiter around
EVERY test.
``vtt_orchestrator.server`` keeps its 60 s windows in module state
(``_rate_windows``), keyed by ``(client_ip, bucket)``. Every TestClient in
the suite shares one source address ("testclient"), and several modules
hammer the auth bucket, whose limit is only 30 requests / 60 s. Without a
reset owned by the harness itself, whether a given module passes depends on
how many auth requests OTHER modules happened to make within the last
minute of wall clock — a nondeterministic flake that no single test can fix
from inside.
Two modules (test_campaign_sim, test_ai_companion) already carried their
own copies of this fixture; it lives here so no module's green-ness depends
on another module remembering to clean up. Per-test scope is deliberate:
clearing costs a dict clear (~microseconds), while module-scope would still
let one long test starve its siblings inside the same file.
"""
from vtt_orchestrator import server as server_module
server_module._rate_windows.clear()
yield
# Also clear on exit so hits recorded by a test never leak into ambient
# state (e.g. a later test asserting on window contents).
server_module._rate_windows.clear()