Files
journal_organizer/tests/integration/test_conversational_agent.py
T

431 lines
18 KiB
Python
Raw Normal View History

"""
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