Files
journal_organizer/tests/integration/test_claude_skills.py
T
windyboy f7e54692a9 Initial project setup: Obsidian intelligent journal organizer
- Add core agent architecture with Command + Skill pattern
- Implement Claude API integration for content analysis
- Add Obsidian REST API integration for vault operations
- Create conversational interface (v2.0) with natural language processing
- Add comprehensive configuration management and validation
- Include project documentation and developer guides
- Set up testing framework with unit, integration, and property tests
- Add Kiro specs for Claude API configuration and code quality improvements
- Configure project steering files for development guidelines
2025-12-31 17:55:10 +08:00

516 lines
19 KiB
Python

"""
Integration tests for Claude Skills.
Tests Skills with mocked API responses to verify AI integration functionality.
"""
import pytest
import json
from unittest.mock import AsyncMock, patch, Mock
from agent_core import CommandContext, SkillResult
from skills.claude_skill import ClaudeAnalyzeSkill, ClaudeTransformSkill
class TestClaudeAnalyzeSkill:
"""Integration tests for ClaudeAnalyzeSkill"""
@pytest.fixture
def skill(self):
"""Create ClaudeAnalyzeSkill instance"""
return ClaudeAnalyzeSkill()
@pytest.fixture
def context(self, sample_config):
"""Create command context with analysis parameters"""
return CommandContext(
command_name="analyze",
args={
"content": """# 2024-01-15 Daily Journal
## 今天的经历
- 完成了项目的重要里程碑
- 与团队进行了有效的沟通
## 学到的东西
- 学会了新的Python异步编程技巧
- 理解了更好的错误处理模式
## 遇到的问题
- API调用偶尔超时
- 配置文件格式需要改进
## 明天的计划
- 优化API调用的重试机制
- 更新文档
""",
"analysis_type": "extract_experiences"
},
config=sample_config
)
@pytest.mark.asyncio
async def test_analyze_journal_success(self, skill, context):
"""Test successful journal analysis"""
mock_response = {
"experiences": [
{
"title": "项目里程碑完成",
"description": "成功完成了项目的重要里程碑,展现了良好的项目管理能力",
"category": "项目管理",
"importance": "high"
},
{
"title": "团队沟通改进",
"description": "与团队进行了有效的沟通,提升了协作效率",
"category": "团队协作",
"importance": "medium"
}
],
"lessons": [
{
"title": "Python异步编程",
"description": "学会了新的Python异步编程技巧,提升了代码效率",
"category": "技术学习",
"application": "可以应用到当前项目的API调用优化中"
}
],
"problems": [
{
"title": "API调用超时",
"description": "API调用偶尔出现超时问题",
"severity": "medium",
"suggested_solution": "实现重试机制和超时处理"
}
]
}
# Mock the Claude API client
with patch('skills.claude_skill.anthropic') as mock_anthropic:
mock_client = Mock()
mock_anthropic.Anthropic.return_value = mock_client
# Mock the messages.create method
mock_message = Mock()
mock_message.content = [Mock(text=json.dumps(mock_response, ensure_ascii=False))]
mock_client.messages.create.return_value = mock_message
result = await skill.execute(context)
assert result.success is True
assert "experiences" in result.data
assert "lessons" in result.data
assert "problems" in result.data
assert len(result.data["experiences"]) == 2
assert len(result.data["lessons"]) == 1
assert len(result.data["problems"]) == 1
# Verify API was called with correct parameters
mock_client.messages.create.assert_called_once()
call_args = mock_client.messages.create.call_args
assert call_args[1]["model"] == "claude-3-5-sonnet-20241022"
assert call_args[1]["max_tokens"] == 4096
@pytest.mark.asyncio
async def test_analyze_empty_content(self, skill, sample_config):
"""Test analysis with empty content"""
context = CommandContext(
command_name="analyze",
args={"content": "", "analysis_type": "extract_experiences"},
config=sample_config
)
result = await skill.execute(context)
assert result.success is False
assert "content" in result.error.lower()
@pytest.mark.asyncio
async def test_analyze_api_error(self, skill, context):
"""Test handling of Claude API errors"""
with patch('skills.claude_skill.anthropic') as mock_anthropic:
mock_client = Mock()
mock_anthropic.Anthropic.return_value = mock_client
# Mock API error
mock_client.messages.create.side_effect = Exception("API rate limit exceeded")
result = await skill.execute(context)
assert result.success is False
assert "api" in result.error.lower() or "rate limit" in result.error.lower()
@pytest.mark.asyncio
async def test_analyze_invalid_json_response(self, skill, context):
"""Test handling of invalid JSON response from Claude"""
with patch('skills.claude_skill.anthropic') as mock_anthropic:
mock_client = Mock()
mock_anthropic.Anthropic.return_value = mock_client
# Mock invalid JSON response
mock_message = Mock()
mock_message.content = [Mock(text="Invalid JSON response")]
mock_client.messages.create.return_value = mock_message
result = await skill.execute(context)
assert result.success is False
assert "json" in result.error.lower() or "parse" in result.error.lower()
@pytest.mark.asyncio
async def test_analyze_different_types(self, skill, sample_config):
"""Test different analysis types"""
analysis_types = ["extract_experiences", "extract_lessons", "extract_problems", "summarize"]
for analysis_type in analysis_types:
context = CommandContext(
command_name="analyze",
args={
"content": "Sample journal content for testing",
"analysis_type": analysis_type
},
config=sample_config
)
mock_response = {"result": f"Analysis result for {analysis_type}"}
with patch('skills.claude_skill.anthropic') as mock_anthropic:
mock_client = Mock()
mock_anthropic.Anthropic.return_value = mock_client
mock_message = Mock()
mock_message.content = [Mock(text=json.dumps(mock_response))]
mock_client.messages.create.return_value = mock_message
result = await skill.execute(context)
assert result.success is True
assert result.data["analysis_type"] == analysis_type
class TestClaudeTransformSkill:
"""Integration tests for ClaudeTransformSkill"""
@pytest.fixture
def skill(self):
"""Create ClaudeTransformSkill instance"""
return ClaudeTransformSkill()
@pytest.fixture
def context(self, sample_config):
"""Create command context with transformation parameters"""
return CommandContext(
command_name="transform",
args={
"content": {
"title": "项目里程碑完成",
"description": "成功完成了项目的重要里程碑",
"category": "项目管理"
},
"transform_type": "create_experience_note",
"target_format": "markdown"
},
config=sample_config
)
@pytest.mark.asyncio
async def test_transform_to_markdown_success(self, skill, context):
"""Test successful content transformation to markdown"""
mock_response = """# 项目里程碑完成
## 经验描述
成功完成了项目的重要里程碑,这次经历展现了良好的项目管理能力和团队协作精神。
## 关键要点
- 项目管理技能得到提升
- 团队协作效率显著改善
- 里程碑按时完成
## 应用场景
这个经验可以应用到未来的项目管理中,特别是在设定和跟踪项目里程碑方面。
## 相关标签
#项目管理 #里程碑 #团队协作
---
*创建时间: 2024-01-15*
*来源: 日记整理*
"""
with patch('skills.claude_skill.anthropic') as mock_anthropic:
mock_client = Mock()
mock_anthropic.Anthropic.return_value = mock_client
mock_message = Mock()
mock_message.content = [Mock(text=mock_response)]
mock_client.messages.create.return_value = mock_message
result = await skill.execute(context)
assert result.success is True
assert result.data["transformed_content"] == mock_response
assert result.data["transform_type"] == "create_experience_note"
assert result.data["target_format"] == "markdown"
# Verify the content contains expected markdown elements
assert "# 项目里程碑完成" in result.data["transformed_content"]
assert "## 经验描述" in result.data["transformed_content"]
assert "#项目管理" in result.data["transformed_content"]
@pytest.mark.asyncio
async def test_transform_different_types(self, skill, sample_config):
"""Test different transformation types"""
transform_types = [
"create_experience_note",
"create_lesson_note",
"create_problem_note",
"create_summary"
]
for transform_type in transform_types:
context = CommandContext(
command_name="transform",
args={
"content": {"title": "Test", "description": "Test content"},
"transform_type": transform_type,
"target_format": "markdown"
},
config=sample_config
)
mock_response = f"# Transformed Content\n\nContent for {transform_type}"
with patch('skills.claude_skill.anthropic') as mock_anthropic:
mock_client = Mock()
mock_anthropic.Anthropic.return_value = mock_client
mock_message = Mock()
mock_message.content = [Mock(text=mock_response)]
mock_client.messages.create.return_value = mock_message
result = await skill.execute(context)
assert result.success is True
assert result.data["transform_type"] == transform_type
@pytest.mark.asyncio
async def test_transform_missing_content(self, skill, sample_config):
"""Test transformation with missing content"""
context = CommandContext(
command_name="transform",
args={
"transform_type": "create_experience_note",
"target_format": "markdown"
# Missing content
},
config=sample_config
)
result = await skill.execute(context)
assert result.success is False
assert "content" in result.error.lower()
@pytest.mark.asyncio
async def test_transform_api_error(self, skill, context):
"""Test handling of Claude API errors during transformation"""
with patch('skills.claude_skill.anthropic') as mock_anthropic:
mock_client = Mock()
mock_anthropic.Anthropic.return_value = mock_client
# Mock API error
mock_client.messages.create.side_effect = Exception("API authentication failed")
result = await skill.execute(context)
assert result.success is False
assert "api" in result.error.lower() or "authentication" in result.error.lower()
class TestClaudeSkillsIntegration:
"""Integration tests combining Claude skills"""
@pytest.mark.asyncio
async def test_analyze_then_transform_workflow(self, sample_config):
"""Test complete analyze-then-transform workflow"""
analyze_skill = ClaudeAnalyzeSkill()
transform_skill = ClaudeTransformSkill()
# Step 1: Analyze journal content
analyze_context = CommandContext(
command_name="analyze",
args={
"content": sample_journal_content,
"analysis_type": "extract_experiences"
},
config=sample_config
)
# Step 2: Transform extracted experience to note
experience_data = {
"title": "项目里程碑完成",
"description": "成功完成了项目的重要里程碑",
"category": "项目管理",
"importance": "high"
}
transform_context = CommandContext(
command_name="transform",
args={
"content": experience_data,
"transform_type": "create_experience_note",
"target_format": "markdown"
},
config=sample_config
)
# Mock responses
analyze_response = {
"experiences": [experience_data],
"lessons": [],
"problems": []
}
transform_response = """# 项目里程碑完成
## 经验描述
成功完成了项目的重要里程碑
## 分类
项目管理
## 重要程度
#项目管理 #里程碑
"""
with patch('skills.claude_skill.anthropic') as mock_anthropic:
mock_client = Mock()
mock_anthropic.Anthropic.return_value = mock_client
# Mock analyze response
mock_analyze_message = Mock()
mock_analyze_message.content = [Mock(text=json.dumps(analyze_response, ensure_ascii=False))]
# Mock transform response
mock_transform_message = Mock()
mock_transform_message.content = [Mock(text=transform_response)]
# Set up side_effect to return different responses for different calls
mock_client.messages.create.side_effect = [mock_analyze_message, mock_transform_message]
# Execute analyze
analyze_result = await analyze_skill.execute(analyze_context)
assert analyze_result.success is True
assert len(analyze_result.data["experiences"]) == 1
# Execute transform using analyze result
transform_result = await transform_skill.execute(transform_context)
assert transform_result.success is True
assert "项目里程碑完成" in transform_result.data["transformed_content"]
# Verify both API calls were made
assert mock_client.messages.create.call_count == 2
@pytest.mark.asyncio
async def test_batch_analysis_and_transformation(self, sample_config):
"""Test batch processing of multiple content pieces"""
analyze_skill = ClaudeAnalyzeSkill()
transform_skill = ClaudeTransformSkill()
# Multiple journal entries to process
journal_entries = [
"今天学会了新的编程技巧",
"解决了一个复杂的技术问题",
"与客户进行了重要的项目讨论"
]
with patch('skills.claude_skill.anthropic') as mock_anthropic:
mock_client = Mock()
mock_anthropic.Anthropic.return_value = mock_client
# Mock responses for each entry
mock_responses = []
for i, entry in enumerate(journal_entries):
analyze_response = {
"experiences": [{
"title": f"Experience {i+1}",
"description": entry,
"category": "学习"
}]
}
transform_response = f"# Experience {i+1}\n\n{entry}\n\n#学习"
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=transform_response)]
mock_responses.extend([mock_analyze_message, mock_transform_message])
mock_client.messages.create.side_effect = mock_responses
# Process each entry
results = []
for entry in journal_entries:
# Analyze
analyze_context = CommandContext(
command_name="analyze",
args={"content": entry, "analysis_type": "extract_experiences"},
config=sample_config
)
analyze_result = await analyze_skill.execute(analyze_context)
# Transform
if analyze_result.success and analyze_result.data["experiences"]:
experience = analyze_result.data["experiences"][0]
transform_context = CommandContext(
command_name="transform",
args={
"content": experience,
"transform_type": "create_experience_note",
"target_format": "markdown"
},
config=sample_config
)
transform_result = await transform_skill.execute(transform_context)
results.append((analyze_result, transform_result))
# Verify all entries were processed successfully
assert len(results) == len(journal_entries)
for analyze_result, transform_result in results:
assert analyze_result.success is True
assert transform_result.success is True
# Verify correct number of API calls (2 per entry: analyze + transform)
assert mock_client.messages.create.call_count == len(journal_entries) * 2
# Sample journal content for testing
sample_journal_content = """# 2024-01-15 Daily Journal
## 今天的经历
- 完成了项目的重要里程碑
- 与团队进行了有效的沟通
- 参加了技术分享会议
## 学到的东西
- 学会了新的Python异步编程技巧
- 理解了更好的错误处理模式
- 掌握了新的项目管理方法
## 遇到的问题
- API调用偶尔超时
- 配置文件格式需要改进
- 团队沟通中存在信息不对称
## 明天的计划
- 优化API调用的重试机制
- 更新文档
- 组织团队同步会议
"""