from __future__ import annotations from pathlib import Path def test_tts_cache_hit_avoids_provider_call(client, monkeypatch, tmp_path: Path) -> None: from app.core import config from app.schemas.video import VideoSceneAudioInput, VideoScenePlanAudioInput from app.services import tts_provider monkeypatch.setenv("TTS_PROVIDER", "mock") monkeypatch.setenv("TTS_OUTPUT_DIR", str(tmp_path / "audio")) config.get_settings.cache_clear() class CountingProvider(tts_provider.MockTTSProvider): calls = 0 def generate_scene_audio(self, **kwargs): type(self).calls += 1 return super().generate_scene_audio(**kwargs) monkeypatch.setattr(tts_provider, "get_tts_provider", lambda provider_name=None: CountingProvider()) scene = VideoSceneAudioInput( scene_id=1, id=1, type="explanation", duration_seconds=2, screen_text="Photosynthesis", voice_text="Plants make food using sunlight.", subtitle_text="Plants make food using sunlight.", keywords=["plants"], ) plan = VideoScenePlanAudioInput( title="Biology", duration_minutes=0.1, language="English", style="clean_explainer", visual_style="clean_explainer", video_format="16:9", voice_mode="english_soft", scenes=[scene], ) first = tts_provider.generate_audio_for_scene_plan( scene_plan=plan, voice_mode="english_soft", voice="nila", language="English", provider_name="mock", video_id="video-one", user_id="usr_demo_student", use_cache=True, ) second = tts_provider.generate_audio_for_scene_plan( scene_plan=plan, voice_mode="english_soft", voice="nila", language="English", provider_name="mock", video_id="video-two", user_id="usr_demo_student", use_cache=True, ) assert CountingProvider.calls == 1 assert first["audio_files"][0]["cache_hit"] is False assert second["audio_files"][0]["cache_hit"] is True def test_scene_plan_cache_hit_avoids_regeneration(client) -> None: from app.core.database import SessionLocal from app.services.generation_cache import ( get_cached_generation, scene_plan_cache_key, store_generation_cache, ) key = scene_plan_cache_key( document_id="doc_1", material_hash="hash-a", video_mode="exam_study_video", target_duration_seconds=600, language="English", evidence_level="document_only", prompt_version="v1", ) with SessionLocal() as db: store_generation_cache( db=db, cache_key=key, task_type="scene_plan", provider="openrouter", input_hash="hash-a", output_json={"title": "Cached plan", "scenes": []}, ) cached = get_cached_generation( db=db, cache_key=key, task_type="scene_plan", provider="openrouter", ) assert cached is not None assert cached.output_json == {"title": "Cached plan", "scenes": []} def test_cache_key_changes_when_prompt_version_changes() -> None: from app.services.generation_cache import study_material_cache_key v1 = study_material_cache_key( document_id="doc_1", content_hash="content-a", task_type="quiz_generation", evidence_level="document_only", prompt_version="v1", ) v2 = study_material_cache_key( document_id="doc_1", content_hash="content-a", task_type="quiz_generation", evidence_level="document_only", prompt_version="v2", ) assert v1 != v2 def test_cache_hit_records_usage_with_cache_hit_true(client) -> None: from app.core.database import SessionLocal from app.models.provider_usage_log import ProviderUsageLog from app.services.generation_cache import get_cached_generation, store_generation_cache with SessionLocal() as db: store_generation_cache( db=db, cache_key="notes:abc", task_type="notes_generation", provider="openrouter", input_hash="abc", output_text="cached notes", ) cached = get_cached_generation( db=db, cache_key="notes:abc", task_type="notes_generation", provider="openrouter", user_id="usr_demo_student", ) logs = db.query(ProviderUsageLog).filter(ProviderUsageLog.cache_hit.is_(True)).all() assert cached is not None assert len(logs) == 1 assert logs[0].request_units == 0 assert logs[0].estimated_cost_usd == 0 def test_cache_hit_records_usage_when_helper_owns_session(client) -> None: from app.core.database import SessionLocal from app.models.provider_usage_log import ProviderUsageLog from app.services.generation_cache import get_cached_generation, store_generation_cache store_generation_cache( cache_key="notes:owned-session", task_type="notes_generation", provider="openrouter", input_hash="owned-session", output_text="cached notes", ) cached = get_cached_generation( cache_key="notes:owned-session", task_type="notes_generation", provider="openrouter", user_id="usr_demo_student", ) with SessionLocal() as db: logs = ( db.query(ProviderUsageLog) .filter( ProviderUsageLog.task_type == "notes_generation", ProviderUsageLog.cache_hit.is_(True), ) .all() ) assert cached is not None assert any(log.provider == "openrouter" for log in logs)