164 lines
5.8 KiB
Python
164 lines
5.8 KiB
Python
"""Unit tests for mcp-maildir server pure functions."""
|
|
|
|
import os
|
|
os.environ["QDRANT_URL"] = "http://localhost:6333"
|
|
os.environ["COLLECTION_NAME"] = "test"
|
|
|
|
from server import (
|
|
normalize_email_address,
|
|
payload_matches_participant,
|
|
format_search_result,
|
|
build_filter,
|
|
)
|
|
|
|
|
|
class TestNormalizeEmailAddress:
|
|
def test_empty_value(self):
|
|
assert normalize_email_address("") == ""
|
|
assert normalize_email_address(None) == ""
|
|
|
|
def test_simple_email(self):
|
|
assert normalize_email_address("user@example.com") == "user@example.com"
|
|
|
|
def test_display_name_with_email(self):
|
|
assert normalize_email_address("John Doe <john@example.com>") == "john@example.com"
|
|
|
|
def test_whitespace_and_case(self):
|
|
assert normalize_email_address(" USER@Example.COM ") == "user@example.com"
|
|
|
|
def test_invalid_email_fallback(self):
|
|
result = normalize_email_address("not-an-email")
|
|
# Returns stripped lowercase version of the input
|
|
assert result == "not-an-email"
|
|
|
|
|
|
class TestPayloadMatchesParticipant:
|
|
def test_sender_match_normalized(self):
|
|
payload = {"sender": "alice@example.com"}
|
|
assert payload_matches_participant(payload, "alice@example.com") is True
|
|
|
|
def test_receiver_match_normalized(self):
|
|
payload = {"receiver": "bob@example.com"}
|
|
assert payload_matches_participant(payload, "bob@example.com") is True
|
|
|
|
def test_sender_raw_match(self):
|
|
payload = {"sender_raw": "alice@other.com"}
|
|
assert payload_matches_participant(payload, "alice@other.com") is True
|
|
|
|
def test_no_match(self):
|
|
payload = {"sender": "alice@example.com", "receiver": "bob@example.com"}
|
|
assert payload_matches_participant(payload, "carol@example.com") is False
|
|
|
|
def test_display_name_in_sender(self):
|
|
payload = {"sender": "Alice <alice@example.com>"}
|
|
assert payload_matches_participant(payload, "alice@example.com") is True
|
|
|
|
def test_display_name_in_receiver(self):
|
|
payload = {"receiver": "Bob Smith <bob@example.com>"}
|
|
assert payload_matches_participant(payload, "bob@example.com") is True
|
|
|
|
def test_empty_payload(self):
|
|
assert payload_matches_participant({}, "someone@example.com") is False
|
|
|
|
def test_case_insensitive(self):
|
|
payload = {"sender": "ALICE@EXAMPLE.COM"}
|
|
assert payload_matches_participant(payload, "alice@example.com") is True
|
|
|
|
|
|
class TestFormatSearchResult:
|
|
def test_basic_formatting(self):
|
|
class MockPoint:
|
|
payload = {
|
|
"message_id": "<abc@def>",
|
|
"date": "2026-01-15T10:00:00",
|
|
"sender": "alice@example.com",
|
|
"receiver": "bob@example.com",
|
|
"subject": "Hello",
|
|
"attachments": ["file.pdf"],
|
|
}
|
|
score = 0.95
|
|
|
|
result = format_search_result(MockPoint())
|
|
assert result["message_id"] == "<abc@def>"
|
|
assert result["date"] == "2026-01-15T10:00:00"
|
|
assert result["sender"] == "alice@example.com"
|
|
assert result["receiver"] == "bob@example.com"
|
|
assert result["subject"] == "Hello"
|
|
assert result["attachments"] == ["file.pdf"]
|
|
assert result["score"] == 0.95
|
|
|
|
def test_empty_payload(self):
|
|
class MockPoint:
|
|
payload = None
|
|
score = None
|
|
|
|
result = format_search_result(MockPoint())
|
|
assert result["message_id"] is None
|
|
assert result["score"] is None
|
|
assert result["attachments"] == []
|
|
|
|
def test_missing_fields(self):
|
|
class MockPoint:
|
|
payload = {"message_id": "<123>"}
|
|
score = 0.5
|
|
|
|
result = format_search_result(MockPoint())
|
|
assert result["message_id"] == "<123>"
|
|
assert result["date"] is None
|
|
assert result["sender"] is None
|
|
assert result["receiver"] is None
|
|
assert result["subject"] is None
|
|
assert result["attachments"] == []
|
|
|
|
|
|
class TestBuildFilter:
|
|
def test_no_filters_returns_none(self):
|
|
assert build_filter(None, None, None) is None
|
|
|
|
def test_empty_strings_return_none(self):
|
|
assert build_filter("", "", "") is None
|
|
|
|
def test_participant_filter_is_should_clause_on_sender_and_receiver(self):
|
|
f = build_filter("alice@example.com", None, None)
|
|
assert f is not None
|
|
# The date conditions list must be absent and only the participant filter present
|
|
assert len(f.must) == 1
|
|
participant = f.must[0]
|
|
keys = [c.key for c in participant.should]
|
|
assert keys == ["sender", "receiver"]
|
|
assert all(c.match.value == "alice@example.com" for c in participant.should)
|
|
|
|
def test_participant_normalized_before_match(self):
|
|
f = build_filter("Alice <Alice@Example.COM>", None, None)
|
|
should = f.must[0].should
|
|
assert all(c.match.value == "alice@example.com" for c in should)
|
|
|
|
def test_date_range_only(self):
|
|
f = build_filter(None, "2026-01-01", "2026-06-30")
|
|
assert len(f.must) == 1
|
|
cond = f.must[0]
|
|
assert cond.key == "date"
|
|
assert cond.range.gte is not None
|
|
assert cond.range.lte is not None
|
|
|
|
def test_start_date_only(self):
|
|
f = build_filter(None, "2026-03-15", None)
|
|
cond = f.must[0]
|
|
assert cond.range.gte is not None
|
|
assert cond.range.lte is None
|
|
|
|
def test_end_date_only(self):
|
|
f = build_filter(None, None, "2026-03-15")
|
|
cond = f.must[0]
|
|
assert cond.range.gte is None
|
|
assert cond.range.lte is not None
|
|
|
|
def test_combined_participant_and_date(self):
|
|
f = build_filter("bob@example.org", "2026-01-01", "2026-12-31")
|
|
assert len(f.must) == 2
|
|
# One is the participant should-filter, the other the date range
|
|
kinds = sorted(
|
|
"participant" if getattr(c, "should", None) else "date" for c in f.must
|
|
)
|
|
assert kinds == ["date", "participant"]
|