feat(tagging-system): Add comprehensive tagging system implementation
- Add tagging system Python package with modular architecture (config, core, impl) - Create core interfaces for file discovery, content analysis, and tag generation - Implement content analyzer with language detection and topic extraction - Implement file discovery engine with directory scanning and filtering - Implement tag generator with hierarchical tag creation and consolidation - Add configuration management with example config and validation - Create comprehensive design and requirements documentation in .kiro/specs - Add pytest test suite with unit tests for models, interfaces, and config - Add setup.py, requirements.txt, and pytest.ini for package management - Add README.md with project overview and usage instructions - Update workspace.json with new project structure - Add Excalidraw diagram for system architecture visualization - Establish foundation for automated vault tagging and metadata management
This commit is contained in:
@@ -0,0 +1,232 @@
|
||||
"""Unit tests for core interfaces and protocols."""
|
||||
|
||||
import pytest
|
||||
from typing import List
|
||||
from tagging_system.core.interfaces import (
|
||||
FileDiscovery,
|
||||
ContentAnalyzer,
|
||||
TagGenerator,
|
||||
FrontmatterManager,
|
||||
BaseFileDiscovery,
|
||||
BaseContentAnalyzer,
|
||||
BaseTagGenerator,
|
||||
BaseFrontmatterManager
|
||||
)
|
||||
from tagging_system.core.models import (
|
||||
FileInfo,
|
||||
ContentAnalysis,
|
||||
TagStructure,
|
||||
FrontmatterData,
|
||||
ValidationResult,
|
||||
ContentType,
|
||||
LanguageInfo
|
||||
)
|
||||
|
||||
|
||||
class MockFileDiscovery(BaseFileDiscovery):
|
||||
"""Mock implementation of FileDiscovery for testing."""
|
||||
|
||||
def scan_directory(self, path: str) -> List[FileInfo]:
|
||||
"""Mock directory scanning."""
|
||||
from datetime import datetime
|
||||
return [
|
||||
FileInfo(
|
||||
path=f"{path}/test.md",
|
||||
name="test.md",
|
||||
directory=path,
|
||||
extension=".md",
|
||||
size=100,
|
||||
created=datetime.now(),
|
||||
modified=datetime.now()
|
||||
)
|
||||
]
|
||||
|
||||
def filter_by_type(self, files: List[FileInfo], types: List[str]) -> List[FileInfo]:
|
||||
"""Mock file filtering."""
|
||||
return [f for f in files if f.extension in types]
|
||||
|
||||
def exclude_sensitive(self, files: List[FileInfo]) -> List[FileInfo]:
|
||||
"""Mock sensitive file exclusion."""
|
||||
return [f for f in files if "sensitive" not in f.path]
|
||||
|
||||
|
||||
class MockContentAnalyzer(BaseContentAnalyzer):
|
||||
"""Mock implementation of ContentAnalyzer for testing."""
|
||||
|
||||
def analyze_content(self, content: str) -> ContentAnalysis:
|
||||
"""Mock content analysis."""
|
||||
return ContentAnalysis(
|
||||
language=LanguageInfo.ENGLISH,
|
||||
content_type=ContentType.NOTE,
|
||||
topics=["test"],
|
||||
mentions={'tools': [], 'technologies': [], 'people': [], 'organizations': []}
|
||||
)
|
||||
|
||||
def detect_language(self, content: str) -> str:
|
||||
"""Mock language detection."""
|
||||
return "en"
|
||||
|
||||
def extract_topics(self, content: str) -> List[str]:
|
||||
"""Mock topic extraction."""
|
||||
return ["test", "mock"]
|
||||
|
||||
def classify_content_type(self, content: str, filename: str) -> str:
|
||||
"""Mock content type classification."""
|
||||
return "note"
|
||||
|
||||
|
||||
class MockTagGenerator(BaseTagGenerator):
|
||||
"""Mock implementation of TagGenerator for testing."""
|
||||
|
||||
def generate_directory_tags(self, filepath: str) -> List[str]:
|
||||
"""Mock directory tag generation."""
|
||||
if "100-project" in filepath:
|
||||
return ["project"]
|
||||
return ["unknown"]
|
||||
|
||||
def generate_content_tags(self, analysis: ContentAnalysis) -> List[str]:
|
||||
"""Mock content tag generation."""
|
||||
return analysis.topics
|
||||
|
||||
def generate_hierarchical_tags(self, topics: List[str]) -> List[str]:
|
||||
"""Mock hierarchical tag generation."""
|
||||
return [f"topic/{topic}" for topic in topics]
|
||||
|
||||
def consolidate_tags(self, tags: List[str]) -> List[str]:
|
||||
"""Mock tag consolidation."""
|
||||
return list(set(tags)) # Remove duplicates
|
||||
|
||||
|
||||
class MockFrontmatterManager(BaseFrontmatterManager):
|
||||
"""Mock implementation of FrontmatterManager for testing."""
|
||||
|
||||
def parse_frontmatter(self, content: str) -> FrontmatterData:
|
||||
"""Mock frontmatter parsing."""
|
||||
return FrontmatterData(
|
||||
title="Test",
|
||||
tags=["test"],
|
||||
created="2024-01-01"
|
||||
)
|
||||
|
||||
def update_frontmatter(self, content: str, updates: FrontmatterData) -> str:
|
||||
"""Mock frontmatter updating."""
|
||||
return f"---\ntitle: {updates.title}\ntags: {updates.tags}\n---\n{content}"
|
||||
|
||||
def validate_frontmatter(self, data: FrontmatterData) -> ValidationResult:
|
||||
"""Mock frontmatter validation."""
|
||||
result = ValidationResult(is_valid=True)
|
||||
if not data.title:
|
||||
result.add_error("Title is required")
|
||||
return result
|
||||
|
||||
|
||||
class TestProtocolCompliance:
|
||||
"""Test that mock implementations comply with protocols."""
|
||||
|
||||
def test_file_discovery_protocol_compliance(self):
|
||||
"""Test that MockFileDiscovery implements FileDiscovery protocol."""
|
||||
mock = MockFileDiscovery()
|
||||
|
||||
assert isinstance(mock, FileDiscovery)
|
||||
|
||||
# Test method calls
|
||||
files = mock.scan_directory("test")
|
||||
assert len(files) == 1
|
||||
assert files[0].name == "test.md"
|
||||
|
||||
filtered = mock.filter_by_type(files, [".md"])
|
||||
assert len(filtered) == 1
|
||||
|
||||
non_sensitive = mock.exclude_sensitive(files)
|
||||
assert len(non_sensitive) == 1
|
||||
|
||||
def test_content_analyzer_protocol_compliance(self):
|
||||
"""Test that MockContentAnalyzer implements ContentAnalyzer protocol."""
|
||||
mock = MockContentAnalyzer()
|
||||
|
||||
assert isinstance(mock, ContentAnalyzer)
|
||||
|
||||
# Test method calls
|
||||
analysis = mock.analyze_content("test content")
|
||||
assert analysis.language == LanguageInfo.ENGLISH
|
||||
assert analysis.content_type == ContentType.NOTE
|
||||
|
||||
language = mock.detect_language("test content")
|
||||
assert language == "en"
|
||||
|
||||
topics = mock.extract_topics("test content")
|
||||
assert "test" in topics
|
||||
|
||||
content_type = mock.classify_content_type("test content", "test.md")
|
||||
assert content_type == "note"
|
||||
|
||||
def test_tag_generator_protocol_compliance(self):
|
||||
"""Test that MockTagGenerator implements TagGenerator protocol."""
|
||||
mock = MockTagGenerator()
|
||||
|
||||
assert isinstance(mock, TagGenerator)
|
||||
|
||||
# Test method calls
|
||||
dir_tags = mock.generate_directory_tags("100-project/test.md")
|
||||
assert "project" in dir_tags
|
||||
|
||||
analysis = ContentAnalysis(
|
||||
language=LanguageInfo.ENGLISH,
|
||||
content_type=ContentType.NOTE,
|
||||
topics=["ai", "ml"]
|
||||
)
|
||||
content_tags = mock.generate_content_tags(analysis)
|
||||
assert "ai" in content_tags
|
||||
|
||||
hierarchical = mock.generate_hierarchical_tags(["ai", "ml"])
|
||||
assert "topic/ai" in hierarchical
|
||||
|
||||
consolidated = mock.consolidate_tags(["tag1", "tag1", "tag2"])
|
||||
assert len(consolidated) == 2
|
||||
|
||||
def test_frontmatter_manager_protocol_compliance(self):
|
||||
"""Test that MockFrontmatterManager implements FrontmatterManager protocol."""
|
||||
mock = MockFrontmatterManager()
|
||||
|
||||
assert isinstance(mock, FrontmatterManager)
|
||||
|
||||
# Test method calls
|
||||
frontmatter = mock.parse_frontmatter("---\ntitle: Test\n---\nContent")
|
||||
assert frontmatter.title == "Test"
|
||||
|
||||
updated = mock.update_frontmatter("Content", frontmatter)
|
||||
assert "title: Test" in updated
|
||||
|
||||
validation = mock.validate_frontmatter(frontmatter)
|
||||
assert validation.is_valid is True
|
||||
|
||||
|
||||
class TestAbstractBaseClasses:
|
||||
"""Test abstract base class behavior."""
|
||||
|
||||
def test_base_classes_cannot_be_instantiated(self):
|
||||
"""Test that abstract base classes cannot be instantiated directly."""
|
||||
with pytest.raises(TypeError):
|
||||
BaseFileDiscovery()
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
BaseContentAnalyzer()
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
BaseTagGenerator()
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
BaseFrontmatterManager()
|
||||
|
||||
def test_concrete_implementations_work(self):
|
||||
"""Test that concrete implementations of base classes work."""
|
||||
file_discovery = MockFileDiscovery()
|
||||
content_analyzer = MockContentAnalyzer()
|
||||
tag_generator = MockTagGenerator()
|
||||
frontmatter_manager = MockFrontmatterManager()
|
||||
|
||||
# All should be instances of their respective base classes
|
||||
assert isinstance(file_discovery, BaseFileDiscovery)
|
||||
assert isinstance(content_analyzer, BaseContentAnalyzer)
|
||||
assert isinstance(tag_generator, BaseTagGenerator)
|
||||
assert isinstance(frontmatter_manager, BaseFrontmatterManager)
|
||||
Reference in New Issue
Block a user