from types import SimpleNamespace from app.services import learn_lesson_builder def test_mock_provider_builds_a_cached_reading_lesson_without_external_calls( monkeypatch, tmp_path ) -> None: monkeypatch.setattr( learn_lesson_builder, "get_settings", lambda: SimpleNamespace(ai_provider="mock"), ) monkeypatch.setattr(learn_lesson_builder, "PUBLIC_ROOT", tmp_path) monkeypatch.setattr( learn_lesson_builder, "generate_lesson_script", lambda **_kwargs: (_ for _ in ()).throw(AssertionError("Groq should not run")), ) monkeypatch.setattr( learn_lesson_builder, "synthesize_beats", lambda *_args, **_kwargs: (_ for _ in ()).throw( AssertionError("TTS should not run") ), ) manifest = learn_lesson_builder.build_lesson( topic="Python programming", lesson_title="Python Programming", level="beginner", ) assert manifest["deliveryMode"] == "reading" assert manifest["isFallback"] is True assert "Local preview" in manifest["deliveryNotice"] assert len(manifest["beats"]) >= 6 assert all(beat["audio_src"] == "" for beat in manifest["beats"]) assert "variables" in str(manifest).lower() assert (tmp_path / "unowned" / manifest["lessonHash"] / "lesson.json").exists() def test_provider_timeout_returns_bounded_reading_lesson(monkeypatch, tmp_path) -> None: monkeypatch.setattr( learn_lesson_builder, "get_settings", lambda: SimpleNamespace(ai_provider="sarvam"), ) monkeypatch.setattr(learn_lesson_builder, "PUBLIC_ROOT", tmp_path) monkeypatch.setattr(learn_lesson_builder, "_lesson_sync_tts_enabled", lambda: False) monkeypatch.setattr(learn_lesson_builder, "_lesson_generation_budget_seconds", lambda: 1.0) monkeypatch.setattr( learn_lesson_builder, "_script_llm_providers", lambda: [{"name": "test", "call": lambda _prompt: (_ for _ in ()).throw(TimeoutError())}], ) monkeypatch.setattr( learn_lesson_builder, "synthesize_beats", lambda *_args, **_kwargs: (_ for _ in ()).throw(AssertionError("TTS must not run")), ) manifest = learn_lesson_builder.build_lesson( topic="Basic Python for data analysis", lesson_title="Python foundations", level="beginner", ) assert manifest["deliveryMode"] == "reading" assert manifest["isFallback"] is True assert "reading" in manifest["deliveryNotice"].lower() assert manifest["beats"] def test_authored_lesson_can_return_reading_mode_without_audio(monkeypatch, tmp_path) -> None: monkeypatch.setattr( learn_lesson_builder, "get_settings", lambda: SimpleNamespace(ai_provider="sarvam"), ) monkeypatch.setattr(learn_lesson_builder, "PUBLIC_ROOT", tmp_path) monkeypatch.setattr(learn_lesson_builder, "_lesson_sync_tts_enabled", lambda: False) monkeypatch.setattr( learn_lesson_builder, "generate_lesson_script", lambda **_kwargs: { "lesson_title": "Authored lesson", "summary": "A real authored reading class.", "objectives": ["Explain the idea"], "beats": [ { "kind": "explain", "narration": "This is a complete authored explanation that a student can read and study without waiting for a voice asset.", "board_heading": "Core idea", "board_lines": ["Reason first"], } ], "notes": ["A useful note."], "flashcards": [{"front": "What matters?", "back": "The reason."}], }, ) manifest = learn_lesson_builder.build_lesson( topic="A real topic", lesson_title="Authored lesson", level="beginner", ) assert manifest["deliveryMode"] == "reading" assert manifest["summary"] == "A real authored reading class." assert manifest["beats"][0]["audio_src"] == ""