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
This commit is contained in:
windyboy
2025-12-31 17:55:10 +08:00
parent 3200ad3dd5
commit f7e54692a9
67 changed files with 23088 additions and 0 deletions
+516
View File
@@ -0,0 +1,516 @@
"""
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调用的重试机制
- 更新文档
- 组织团队同步会议
"""