- Add InviteCompressor for terminal-friendly calendar invite summaries - Add test fixtures for large group invite and cancellation emails - Compress To/CC headers to single line with '... (+N more)' truncation - Add 'h' keybinding to toggle between compressed and full headers - EnvelopeHeader now shows first 2 recipients by default
184 lines
6.4 KiB
Python
184 lines
6.4 KiB
Python
"""Tests for calendar invite compression."""
|
|
|
|
import pytest
|
|
from pathlib import Path
|
|
|
|
from src.mail.invite_compressor import InviteCompressor, compress_invite
|
|
from src.mail.utils.calendar_parser import (
|
|
parse_calendar_from_raw_message,
|
|
is_cancelled_event,
|
|
is_event_request,
|
|
)
|
|
from src.mail.notification_detector import is_calendar_email
|
|
|
|
|
|
class TestInviteDetection:
|
|
"""Test detection of calendar invite emails."""
|
|
|
|
def test_detect_large_group_invite(self):
|
|
"""Test detection of large group meeting invite."""
|
|
fixture_path = Path(
|
|
"tests/fixtures/test_mailbox/INBOX/cur/17051227-large-group-invite.test:2,S"
|
|
)
|
|
assert fixture_path.exists(), f"Fixture not found: {fixture_path}"
|
|
|
|
with open(fixture_path, "r") as f:
|
|
raw_message = f.read()
|
|
|
|
# Create envelope from message
|
|
envelope = {
|
|
"from": {"addr": "product.dev@example.com", "name": "Product Development"},
|
|
"subject": "Project Kickoff Meeting",
|
|
"date": "2025-12-19T21:42:58+00:00",
|
|
}
|
|
|
|
# Should be detected as calendar email
|
|
assert is_calendar_email(envelope) is True
|
|
|
|
# Parse the ICS
|
|
event = parse_calendar_from_raw_message(raw_message)
|
|
assert event is not None
|
|
assert event.method == "REQUEST"
|
|
assert is_event_request(event) is True
|
|
assert event.summary == "Project Kickoff Meeting"
|
|
assert len(event.attendees) >= 20 # Large group
|
|
|
|
def test_detect_cancellation(self):
|
|
"""Test detection of meeting cancellation."""
|
|
fixture_path = Path(
|
|
"tests/fixtures/test_mailbox/INBOX/cur/17051228-cancellation.test:2,S"
|
|
)
|
|
assert fixture_path.exists(), f"Fixture not found: {fixture_path}"
|
|
|
|
with open(fixture_path, "r") as f:
|
|
raw_message = f.read()
|
|
|
|
envelope = {
|
|
"from": {"addr": "cody.marshall@example.com", "name": "Marshall, Cody"},
|
|
"subject": "Canceled: Technical Refinement",
|
|
"date": "2025-12-19T19:12:46+00:00",
|
|
}
|
|
|
|
# Should be detected as calendar email
|
|
assert is_calendar_email(envelope) is True
|
|
|
|
# Parse the ICS
|
|
event = parse_calendar_from_raw_message(raw_message)
|
|
assert event is not None
|
|
assert event.method == "CANCEL"
|
|
assert is_cancelled_event(event) is True
|
|
assert event.status == "CANCELLED"
|
|
|
|
|
|
class TestInviteCompression:
|
|
"""Test compression of calendar invite content."""
|
|
|
|
def test_compress_large_group_invite(self):
|
|
"""Test compression of large group meeting invite."""
|
|
fixture_path = Path(
|
|
"tests/fixtures/test_mailbox/INBOX/cur/17051227-large-group-invite.test:2,S"
|
|
)
|
|
with open(fixture_path, "r") as f:
|
|
raw_message = f.read()
|
|
|
|
envelope = {
|
|
"from": {"addr": "product.dev@example.com", "name": "Product Development"},
|
|
"subject": "Project Kickoff Meeting",
|
|
"date": "2025-12-19T21:42:58+00:00",
|
|
}
|
|
|
|
compressor = InviteCompressor(mode="summary")
|
|
compressed, event = compressor.compress(raw_message, envelope)
|
|
|
|
assert event is not None
|
|
assert "MEETING INVITE" in compressed
|
|
assert "Project Kickoff Meeting" in compressed
|
|
# Should show compressed attendee list
|
|
assert "more)" in compressed # Truncated attendee list
|
|
# Should show action hints for REQUEST
|
|
assert "Accept" in compressed
|
|
|
|
def test_compress_cancellation(self):
|
|
"""Test compression of meeting cancellation."""
|
|
fixture_path = Path(
|
|
"tests/fixtures/test_mailbox/INBOX/cur/17051228-cancellation.test:2,S"
|
|
)
|
|
with open(fixture_path, "r") as f:
|
|
raw_message = f.read()
|
|
|
|
envelope = {
|
|
"from": {"addr": "cody.marshall@example.com", "name": "Marshall, Cody"},
|
|
"subject": "Canceled: Technical Refinement",
|
|
"date": "2025-12-19T19:12:46+00:00",
|
|
}
|
|
|
|
compressor = InviteCompressor(mode="summary")
|
|
compressed, event = compressor.compress(raw_message, envelope)
|
|
|
|
assert event is not None
|
|
assert "CANCELLED" in compressed
|
|
# Title should be strikethrough (without the Canceled: prefix)
|
|
assert "~~Technical Refinement~~" in compressed
|
|
# Should NOT show action hints for cancelled meetings
|
|
assert "Accept" not in compressed
|
|
|
|
def test_attendee_compression(self):
|
|
"""Test attendee list compression."""
|
|
compressor = InviteCompressor()
|
|
|
|
# Test with few attendees
|
|
attendees = ["Alice <alice@example.com>", "Bob <bob@example.com>"]
|
|
result = compressor._compress_attendees(attendees)
|
|
assert result == "Alice, Bob"
|
|
|
|
# Test with many attendees
|
|
many_attendees = [
|
|
"Alice <alice@example.com>",
|
|
"Bob <bob@example.com>",
|
|
"Carol <carol@example.com>",
|
|
"Dave <dave@example.com>",
|
|
"Eve <eve@example.com>",
|
|
]
|
|
result = compressor._compress_attendees(many_attendees, max_shown=3)
|
|
assert "Alice" in result
|
|
assert "Bob" in result
|
|
assert "Carol" in result
|
|
assert "(+2 more)" in result
|
|
|
|
def test_compress_off_mode(self):
|
|
"""Test that compression can be disabled."""
|
|
fixture_path = Path(
|
|
"tests/fixtures/test_mailbox/INBOX/cur/17051227-large-group-invite.test:2,S"
|
|
)
|
|
with open(fixture_path, "r") as f:
|
|
raw_message = f.read()
|
|
|
|
envelope = {
|
|
"from": {"addr": "product.dev@example.com"},
|
|
"subject": "Project Kickoff Meeting",
|
|
}
|
|
|
|
compressor = InviteCompressor(mode="off")
|
|
assert compressor.should_compress(envelope) is False
|
|
|
|
compressed, event = compressor.compress(raw_message, envelope)
|
|
assert compressed == ""
|
|
assert event is None
|
|
|
|
def test_convenience_function(self):
|
|
"""Test the compress_invite convenience function."""
|
|
fixture_path = Path(
|
|
"tests/fixtures/test_mailbox/INBOX/cur/17051227-large-group-invite.test:2,S"
|
|
)
|
|
with open(fixture_path, "r") as f:
|
|
raw_message = f.read()
|
|
|
|
envelope = {
|
|
"from": {"addr": "product.dev@example.com"},
|
|
"subject": "Project Kickoff Meeting",
|
|
}
|
|
|
|
compressed, event = compress_invite(raw_message, envelope)
|
|
assert event is not None
|
|
assert "Project Kickoff Meeting" in compressed
|