""" Integration tests for conversational agent flow. Tests the v2.0 conversational interface with natural language processing. """ import pytest import json from unittest.mock import AsyncMock, patch, Mock from aioresponses import aioresponses from agent_core import CommandContext, SkillResult from conversation.conversational_agent import ConversationalAgent from conversation.conversation_state import ConversationState from conversation.intent_understanding import IntentUnderstanding from conversation.response_generator import ResponseGenerator class TestConversationalAgent: """Integration tests for ConversationalAgent""" @pytest.fixture def agent(self, sample_config): """Create ConversationalAgent instance""" return ConversationalAgent(sample_config) @pytest.fixture def sample_user_inputs(self): """Sample user inputs for testing""" return [ "整理今天的日记", "organize today's journal", "分析2024年1月15日的日记", "help me organize my notes from yesterday", "今天学到了什么?", "what did I learn today?", "整理昨天的经验和教训" ] @pytest.mark.asyncio async def test_conversational_agent_basic_flow(self, agent): """Test basic conversational flow""" user_input = "整理今天的日记" # Mock the underlying organize command execution with patch.object(agent.agent, 'execute_command') as mock_execute: mock_result = SkillResult( success=True, data={ "summary": "Successfully organized journal", "created_notes": [ {"type": "experience", "title": "Test Experience", "path": "Knowledge/Experiences/test.md"} ] }, message="Journal organized successfully" ) mock_execute.return_value = mock_result response = await agent.process_message(user_input) assert response is not None assert "成功" in response or "successfully" in response.lower() mock_execute.assert_called_once() # Verify the command was called with correct parameters call_args = mock_execute.call_args assert call_args[0][0] == "organize" # Command name @pytest.mark.asyncio async def test_conversational_agent_with_date_extraction(self, agent): """Test conversational agent with date parameter extraction""" user_input = "分析2024年1月15日的日记" with patch.object(agent.agent, 'execute_command') as mock_execute: mock_result = SkillResult(success=True, data={}, message="Analysis completed") mock_execute.return_value = mock_result response = await agent.process_message(user_input) assert response is not None mock_execute.assert_called_once() # Verify date was extracted and passed call_args = mock_execute.call_args assert "date" in call_args[1]["args"] # Should have extracted date @pytest.mark.asyncio async def test_conversational_agent_error_handling(self, agent): """Test conversational agent error handling""" user_input = "整理今天的日记" with patch.object(agent.agent, 'execute_command') as mock_execute: mock_result = SkillResult( success=False, error="Journal file not found", message="Failed to organize journal" ) mock_execute.return_value = mock_result response = await agent.process_message(user_input) assert response is not None assert "错误" in response or "error" in response.lower() or "failed" in response.lower() @pytest.mark.asyncio async def test_conversational_agent_unknown_intent(self, agent): """Test conversational agent with unknown intent""" user_input = "今天天气怎么样?" # Weather question, not related to journal organization response = await agent.process_message(user_input) assert response is not None assert "不理解" in response or "不明白" in response or "help" in response.lower() @pytest.mark.asyncio async def test_conversational_agent_help_request(self, agent): """Test conversational agent help functionality""" help_inputs = ["help", "帮助", "你能做什么?", "what can you do?"] for user_input in help_inputs: response = await agent.process_message(user_input) assert response is not None assert "整理" in response or "organize" in response.lower() assert "日记" in response or "journal" in response.lower() @pytest.mark.asyncio async def test_conversational_agent_multiple_turns(self, agent): """Test multi-turn conversation""" conversation_turns = [ ("你好", "greeting"), ("整理今天的日记", "organize"), ("谢谢", "thanks") ] for user_input, expected_intent in conversation_turns: if expected_intent == "organize": with patch.object(agent.agent, 'execute_command') as mock_execute: mock_result = SkillResult(success=True, data={}, message="Success") mock_execute.return_value = mock_result response = await agent.process_message(user_input) else: response = await agent.process_message(user_input) assert response is not None assert len(response) > 0 class TestIntentUnderstanding: """Integration tests for IntentUnderstanding""" @pytest.fixture def intent_processor(self): """Create IntentUnderstanding instance""" return IntentUnderstanding() def test_organize_intent_detection(self, intent_processor): """Test detection of organize intents""" organize_inputs = [ "整理今天的日记", "organize today's journal", "分析我的日记", "help me organize my notes", "整理昨天的笔记" ] for user_input in organize_inputs: intent = intent_processor.understand_intent(user_input) assert intent["action"] == "organize" assert "command" in intent assert intent["command"] == "organize" def test_date_parameter_extraction(self, intent_processor): """Test extraction of date parameters""" date_inputs = [ ("整理2024年1月15日的日记", "2024-01-15"), ("analyze journal from yesterday", "yesterday"), ("organize today's notes", "today"), ("分析昨天的日记", "yesterday") ] for user_input, expected_date in date_inputs: intent = intent_processor.understand_intent(user_input) if expected_date in ["today", "yesterday"]: # These should be converted to actual dates assert "date" in intent["parameters"] else: assert intent["parameters"].get("date") == expected_date def test_help_intent_detection(self, intent_processor): """Test detection of help intents""" help_inputs = [ "help", "帮助", "你能做什么?", "what can you do?", "how to use this?" ] for user_input in help_inputs: intent = intent_processor.understand_intent(user_input) assert intent["action"] == "help" def test_unknown_intent_handling(self, intent_processor): """Test handling of unknown intents""" unknown_inputs = [ "今天天气怎么样?", "what's the weather like?", "计算1+1等于多少", "play music" ] for user_input in unknown_inputs: intent = intent_processor.understand_intent(user_input) assert intent["action"] == "unknown" assert "confidence" in intent assert intent["confidence"] < 0.5 # Low confidence for unknown intents class TestResponseGenerator: """Integration tests for ResponseGenerator""" @pytest.fixture def response_generator(self): """Create ResponseGenerator instance""" return ResponseGenerator() def test_success_response_generation(self, response_generator): """Test generation of success responses""" result = SkillResult( success=True, data={ "summary": "Successfully organized journal", "created_notes": [ {"type": "experience", "title": "Project Milestone", "path": "Knowledge/Experiences/milestone.md"}, {"type": "lesson", "title": "Python Tips", "path": "Knowledge/Lessons/python.md"} ] }, message="Journal organized successfully" ) response = response_generator.generate_response(result, "organize") assert response is not None assert "成功" in response or "successfully" in response.lower() assert "2" in response # Should mention number of notes created assert "经验" in response or "experience" in response.lower() assert "教训" in response or "lesson" in response.lower() def test_error_response_generation(self, response_generator): """Test generation of error responses""" result = SkillResult( success=False, error="Journal file not found for date 2024-01-15", message="Failed to organize journal" ) response = response_generator.generate_response(result, "organize") assert response is not None assert "错误" in response or "error" in response.lower() or "失败" in response assert "2024-01-15" in response # Should include the problematic date def test_help_response_generation(self, response_generator): """Test generation of help responses""" response = response_generator.generate_help_response() assert response is not None assert "整理" in response or "organize" in response.lower() assert "日记" in response or "journal" in response.lower() assert "命令" in response or "command" in response.lower() def test_unknown_intent_response(self, response_generator): """Test generation of unknown intent responses""" response = response_generator.generate_unknown_response("今天天气怎么样?") assert response is not None assert "不理解" in response or "不明白" in response or "understand" in response.lower() assert "帮助" in response or "help" in response.lower() class TestConversationState: """Integration tests for ConversationState""" @pytest.fixture def conversation_state(self): """Create ConversationState instance""" return ConversationState() def test_conversation_history_tracking(self, conversation_state): """Test conversation history tracking""" # Add some conversation turns conversation_state.add_turn("user", "整理今天的日记") conversation_state.add_turn("assistant", "好的,我来帮您整理今天的日记。") conversation_state.add_turn("user", "谢谢") conversation_state.add_turn("assistant", "不客气!还有其他需要帮助的吗?") history = conversation_state.get_history() assert len(history) == 4 assert history[0]["role"] == "user" assert history[0]["content"] == "整理今天的日记" assert history[1]["role"] == "assistant" assert history[-1]["role"] == "assistant" def test_context_management(self, conversation_state): """Test conversation context management""" # Set some context conversation_state.set_context("last_command", "organize") conversation_state.set_context("last_date", "2024-01-15") conversation_state.set_context("user_preference", "detailed_summary") # Retrieve context assert conversation_state.get_context("last_command") == "organize" assert conversation_state.get_context("last_date") == "2024-01-15" assert conversation_state.get_context("user_preference") == "detailed_summary" assert conversation_state.get_context("nonexistent") is None def test_conversation_reset(self, conversation_state): """Test conversation reset functionality""" # Add some data conversation_state.add_turn("user", "test message") conversation_state.set_context("test_key", "test_value") # Verify data exists assert len(conversation_state.get_history()) == 1 assert conversation_state.get_context("test_key") == "test_value" # Reset conversation conversation_state.reset() # Verify data is cleared assert len(conversation_state.get_history()) == 0 assert conversation_state.get_context("test_key") is None class TestFullConversationalFlow: """End-to-end integration tests for the complete conversational flow""" @pytest.mark.asyncio async def test_complete_organize_conversation(self, sample_config, sample_journal_content): """Test complete conversation flow for journal organization""" agent = ConversationalAgent(sample_config) # Mock all external dependencies with aioresponses() as m: # Mock Obsidian API m.get( "https://localhost:27123/vault/Daily/2024-01-15.md", payload={ "content": sample_journal_content, "stat": {"ctime": 1642204800000, "mtime": 1642204800000, "size": len(sample_journal_content)} }, status=200 ) # Mock note creation m.put( "https://localhost:27123/vault/Knowledge/Experiences/test.md", payload={"path": "Knowledge/Experiences/test.md", "stat": {}}, status=200 ) # Mock Claude API with patch('skills.claude_skill.anthropic') as mock_anthropic: mock_client = Mock() mock_anthropic.Anthropic.return_value = mock_client # Mock analysis response analyze_response = { "experiences": [{"title": "Test Experience", "description": "Test", "category": "Test"}], "lessons": [], "problems": [], "achievements": [] } mock_analyze_message = Mock() mock_analyze_message.content = [Mock(text=json.dumps(analyze_response, ensure_ascii=False))] mock_transform_message = Mock() mock_transform_message.content = [Mock(text="# Test Experience\n\nTest content")] mock_client.messages.create.side_effect = [mock_analyze_message, mock_transform_message] # Simulate conversation conversation_turns = [ "你好", "整理今天的日记", "谢谢你的帮助" ] responses = [] for user_input in conversation_turns: response = await agent.process_message(user_input) responses.append(response) assert response is not None assert len(response) > 0 # Verify conversation flow assert "你好" in responses[0] or "hello" in responses[0].lower() # Greeting response assert "成功" in responses[1] or "successfully" in responses[1].lower() # Success response assert "不客气" in responses[2] or "welcome" in responses[2].lower() # Thanks response @pytest.mark.asyncio async def test_error_recovery_conversation(self, sample_config): """Test conversation flow with error recovery""" agent = ConversationalAgent(sample_config) with aioresponses() as m: # Mock journal file not found m.get( "https://localhost:27123/vault/Daily/2024-01-15.md", status=404, payload={"error": "File not found"} ) # Simulate error scenario user_input = "整理今天的日记" response = await agent.process_message(user_input) assert response is not None assert "找不到" in response or "not found" in response.lower() or "错误" in response # Follow up with help request help_response = await agent.process_message("我应该怎么办?") assert help_response is not None assert "建议" in help_response or "suggest" in help_response.lower() or "帮助" in help_response