import pytest from unittest.mock import MagicMock from app.agents.tutor_agent import TutorAgent from app.schemas.graph import HTML5VisualPayload, NodeData class AsyncIterator: def __init__(self, items): self.items = items def __aiter__(self): return self async def __anext__(self): if not self.items: raise StopAsyncIteration return self.items.pop(0) @pytest.mark.asyncio async def test_stream_lesson_content_only(): tutor = TutorAgent.__new__(TutorAgent) mock_client = MagicMock() tutor._client = mock_client mock_client.stream_complete.return_value = AsyncIterator(["This ", "is ", "a ", "lesson."]) node = NodeData(id="n1", label="Smart Pace", status="ongoing") chunks = [{"source": "adam.pdf", "text": "Adam optimizer uses moment estimation."}] tokens = [] async for token in tutor.stream_lesson(node, chunks, "high_school", "content_only"): tokens.append(token) assert "".join(tokens) == "This is a lesson." mock_client.stream_complete.assert_called_once() messages = mock_client.stream_complete.call_args[0][0] assert len(messages) == 2 assert "Smart Pace" in messages[1]["content"] assert "Base the lesson EXCLUSIVELY on the provided source material" in messages[0]["content"] @pytest.mark.asyncio async def test_stream_lesson_net_support(): tutor = TutorAgent.__new__(TutorAgent) mock_client = MagicMock() tutor._client = mock_client mock_client.stream_complete.return_value = AsyncIterator(["This ", "is ", "web ", "lesson."]) node = NodeData(id="n1", label="Smart Pace", status="ongoing") chunks = [{"source": "adam.pdf", "text": "Adam optimizer uses moment estimation."}] web_context = "Smart Pace is a new dynamic step size method." tokens = [] async for token in tutor.stream_lesson( node, chunks, "high_school", "net_support", web_context=web_context ): tokens.append(token) assert "".join(tokens) == "This is web lesson." mock_client.stream_complete.assert_called_once() messages = mock_client.stream_complete.call_args[0][0] assert "WEB SOURCE MATERIAL:" in messages[1]["content"] assert "Smart Pace is a new dynamic step size method." in messages[1]["content"] assert "Ground your explanation in the provided SOURCE MATERIAL and WEB SOURCE MATERIAL" in messages[0]["content"] def test_repair_visual_blank_render_error_gets_blank_render_guidance(): tutor = TutorAgent.__new__(TutorAgent) mock_client = MagicMock() tutor._client = mock_client fake_payload = HTML5VisualPayload(html_code="", animation_type="3d") mock_client.structured_complete.return_value = fake_payload result = tutor.repair_visual( original_html="original", error_message="BlankRender: no renderable objects were added within 900ms", ) assert result is fake_payload mock_client.structured_complete.assert_called_once() messages = mock_client.structured_complete.call_args[0][0] system_content = messages[0]["content"] assert "did NOT crash" in system_content assert "group.add(...)" in system_content assert "return { ... }" in system_content or "return {" in system_content assert "scene.add(...)" in system_content def test_repair_visual_regular_error_gets_normal_crash_prompt(): tutor = TutorAgent.__new__(TutorAgent) mock_client = MagicMock() tutor._client = mock_client fake_payload = HTML5VisualPayload(html_code="", animation_type="2d_anim") mock_client.structured_complete.return_value = fake_payload result = tutor.repair_visual( original_html="original", error_message="ReferenceError: foo is not defined", ) assert result is fake_payload mock_client.structured_complete.assert_called_once() messages = mock_client.structured_complete.call_args[0][0] system_content = messages[0]["content"] assert "Fix the JavaScript error and return the corrected complete HTML." in system_content assert "did NOT crash" not in system_content assert "group.add(...)" not in system_content