From 09fc20c9dfa463b840b75fd60fd4e962cdfba762 Mon Sep 17 00:00:00 2001 From: Jonny Date: Sun, 13 Sep 2026 12:24:01 +0200 Subject: [PATCH] chore: track pre-existing test_chat_tools.py baseline Was never committed before. Tracking it now (unmodified) so Task 6 of the cross-chat-memory plan, which edits this file, produces a clean incremental diff instead of showing the whole file as new. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_019iZMEPk2Kt1UC96Lo9bC5w --- Claude outputs/tests/test_chat_tools.py | 138 ++++++++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 Claude outputs/tests/test_chat_tools.py diff --git a/Claude outputs/tests/test_chat_tools.py b/Claude outputs/tests/test_chat_tools.py new file mode 100644 index 0000000..8d1595f --- /dev/null +++ b/Claude outputs/tests/test_chat_tools.py @@ -0,0 +1,138 @@ +import os +import sys +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 + + text, output_tokens, total_tokens = await main.run_chat_completion([{"role": "user", "content": "Hi"}]) + + 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=[])): + text, output_tokens, total_tokens = await main.run_chat_completion( + [{"role": "user", "content": "Was steht diese Woche an?"}] + ) + + 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"}) + ): + text, output_tokens, total_tokens = await main.run_chat_completion( + [{"role": "user", "content": "Verschiebe den Termin X"}] + ) + + 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=[])): + text, output_tokens, total_tokens = await main.run_chat_completion( + [{"role": "user", "content": "Was ist neu im Postfach?"}] + ) + + assert text == "Du hast 5 neue Mails." + assert output_tokens == 6 + 8 + assert total_tokens == 15 + 6 + 20 + 8