- Updated the client submodule to the latest commit for enhanced compatibility. - Refactored HTML export logic to utilize a dedicated function for building HTML documents, improving code clarity and maintainability. - Enhanced test assertions across various files to include descriptive messages, aiding in debugging and understanding test failures. All quality checks pass.
325 lines
15 KiB
Python
325 lines
15 KiB
Python
"""Tests for Summary, KeyPoint, and ActionItem entities."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
|
|
import pytest
|
|
|
|
from noteflow.domain.entities.summary import ActionItem, KeyPoint, Summary
|
|
from noteflow.domain.value_objects import MeetingId
|
|
|
|
# Test constants
|
|
MANY_SEGMENT_IDS_COUNT = 50
|
|
KEY_POINT_START_TIME = 10.5
|
|
KEY_POINT_END_TIME = 25.0
|
|
VERY_LONG_SUMMARY_LENGTH = 10000
|
|
|
|
|
|
class TestKeyPoint:
|
|
"""Tests for KeyPoint entity."""
|
|
|
|
@pytest.mark.parametrize(
|
|
"attr,expected",
|
|
[
|
|
("text", "Important discussion about architecture"),
|
|
("segment_ids", []),
|
|
("start_time", 0.0),
|
|
("end_time", 0.0),
|
|
],
|
|
)
|
|
def test_key_point_defaults(self, attr: str, expected: object) -> None:
|
|
"""Test KeyPoint default attribute values."""
|
|
kp = KeyPoint(text="Important discussion about architecture")
|
|
assert getattr(kp, attr) == expected, f"expected {attr}={expected!r}, got {getattr(kp, attr)!r}"
|
|
|
|
def test_key_point_is_sourced_false(self) -> None:
|
|
"""Test is_sourced returns False when no segment_ids."""
|
|
kp = KeyPoint(text="No evidence")
|
|
assert kp.is_sourced() is False, "key point without segment_ids should not be sourced"
|
|
|
|
def test_key_point_is_sourced_true(self) -> None:
|
|
"""Test is_sourced returns True with segment_ids."""
|
|
kp = KeyPoint(text="With evidence", segment_ids=[1, 2, 3])
|
|
assert kp.is_sourced() is True, "key point with segment_ids should be sourced"
|
|
|
|
def test_key_point_with_timing(self) -> None:
|
|
"""Test KeyPoint with timing information."""
|
|
kp = KeyPoint(
|
|
text="Timed point",
|
|
segment_ids=[0, 1],
|
|
start_time=KEY_POINT_START_TIME,
|
|
end_time=KEY_POINT_END_TIME,
|
|
)
|
|
assert kp.start_time == KEY_POINT_START_TIME, "start_time should match provided value"
|
|
assert kp.end_time == KEY_POINT_END_TIME, "end_time should match provided value"
|
|
|
|
|
|
class TestActionItem:
|
|
"""Tests for ActionItem entity."""
|
|
|
|
@pytest.mark.parametrize(
|
|
"attr,expected",
|
|
[
|
|
("text", "Review PR #123"),
|
|
("assignee", ""),
|
|
("due_date", None),
|
|
("priority", 0),
|
|
("segment_ids", []),
|
|
],
|
|
)
|
|
def test_action_item_defaults(self, attr: str, expected: object) -> None:
|
|
"""Test ActionItem default attribute values."""
|
|
ai = ActionItem(text="Review PR #123")
|
|
assert getattr(ai, attr) == expected, f"expected {attr}={expected!r}, got {getattr(ai, attr)!r}"
|
|
|
|
def test_action_item_has_evidence_false(self) -> None:
|
|
"""Test has_evidence returns False when no segment_ids."""
|
|
ai = ActionItem(text="Task without evidence")
|
|
assert ai.has_evidence() is False, "action item without segment_ids should not have evidence"
|
|
|
|
def test_action_item_has_evidence_true(self) -> None:
|
|
"""Test has_evidence returns True with segment_ids."""
|
|
ai = ActionItem(text="Task with evidence", segment_ids=[5])
|
|
assert ai.has_evidence() is True, "action item with segment_ids should have evidence"
|
|
|
|
def test_action_item_is_assigned_false(self) -> None:
|
|
"""Test is_assigned returns False when no assignee."""
|
|
ai = ActionItem(text="Unassigned task")
|
|
assert ai.is_assigned() is False, "action item without assignee should not be assigned"
|
|
|
|
def test_action_item_is_assigned_true(self) -> None:
|
|
"""Test is_assigned returns True with assignee."""
|
|
ai = ActionItem(text="Assigned task", assignee="Alice")
|
|
assert ai.is_assigned() is True, "action item with assignee should be assigned"
|
|
|
|
def test_action_item_has_due_date_false(self) -> None:
|
|
"""Test has_due_date returns False when no due_date."""
|
|
ai = ActionItem(text="No deadline")
|
|
assert ai.has_due_date() is False, "action item without due_date should not have due date"
|
|
|
|
def test_action_item_has_due_date_true(self) -> None:
|
|
"""Test has_due_date returns True with due_date."""
|
|
ai = ActionItem(text="With deadline", due_date=datetime(2024, 12, 31))
|
|
assert ai.has_due_date() is True, "action item with due_date should have due date"
|
|
|
|
|
|
class TestSummary:
|
|
"""Tests for Summary entity."""
|
|
|
|
@pytest.mark.parametrize(
|
|
"attr,expected",
|
|
[
|
|
("executive_summary", ""),
|
|
("key_points", []),
|
|
("action_items", []),
|
|
("generated_at", None),
|
|
("model_version", ""),
|
|
],
|
|
)
|
|
def test_summary_defaults(self, meeting_id: MeetingId, attr: str, expected: object) -> None:
|
|
"""Test Summary default attribute values."""
|
|
summary = Summary(meeting_id=meeting_id)
|
|
assert getattr(summary, attr) == expected, f"expected {attr}={expected!r}, got {getattr(summary, attr)!r}"
|
|
|
|
def test_summary_meeting_id(self, meeting_id: MeetingId) -> None:
|
|
"""Test Summary stores meeting_id correctly."""
|
|
summary = Summary(meeting_id=meeting_id)
|
|
assert summary.meeting_id == meeting_id, f"expected meeting_id={meeting_id}, got {summary.meeting_id}"
|
|
|
|
def test_summary_key_point_count(self, meeting_id: MeetingId) -> None:
|
|
"""Test key_point_count property."""
|
|
summary = Summary(
|
|
meeting_id=meeting_id,
|
|
key_points=[
|
|
KeyPoint(text="Point 1"),
|
|
KeyPoint(text="Point 2"),
|
|
KeyPoint(text="Point 3"),
|
|
],
|
|
)
|
|
assert summary.key_point_count == 3, f"expected key_point_count=3, got {summary.key_point_count}"
|
|
|
|
def test_summary_action_item_count(self, meeting_id: MeetingId) -> None:
|
|
"""Test action_item_count property."""
|
|
summary = Summary(
|
|
meeting_id=meeting_id,
|
|
action_items=[
|
|
ActionItem(text="Task 1"),
|
|
ActionItem(text="Task 2"),
|
|
],
|
|
)
|
|
assert summary.action_item_count == 2, f"expected action_item_count=2, got {summary.action_item_count}"
|
|
|
|
def test_all_points_have_evidence_true(self, meeting_id: MeetingId) -> None:
|
|
"""Test all_points_have_evidence returns True when all evidenced."""
|
|
summary = Summary(
|
|
meeting_id=meeting_id,
|
|
key_points=[
|
|
KeyPoint(text="Point 1", segment_ids=[0]),
|
|
KeyPoint(text="Point 2", segment_ids=[1, 2]),
|
|
],
|
|
)
|
|
assert summary.all_points_have_evidence() is True, "all points with segment_ids should be evidenced"
|
|
|
|
def test_all_points_have_evidence_false(self, meeting_id: MeetingId) -> None:
|
|
"""Test all_points_have_evidence returns False when some unevidenced."""
|
|
summary = Summary(
|
|
meeting_id=meeting_id,
|
|
key_points=[
|
|
KeyPoint(text="Point 1", segment_ids=[0]),
|
|
KeyPoint(text="Point 2"), # No evidence
|
|
],
|
|
)
|
|
assert summary.all_points_have_evidence() is False, "should be False when some points lack segment_ids"
|
|
|
|
def test_all_actions_have_evidence_true(self, meeting_id: MeetingId) -> None:
|
|
"""Test all_actions_have_evidence returns True when all evidenced."""
|
|
summary = Summary(
|
|
meeting_id=meeting_id,
|
|
action_items=[
|
|
ActionItem(text="Task 1", segment_ids=[0]),
|
|
],
|
|
)
|
|
assert summary.all_actions_have_evidence() is True, "all actions with segment_ids should be evidenced"
|
|
|
|
def test_all_actions_have_evidence_false(self, meeting_id: MeetingId) -> None:
|
|
"""Test all_actions_have_evidence returns False when some unevidenced."""
|
|
summary = Summary(
|
|
meeting_id=meeting_id,
|
|
action_items=[
|
|
ActionItem(text="Task 1"), # No evidence
|
|
],
|
|
)
|
|
assert summary.all_actions_have_evidence() is False, "should be False when some actions lack segment_ids"
|
|
|
|
def test_is_fully_evidenced_true(self, meeting_id: MeetingId) -> None:
|
|
"""Test is_fully_evidenced returns True when all items evidenced."""
|
|
summary = Summary(
|
|
meeting_id=meeting_id,
|
|
key_points=[KeyPoint(text="KP", segment_ids=[0])],
|
|
action_items=[ActionItem(text="AI", segment_ids=[1])],
|
|
)
|
|
assert summary.is_fully_evidenced() is True, "summary with all evidenced items should be fully evidenced"
|
|
|
|
def test_is_fully_evidenced_false_points(self, meeting_id: MeetingId) -> None:
|
|
"""Test is_fully_evidenced returns False with unevidenced points."""
|
|
summary = Summary(
|
|
meeting_id=meeting_id,
|
|
key_points=[KeyPoint(text="KP")], # No evidence
|
|
action_items=[ActionItem(text="AI", segment_ids=[1])],
|
|
)
|
|
assert summary.is_fully_evidenced() is False, "summary with unevidenced points should not be fully evidenced"
|
|
|
|
def test_unevidenced_points(self, meeting_id: MeetingId) -> None:
|
|
"""Test unevidenced_points property filters correctly."""
|
|
kp_no_evidence = KeyPoint(text="No evidence")
|
|
kp_with_evidence = KeyPoint(text="With evidence", segment_ids=[0])
|
|
summary = Summary(
|
|
meeting_id=meeting_id,
|
|
key_points=[kp_no_evidence, kp_with_evidence],
|
|
)
|
|
unevidenced = summary.unevidenced_points
|
|
assert len(unevidenced) == 1, "should have exactly one unevidenced point"
|
|
assert unevidenced[0] == kp_no_evidence, "unevidenced list should contain the point without segment_ids"
|
|
|
|
def test_unevidenced_actions(self, meeting_id: MeetingId) -> None:
|
|
"""Test unevidenced_actions property filters correctly."""
|
|
ai_no_evidence = ActionItem(text="No evidence")
|
|
ai_with_evidence = ActionItem(text="With evidence", segment_ids=[0])
|
|
summary = Summary(
|
|
meeting_id=meeting_id,
|
|
action_items=[ai_no_evidence, ai_with_evidence],
|
|
)
|
|
unevidenced = summary.unevidenced_actions
|
|
assert len(unevidenced) == 1, "should have exactly one unevidenced action"
|
|
assert unevidenced[0] == ai_no_evidence, "unevidenced list should contain the action without segment_ids"
|
|
|
|
|
|
class TestSummaryEdgeCases:
|
|
"""Edge case tests for Summary entity."""
|
|
|
|
def test_all_points_have_evidence_empty_list(self, meeting_id: MeetingId) -> None:
|
|
"""Test all_points_have_evidence returns True for empty key_points."""
|
|
summary = Summary(meeting_id=meeting_id, key_points=[])
|
|
assert summary.all_points_have_evidence() is True, "empty key_points list should be considered all evidenced"
|
|
|
|
def test_all_actions_have_evidence_empty_list(self, meeting_id: MeetingId) -> None:
|
|
"""Test all_actions_have_evidence returns True for empty action_items."""
|
|
summary = Summary(meeting_id=meeting_id, action_items=[])
|
|
assert summary.all_actions_have_evidence() is True, "empty action_items list should be considered all evidenced"
|
|
|
|
def test_is_fully_evidenced_empty_summary(self, meeting_id: MeetingId) -> None:
|
|
"""Test is_fully_evidenced returns True for empty summary."""
|
|
summary = Summary(meeting_id=meeting_id)
|
|
assert summary.is_fully_evidenced() is True, "empty summary should be considered fully evidenced"
|
|
|
|
def test_all_points_unevidenced(self, meeting_id: MeetingId) -> None:
|
|
"""Test all_points_have_evidence returns False when all points unevidenced."""
|
|
summary = Summary(
|
|
meeting_id=meeting_id,
|
|
key_points=[
|
|
KeyPoint(text="Point 1"),
|
|
KeyPoint(text="Point 2"),
|
|
KeyPoint(text="Point 3"),
|
|
],
|
|
)
|
|
assert summary.all_points_have_evidence() is False, "should return False when no points have evidence"
|
|
assert len(summary.unevidenced_points) == 3, "all three points should be unevidenced"
|
|
|
|
def test_mixed_evidence_counts(self, meeting_id: MeetingId) -> None:
|
|
"""Test summary with mixed evidence states across points and actions."""
|
|
summary = Summary(
|
|
meeting_id=meeting_id,
|
|
key_points=[
|
|
KeyPoint(text="Evidenced point", segment_ids=[0]),
|
|
KeyPoint(text="Unevidenced point"),
|
|
],
|
|
action_items=[
|
|
ActionItem(text="Evidenced action", segment_ids=[1]),
|
|
ActionItem(text="Unevidenced action 1"),
|
|
ActionItem(text="Unevidenced action 2"),
|
|
],
|
|
)
|
|
assert len(summary.unevidenced_points) == 1, "one point lacks evidence"
|
|
assert len(summary.unevidenced_actions) == 2, "two actions lack evidence"
|
|
assert summary.is_fully_evidenced() is False, "mixed evidence means not fully evidenced"
|
|
|
|
def test_key_point_with_many_segment_ids(self) -> None:
|
|
"""Test key point with many segment references."""
|
|
many_segments = list(range(MANY_SEGMENT_IDS_COUNT))
|
|
kp = KeyPoint(text="Well-sourced point", segment_ids=many_segments)
|
|
assert kp.is_sourced() is True, "key point with segments should be sourced"
|
|
assert len(kp.segment_ids) == MANY_SEGMENT_IDS_COUNT, "all segment_ids should be preserved"
|
|
|
|
def test_action_item_with_all_fields(self) -> None:
|
|
"""Test action item with all optional fields populated."""
|
|
ai = ActionItem(
|
|
text="Complete task",
|
|
assignee="Alice",
|
|
due_date=datetime(2024, 12, 31, 23, 59, 59),
|
|
priority=1,
|
|
segment_ids=[1, 2, 3],
|
|
)
|
|
assert ai.is_assigned() is True, "should be assigned when assignee provided"
|
|
assert ai.has_due_date() is True, "should have due date when provided"
|
|
assert ai.has_evidence() is True, "should have evidence when segment_ids provided"
|
|
assert ai.priority == 1, "priority should match provided value"
|
|
|
|
def test_summary_with_unicode_content(self, meeting_id: MeetingId) -> None:
|
|
"""Test summary handles unicode content."""
|
|
summary = Summary(
|
|
meeting_id=meeting_id,
|
|
executive_summary="会议总结 🎯",
|
|
key_points=[KeyPoint(text="重要讨论点")],
|
|
action_items=[ActionItem(text="完成任务 ✅", assignee="张三")],
|
|
)
|
|
assert "会议总结" in summary.executive_summary, "executive_summary should contain unicode"
|
|
assert summary.key_points[0].text == "重要讨论点", "key point text should preserve unicode"
|
|
assert summary.action_items[0].assignee == "张三", "action item assignee should preserve unicode"
|
|
|
|
def test_summary_very_long_executive_summary(self, meeting_id: MeetingId) -> None:
|
|
"""Test summary handles very long executive summary."""
|
|
long_summary = "A" * VERY_LONG_SUMMARY_LENGTH
|
|
summary = Summary(meeting_id=meeting_id, executive_summary=long_summary)
|
|
assert len(summary.executive_summary) == VERY_LONG_SUMMARY_LENGTH, f"expected length {VERY_LONG_SUMMARY_LENGTH}, got {len(summary.executive_summary)}"
|