Files
OpenMontage/tests/conftest.py
2026-08-13 10:40:29 -07:00

132 lines
4.3 KiB
Python

"""Session-wide test safety net.
**No test may open a network connection.** Provider tools bill per call, so a
test that reaches a real endpoint costs the developer money — silently, and
every time CI runs. This blocks outbound sockets for the whole test session.
The guard is at the socket layer on purpose. Patching `requests` only covers
tools that use `requests`; the fleet also talks to vendor SDKs (google-cloud,
openai, boto3), `httpx`, and raw `urllib`. Everything bottoms out in
`socket.connect`, so that is where the wall goes.
Loopback is still allowed — local servers, ffmpeg RPC, and Backlot fixtures need it.
To write a test that genuinely hits a live API:
@pytest.mark.live_api
def test_real_call():
...
Marked tests are **skipped by default** and only run with the env flag set:
OPENMONTAGE_ALLOW_NETWORK=1 pytest -m live_api
Limitation: this guards the pytest process. A test that shells out to a
subprocess (node, ffmpeg, npx) is outside its reach — don't call paid APIs
from a subprocess in tests.
"""
from __future__ import annotations
import os
import socket
import pytest
_ALLOW_ENV_FLAG = "OPENMONTAGE_ALLOW_NETWORK"
_LOOPBACK_HOSTS = {"localhost", "127.0.0.1", "::1", "0.0.0.0", ""}
_real_connect = socket.socket.connect
_real_connect_ex = socket.socket.connect_ex
_real_create_connection = socket.create_connection
class NetworkCallInTestError(RuntimeError):
"""Raised when a test tries to open a non-loopback connection."""
def _network_allowed() -> bool:
return os.environ.get(_ALLOW_ENV_FLAG, "").strip().lower() in {"1", "true", "yes"}
def _is_loopback(address) -> bool:
"""True for loopback TCP/UDP targets and for AF_UNIX socket paths."""
if isinstance(address, (str, bytes)):
return True # AF_UNIX / abstract socket — local by definition
if not isinstance(address, (tuple, list)) or not address:
return True # unrecognised shape; let the real call decide
host = address[0]
if isinstance(host, bytes):
host = host.decode("utf-8", "replace")
if not isinstance(host, str):
return False
host = host.strip("[]").lower()
if host in _LOOPBACK_HOSTS:
return True
return host.startswith("127.")
def _blocked(address) -> NetworkCallInTestError:
return NetworkCallInTestError(
f"Blocked a network connection to {address!r} during a test.\n"
f"\n"
f"Tests must not call real endpoints — provider APIs bill per request.\n"
f"Mock the transport instead (see tests/tools/test_atlas_video.py for the\n"
f"fake-`requests` pattern), or mark the test @pytest.mark.live_api and run\n"
f"it deliberately with {_ALLOW_ENV_FLAG}=1."
)
@pytest.fixture(scope="session", autouse=True)
def _block_network():
"""Refuse non-loopback sockets for the entire session."""
if _network_allowed():
yield
return
def guarded_connect(self, address, *args, **kwargs):
if not _is_loopback(address):
raise _blocked(address)
return _real_connect(self, address, *args, **kwargs)
def guarded_connect_ex(self, address, *args, **kwargs):
if not _is_loopback(address):
raise _blocked(address)
return _real_connect_ex(self, address, *args, **kwargs)
def guarded_create_connection(address, *args, **kwargs):
if not _is_loopback(address):
raise _blocked(address)
return _real_create_connection(address, *args, **kwargs)
socket.socket.connect = guarded_connect
socket.socket.connect_ex = guarded_connect_ex
socket.create_connection = guarded_create_connection
try:
yield
finally:
socket.socket.connect = _real_connect
socket.socket.connect_ex = _real_connect_ex
socket.create_connection = _real_create_connection
def pytest_configure(config):
config.addinivalue_line(
"markers",
"live_api: test performs a real, billable API call. Skipped unless "
f"{_ALLOW_ENV_FLAG}=1 is set.",
)
def pytest_collection_modifyitems(config, items):
"""Skip live_api tests unless the operator explicitly opted in."""
if _network_allowed():
return
skip = pytest.mark.skip(
reason=f"live API test — costs money; set {_ALLOW_ENV_FLAG}=1 to run"
)
for item in items:
if "live_api" in item.keywords:
item.add_marker(skip)