Spaces:
Sleeping
Sleeping
| """ | |
| 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 βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def client(): | |
| with TestClient(app) as c: | |
| yield c | |
| 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 | |