From c84f2f04b3735f2f73a2c0b7a01d492800e77479 Mon Sep 17 00:00:00 2001 From: Trenton H <797416+stumpylog@users.noreply.github.com> Date: Tue, 27 Jan 2026 11:35:12 -0800 Subject: [PATCH] Chore: Switch to a local IMAP server instead of a real email service (#11913) --- .github/workflows/ci-backend.yml | 3 - docker/compose/docker-compose.ci-test.yml | 11 +++ src/paperless_mail/tests/conftest.py | 28 ++++---- src/paperless_mail/tests/test_live_mail.py | 79 ++++++++++------------ 4 files changed, 63 insertions(+), 58 deletions(-) diff --git a/.github/workflows/ci-backend.yml b/.github/workflows/ci-backend.yml index 98c10396c..a619f01c1 100644 --- a/.github/workflows/ci-backend.yml +++ b/.github/workflows/ci-backend.yml @@ -75,9 +75,6 @@ jobs: env: NLTK_DATA: ${{ env.NLTK_DATA }} PAPERLESS_CI_TEST: 1 - PAPERLESS_MAIL_TEST_HOST: ${{ secrets.TEST_MAIL_HOST }} - PAPERLESS_MAIL_TEST_USER: ${{ secrets.TEST_MAIL_USER }} - PAPERLESS_MAIL_TEST_PASSWD: ${{ secrets.TEST_MAIL_PASSWD }} run: | uv run \ --python ${{ steps.setup-python.outputs.python-version }} \ diff --git a/docker/compose/docker-compose.ci-test.yml b/docker/compose/docker-compose.ci-test.yml index d277406b8..f07f7fadb 100644 --- a/docker/compose/docker-compose.ci-test.yml +++ b/docker/compose/docker-compose.ci-test.yml @@ -23,3 +23,14 @@ services: container_name: tika network_mode: host restart: unless-stopped + greenmail: + image: greenmail/standalone:2.1.8 + hostname: greenmail + container_name: greenmail + environment: + # Enable only IMAP for now (SMTP available via 3025 if needed later) + GREENMAIL_OPTS: >- + -Dgreenmail.setup.test.imap -Dgreenmail.users=test@localhost:test -Dgreenmail.users.login=test@localhost -Dgreenmail.verbose + ports: + - "3143:3143" # IMAP + restart: unless-stopped diff --git a/src/paperless_mail/tests/conftest.py b/src/paperless_mail/tests/conftest.py index 01a98d57d..d6b74dfbf 100644 --- a/src/paperless_mail/tests/conftest.py +++ b/src/paperless_mail/tests/conftest.py @@ -1,4 +1,3 @@ -import os from collections.abc import Generator from pathlib import Path @@ -70,18 +69,21 @@ def mail_parser() -> MailDocumentParser: @pytest.fixture() -def live_mail_account() -> Generator[MailAccount, None, None]: - try: - account = MailAccount.objects.create( - name="test", - imap_server=os.environ["PAPERLESS_MAIL_TEST_HOST"], - username=os.environ["PAPERLESS_MAIL_TEST_USER"], - password=os.environ["PAPERLESS_MAIL_TEST_PASSWD"], - imap_port=993, - ) - yield account - finally: - account.delete() +def greenmail_mail_account(db: None) -> Generator[MailAccount, None, None]: + """ + Create a mail account configured for local Greenmail server. + """ + account = MailAccount.objects.create( + name="Greenmail Test", + imap_server="localhost", + imap_port=3143, + imap_security=MailAccount.ImapSecurity.NONE, + username="test@localhost", + password="test", + character_set="UTF-8", + ) + yield account + account.delete() @pytest.fixture() diff --git a/src/paperless_mail/tests/test_live_mail.py b/src/paperless_mail/tests/test_live_mail.py index ecf9f73b6..c7dcffadd 100644 --- a/src/paperless_mail/tests/test_live_mail.py +++ b/src/paperless_mail/tests/test_live_mail.py @@ -1,6 +1,3 @@ -import os -import warnings - import pytest from paperless_mail.mail import MailAccountHandler @@ -9,53 +6,51 @@ from paperless_mail.models import MailAccount from paperless_mail.models import MailRule -# Only run if the environment is setup -# And the environment is not empty (forks, I think) -@pytest.mark.skipif( - "PAPERLESS_MAIL_TEST_HOST" not in os.environ - or not len(os.environ["PAPERLESS_MAIL_TEST_HOST"]), - reason="Live server testing not enabled", -) -@pytest.mark.django_db() -class TestMailLiveServer: - def test_process_non_gmail_server_flag( +@pytest.mark.django_db +class TestMailGreenmail: + """ + Mail tests using local Greenmail server + """ + + def test_process_flag( self, mail_account_handler: MailAccountHandler, - live_mail_account: MailAccount, - ): + greenmail_mail_account: MailAccount, + ) -> None: + """ + Test processing mail with FLAG action. + """ + rule = MailRule.objects.create( + name="testrule", + account=greenmail_mail_account, + action=MailRule.MailAction.FLAG, + ) + try: - rule1 = MailRule.objects.create( - name="testrule", - account=live_mail_account, - action=MailRule.MailAction.FLAG, - ) - - mail_account_handler.handle_mail_account(live_mail_account) - - rule1.delete() - + mail_account_handler.handle_mail_account(greenmail_mail_account) except MailError as e: pytest.fail(f"Failure: {e}") - except Exception as e: - warnings.warn(f"Unhandled exception: {e}") + finally: + rule.delete() - def test_process_non_gmail_server_tag( + def test_process_tag( self, mail_account_handler: MailAccountHandler, - live_mail_account: MailAccount, - ): + greenmail_mail_account: MailAccount, + ) -> None: + """ + Test processing mail with TAG action. + """ + rule = MailRule.objects.create( + name="testrule", + account=greenmail_mail_account, + action=MailRule.MailAction.TAG, + action_parameter="TestTag", + ) + try: - rule2 = MailRule.objects.create( - name="testrule", - account=live_mail_account, - action=MailRule.MailAction.TAG, - ) - - mail_account_handler.handle_mail_account(live_mail_account) - - rule2.delete() - + mail_account_handler.handle_mail_account(greenmail_mail_account) except MailError as e: pytest.fail(f"Failure: {e}") - except Exception as e: - warnings.warn(f"Unhandled exception: {e}") + finally: + rule.delete()