Gargi_AI / tests /conftest.py
Sameer Singh
Added
5aaf5ba
Raw
History Blame Contribute Delete
4.22 kB
import os
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from pathlib import Path
from types import SimpleNamespace
import pytest
from fastapi.testclient import TestClient
os.environ["DATABASE_URL"] = "sqlite:///./data/test_gargi_ai.db"
os.environ["GEMINI_API_KEY"] = "test-key"
from gargi_ai.config import get_settings # noqa: E402
from gargi_ai.database import Base, engine # noqa: E402
from gargi_ai.main import app # noqa: E402
from gargi_ai.providers import LiveProvider, LLMProvider, TTSProvider # noqa: E402
from gargi_ai.schemas import QuizQuestion, TeachingPayload, WhiteboardBlock # noqa: E402
class FakeLLM(LLMProvider):
async def stream_explanation(self, prompt: str) -> AsyncIterator[str]:
yield "Gravity "
yield "pulls objects together."
async def generate_artifact(self, prompt: str) -> TeachingPayload:
return TeachingPayload(
spoken_explanation="Gravity pulls objects together.",
summary="Gravity is an attractive force.",
whiteboard_blocks=[
WhiteboardBlock(type="heading", content="Gravity"),
WhiteboardBlock(type="equation", content="F = Gm1m2/r^2"),
],
quiz=[
QuizQuestion(
id="q1",
type="multiple_choice",
question="What does gravity do?",
options=["Attracts", "Repels"],
expected_answer="Attracts",
explanation="Masses attract.",
),
QuizQuestion(
id="q2",
type="multiple_choice",
question="Which has gravity?",
options=["Mass", "Nothing"],
expected_answer="Mass",
explanation="All mass gravitates.",
),
QuizQuestion(
id="q3",
type="short_answer",
question="Name the force.",
expected_answer="Gravity",
explanation="The force is gravity.",
),
],
detected_misconceptions=[],
learner_level_update="beginner",
suggested_follow_up="Explore orbits.",
)
class FakeTTS(TTSProvider):
def __init__(self, audio_dir: Path):
self.audio_dir = audio_dir
async def synthesize(self, text: str, voice: str):
audio_id = "11111111-1111-1111-1111-111111111111"
output = self.audio_dir / f"{audio_id}.mp3"
output.parent.mkdir(parents=True, exist_ok=True)
output.write_bytes(b"fake-mp3")
return audio_id, output
class FakeLiveSession:
def __init__(self):
self.received_audio = False
self.delivered = False
async def send_realtime_input(self, **kwargs):
if kwargs.get("audio"):
self.received_audio = True
async def receive(self):
while not self.received_audio or self.delivered:
import asyncio
await asyncio.sleep(0.01)
self.delivered = True
part = SimpleNamespace(
inline_data=SimpleNamespace(data=b"fake-live-pcm")
)
content = SimpleNamespace(
input_transcription=SimpleNamespace(text="Why does gravity work?"),
output_transcription=SimpleNamespace(
text="Gravity attracts objects with mass."
),
model_turn=SimpleNamespace(parts=[part]),
interrupted=False,
turn_complete=True,
)
yield SimpleNamespace(server_content=content)
class FakeLive(LiveProvider):
@asynccontextmanager
async def connect(self, system_instruction: str):
yield FakeLiveSession()
@pytest.fixture
def client():
settings = get_settings()
Base.metadata.drop_all(bind=engine)
settings.audio_dir.mkdir(parents=True, exist_ok=True)
with TestClient(app) as test_client:
app.state.llm = FakeLLM()
app.state.tts = FakeTTS(settings.audio_dir)
app.state.live = FakeLive()
yield test_client
Base.metadata.drop_all(bind=engine)