diff options
author | Anna Larch <anna@nextcloud.com> | 2021-06-23 14:03:52 +0300 |
---|---|---|
committer | Anna Larch <anna@nextcloud.com> | 2021-08-24 13:41:14 +0300 |
commit | 2422e15270ab69b1da62f01231018c078738a66d (patch) | |
tree | 65bb81dd0153972c48f740dfb72f65e07b179872 /tests | |
parent | 251d6f0d23a3dcaa41daaf84d12fd10a85095515 (diff) |
Create anti spam report feature
Signed-off-by: Anna Larch <anna@nextcloud.com>
Diffstat (limited to 'tests')
-rw-r--r-- | tests/Integration/Framework/ImapTestAccount.php | 1 | ||||
-rw-r--r-- | tests/Integration/Service/AntiSpamServiceIntegrationTest.php | 109 | ||||
-rw-r--r-- | tests/Unit/Listener/HamReportListenerTest.php | 156 | ||||
-rw-r--r-- | tests/Unit/Listener/SpamReportListenerTest.php | 149 | ||||
-rw-r--r-- | tests/Unit/Service/AntiSpamServiceTest.php | 204 | ||||
-rw-r--r-- | tests/Unit/Settings/AdminSettingsTest.php | 17 | ||||
-rw-r--r-- | tests/psalm-baseline.xml | 21 |
7 files changed, 651 insertions, 6 deletions
diff --git a/tests/Integration/Framework/ImapTestAccount.php b/tests/Integration/Framework/ImapTestAccount.php index 846f332fd..0dac89327 100644 --- a/tests/Integration/Framework/ImapTestAccount.php +++ b/tests/Integration/Framework/ImapTestAccount.php @@ -48,6 +48,7 @@ trait ImapTestAccount { $mailAccount = new MailAccount(); $mailAccount->setUserId($this->getTestAccountUserId()); + $mailAccount->setName('Tester'); $mailAccount->setEmail('user@domain.tld'); $mailAccount->setInboundHost('127.0.0.1'); $mailAccount->setInboundPort(993); diff --git a/tests/Integration/Service/AntiSpamServiceIntegrationTest.php b/tests/Integration/Service/AntiSpamServiceIntegrationTest.php new file mode 100644 index 000000000..0f5d14a93 --- /dev/null +++ b/tests/Integration/Service/AntiSpamServiceIntegrationTest.php @@ -0,0 +1,109 @@ +<?php + +/** + * @copyright 2021 Anna Larch <anna@nextcloud.com> + * + * @copyright 2021 Anna Larch <anna@nextcloud.com> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\Mail\Tests\Integration\Service; + +use Horde_Imap_Client; +use OC; +use OCA\Mail\Account; +use ChristophWurst\Nextcloud\Testing\TestCase; +use OCA\Mail\Contracts\IMailManager; +use OCA\Mail\Db\MessageMapper; +use OCA\Mail\IMAP\MessageMapper as ImapMessageMapper; +use OCA\Mail\Service\AntiSpamService; +use OCA\Mail\Service\Sync\SyncService; +use OCA\Mail\Tests\Integration\Framework\ImapTest; +use OCA\Mail\Tests\Integration\Framework\ImapTestAccount; + +class AntiSpamServiceIntegrationTest extends TestCase { + use ImapTest, + ImapTestAccount; + + /** @var AntiSpamService */ + private $service; + + public function setUp():void { + parent::setUp(); + $this->service = OC::$server->get(AntiSpamService::class); + $this->service->setSpamEmail('spam@domain.tld'); + $this->service->setHamEmail('notspam@domain.tld'); + } + + public function tearDown(): void { + $this->resetImapAccount(); + $this->service->deleteConfig(); + } + + public function testFlagJunkWithSpamReportActive(): void { + // First, set up account and retrieve sync token + $this->resetImapAccount(); + $account = $this->createTestAccount(); + + /** @var SyncService $syncService */ + $syncService = OC::$server->get(SyncService::class); + /** @var ImapMessageMapper $imapMessageMapper */ + $imapMessageMapper = OC::$server->get(ImapMessageMapper::class); + /** @var MessageMapper $messageMapper */ + $messageMapper = OC::$server->get(MessageMapper::class); + /** @var IMailManager $mailManager */ + $mailManager = OC::$server->get(IMailManager::class); + $mailBoxes = $mailManager->getMailboxes(new Account($account)); + $inbox = null; + foreach ($mailBoxes as $mailBox) { + if ($mailBox->getName() === 'INBOX') { + $inbox = $mailBox; + break; + } + } + + // Second, put a new message into the mailbox + $message = $this->getMessageBuilder() + ->from('buffington@domain.tld') + ->to('user@domain.tld') + ->finish(); + $newUid = $this->saveMessage($inbox->getName(), $message, $account); + + // sync in between creating and flagging otherwise it can't be found + $syncService->syncMailbox( + new Account($account), + $inbox, + Horde_Imap_Client::SYNC_NEWMSGSUIDS | Horde_Imap_Client::SYNC_FLAGSUIDS | Horde_Imap_Client::SYNC_VANISHEDUIDS, + null, + false + ); + + // now we flag this message as junk + $mailManager->flagMessage(new Account($account), $inbox->getName(), $newUid, 'junk', true); + + // if everything runs through, we can assert the run has been fine, + // but we can't really test if Listener and Transmission have actually sent the message + $this->addToAssertionCount(1); + + // now we flag this message as not junk + $mailManager->flagMessage(new Account($account), $inbox->getName(), $newUid, 'notjunk', true); + + // same as before + $this->addToAssertionCount(1); + } +} diff --git a/tests/Unit/Listener/HamReportListenerTest.php b/tests/Unit/Listener/HamReportListenerTest.php new file mode 100644 index 000000000..71c70f4c6 --- /dev/null +++ b/tests/Unit/Listener/HamReportListenerTest.php @@ -0,0 +1,156 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright 2021 Anna Larch <anna@nextcloud.com> + * + * @author Anna Larch <anna@nextcloud.com> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +namespace OCA\Mail\Tests\Unit\Listener; + +use ChristophWurst\Nextcloud\Testing\ServiceMockObject; +use ChristophWurst\Nextcloud\Testing\TestCase; +use OCA\Mail\Account; +use OCA\Mail\Db\Mailbox; +use OCA\Mail\Db\Tag; +use OCA\Mail\Events\MessageFlaggedEvent; +use OCA\Mail\Exception\ServiceException; +use OCA\Mail\Listener\HamReportListener; +use OCA\Mail\Listener\SpamReportListener; +use OCP\EventDispatcher\Event; + +class HamReportListenerTest extends TestCase { + + /** @var ServiceMockObject */ + private $serviceMock; + + /** @var SpamReportListener */ + private $listener; + + protected function setUp(): void { + parent::setUp(); + + $this->serviceMock = $this->createServiceMock(HamReportListener::class); + $this->listener = $this->serviceMock->getService(); + } + + public function testHandleUnrelated(): void { + $event = new Event(); + + $this->serviceMock->getParameter('antiSpamService') + ->expects(self::never()) + ->method('sendReportEmail'); + $this->serviceMock->getParameter('logger') + ->expects(self::never()) + ->method('error'); + + $this->listener->handle($event); + } + + public function testHandleNotFlaggedJunk(): void { + $account = $this->createMock(Account::class); + $mailbox = $this->createMock(Mailbox::class); + $event = new MessageFlaggedEvent( + $account, + $mailbox, + 123, + Tag::LABEL_IMPORTANT, + true + ); + + $this->serviceMock->getParameter('antiSpamService') + ->expects(self::never()) + ->method('sendReportEmail'); + $this->serviceMock->getParameter('logger') + ->expects(self::never()) + ->method('error'); + + $this->listener->handle($event); + } + + public function testHandleNotUnflaggingNonjunk(): void { + $account = $this->createMock(Account::class); + $mailbox = $this->createMock(Mailbox::class); + $event = new MessageFlaggedEvent( + $account, + $mailbox, + 123, + '$notjunk', + false + ); + + $this->serviceMock->getParameter('antiSpamService') + ->expects(self::never()) + ->method('getHamEmail'); + $this->serviceMock->getParameter('antiSpamService') + ->expects(self::never()) + ->method('sendReportEmail'); + $this->serviceMock->getParameter('logger') + ->expects(self::never()) + ->method('error'); + + $this->listener->handle($event); + } + + public function testHandleJunkExceptionOnSend() : void { + $account = $this->createMock(Account::class); + $mailbox = $this->createMock(Mailbox::class); + $event = new MessageFlaggedEvent( + $account, + $mailbox, + 123, + '$notjunk', + true + ); + + $this->serviceMock->getParameter('antiSpamService') + ->expects(self::once()) + ->method('sendReportEmail') + ->with($account, $mailbox, 123, '$notjunk') + ->willThrowException(new ServiceException()); + $this->serviceMock->getParameter('logger') + ->expects(self::once()) + ->method('error'); + + $this->listener->handle($event); + } + + public function testHandle(): void { + $account = $this->createMock(Account::class); + $mailbox = $this->createMock(Mailbox::class); + $event = new MessageFlaggedEvent( + $account, + $mailbox, + 123, + '$notjunk', + true + ); + + $this->serviceMock->getParameter('antiSpamService') + ->expects(self::once()) + ->method('sendReportEmail') + ->with($account, $mailbox, 123, '$notjunk'); + $this->serviceMock->getParameter('logger') + ->expects(self::never()) + ->method('error'); + + $this->listener->handle($event); + } +} diff --git a/tests/Unit/Listener/SpamReportListenerTest.php b/tests/Unit/Listener/SpamReportListenerTest.php new file mode 100644 index 000000000..d7068990b --- /dev/null +++ b/tests/Unit/Listener/SpamReportListenerTest.php @@ -0,0 +1,149 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright 2021 Anna Larch <anna@nextcloud.com> + * + * @author Anna Larch <anna@nextcloud.com> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +namespace OCA\Mail\Tests\Unit\Listener; + +use ChristophWurst\Nextcloud\Testing\ServiceMockObject; +use ChristophWurst\Nextcloud\Testing\TestCase; +use OCA\Mail\Account; +use OCA\Mail\Db\Mailbox; +use OCA\Mail\Db\Tag; +use OCA\Mail\Events\MessageFlaggedEvent; +use OCA\Mail\Exception\ServiceException; +use OCA\Mail\Listener\SpamReportListener; +use OCP\EventDispatcher\Event; + +class SpamReportListenerTest extends TestCase { + + /** @var ServiceMockObject */ + private $serviceMock; + + /** @var SpamReportListener */ + private $listener; + + protected function setUp(): void { + parent::setUp(); + + $this->serviceMock = $this->createServiceMock(SpamReportListener::class); + $this->listener = $this->serviceMock->getService(); + } + + public function testHandleUnrelated(): void { + $event = new Event(); + + $this->serviceMock->getParameter('antiSpamService') + ->expects(self::never()) + ->method('sendReportEmail'); + $this->serviceMock->getParameter('logger') + ->expects(self::never()) + ->method('error'); + + $this->listener->handle($event); + } + + public function testHandleNotFlaggedJunk(): void { + $account = $this->createMock(Account::class); + $mailbox = $this->createMock(Mailbox::class); + $event = new MessageFlaggedEvent( + $account, + $mailbox, + 123, + Tag::LABEL_IMPORTANT, + true + ); + + $this->serviceMock->getParameter('antiSpamService') + ->expects(self::never()) + ->method('sendReportEmail'); + $this->serviceMock->getParameter('logger') + ->expects(self::never()) + ->method('error'); + + $this->listener->handle($event); + } + + public function testHandleNotUnflaggingJunk(): void { + $account = $this->createMock(Account::class); + $mailbox = $this->createMock(Mailbox::class); + $event = new MessageFlaggedEvent( + $account, + $mailbox, + 123, + '$junk', + false + ); + + $this->serviceMock->getParameter('logger') + ->expects(self::never()) + ->method('error'); + + $this->listener->handle($event); + } + + public function testHandleJunkExceptionOnSend(): void { + $account = $this->createMock(Account::class); + $mailbox = $this->createMock(Mailbox::class); + $event = new MessageFlaggedEvent( + $account, + $mailbox, + 123, + '$junk', + true + ); + + $this->serviceMock->getParameter('antiSpamService') + ->expects(self::once()) + ->method('sendReportEmail') + ->with($account, $mailbox, 123, '$junk') + ->willThrowException(new ServiceException()); + $this->serviceMock->getParameter('logger') + ->expects(self::once()) + ->method('error'); + + $this->listener->handle($event); + } + + public function testHandle(): void { + $account = $this->createMock(Account::class); + $mailbox = $this->createMock(Mailbox::class); + $event = new MessageFlaggedEvent( + $account, + $mailbox, + 123, + '$junk', + true + ); + + $this->serviceMock->getParameter('antiSpamService') + ->expects(self::once()) + ->method('sendReportEmail') + ->with($account, $mailbox, 123, '$junk'); + $this->serviceMock->getParameter('logger') + ->expects(self::never()) + ->method('error'); + + $this->listener->handle($event); + } +} diff --git a/tests/Unit/Service/AntiSpamServiceTest.php b/tests/Unit/Service/AntiSpamServiceTest.php new file mode 100644 index 000000000..d8da88b42 --- /dev/null +++ b/tests/Unit/Service/AntiSpamServiceTest.php @@ -0,0 +1,204 @@ +<?php + +/** + * @copyright 2021 Anna Larch <anna@nextcloud.com> + * + * @author Anna Larch <anna@nextcloud.com> + * + * Mail + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OCA\Mail\Tests\Unit\Service; + +use ChristophWurst\Nextcloud\Testing\TestCase; +use OCA\Mail\Account; +use OCA\Mail\Contracts\IMailTransmission; +use OCA\Mail\Db\MessageMapper; +use OCA\Mail\Events\MessageFlaggedEvent; +use OCA\Mail\Exception\ServiceException; +use OCA\Mail\Db\Mailbox; +use OCA\Mail\Model\NewMessageData; +use OCA\Mail\Service\AntiSpamService; +use OCP\IConfig; +use PHPUnit\Framework\MockObject\MockObject; + +class AntiSpamServiceTest extends TestCase { + + /** @var AntiSpamService */ + private $service; + + /** @var IConfig|MockObject */ + private $config; + + /** @var MessageMapper|MockObject */ + private $messageMapper; + + /** @var IMailTransmission|MockObject */ + private $transmission; + + protected function setUp(): void { + parent::setUp(); + + $this->config = $this->createMock(IConfig::class); + $this->messageMapper = $this->createMock(MessageMapper::class); + $this->transmission = $this->createMock(IMailTransmission::class); + + $this->service = new AntiSpamService( + $this->config, + $this->messageMapper, + $this->transmission + ); + } + + public function testSendReportEmailNoEmailFound(): void { + $event = $this->createConfiguredMock(MessageFlaggedEvent::class, [ + 'getAccount' => $this->createMock(Account::class), + 'getMailbox' => $this->createMock(Mailbox::class), + 'getFlag' => '$junk' + ]); + + $this->config->expects(self::once()) + ->method('getAppValue') + ->with('mail', 'antispam_reporting_spam') + ->willReturn(''); + $this->messageMapper->expects(self::never()) + ->method('getIdForUid'); + $this->transmission->expects(self::never()) + ->method('sendMessage'); + + $this->service->sendReportEmail($event->getAccount(), $event->getMailbox(), 123, $event->getFlag()); + } + + public function testSendReportEmailNoMessageFound(): void { + $event = $this->createConfiguredMock(MessageFlaggedEvent::class, [ + 'getAccount' => $this->createMock(Account::class), + 'getMailbox' => $this->createMock(Mailbox::class), + 'getFlag' => '$junk' + ]); + + $this->config->expects(self::once()) + ->method('getAppValue') + ->with('mail', 'antispam_reporting_spam') + ->willReturn('test@test.com'); + $this->messageMapper->expects(self::once()) + ->method('getIdForUid') + ->with($event->getMailbox(), 123) + ->willReturn(null); + $this->expectException(ServiceException::class); + $this->transmission->expects(self::never()) + ->method('sendMessage'); + + $this->service->sendReportEmail($event->getAccount(), $event->getMailbox(), 123, $event->getFlag()); + } + + public function testSendReportEmailTransmissionError(): void { + $event = $this->createConfiguredMock(MessageFlaggedEvent::class, [ + 'getAccount' => $this->createMock(Account::class), + 'getMailbox' => $this->createMock(Mailbox::class), + 'getFlag' => '$junk' + ]); + + $this->config->expects(self::once()) + ->method('getAppValue') + ->with('mail', 'antispam_reporting_spam') + ->willReturn('test@test.com'); + $messageData = NewMessageData::fromRequest( + $event->getAccount(), + 'test@test.com', + null, + null, + 'Learn as Junk', + 'Learn as Junk', + [['id' => 123, 'type' => 'message/rfc822']] + ); + + $this->messageMapper->expects(self::once()) + ->method('getIdForUid') + ->with($event->getMailbox(), 123) + ->willReturn(123); + $this->transmission->expects(self::once()) + ->method('sendMessage') + ->with($messageData) + ->willThrowException(new ServiceException()); + $this->expectException(ServiceException::class); + + $this->service->sendReportEmail($event->getAccount(), $event->getMailbox(), 123, $event->getFlag()); + } + + public function testSendReportEmail(): void { + $event = $this->createConfiguredMock(MessageFlaggedEvent::class, [ + 'getAccount' => $this->createMock(Account::class), + 'getMailbox' => $this->createMock(Mailbox::class), + 'getFlag' => '$junk' + ]); + + $this->config->expects(self::once()) + ->method('getAppValue') + ->with('mail', 'antispam_reporting_spam') + ->willReturn('test@test.com'); + $messageData = NewMessageData::fromRequest( + $event->getAccount(), + 'test@test.com', + null, + null, + 'Learn as Junk', + 'Learn as Junk', + [['id' => 123, 'type' => 'message/rfc822']] + ); + + $this->messageMapper->expects(self::once()) + ->method('getIdForUid') + ->with($event->getMailbox(), 123) + ->willReturn(123); + $this->transmission->expects(self::once()) + ->method('sendMessage') + ->with($messageData); + + $this->service->sendReportEmail($event->getAccount(), $event->getMailbox(), 123, $event->getFlag()); + } + + public function testSendReportEmailForHam(): void { + $event = $this->createConfiguredMock(MessageFlaggedEvent::class, [ + 'getAccount' => $this->createMock(Account::class), + 'getMailbox' => $this->createMock(Mailbox::class), + 'getFlag' => '$notjunk' + ]); + + $this->config->expects(self::once()) + ->method('getAppValue') + ->with('mail', 'antispam_reporting_ham') + ->willReturn('test@test.com'); + $messageData = NewMessageData::fromRequest( + $event->getAccount(), + 'test@test.com', + null, + null, + 'Learn as Not Junk', + 'Learn as Not Junk', + [['id' => 123, 'type' => 'message/rfc822']] + ); + + $this->messageMapper->expects(self::once()) + ->method('getIdForUid') + ->with($event->getMailbox(), 123) + ->willReturn(123); + $this->transmission->expects(self::once()) + ->method('sendMessage') + ->with($messageData); + + $this->service->sendReportEmail($event->getAccount(), $event->getMailbox(), 123, $event->getFlag()); + } +} diff --git a/tests/Unit/Settings/AdminSettingsTest.php b/tests/Unit/Settings/AdminSettingsTest.php index 7cdfca83a..a2eea685d 100644 --- a/tests/Unit/Settings/AdminSettingsTest.php +++ b/tests/Unit/Settings/AdminSettingsTest.php @@ -54,12 +54,19 @@ class AdminSettingsTest extends TestCase { } public function testGetForm() { - $this->serviceMock->getParameter('initialStateService')->expects($this->once()) + $this->serviceMock->getParameter('initialStateService')->expects($this->exactly(2)) ->method('provideInitialState') - ->with( - Application::APP_ID, - 'provisioning_settings', - $this->anything() + ->withConsecutive( + [ + Application::APP_ID, + 'provisioning_settings', + $this->anything() + ], + [ + Application::APP_ID, + 'antispam_setting', + $this->anything() + ] ); $expected = new TemplateResponse(Application::APP_ID, 'settings-admin'); diff --git a/tests/psalm-baseline.xml b/tests/psalm-baseline.xml index a8707bdf4..19d2f3e35 100644 --- a/tests/psalm-baseline.xml +++ b/tests/psalm-baseline.xml @@ -1,11 +1,13 @@ <?xml version="1.0" encoding="UTF-8"?> <files psalm-version="4.x-dev@"> <file src="lib/AppInfo/Application.php"> - <MissingDependency occurrences="12"> + <MissingDependency occurrences="14"> <code>DraftSavedEvent</code> <code>MailboxesSynchronizedEvent</code> <code>MessageDeletedEvent</code> <code>MessageFlaggedEvent</code> + <code>MessageFlaggedEvent</code> + <code>MessageFlaggedEvent</code> <code>MessageSentEvent</code> <code>MessageSentEvent</code> <code>MessageSentEvent</code> @@ -228,6 +230,12 @@ <code>MessageSentEvent</code> </MissingDependency> </file> + <file src="lib/Listener/HamReportListener.php"> + <MissingDependency occurrences="2"> + <code>HamReportListener</code> + <code>MessageFlaggedEvent</code> + </MissingDependency> + </file> <file src="lib/Listener/InteractionListener.php"> <MissingDependency occurrences="2"> <code>InteractionListener</code> @@ -258,12 +266,23 @@ <code>SaveSentMessageListener</code> </MissingDependency> </file> + <file src="lib/Listener/SpamReportListener.php"> + <MissingDependency occurrences="2"> + <code>MessageFlaggedEvent</code> + <code>SpamReportListener</code> + </MissingDependency> + </file> <file src="lib/Listener/UserDeletedListener.php"> <MissingDependency occurrences="2"> <code>UserDeletedEvent</code> <code>UserDeletedListener</code> </MissingDependency> </file> + <file src="lib/Service/AntiSpamService.php"> + <MissingDependency occurrences="1"> + <code>MessageFlaggedEvent</code> + </MissingDependency> + </file> <file src="lib/Service/Classification/ImportanceClassifier.php"> <InvalidScalarArgument occurrences="1"> <code>$predictedValidationLabel</code> |