lecturelens-api / tests /test_api.py
aliSaac510's picture
feat: combined audio/video analysis with reduced alert noise
69923c9
Raw
History Blame Contribute Delete
3.94 kB
"""
LectureLens β€” Tests: API Integration
Run with: pytest tests/test_api.py -v
Requires all dependencies installed (no Docker).
"""
from __future__ import annotations
import io
import time
import wave
import numpy as np
import pytest
from fastapi.testclient import TestClient
from app.main import app
# ── Fixtures ───────────────────────────────────────────────────────────────────
@pytest.fixture(scope="module")
def client():
with TestClient(app) as c:
yield c
@pytest.fixture(scope="module")
def wav_bytes() -> bytes:
"""Minimal 2-second sine WAV in memory."""
sr = 16_000
duration = 2
t = np.linspace(0, duration, sr * duration, endpoint=False)
samples = (np.sin(2 * np.pi * 440 * t) * 0.3 * 32767).astype(np.int16)
buf = io.BytesIO()
with wave.open(buf, "w") as wf:
wf.setnchannels(1)
wf.setsampwidth(2)
wf.setframerate(sr)
wf.writeframes(samples.tobytes())
return buf.getvalue()
# ── Health ─────────────────────────────────────────────────────────────────────
def test_health(client):
r = client.get("/health")
assert r.status_code == 200
assert r.json()["status"] == "ok"
# ── Polling Analysis ───────────────────────────────────────────────────────────
def test_audio_analysis_polling(client, wav_bytes):
# 1. Submit the job
r = client.post(
"/analyze",
data={"media_type": "audio", "participant_label": "test_speaker"},
files={"file": ("test.wav", wav_bytes, "audio/wav")},
)
assert r.status_code == 200, r.text
submit_body = r.json()
assert submit_body["status"] == "processing"
assert "job_id" in submit_body
job_id = submit_body["job_id"]
# 2. Poll for the result
for _ in range(10):
r_poll = client.get(f"/analyze/{job_id}")
assert r_poll.status_code == 200
poll_body = r_poll.json()
# TestClient actually runs BackgroundTasks synchronously at the end of the request.
# So by the time we poll, it's usually already 'completed'.
if poll_body["status"] == "completed":
res = poll_body["result"]
assert res["media_type"] == "audio"
assert res["participant_label"] == "test_speaker"
assert "audio_metrics" in res
assert res["audio_metrics"] is not None
assert res["video_metrics"] is None
assert "alerts" in res
assert res["overall_audio_score"] is not None
break
elif poll_body["status"] == "failed":
pytest.fail(f"Job failed: {poll_body['error_detail']}")
time.sleep(0.5)
else:
pytest.fail("Job did not complete within timeout")
# ── Validation ─────────────────────────────────────────────────────────────────
def test_wrong_extension_returns_400(client, wav_bytes):
r = client.post(
"/analyze",
data={"media_type": "video"}, # declare video but send .wav
files={"file": ("test.wav", wav_bytes, "audio/wav")},
)
assert r.status_code == 400
assert r.json()["error_code"] == "INVALID_MEDIA_TYPE"
def test_job_not_found(client):
r = client.get("/analyze/non-existent-job-id")
assert r.status_code == 404
assert r.json()["error_code"] == "NOT_FOUND"
def test_docs_accessible(client):
r = client.get("/docs")
assert r.status_code == 200