Files
detections/e2e/conftest.py
T
2026-03-23 14:07:54 +02:00

191 lines
4.6 KiB
Python

import base64
import json
import random
import time
from contextlib import contextmanager
from pathlib import Path
import pytest
import requests
import sseclient
from pytest import ExitCode
@pytest.hookimpl(trylast=True)
def pytest_sessionfinish(session, exitstatus):
if exitstatus in (ExitCode.NO_TESTS_COLLECTED, 5):
session.exitstatus = ExitCode.OK
class _SessionWithBase(requests.Session):
def __init__(self, base: str, default_timeout: float = 30):
super().__init__()
self._base = base.rstrip("/")
self._default_timeout = default_timeout
def request(self, method, url, **kwargs):
if url.startswith("http://") or url.startswith("https://"):
full = url
else:
path = url if url.startswith("/") else f"/{url}"
full = f"{self._base}{path}"
kwargs.setdefault("timeout", self._default_timeout)
return super().request(method, full, **kwargs)
@pytest.fixture(scope="session")
def base_url():
return "http://detections:8080"
@pytest.fixture(scope="session")
def http_client(base_url):
return _SessionWithBase(base_url, 30)
@pytest.fixture
def sse_client_factory(http_client):
@contextmanager
def _open():
with http_client.get("/detect/stream", stream=True, timeout=600) as resp:
resp.raise_for_status()
yield sseclient.SSEClient(resp)
return _open
@pytest.fixture(scope="session")
def mock_loader_url():
return "http://mock-loader:8080"
@pytest.fixture(scope="session")
def mock_annotations_url():
return "http://mock-annotations:8081"
@pytest.fixture(scope="session", autouse=True)
def wait_for_services(base_url, mock_loader_url, mock_annotations_url):
urls = [
f"{base_url}/health",
f"{mock_loader_url}/mock/status",
f"{mock_annotations_url}/mock/status",
]
deadline = time.time() + 120
while time.time() < deadline:
ok = True
for u in urls:
try:
r = requests.get(u, timeout=5)
if r.status_code != 200:
ok = False
break
except OSError:
ok = False
break
if ok:
return
time.sleep(2)
pytest.fail("services not ready within 120s")
@pytest.fixture(autouse=True)
def reset_mocks(mock_loader_url, mock_annotations_url):
requests.post(f"{mock_loader_url}/mock/reset", timeout=10)
requests.post(f"{mock_annotations_url}/mock/reset", timeout=10)
yield
def _read_media(name: str) -> bytes:
p = Path("/media") / name
if not p.is_file():
pytest.skip(f"missing {p}")
return p.read_bytes()
@pytest.fixture(scope="session")
def image_small():
return _read_media("image_small.jpg")
@pytest.fixture(scope="session")
def image_large():
return _read_media("image_large.JPG")
@pytest.fixture(scope="session")
def image_dense():
return _read_media("image_dense01.jpg")
@pytest.fixture(scope="session")
def image_dense_02():
return _read_media("image_dense02.jpg")
@pytest.fixture(scope="session")
def image_different_types():
return _read_media("image_different_types.jpg")
@pytest.fixture(scope="session")
def image_empty_scene():
return _read_media("image_empty_scene.jpg")
@pytest.fixture(scope="session")
def video_short_path():
return "/media/video_short01.mp4"
@pytest.fixture(scope="session")
def video_short_02_path():
return "/media/video_short02.mp4"
@pytest.fixture(scope="session")
def video_long_path():
return "/media/video_long03.mp4"
@pytest.fixture(scope="session")
def empty_image():
return b""
@pytest.fixture(scope="session")
def corrupt_image():
random.seed(42)
return random.randbytes(1024)
def _b64url_obj(obj: dict) -> str:
raw = json.dumps(obj, separators=(",", ":")).encode()
return base64.urlsafe_b64encode(raw).decode().rstrip("=")
@pytest.fixture
def jwt_token():
header = (
base64.urlsafe_b64encode(json.dumps({"alg": "none", "typ": "JWT"}).encode())
.decode()
.rstrip("=")
)
payload = _b64url_obj({"exp": int(time.time()) + 3600, "sub": "test"})
return f"{header}.{payload}.signature"
@pytest.fixture(scope="module")
def warm_engine(http_client, image_small):
deadline = time.time() + 120
files = {"file": ("warm.jpg", image_small, "image/jpeg")}
while time.time() < deadline:
try:
r = http_client.post("/detect", files=files)
if r.status_code == 200:
return
except OSError:
pass
time.sleep(2)
pytest.fail("engine warm-up failed after 120s")