[AZ-137] [AZ-138] Decompose test tasks and scaffold E2E test infrastructure

Made-with: Cursor
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-03-23 14:07:54 +02:00
parent 091d9a8fb0
commit 86d8e7e22d
47 changed files with 1883 additions and 88 deletions
+190
View File
@@ -0,0 +1,190 @@
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")