From 1194c3a89627fcc4e4255941004a822639d7e9b8 Mon Sep 17 00:00:00 2001 From: Christoph Wurst <1374172+ChristophWurst@users.noreply.github.com> Date: Thu, 3 Sep 2026 09:55:31 +0200 Subject: [PATCH] fix(imap): catch ClientException in SubmitContentJob MailManager::getImapMessage() documents throwing ClientException when a message no longer exists on the remote server, but this job only caught ServiceException and SmimeDecryptException. A message vanishing between the DB write and this job running aborted the whole run instead of skipping that one message. Assisted-by: ClaudeCode:claude-opus-4-8 Signed-off-by: Christoph Wurst <1374172+ChristophWurst@users.noreply.github.com> --- .../ContextChat/SubmitContentJob.php | 3 +- .../ContextChat/SubmitContentJobTest.php | 47 +++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/lib/BackgroundJob/ContextChat/SubmitContentJob.php b/lib/BackgroundJob/ContextChat/SubmitContentJob.php index 3f73d9861b..698881800a 100644 --- a/lib/BackgroundJob/ContextChat/SubmitContentJob.php +++ b/lib/BackgroundJob/ContextChat/SubmitContentJob.php @@ -13,6 +13,7 @@ use OCA\Mail\Db\MailboxMapper; use OCA\Mail\Db\Message; use OCA\Mail\Db\MessageMapper; +use OCA\Mail\Exception\ClientException; use OCA\Mail\Exception\ServiceException; use OCA\Mail\Exception\SmimeDecryptException; use OCA\Mail\Service\AccountService; @@ -115,7 +116,7 @@ protected function run($argument): void { } try { $imapMessage = $this->mailManager->getImapMessage($account, $mailbox, $message, true); - } catch (ServiceException $e) { + } catch (ServiceException|ClientException $e) { // couldn't load message, let's skip it. Retrying would be too costly continue; } catch (SmimeDecryptException $e) { diff --git a/tests/Unit/BackgroundJob/ContextChat/SubmitContentJobTest.php b/tests/Unit/BackgroundJob/ContextChat/SubmitContentJobTest.php index 56e87680a3..375895c62a 100644 --- a/tests/Unit/BackgroundJob/ContextChat/SubmitContentJobTest.php +++ b/tests/Unit/BackgroundJob/ContextChat/SubmitContentJobTest.php @@ -21,6 +21,7 @@ use OCA\Mail\Db\MessageMapper; use OCA\Mail\Events\MessageDeletedEvent; use OCA\Mail\Events\NewMessagesSynchronized; +use OCA\Mail\Exception\ClientException; use OCA\Mail\Model\IMAPMessage; use OCA\Mail\Service\AccountService; use OCA\Mail\Service\ContextChat\TaskService; @@ -307,6 +308,52 @@ public function testRunWithContextChatWithEncryptedMessage(): void { $this->submitContentJob->start($this->createMock(IJobList::class)); } + public function testRunWithContextChatSkipsMessageVanishedFromRemote(): void { + $this->contentManager->expects($this->once()) + ->method('isContextChatAvailable') + ->willReturn(true); + $task = new Task(); + $task->setLastMessageId(0); + $task->setMailboxId(1); + $task->setId(1); + $this->taskService->expects($this->once())->method('findNext')->willReturn($task); + $mailbox = new Mailbox(); + $mailbox->setId(1); + $mailbox->setAccountId(5); + $this->mailboxMapper->expects($this->once())->method('findById')->willReturn($mailbox); + $this->time->expects($this->any())->method('getTime') + ->willReturn( + // returned when Job#start asks + 12 * 60 * 60, + 12 * 60 * 60, + // returned when filtering messages + ContextChatProvider::CONTEXT_CHAT_MESSAGE_MAX_AGE, + // returned before processing messages + 0, + // returned on first message + 0, + 0, + 0, + 0, + ); + $this->messageMapper->expects($this->once())->method('findIdsAfter') + ->with($mailbox, 0, 0, ContextChatProvider::CONTEXT_CHAT_IMPORT_MAX_ITEMS)->willReturn([1]); + $account = $this->createMock(Account::class); + $account->expects($this->any())->method('getUserId')->willReturn('user123'); + $this->accountService->expects($this->once())->method('findById')->with()->willReturn($account); + $message = new Message(); + $message->setId(1); + $message->setUid(1); + $this->messageMapper->expects($this->once())->method('findByIds')->willReturn([$message]); + $this->mailManager->expects($this->once())->method('getImapMessage') + ->willThrowException(new ClientException('Message not found on remote server')); + $this->contentManager->expects($this->never())->method('submitContent'); + $this->taskService->expects($this->once())->method('setLastMessage')->with($task->getMailboxId(), 1); + + $this->submitContentJob->setLastRun(0); + $this->submitContentJob->start($this->createMock(IJobList::class)); + } + public function testRunWithContextChatWithFindNextTaskException(): void { $this->contentManager->expects($this->once()) ->method('isContextChatAvailable')