- 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
516 lines
19 KiB
Python
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调用的重试机制
|
|
- 更新文档
|
|
- 组织团队同步会议
|
|
""" |