From b3487f1843a9af5b4d6e5c2747364dbabc8ed03a Mon Sep 17 00:00:00 2001 From: Martin Richtarsky Date: Thu, 3 Oct 2024 05:21:35 +0200 Subject: [PATCH] Enhancement: check for mail destination directory, log post-consume errors (#7808) --------- Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com> --- src/paperless_mail/mail.py | 24 +++++-- src/paperless_mail/tests/test_mail.py | 92 +++++++++++++++++++++++++++ 2 files changed, 110 insertions(+), 6 deletions(-) diff --git a/src/paperless_mail/mail.py b/src/paperless_mail/mail.py index 84f97b742..77d293ea0 100644 --- a/src/paperless_mail/mail.py +++ b/src/paperless_mail/mail.py @@ -28,6 +28,7 @@ from imap_tools import MailboxFolderSelectError from imap_tools import MailBoxUnencrypted from imap_tools import MailMessage from imap_tools import MailMessageFlags +from imap_tools import errors from imap_tools.mailbox import MailBoxTls from imap_tools.query import LogicOperator @@ -266,7 +267,14 @@ def apply_mail_action( M.folder.set(rule.folder) action = get_rule_action(rule, supports_gmail_labels) - action.post_consume(M, message_uid, rule.action_parameter) + try: + action.post_consume(M, message_uid, rule.action_parameter) + except errors.ImapToolsError: + logger = logging.getLogger("paperless_mail") + logger.exception( + "Error while processing mail action during post_consume", + ) + raise ProcessedMail.objects.create( owner=rule.owner, @@ -570,13 +578,17 @@ class MailAccountHandler(LoggingMixin): rule: MailRule, supports_gmail_labels: bool, ): - self.log.debug(f"Rule {rule}: Selecting folder {rule.folder}") - + folders = [rule.folder] + # In case of MOVE, make sure also the destination exists + if rule.action == MailRule.MailAction.MOVE: + folders.insert(0, rule.action_parameter) try: - M.folder.set(rule.folder) + for folder in folders: + self.log.debug(f"Rule {rule}: Selecting folder {folder}") + M.folder.set(folder) except MailboxFolderSelectError as err: self.log.error( - f"Unable to access folder {rule.folder}, attempting folder listing", + f"Unable to access folder {folder}, attempting folder listing", ) try: for folder_info in M.folder.list(): @@ -588,7 +600,7 @@ class MailAccountHandler(LoggingMixin): ) raise MailError( - f"Rule {rule}: Folder {rule.folder} " + f"Rule {rule}: Folder {folder} " f"does not exist in account {rule.account}", ) from err diff --git a/src/paperless_mail/tests/test_mail.py b/src/paperless_mail/tests/test_mail.py index 9078335a6..b1e3ff06e 100644 --- a/src/paperless_mail/tests/test_mail.py +++ b/src/paperless_mail/tests/test_mail.py @@ -10,6 +10,7 @@ import pytest from django.core.management import call_command from django.db import DatabaseError from django.test import TestCase +from django.utils import timezone from imap_tools import NOT from imap_tools import EmailAddress from imap_tools import FolderInfo @@ -17,6 +18,7 @@ from imap_tools import MailboxFolderSelectError from imap_tools import MailboxLoginError from imap_tools import MailMessage from imap_tools import MailMessageFlags +from imap_tools import errors from documents.models import Correspondent from documents.tests.utils import DirectoriesMixin @@ -28,6 +30,7 @@ from paperless_mail.mail import TagMailAction from paperless_mail.mail import apply_mail_action from paperless_mail.models import MailAccount from paperless_mail.models import MailRule +from paperless_mail.models import ProcessedMail @dataclasses.dataclass @@ -1424,6 +1427,95 @@ class TestMail( ) # still 2 +class TestPostConsumeAction(TestCase): + def setUp(self): + self.account = MailAccount.objects.create( + name="test", + imap_server="imap.test.com", + imap_port=993, + imap_security=MailAccount.ImapSecurity.SSL, + username="testuser", + password="password", + ) + self.rule = MailRule.objects.create( + name="testrule", + account=self.account, + action=MailRule.MailAction.MARK_READ, + action_parameter="", + folder="INBOX", + ) + self.message_uid = "12345" + self.message_subject = "Test Subject" + self.message_date = timezone.make_aware(timezone.datetime(2023, 1, 1, 12, 0, 0)) + + @mock.patch("paperless_mail.mail.get_mailbox") + @mock.patch("paperless_mail.mail.mailbox_login") + @mock.patch("paperless_mail.mail.get_rule_action") + def test_post_consume_success( + self, + mock_get_rule_action, + mock_mailbox_login, + mock_get_mailbox, + ): + mock_mailbox = mock.MagicMock() + mock_get_mailbox.return_value.__enter__.return_value = mock_mailbox + mock_action = mock.MagicMock() + mock_get_rule_action.return_value = mock_action + + apply_mail_action( + result=[], + rule_id=self.rule.pk, + message_uid=self.message_uid, + message_subject=self.message_subject, + message_date=self.message_date, + ) + + mock_mailbox_login.assert_called_once_with(mock_mailbox, self.account) + mock_mailbox.folder.set.assert_called_once_with(self.rule.folder) + mock_action.post_consume.assert_called_once_with( + mock_mailbox, + self.message_uid, + self.rule.action_parameter, + ) + + processed_mail = ProcessedMail.objects.get(uid=self.message_uid) + self.assertEqual(processed_mail.status, "SUCCESS") + + @mock.patch("paperless_mail.mail.get_mailbox") + @mock.patch("paperless_mail.mail.mailbox_login") + @mock.patch("paperless_mail.mail.get_rule_action") + def test_post_consume_failure( + self, + mock_get_rule_action, + mock_mailbox_login, + mock_get_mailbox, + ): + mock_mailbox = mock.MagicMock() + mock_get_mailbox.return_value.__enter__.return_value = mock_mailbox + mock_action = mock.MagicMock() + mock_get_rule_action.return_value = mock_action + mock_action.post_consume.side_effect = errors.ImapToolsError("Test Exception") + + with ( + self.assertRaises(errors.ImapToolsError), + self.assertLogs("paperless.mail", level="ERROR") as cm, + ): + apply_mail_action( + result=[], + rule_id=self.rule.pk, + message_uid=self.message_uid, + message_subject=self.message_subject, + message_date=self.message_date, + ) + error_str = cm.output[0] + expected_str = "Error while processing mail action during post_consume" + self.assertIn(expected_str, error_str) + + processed_mail = ProcessedMail.objects.get(uid=self.message_uid) + self.assertEqual(processed_mail.status, "FAILED") + self.assertIn("Test Exception", processed_mail.error) + + class TestManagementCommand(TestCase): @mock.patch( "paperless_mail.management.commands.mail_fetcher.tasks.process_mail_accounts",