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
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
"""
|
||||
Unit tests for content chunking module.
|
||||
"""
|
||||
import unittest
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent / 'src'))
|
||||
|
||||
from content_chunker import ContentChunker, Chunk
|
||||
|
||||
|
||||
class TestContentChunker(unittest.TestCase):
|
||||
"""Test ContentChunker class."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up test fixtures."""
|
||||
self.chunker = ContentChunker(chunk_duration_seconds=900)
|
||||
|
||||
def test_chunker_initialization(self):
|
||||
"""Test chunker initialization."""
|
||||
self.assertEqual(self.chunker.chunk_duration, 900)
|
||||
self.assertEqual(self.chunker.max_chunk_duration, 1800)
|
||||
|
||||
def test_parse_srt(self):
|
||||
"""Test SRT parsing."""
|
||||
srt_content = """1
|
||||
00:00:01,000 --> 00:00:05,000
|
||||
First subtitle
|
||||
|
||||
2
|
||||
00:00:06,000 --> 00:00:10,000
|
||||
Second subtitle
|
||||
|
||||
3
|
||||
00:00:11,000 --> 00:00:15,000
|
||||
Third subtitle"""
|
||||
|
||||
subtitles = self.chunker._parse_srt(srt_content)
|
||||
|
||||
self.assertEqual(len(subtitles), 3)
|
||||
self.assertEqual(subtitles[0], (1.0, 5.0, "First subtitle"))
|
||||
self.assertEqual(subtitles[1], (6.0, 10.0, "Second subtitle"))
|
||||
self.assertEqual(subtitles[2], (11.0, 15.0, "Third subtitle"))
|
||||
|
||||
def test_time_to_seconds(self):
|
||||
"""Test time conversion."""
|
||||
self.assertEqual(self.chunker._time_to_seconds("00:00:30,000"), 30.0)
|
||||
self.assertEqual(self.chunker._time_to_seconds("00:01:30,000"), 90.0)
|
||||
self.assertEqual(self.chunker._time_to_seconds("01:00:00,000"), 3600.0)
|
||||
self.assertEqual(self.chunker._time_to_seconds("01:30:45,500"), 5445.5)
|
||||
|
||||
def test_chunk_by_duration_short_content(self):
|
||||
"""Test chunking short content (single chunk)."""
|
||||
srt_content = """1
|
||||
00:00:00,000 --> 00:00:10,000
|
||||
This is the first line
|
||||
|
||||
2
|
||||
00:00:10,000 --> 00:00:20,000
|
||||
This is the second line"""
|
||||
|
||||
chunks = self.chunker.chunk_by_duration(srt_content, target_duration=900)
|
||||
|
||||
self.assertEqual(len(chunks), 1)
|
||||
self.assertEqual(chunks[0].index, 0)
|
||||
self.assertEqual(chunks[0].start_time, 0.0)
|
||||
self.assertEqual(chunks[0].end_time, 20.0)
|
||||
|
||||
def test_chunk_by_duration_long_content(self):
|
||||
"""Test chunking long content (multiple chunks)."""
|
||||
# Create SRT content that spans 25 minutes
|
||||
srt_lines = []
|
||||
for i in range(25):
|
||||
start = i * 60
|
||||
end = (i + 1) * 60
|
||||
srt_lines.append(f"{i+1}")
|
||||
srt_lines.append(f"00:{i:02d}:00,000 --> 00:{i+1:02d}:00,000")
|
||||
srt_lines.append(f"Minute {i+1} content")
|
||||
srt_lines.append("")
|
||||
|
||||
srt_content = "\n".join(srt_lines)
|
||||
|
||||
# Use 10-minute chunks
|
||||
chunks = self.chunker.chunk_by_duration(srt_content, target_duration=600)
|
||||
|
||||
self.assertGreater(len(chunks), 1)
|
||||
# First chunk should start at 0
|
||||
self.assertEqual(chunks[0].start_time, 0.0)
|
||||
|
||||
def test_chunk_time_range(self):
|
||||
"""Test chunk time range formatting."""
|
||||
chunk = Chunk(index=0, start_time=125.0, end_time=245.0, text="Test")
|
||||
|
||||
self.assertEqual(chunk.time_range, "02:05-04:05")
|
||||
self.assertEqual(chunk.duration, 120.0)
|
||||
|
||||
def test_chunk_time_range_with_hours(self):
|
||||
"""Test chunk time range with hours."""
|
||||
chunk = Chunk(index=0, start_time=3665.0, end_time=7265.0, text="Test")
|
||||
|
||||
self.assertEqual(chunk.time_range, "01:01:05-02:01:05")
|
||||
|
||||
def test_get_chunking_strategy_short(self):
|
||||
"""Test chunking strategy for short content."""
|
||||
strategy = self.chunker.get_chunking_strategy(300) # 5 minutes
|
||||
|
||||
self.assertEqual(strategy['strategy'], 'direct')
|
||||
self.assertEqual(strategy['chunk_count'], 1)
|
||||
|
||||
def test_get_chunking_strategy_medium(self):
|
||||
"""Test chunking strategy for medium content."""
|
||||
strategy = self.chunker.get_chunking_strategy(1200) # 20 minutes
|
||||
|
||||
self.assertEqual(strategy['strategy'], 'truncate')
|
||||
self.assertEqual(strategy['chunk_count'], 1)
|
||||
|
||||
def test_get_chunking_strategy_long(self):
|
||||
"""Test chunking strategy for long content."""
|
||||
strategy = self.chunker.get_chunking_strategy(2400) # 40 minutes
|
||||
|
||||
self.assertEqual(strategy['strategy'], 'two_part')
|
||||
self.assertEqual(strategy['chunk_count'], 2)
|
||||
|
||||
def test_get_chunking_strategy_very_long(self):
|
||||
"""Test chunking strategy for very long content."""
|
||||
strategy = self.chunker.get_chunking_strategy(7200) # 2 hours
|
||||
|
||||
self.assertEqual(strategy['strategy'], 'multi_part')
|
||||
self.assertGreater(strategy['chunk_count'], 2)
|
||||
|
||||
|
||||
class TestChunk(unittest.TestCase):
|
||||
"""Test Chunk dataclass."""
|
||||
|
||||
def test_chunk_properties(self):
|
||||
chunk = Chunk(
|
||||
index=1,
|
||||
start_time=100.0,
|
||||
end_time=250.0,
|
||||
text="Test content"
|
||||
)
|
||||
|
||||
self.assertEqual(chunk.index, 1)
|
||||
self.assertEqual(chunk.duration, 150.0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user