fix(mail) : stop le spam GlitchTip de sync (double-encodage UTF7 + dossiers fantômes)
Deux causes racines généraient ~170 erreurs/cycle (toutes les 10 min) sur la prod : "syncFolder[...] listMessages failed: Folder ... not found". 1. Double-encodage UTF7-IMAP : listFolders() stocke le chemin brut UTF7-IMAP, mais ImapMailProvider rappelait getFolder($path) qui ré-encode UTF8->UTF7-IMAP (webklex Client::getFolderByPath, utf7=false). Le caractère de shift "&" était ré-encodé, rendant introuvables les dossiers à accents/specials. Fix : getFolder($path, null, utf7: true) partout dans ImapMailProvider. 2. Dossiers fantômes jamais purgés : syncFolderStructure() gardait en DB les dossiers disparus du serveur, re-tentés à chaque cycle. Fix : syncFolderStructure() retourne le set des chemins présents sur le serveur ; doSyncAll() skip silencieusement les dossiers DB absents (conservés en DB pour les liens messages/tâches). Fallback historique si listFolders échoue. Test : testSyncAllSkipsFoldersNoLongerPresentOnServer.
This commit is contained in:
@@ -132,6 +132,69 @@ class MailSyncServiceTest extends TestCase
|
||||
$service->syncFolderStructure();
|
||||
}
|
||||
|
||||
public function testSyncAllSkipsFoldersNoLongerPresentOnServer(): void
|
||||
{
|
||||
$config = new MailConfiguration();
|
||||
$config->setEnabled(true);
|
||||
|
||||
$configRepo = $this->createMock(MailConfigurationRepositoryInterface::class);
|
||||
$configRepo->method('findSingleton')->willReturn($config);
|
||||
|
||||
// The server only exposes INBOX; "Trash/STALE" was deleted remotely but
|
||||
// still lingers in the DB.
|
||||
$inboxDto = new MailFolderDto(
|
||||
path: 'INBOX',
|
||||
displayName: 'Inbox',
|
||||
parentPath: null,
|
||||
unreadCount: 0,
|
||||
totalCount: 0,
|
||||
);
|
||||
|
||||
$inboxFolder = new MailFolder();
|
||||
$inboxFolder->setPath('INBOX');
|
||||
|
||||
$staleFolder = new MailFolder();
|
||||
$staleFolder->setPath('Trash/STALE');
|
||||
|
||||
$provider = $this->createMock(MailProviderInterface::class);
|
||||
$provider->method('listFolders')->willReturn([$inboxDto]);
|
||||
// listMessages must only ever be called for INBOX, never the stale folder.
|
||||
$provider->expects(self::once())
|
||||
->method('listMessages')
|
||||
->with('INBOX', 5000, 0)
|
||||
->willReturn([])
|
||||
;
|
||||
|
||||
$folderRepo = $this->createMock(MailFolderRepositoryInterface::class);
|
||||
$folderRepo->method('findByPath')->willReturn($inboxFolder);
|
||||
$folderRepo->method('findAllOrderedByPath')->willReturn([$inboxFolder, $staleFolder]);
|
||||
|
||||
$messageRepo = $this->createMock(MailMessageRepositoryInterface::class);
|
||||
$messageRepo->method('findMaxUidInFolder')->willReturn(0);
|
||||
$messageRepo->method('findAllUidsByFolder')->willReturn([]);
|
||||
$messageRepo->method('findLastNByFolder')->willReturn([]);
|
||||
|
||||
$em = $this->createMock(EntityManagerInterface::class);
|
||||
$em->method('isOpen')->willReturn(true);
|
||||
$lockFactory = $this->makeLockFactory();
|
||||
|
||||
$service = new MailSyncService(
|
||||
provider: $provider,
|
||||
configRepository: $configRepo,
|
||||
folderRepository: $folderRepo,
|
||||
messageRepository: $messageRepo,
|
||||
entityManager: $em,
|
||||
lockFactory: $lockFactory,
|
||||
logger: new NullLogger(),
|
||||
managerRegistry: $this->createMock(ManagerRegistry::class),
|
||||
);
|
||||
|
||||
$report = $service->syncAll();
|
||||
|
||||
self::assertSame(1, $report->foldersScanned);
|
||||
self::assertSame([], $report->errors);
|
||||
}
|
||||
|
||||
public function testSyncFolderAbortsSuppressionWhenOver50Percent(): void
|
||||
{
|
||||
$config = new MailConfiguration();
|
||||
|
||||
Reference in New Issue
Block a user