""" Unit tests for AI summarization with mock Dify responses. """ import pytest from unittest.mock import patch, MagicMock, AsyncMock import json pytestmark = pytest.mark.asyncio class TestDifyResponseParsing: """Tests for parsing Dify LLM responses.""" def test_parse_json_response(self): """Test parsing valid JSON response from Dify.""" from app.routers.ai import parse_dify_response response = '''Here is the summary: ```json { "conclusions": ["Agreed on Q1 budget", "New hire approved"], "action_items": [ {"content": "Submit budget report", "owner": "John", "due_date": "2025-01-15"}, {"content": "Post job listing", "owner": "", "due_date": null} ] } ``` ''' result = parse_dify_response(response) assert len(result["conclusions"]) == 2 assert "Q1 budget" in result["conclusions"][0] assert len(result["action_items"]) == 2 assert result["action_items"][0]["owner"] == "John" def test_parse_inline_json_response(self): """Test parsing inline JSON without code blocks.""" from app.routers.ai import parse_dify_response response = '{"conclusions": ["Budget approved"], "action_items": []}' result = parse_dify_response(response) assert len(result["conclusions"]) == 1 assert result["conclusions"][0] == "Budget approved" def test_parse_non_json_response(self): """Test fallback when response is not JSON.""" from app.routers.ai import parse_dify_response response = "The meeting discussed Q1 budget and hiring plans." result = parse_dify_response(response) # Should return the raw response as a single conclusion assert len(result["conclusions"]) == 1 assert "Q1 budget" in result["conclusions"][0] assert len(result["action_items"]) == 0 def test_parse_empty_response(self): """Test handling empty response.""" from app.routers.ai import parse_dify_response result = parse_dify_response("") assert result["conclusions"] == [] assert result["action_items"] == [] class TestSummarizeEndpoint: """Tests for the AI summarization endpoint.""" @patch("app.routers.ai.httpx.AsyncClient") @patch("app.routers.ai.settings") async def test_summarize_success(self, mock_settings, mock_client_class): """Test successful summarization.""" mock_settings.DIFY_API_URL = "https://dify.test.com/v1" mock_settings.DIFY_API_KEY = "test-key" mock_response = MagicMock() mock_response.status_code = 200 mock_response.json.return_value = { "answer": json.dumps({ "conclusions": ["Decision made"], "action_items": [{"content": "Follow up", "owner": "Alice", "due_date": "2025-01-20"}] }) } mock_client = AsyncMock() mock_client.post.return_value = mock_response mock_client.__aenter__.return_value = mock_client mock_client.__aexit__.return_value = None mock_client_class.return_value = mock_client from app.routers.ai import summarize_transcript from app.models import SummarizeRequest, TokenPayload mock_user = TokenPayload(email="test@test.com", role="user") result = await summarize_transcript( SummarizeRequest(transcript="Test meeting transcript"), current_user=mock_user ) assert len(result.conclusions) == 1 assert len(result.action_items) == 1 assert result.action_items[0].owner == "Alice" @patch("app.routers.ai.httpx.AsyncClient") @patch("app.routers.ai.settings") async def test_summarize_handles_timeout(self, mock_settings, mock_client_class): """Test handling Dify timeout.""" import httpx from fastapi import HTTPException mock_settings.DIFY_API_URL = "https://dify.test.com/v1" mock_settings.DIFY_API_KEY = "test-key" mock_client = AsyncMock() mock_client.post.side_effect = httpx.TimeoutException("Timeout") mock_client.__aenter__.return_value = mock_client mock_client.__aexit__.return_value = None mock_client_class.return_value = mock_client from app.routers.ai import summarize_transcript from app.models import SummarizeRequest, TokenPayload mock_user = TokenPayload(email="test@test.com", role="user") with pytest.raises(HTTPException) as exc_info: await summarize_transcript( SummarizeRequest(transcript="Test"), current_user=mock_user ) assert exc_info.value.status_code == 504 @patch("app.routers.ai.settings") async def test_summarize_no_api_key(self, mock_settings): """Test error when Dify API key is not configured.""" from fastapi import HTTPException mock_settings.DIFY_API_KEY = "" from app.routers.ai import summarize_transcript from app.models import SummarizeRequest, TokenPayload mock_user = TokenPayload(email="test@test.com", role="user") with pytest.raises(HTTPException) as exc_info: await summarize_transcript( SummarizeRequest(transcript="Test"), current_user=mock_user ) assert exc_info.value.status_code == 503 class TestPartialDataHandling: """Tests for handling partial data from AI.""" def test_action_item_with_empty_owner(self): """Test action items with empty owner are handled.""" from app.routers.ai import parse_dify_response response = json.dumps({ "conclusions": [], "action_items": [ {"content": "Task 1", "owner": "", "due_date": None}, {"content": "Task 2", "owner": "Bob", "due_date": "2025-02-01"} ] }) result = parse_dify_response(response) assert result["action_items"][0]["owner"] == "" assert result["action_items"][1]["owner"] == "Bob" def test_action_item_with_missing_fields(self): """Test action items with missing fields.""" from app.routers.ai import parse_dify_response response = json.dumps({ "conclusions": ["Done"], "action_items": [ {"content": "Task only"} ] }) result = parse_dify_response(response) # Should have content but other fields may be missing assert result["action_items"][0]["content"] == "Task only"