From 17008779182a563df69813489f4debbc5eaad37e Mon Sep 17 00:00:00 2001 From: Sagent Date: Thu, 11 Jun 2026 12:18:20 +0000 Subject: [PATCH] chore: add unit tests for pure functions + CI test job - Unit tests for normalize_email_address, payload_matches_participant, format_search_result (9 test cases across 3 test classes) - New 'test' job in CI workflow (runs before build) - pytest.ini for pythonpath config --- .gitea/workflows/docker-build.yaml | 19 +++++- pytest.ini | 3 + tests/test_server.py | 106 +++++++++++++++++++++++++++++ 3 files changed, 125 insertions(+), 3 deletions(-) create mode 100644 pytest.ini create mode 100644 tests/test_server.py diff --git a/.gitea/workflows/docker-build.yaml b/.gitea/workflows/docker-build.yaml index 9bbe8a4..8c597d6 100644 --- a/.gitea/workflows/docker-build.yaml +++ b/.gitea/workflows/docker-build.yaml @@ -9,11 +9,24 @@ on: - cron: '0 0 * * *' jobs: - build: + test: runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + - uses: actions/setup-python@v6 + with: + python-version: "3.13" + - name: Install dependencies + run: | + pip install -r requirements.txt pytest + - name: Run unit tests + run: pytest tests/ -v + + build: + runs-on: ubuntu-latest + needs: test + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 - name: Set up Docker Buildx uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4 diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..80432c2 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +testpaths = tests +pythonpath = src diff --git a/tests/test_server.py b/tests/test_server.py new file mode 100644 index 0000000..1a129b2 --- /dev/null +++ b/tests/test_server.py @@ -0,0 +1,106 @@ +"""Unit tests for mcp-maildir server pure functions.""" + +from server import ( + normalize_email_address, + payload_matches_participant, + format_search_result, +) + + +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" + + 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 "} + assert payload_matches_participant(payload, "alice@example.com") is True + + def test_display_name_in_receiver(self): + payload = {"receiver": "Bob Smith "} + 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": "", + "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"] == "" + 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"] == []