This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
openclaw-skills/skills/obsidian-kb/tests/test_pipeline.py
T
OpenClaw Agent f26d4d8f7f feat: implement obsidian-kb skill with long video chunking
- Multi-source content aggregation (Bilibili/YouTube/Zhihu)
- Smart inbox management with AI quality scoring
- Long video chunking analysis (30min+ auto-segmentation)
- Topic-based organization
- Git auto-sync to Obsidian
- Template system with Jinja2 and fallback rendering
- CLI commands: subscribe, inbox, process, skip, status, search
- Comprehensive test suite with unit and integration tests
- Documentation: README.md and SKILL.md

Checkpoint 7-8 completed by Agent E
2026-02-06 18:15:30 +08:00

332 lines
13 KiB
Python

"""
Integration tests for the complete Obsidian KB pipeline.
Tests the full workflow: subscribe → fetch → analyze → generate → sync
"""
import unittest
import tempfile
import json
from pathlib import Path
from datetime import datetime
import sys
sys.path.insert(0, str(Path(__file__).parent.parent / 'src'))
from models import Content, ContentType, ContentStatus, ContentAnalysis
from storage import Storage
from template_renderer import TemplateRenderer
from config import Config
from content_chunker import ContentChunker
class TestPipeline(unittest.TestCase):
"""Integration tests for the complete pipeline."""
def setUp(self):
"""Set up test environment."""
self.temp_dir = tempfile.mkdtemp()
self.data_dir = Path(self.temp_dir) / "data"
self.vault_dir = Path(self.temp_dir) / "vault"
self.vault_dir.mkdir(parents=True, exist_ok=True)
# Initialize components
self.storage = Storage(base_path=str(self.data_dir))
self.config = Config(config_path=str(self.temp_dir / "config.yaml"))
self.config.set('git.vault_path', str(self.vault_dir))
templates_dir = Path(__file__).parent.parent / 'src' / 'templates'
self.renderer = TemplateRenderer(str(templates_dir))
self.chunker = ContentChunker()
def test_full_pipeline_short_video(self):
"""Test complete pipeline with short video."""
# 1. Create content (simulating subscription/fetch)
content = Content(
id="bvid_BV1test001",
source="bilibili",
title="Python Tips in 5 Minutes",
url="https://bilibili.com/video/BV1test001",
author="CodeMaster",
duration=300, # 5 minutes
content_type=ContentType.VIDEO,
status=ContentStatus.NEW,
tags=["python", "programming"]
)
# 2. Add to inbox
self.storage.add_to_inbox(content)
inbox = self.storage.get_inbox()
self.assertEqual(len(inbox), 1)
# 3. Simulate light scanning
content.status = ContentStatus.SCANNED
content.analysis = ContentAnalysis(
one_liner="Quick Python productivity tips",
summary="This video covers 5 essential Python tips for beginners.",
key_points=["Use list comprehensions", "Leverage built-in functions"],
tags=["python", "productivity"],
quality_score=85
)
self.storage.update_inbox_item(content)
# 4. Generate note
output_path = self.vault_dir / "inbox" / "python_tips.md"
result_path = self.renderer.render_to_file(content, str(output_path))
# 5. Verify output
self.assertTrue(Path(result_path).exists())
note_content = Path(result_path).read_text(encoding='utf-8')
# Verify content
self.assertIn("Python Tips in 5 Minutes", note_content)
self.assertIn("Quick Python productivity tips", note_content)
self.assertIn("#python", note_content)
# 6. Update status to completed
content.status = ContentStatus.GENERATED
content.note_path = str(result_path)
self.storage.update_inbox_item(content)
# 7. Verify final state
completed_item = self.storage.get_item_by_id(content.id)
self.assertEqual(completed_item.status, ContentStatus.GENERATED)
self.assertIsNotNone(completed_item.note_path)
def test_pipeline_long_video_with_chunking(self):
"""Test pipeline with long video requiring chunking."""
# 1. Create long video content
content = Content(
id="bvid_BV2long001",
source="bilibili",
title="Complete Python Course",
url="https://bilibili.com/video/BV2long001",
author="PythonAcademy",
duration=7200, # 2 hours
content_type=ContentType.VIDEO,
status=ContentStatus.NEW,
tags=["python", "tutorial", "course"]
)
# 2. Add to inbox
self.storage.add_to_inbox(content)
# 3. Determine chunking strategy
strategy = self.chunker.get_chunking_strategy(content.duration)
self.assertEqual(strategy['strategy'], 'multi_part')
self.assertGreater(strategy['chunk_count'], 2)
# 4. Simulate chunking analysis
# Create mock chunks
chunks = [
{"index": 0, "start": 0, "end": 1800, "summary": "Introduction to Python"},
{"index": 1, "start": 1800, "end": 3600, "summary": "Data structures"},
{"index": 2, "start": 3600, "end": 5400, "summary": "Functions and classes"},
{"index": 3, "start": 5400, "end": 7200, "summary": "Advanced topics"},
]
# 5. Simulate deep analysis with chunks
content.status = ContentStatus.GENERATED
content.analysis = ContentAnalysis(
one_liner="Comprehensive Python course from basics to advanced",
summary="A 2-hour course covering Python fundamentals and advanced concepts.",
key_points=["Python basics", "Data structures", "OOP", "Advanced patterns"],
tags=["python", "course", "programming"],
quality_score=95,
prerequisites=["Basic programming knowledge"],
outline=[
{"title": "Introduction", "time_range": "00:00-30:00", "summary": "Python basics"},
{"title": "Data Structures", "time_range": "30:00-60:00", "summary": "Lists, dicts, sets"},
{"title": "OOP", "time_range": "60:00-90:00", "summary": "Classes and objects"},
{"title": "Advanced", "time_range": "90:00-120:00", "summary": "Decorators, generators"},
],
pitfalls=["Python 2 vs 3 confusion", "Mutable default arguments"],
practice_advice=["Code along with examples", "Build projects"]
)
# 6. Generate tutorial note
output_path = self.vault_dir / "topics" / "programming" / "python_course.md"
result_path = self.renderer.render_to_file(content, str(output_path))
# 7. Verify tutorial template was used
note_content = Path(result_path).read_text(encoding='utf-8')
self.assertIn("Complete Python Course", note_content)
self.assertIn("Comprehensive Python course", note_content)
self.assertIn("前置知识", note_content) # Prerequisites section
self.assertIn("步骤详解", note_content) # Steps section
self.assertIn("常见坑点", note_content) # Pitfalls section
self.assertIn("实践建议", note_content) # Practice advice section
def test_pipeline_article(self):
"""Test pipeline with article content."""
# 1. Create article content
content = Content(
id="zhihu_article_001",
source="zhihu",
title="Understanding Neural Networks",
url="https://zhihu.com/question/123456",
author="AIResearcher",
content_type=ContentType.ARTICLE,
status=ContentStatus.NEW,
tags=["ai", "machine-learning"]
)
# 2. Add to inbox
self.storage.add_to_inbox(content)
# 3. Simulate analysis
content.status = ContentStatus.GENERATED
content.analysis = ContentAnalysis(
one_liner="Comprehensive guide to neural network fundamentals",
summary="Deep dive into how neural networks work.",
key_points=["Perceptrons", "Backpropagation", "Activation functions"],
tags=["ai", "neural-networks", "deep-learning"],
quality_score=90
)
# 4. Generate note
output_path = self.vault_dir / "inbox" / "neural_networks.md"
result_path = self.renderer.render_to_file(content, str(output_path))
# 5. Verify article template
note_content = Path(result_path).read_text(encoding='utf-8')
self.assertIn("Understanding Neural Networks", note_content)
self.assertIn("原文链接", note_content) # Original link section
def test_inbox_to_topic_workflow(self):
"""Test moving content from inbox to topics."""
# 1. Add content to inbox
content = Content(
id="bvid_BV3topic01",
source="bilibili",
title="React Tutorial",
url="https://bilibili.com/video/BV3topic01",
author="FrontendDev",
duration=1800,
tags=["react", "javascript", "frontend"]
)
content.analysis = ContentAnalysis(
one_liner="React fundamentals",
quality_score=88
)
self.storage.add_to_inbox(content)
# 2. Process and complete
content.status = ContentStatus.GENERATED
self.storage.update_inbox_item(content)
# 3. Move to topic (simulate)
topic_path = self.vault_dir / "topics" / "frontend" / "react_tutorial.md"
topic_path.parent.mkdir(parents=True, exist_ok=True)
result_path = self.renderer.render_to_file(content, str(topic_path))
content.note_path = str(result_path)
self.storage.update_inbox_item(content)
# 4. Verify
self.assertTrue(Path(result_path).exists())
self.assertIn("topics/frontend", content.note_path)
def test_skip_content(self):
"""Test skipping content."""
# 1. Add content
content = Content(
id="bvid_BV4skip001",
source="bilibili",
title="Low Quality Video",
url="https://bilibili.com/video/BV4skip001"
)
content.analysis = ContentAnalysis(quality_score=30) # Low quality
self.storage.add_to_inbox(content)
# 2. Skip it
content.status = ContentStatus.SKIPPED
self.storage.update_inbox_item(content)
# 3. Verify not in pending
pending = self.storage.get_pending_items()
skipped_ids = [c.id for c in self.storage.get_inbox() if c.status == ContentStatus.SKIPPED]
self.assertIn(content.id, skipped_ids)
self.assertNotIn(content, pending)
def test_batch_processing(self):
"""Test processing multiple items."""
# Add multiple items
contents = [
Content(id=f"batch_{i}", source="bilibili", title=f"Video {i}", url=f"https://example.com/{i}")
for i in range(5)
]
for content in contents:
self.storage.add_to_inbox(content)
# Process all pending
pending = self.storage.get_pending_items()
self.assertEqual(len(pending), 5)
for content in pending:
content.status = ContentStatus.GENERATED
content.analysis = ContentAnalysis(
one_liner=f"Summary for {content.title}",
quality_score=80
)
self.storage.update_inbox_item(content)
# Verify all processed
final_pending = self.storage.get_pending_items()
self.assertEqual(len(final_pending), 0)
class TestEndToEnd(unittest.TestCase):
"""End-to-end integration tests."""
def test_config_storage_renderer_integration(self):
"""Test integration of config, storage, and renderer."""
with tempfile.TemporaryDirectory() as tmpdir:
# Setup
data_dir = Path(tmpdir) / "data"
vault_dir = Path(tmpdir) / "vault"
config_path = Path(tmpdir) / "config.yaml"
config = Config(config_path=str(config_path))
config.set('git.vault_path', str(vault_dir))
config.set('chunking.chunk_duration_seconds', 600)
config.save()
storage = Storage(data_dir=str(data_dir))
templates_dir = Path(__file__).parent.parent / 'src' / 'templates'
renderer = TemplateRenderer(str(templates_dir))
# Create and process content
content = Content(
id="e2e_test_001",
source="bilibili",
title="E2E Test Video",
url="https://example.com/e2e",
duration=1200
)
content.analysis = ContentAnalysis(
one_liner="End to end test",
key_points=["Point 1", "Point 2"]
)
# Full workflow
storage.add_to_inbox(content)
output_path = Path(config.vault_path) / "e2e_output.md"
renderer.render_to_file(content, str(output_path))
content.status = ContentStatus.GENERATED
content.note_path = str(output_path)
storage.update_inbox_item(content)
# Verify
self.assertTrue(output_path.exists())
self.assertEqual(storage.get_item_by_id(content.id).status, ContentStatus.GENERATED)
if __name__ == '__main__':
unittest.main()