Files
noteflow/tests/domain/test_annotation.py
Travis Vasceannie 1ce24cdf7b feat: reorganize Claude hooks and add RAG documentation structure with error handling policies
- Moved all hookify configuration files from `.claude/` to `.claude/hooks/` subdirectory for better organization
- Added four new blocking hooks to prevent common error handling anti-patterns:
  - `block-broad-exception-handler`: Prevents catching generic `Exception` with only logging
  - `block-datetime-now-fallback`: Blocks returning `datetime.now()` as fallback on parse failures to prevent data corruption
  - `block-default
2026-01-15 15:58:06 +00:00

145 lines
5.6 KiB
Python

"""Tests for Annotation entity."""
from __future__ import annotations
from uuid import uuid4
import pytest
from noteflow.domain.entities.annotation import Annotation
from noteflow.domain.value_objects import AnnotationId, AnnotationType, MeetingId
# Test constants
TWO_HOURS_SECONDS = 7200.0
class TestAnnotation:
"""Tests for Annotation entity."""
def test_annotation_valid(self, sample_annotation: Annotation) -> None:
"""Annotation fixture has expected properties."""
assert sample_annotation.text == "Sample annotation text", "text should match fixture"
assert sample_annotation.duration == 1.0, "duration should be end - start"
assert sample_annotation.has_segments() is False, "no segment_ids means has_segments is False"
def test_annotation_invalid_times_raises(self) -> None:
"""Annotation raises when end_time < start_time."""
with pytest.raises(ValueError, match=r"end_time .* must be >= start_time"):
Annotation(
id=AnnotationId(uuid4()),
meeting_id=MeetingId(uuid4()),
annotation_type=AnnotationType.DECISION,
text="Bad timing",
start_time=5.0,
end_time=2.0,
)
def test_annotation_has_segments(self) -> None:
"""has_segments reflects segment_ids list."""
annotation = Annotation(
id=AnnotationId(uuid4()),
meeting_id=MeetingId(uuid4()),
annotation_type=AnnotationType.ACTION_ITEM,
text="Follow up",
start_time=0.0,
end_time=1.0,
segment_ids=[1, 2],
)
assert annotation.has_segments() is True, "has_segments should be True when segment_ids provided"
assert annotation.duration == 1.0, "duration should be end - start"
class TestAnnotationEdgeCases:
"""Edge case tests for Annotation entity."""
def test_annotation_zero_duration(self) -> None:
"""Test annotation with zero duration is valid."""
annotation = Annotation(
id=AnnotationId(uuid4()),
meeting_id=MeetingId(uuid4()),
annotation_type=AnnotationType.NOTE,
text="Instant note",
start_time=5.0,
end_time=5.0,
)
assert annotation.duration == 0.0, "duration should be 0.0 when start_time equals end_time"
def test_annotation_empty_text(self) -> None:
"""Test annotation with empty text."""
annotation = Annotation(
id=AnnotationId(uuid4()),
meeting_id=MeetingId(uuid4()),
annotation_type=AnnotationType.NOTE,
text="",
start_time=0.0,
end_time=1.0,
)
assert annotation.text == "", "empty text should be preserved as empty string"
def test_annotation_unicode_text(self) -> None:
"""Test annotation with unicode text."""
annotation = Annotation(
id=AnnotationId(uuid4()),
meeting_id=MeetingId(uuid4()),
annotation_type=AnnotationType.NOTE,
text="重要な点 🎯 café",
start_time=0.0,
end_time=1.0,
)
assert "🎯" in annotation.text, f"unicode emoji should be preserved in text, got: {annotation.text}"
def test_annotation_many_segment_ids(self) -> None:
"""Test annotation with many segment IDs."""
many_segments = list(range(100))
annotation = Annotation(
id=AnnotationId(uuid4()),
meeting_id=MeetingId(uuid4()),
annotation_type=AnnotationType.DECISION,
text="Major decision",
start_time=0.0,
end_time=60.0,
segment_ids=many_segments,
)
assert annotation.has_segments() is True, "has_segments should be True with many segments"
assert len(annotation.segment_ids) == 100, "all segment_ids should be preserved"
def test_annotation_very_long_duration(self) -> None:
"""Test annotation spanning long duration."""
annotation = Annotation(
id=AnnotationId(uuid4()),
meeting_id=MeetingId(uuid4()),
annotation_type=AnnotationType.NOTE,
text="Long meeting note",
start_time=0.0,
end_time=TWO_HOURS_SECONDS,
)
assert annotation.duration == TWO_HOURS_SECONDS, f"duration should be {TWO_HOURS_SECONDS} seconds (2 hours), got {annotation.duration}"
@pytest.mark.parametrize("annotation_type", list(AnnotationType))
def test_annotation_all_types(self, annotation_type: AnnotationType) -> None:
"""Test all annotation types are valid."""
meeting_id = MeetingId(uuid4())
annotation = Annotation(
id=AnnotationId(uuid4()),
meeting_id=meeting_id,
annotation_type=annotation_type,
text=f"Test {annotation_type.name}",
start_time=0.0,
end_time=1.0,
)
assert annotation.annotation_type == annotation_type, f"annotation_type should match {annotation_type}, got {annotation.annotation_type}"
def test_annotation_empty_segment_ids_list(self) -> None:
"""Test annotation with explicitly empty segment_ids."""
annotation = Annotation(
id=AnnotationId(uuid4()),
meeting_id=MeetingId(uuid4()),
annotation_type=AnnotationType.NOTE,
text="No segments",
start_time=0.0,
end_time=1.0,
segment_ids=[],
)
assert annotation.has_segments() is False, "empty segment_ids list should result in has_segments() returning False"