Files
OpenMontage/tests/conftest.py

132 lines
4.3 KiB
Python
Raw Permalink Normal View History

"""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)