"""Test di scripts/camera_service.py (#72). Uso: vendor/visionsuite/.venv/bin/python -m pytest scripts/tests/test_camera_service.py -q Dipende da: venv di VisionSuite (vs-camera, fastapi, httpx, opencv, pytest). """ import importlib.util from pathlib import Path import cv2 import numpy as np import pytest from fastapi.testclient import TestClient MODULO = Path(__file__).resolve().parents[1] / "camera_service.py" def _carica(): spec = importlib.util.spec_from_file_location("camera_service", MODULO) mod = importlib.util.module_from_spec(spec) spec.loader.exec_module(mod) return mod @pytest.fixture def cartella(tmp_path): for nome, (w, h) in {"a.png": (64, 48), "b.png": (64, 48)}.items(): cv2.imwrite(str(tmp_path / nome), np.full((h, w, 3), 128, np.uint8)) return tmp_path @pytest.fixture def client(cartella): mod = _carica() with TestClient(mod.create_app(cartella)) as c: c.mod = mod yield c def test_sources_contiene_folder(client): r = client.get("/camera/sources") assert r.status_code == 200 nomi = [s["name"] for s in r.json()["sources"]] assert "camera:folder" in nomi voce = next(s for s in r.json()["sources"] if s["name"] == "camera:folder") assert {"name", "kind", "label"} <= set(voce) def test_grab_folder_restituisce_png(client): r = client.post("/camera/grab", json={"source": "camera:folder"}) assert r.status_code == 200 assert r.headers["content-type"] == "image/png" img = cv2.imdecode(np.frombuffer(r.content, np.uint8), cv2.IMREAD_COLOR) assert img.shape[:2] == (48, 64) def test_grab_ripetuto_in_loop(client): # due immagini in cartella, tre grab: il loop non deve esaurirsi for _ in range(3): assert client.post("/camera/grab", json={"source": "camera:folder"}).status_code == 200 def test_source_mancante_400(client): assert client.post("/camera/grab", json={}).status_code == 400 assert client.post("/camera/grab", json={"source": " "}).status_code == 400 def test_source_inesistente_404(client): assert client.post("/camera/grab", json={"source": "camera:non-esiste"}).status_code == 404 def test_timeout_non_valido_400(client): r = client.post("/camera/grab", json={"source": "camera:folder", "timeout_ms": 0}) assert r.status_code == 400 def test_errore_camera_503_tradotto(client, monkeypatch): from visionsuite.camera.camera_interface import CameraGrabError class Rotta: def grab(self, timeout): raise CameraGrabError("Camera is not open") def close(self): pass monkeypatch.setattr(client.mod, "open_source", lambda *a, **k: Rotta()) r = client.post("/camera/grab", json={"source": "camera:finta"}) assert r.status_code == 503 assert r.json()["detail"] == "La camera non รจ aperta" def test_timeout_504(client, monkeypatch): class Lenta: def grab(self, timeout): raise TimeoutError("scaduto") def close(self): pass monkeypatch.setattr(client.mod, "open_source", lambda *a, **k: Lenta()) r = client.post("/camera/grab", json={"source": "camera:lenta"}) assert r.status_code == 504 def test_sorgente_aperta_una_volta(client, monkeypatch): aperture = [] reale = client.mod.open_source def conta(*a, **k): aperture.append(a[0]) return reale(*a, **k) monkeypatch.setattr(client.mod, "open_source", conta) for _ in range(3): client.post("/camera/grab", json={"source": "camera:folder"}) assert aperture == ["camera:folder"]