import json import pytest from unittest.mock import patch, AsyncMock # --------------------------------------------------------------------------- # read_article tool tests # --------------------------------------------------------------------------- @pytest.mark.asyncio async def test_read_article_success(): """read_article returns success with content when _fetch_full_article succeeds.""" from fabledassistant.services.tools import execute_tool with patch( "fabledassistant.services.rss._fetch_full_article", new_callable=AsyncMock, return_value="Article body text here.", ): result = await execute_tool(user_id=1, tool_name="read_article", arguments={"url": "https://example.com/article"}) assert result["success"] is True assert result["type"] == "article_content" assert result["url"] == "https://example.com/article" assert result["content"] == "Article body text here." assert result["truncated"] is False @pytest.mark.asyncio async def test_read_article_fetch_failure(): """read_article returns success=False when _fetch_full_article returns None.""" from fabledassistant.services.tools import execute_tool with patch( "fabledassistant.services.rss._fetch_full_article", new_callable=AsyncMock, return_value=None, ): result = await execute_tool(user_id=1, tool_name="read_article", arguments={"url": "https://example.com/broken"}) assert result["success"] is False assert "Could not fetch" in result["error"] @pytest.mark.asyncio async def test_read_article_truncates_at_40k(): """read_article truncates content at 40,000 chars and sets truncated=True.""" from fabledassistant.services.tools import execute_tool long_content = "x" * 50_000 with patch( "fabledassistant.services.rss._fetch_full_article", new_callable=AsyncMock, return_value=long_content, ): result = await execute_tool(user_id=1, tool_name="read_article", arguments={"url": "https://example.com/long"}) assert result["success"] is True assert len(result["content"]) == 40_000 assert result["truncated"] is True @pytest.mark.asyncio async def test_read_article_empty_url(): """read_article returns success=False when URL is empty.""" from fabledassistant.services.tools import execute_tool result = await execute_tool(user_id=1, tool_name="read_article", arguments={"url": ""}) assert result["success"] is False assert "No URL provided" in result["error"] # --------------------------------------------------------------------------- # History builder tests # --------------------------------------------------------------------------- def _build_history(messages: list[dict]) -> list[dict]: """Replicate the fixed history builder from routes/chat.py.""" history = [] for msg in messages: if msg["role"] == "system": continue msg_dict = {"role": msg["role"], "content": msg.get("content") or ""} tool_calls = msg.get("tool_calls") if tool_calls: msg_dict["tool_calls"] = [ {"function": {"name": tc["function"], "arguments": tc["arguments"]}} for tc in tool_calls ] history.append(msg_dict) for tc in tool_calls: history.append({"role": "tool", "content": json.dumps(tc.get("result", {}))}) else: history.append(msg_dict) return history def test_history_builder_replays_tool_calls(): """History builder with tool_calls produces assistant entry + tool result entry.""" messages = [ { "role": "assistant", "content": "", "tool_calls": [ { "function": "read_article", "arguments": {"url": "https://example.com"}, "result": {"success": True, "content": "Article text"}, } ], }, {"role": "user", "content": "Summarize it", "tool_calls": None}, ] history = _build_history(messages) assert len(history) == 3 assert history[0]["role"] == "assistant" assert history[0]["tool_calls"][0]["function"]["name"] == "read_article" assert history[1]["role"] == "tool" assert json.loads(history[1]["content"])["success"] is True assert history[2]["role"] == "user" assert history[2]["content"] == "Summarize it" def test_history_builder_no_tool_calls_unchanged(): """History builder with tool_calls=None produces same output as before.""" messages = [ {"role": "user", "content": "Hello", "tool_calls": None}, {"role": "assistant", "content": "Hi there!", "tool_calls": None}, ] history = _build_history(messages) assert len(history) == 2 assert history[0] == {"role": "user", "content": "Hello"} assert history[1] == {"role": "assistant", "content": "Hi there!"}