mirror of
https://github.com/azaion/detections.git
synced 2026-04-22 23:36:32 +00:00
86d8e7e22d
Made-with: Cursor
191 lines
4.6 KiB
Python
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")
|