import os import sys from datetime import datetime from unittest.mock import AsyncMock, MagicMock, patch sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) import pytest import main def _text_block(text): block = MagicMock() block.type = "text" block.text = text return block def _tool_use_block(name, tool_input, tool_id="tool_1"): block = MagicMock() block.type = "tool_use" block.name = name block.input = tool_input block.id = tool_id return block def _usage(input_tokens, output_tokens): usage = MagicMock() usage.input_tokens = input_tokens usage.output_tokens = output_tokens return usage @pytest.mark.asyncio async def test_run_chat_completion_without_tool_use(): completion = MagicMock() completion.stop_reason = "end_turn" completion.content = [_text_block("Hallo!")] completion.usage = _usage(10, 5) main.claude_client = MagicMock() main.claude_client.messages.create.return_value = completion with patch.object(main, "build_memory_context", new=AsyncMock(return_value="")), patch.object( main, "_identity_system_prompt", new=AsyncMock(return_value="Du bist JARVIS.") ): text, output_tokens, total_tokens = await main.run_chat_completion( [{"role": "user", "content": "Hi"}], conversation_id=1 ) assert text == "Hallo!" assert output_tokens == 5 assert total_tokens == 15 @pytest.mark.asyncio async def test_run_chat_completion_executes_tool_and_returns_followup(): first = MagicMock() first.stop_reason = "tool_use" first.content = [_tool_use_block("list_calendar_events", {"days_ahead": 7})] first.usage = _usage(20, 8) second = MagicMock() second.stop_reason = "end_turn" second.content = [_text_block("Naechste Woche steht nichts an.")] second.usage = _usage(30, 12) main.claude_client = MagicMock() main.claude_client.messages.create.side_effect = [first, second] with patch.object(main, "list_upcoming_events", new=AsyncMock(return_value=[])), patch.object( main, "build_memory_context", new=AsyncMock(return_value="") ), patch.object(main, "_identity_system_prompt", new=AsyncMock(return_value="Du bist JARVIS.")): text, output_tokens, total_tokens = await main.run_chat_completion( [{"role": "user", "content": "Was steht diese Woche an?"}], conversation_id=1 ) assert text == "Naechste Woche steht nichts an." assert output_tokens == 8 + 12 assert total_tokens == 20 + 8 + 30 + 12 assert main.claude_client.messages.create.call_count == 2 @pytest.mark.asyncio async def test_run_chat_completion_handles_two_sequential_tool_calls(): """Regression test: 'verschiebe den Termin X' needs list_calendar_events to find the uid, THEN update_calendar_event - a single tool round used to silently drop the second call and return an empty response.""" first = MagicMock() first.stop_reason = "tool_use" first.content = [_tool_use_block("list_calendar_events", {"days_ahead": 30}, "tool_1")] first.usage = _usage(20, 8) second = MagicMock() second.stop_reason = "tool_use" second.content = [ _tool_use_block("update_calendar_event", {"uid": "abc", "start": "2026-09-30T11:00:00"}, "tool_2") ] second.usage = _usage(40, 10) third = MagicMock() third.stop_reason = "end_turn" third.content = [_text_block("Termin verschoben.")] third.usage = _usage(50, 6) main.claude_client = MagicMock() main.claude_client.messages.create.side_effect = [first, second, third] with patch.object(main, "list_upcoming_events", new=AsyncMock(return_value=[{"uid": "abc"}])), patch.object( main, "update_event", new=AsyncMock(return_value={"uid": "abc"}) ), patch.object(main, "build_memory_context", new=AsyncMock(return_value="")), patch.object( main, "_identity_system_prompt", new=AsyncMock(return_value="Du bist JARVIS.") ): text, output_tokens, total_tokens = await main.run_chat_completion( [{"role": "user", "content": "Verschiebe den Termin X"}], conversation_id=1 ) assert text == "Termin verschoben." assert output_tokens == 8 + 10 + 6 assert total_tokens == 20 + 8 + 40 + 10 + 50 + 6 assert main.claude_client.messages.create.call_count == 3 @pytest.mark.asyncio async def test_run_chat_completion_lists_recent_emails(): first = MagicMock() first.stop_reason = "tool_use" first.content = [_tool_use_block("list_recent_emails", {"limit": 5}, "tool_1")] first.usage = _usage(15, 6) second = MagicMock() second.stop_reason = "end_turn" second.content = [_text_block("Du hast 5 neue Mails.")] second.usage = _usage(20, 8) main.claude_client = MagicMock() main.claude_client.messages.create.side_effect = [first, second] with patch.object(main, "list_recent_emails", new=AsyncMock(return_value=[])), patch.object( main, "build_memory_context", new=AsyncMock(return_value="") ), patch.object(main, "_identity_system_prompt", new=AsyncMock(return_value="Du bist JARVIS.")): text, output_tokens, total_tokens = await main.run_chat_completion( [{"role": "user", "content": "Was ist neu im Postfach?"}], conversation_id=1 ) assert text == "Du hast 5 neue Mails." assert output_tokens == 6 + 8 assert total_tokens == 15 + 6 + 20 + 8 def test_current_datetime_context_formats_german_weekday_and_month(): fixed = datetime(2026, 9, 13, 21, 15, tzinfo=main.CALENDAR_TIMEZONE) context = main._current_datetime_context(fixed) assert context == ( "Aktuelles Datum und Uhrzeit: Sonntag, 13. September 2026, 21:15 Uhr (13.09.2026)." ) @pytest.mark.asyncio async def test_run_chat_completion_includes_current_datetime_in_system_prompt(): completion = MagicMock() completion.stop_reason = "end_turn" completion.content = [_text_block("Hallo!")] completion.usage = _usage(10, 5) main.claude_client = MagicMock() main.claude_client.messages.create.return_value = completion with patch.object(main, "build_memory_context", new=AsyncMock(return_value="")), patch.object( main, "_current_datetime_context", return_value="Aktuelles Datum und Uhrzeit: TESTMARKER" ), patch.object(main, "_identity_system_prompt", new=AsyncMock(return_value="Du bist JARVIS.")): await main.run_chat_completion([{"role": "user", "content": "Hi"}], conversation_id=1) _, kwargs = main.claude_client.messages.create.call_args assert "TESTMARKER" in kwargs["system"] @pytest.mark.asyncio async def test_run_chat_completion_includes_identity_prompt_in_system_prompt(): completion = MagicMock() completion.stop_reason = "end_turn" completion.content = [_text_block("Hallo!")] completion.usage = _usage(10, 5) main.claude_client = MagicMock() main.claude_client.messages.create.return_value = completion with patch.object(main, "build_memory_context", new=AsyncMock(return_value="")), patch.object( main, "_identity_system_prompt", new=AsyncMock(return_value="IDENTITYMARKER Du bist JARVIS.") ): await main.run_chat_completion([{"role": "user", "content": "Hi"}], conversation_id=1) _, kwargs = main.claude_client.messages.create.call_args assert "IDENTITYMARKER" in kwargs["system"]