arcisvlm / tests /test_api.py
Hardik Sanghvi
feat: integrate Gemma 4 E2B backbone for production-quality VLM inference
7a564e3
Raw
History Blame Contribute Delete
6.72 kB
"""
Tests for the ArcisVLM FastAPI backend.
Uses TestClient (synchronous) from FastAPI for fast, no-server-needed testing.
Model is NOT loaded in tests — we test API contract, routing, and validation.
"""
import pytest
import sys
import os
# Ensure project root is on path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
# Set env to skip model loading for fast tests
os.environ["ARCISVLM_CONFIG"] = "configs/scale_1.3b.yaml"
# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------
@pytest.fixture(scope="module")
def client():
"""Create a TestClient for the FastAPI app."""
from fastapi.testclient import TestClient
from api.main import app
with TestClient(app) as c:
yield c
# ---------------------------------------------------------------------------
# Health endpoint
# ---------------------------------------------------------------------------
class TestHealth:
def test_health_returns_200(self, client):
resp = client.get("/health")
assert resp.status_code == 200
def test_health_has_status(self, client):
resp = client.get("/health")
data = resp.json()
assert "status" in data
assert data["status"] in ("ok", "starting")
def test_health_has_model_info(self, client):
resp = client.get("/health")
data = resp.json()
assert "model" in data
assert data["model"] == "arcisvlm-1.6b"
assert "version" in data
# ---------------------------------------------------------------------------
# Inference endpoints
# ---------------------------------------------------------------------------
class TestInference:
def test_query_requires_question(self, client):
"""POST /api/v1/query without 'question' should return 422."""
resp = client.post("/api/v1/query", json={})
assert resp.status_code == 422
def test_query_requires_image_source(self, client):
"""POST /api/v1/query with question but no image should return 400."""
resp = client.post("/api/v1/query", json={"question": "What is this?"})
assert resp.status_code == 400
def test_query_validates_temperature(self, client):
"""Temperature out of range should return 422."""
resp = client.post("/api/v1/query", json={
"question": "test",
"image_path": "/tmp/test.jpg",
"temperature": 5.0,
})
assert resp.status_code == 422
def test_query_validates_max_tokens(self, client):
"""max_tokens < 1 should return 422."""
resp = client.post("/api/v1/query", json={
"question": "test",
"image_path": "/tmp/test.jpg",
"max_tokens": 0,
})
assert resp.status_code == 422
def test_embed_requires_image(self, client):
"""POST /api/v1/embed without image should return 400 or 503."""
resp = client.post("/api/v1/embed", json={})
assert resp.status_code in (400, 503)
# ---------------------------------------------------------------------------
# Agent endpoints
# ---------------------------------------------------------------------------
class TestAgents:
def test_agents_status_returns_200(self, client):
resp = client.get("/api/v1/agents/status")
assert resp.status_code == 200
def test_agents_status_has_agents_key(self, client):
resp = client.get("/api/v1/agents/status")
data = resp.json()
assert "agents" in data
assert "total_agents" in data
def test_agent_types_returns_list(self, client):
resp = client.get("/api/v1/agents/types")
assert resp.status_code == 200
data = resp.json()
assert "expert_types" in data
assert "vqa" in data["expert_types"]
assert "detect" in data["expert_types"]
# ---------------------------------------------------------------------------
# Alert endpoints
# ---------------------------------------------------------------------------
class TestAlerts:
def test_list_rules_returns_200(self, client):
resp = client.get("/api/v1/alerts/rules")
assert resp.status_code == 200
assert isinstance(resp.json(), list)
def test_create_rule(self, client):
"""Create an alert rule and verify it returns correctly."""
rule = {
"rule_id": "test-person-alert",
"condition_type": "presence",
"target_object": "person",
"action": "log",
}
resp = client.post("/api/v1/alerts/rules", json=rule)
# May return 200 if orchestrator is up, or 503 if not
assert resp.status_code in (200, 503)
if resp.status_code == 200:
data = resp.json()
assert data["rule_id"] == "test-person-alert"
assert data["condition_type"] == "presence"
def test_alert_history_returns_200(self, client):
resp = client.get("/api/v1/alerts/history")
assert resp.status_code == 200
assert isinstance(resp.json(), list)
def test_alert_history_limit(self, client):
resp = client.get("/api/v1/alerts/history?limit=10")
assert resp.status_code == 200
# ---------------------------------------------------------------------------
# Stream endpoints
# ---------------------------------------------------------------------------
class TestStreams:
def test_streams_status_returns_200(self, client):
resp = client.get("/api/v1/streams/status")
assert resp.status_code == 200
assert isinstance(resp.json(), list)
def test_start_stream_validates_fields(self, client):
"""Missing required fields should return 422."""
resp = client.post("/api/v1/streams/start", json={})
assert resp.status_code == 422
def test_start_stream_validates_fps(self, client):
"""FPS out of range should return 422."""
resp = client.post("/api/v1/streams/start", json={
"camera_id": "test",
"rtsp_url": "rtsp://test",
"target_fps": 100.0,
})
assert resp.status_code == 422
# ---------------------------------------------------------------------------
# OpenAPI docs
# ---------------------------------------------------------------------------
class TestDocs:
def test_openapi_schema(self, client):
resp = client.get("/openapi.json")
assert resp.status_code == 200
schema = resp.json()
assert schema["info"]["title"] == "ArcisVLM API"
assert "/health" in schema["paths"]
assert "/api/v1/query" in schema["paths"]