From 45c5ab9277ba50b2499ca05e7bf3e568d3ea1a44 Mon Sep 17 00:00:00 2001
From: Daniel Kesselberg
Date: Tue, 7 Mar 2023 21:56:18 +0100
Subject: [PATCH] feat: move messages to junk folder
Signed-off-by: Daniel Kesselberg
---
appinfo/info.xml | 2 +-
lib/AppInfo/Application.php | 2 +
lib/Controller/AccountsController.php | 11 +-
lib/Db/MailAccount.php | 11 +
...xesSynchronizedSpecialMailboxesUpdater.php | 9 +
lib/Listener/MoveJunkListener.php | 102 ++++++++
.../Version3001Date20230307113544.php | 52 ++++
src/components/AccountDefaultsSettings.vue | 32 +++
src/components/AccountSettings.vue | 7 +-
src/components/Envelope.vue | 26 +-
src/components/JunkSettings.vue | 75 ++++++
src/components/MenuEnvelope.vue | 26 +-
src/components/NavigationMailbox.vue | 4 +
src/store/actions.js | 34 ++-
src/store/getters.js | 6 +
src/tests/unit/store/actions.spec.js | 85 +++++++
src/tests/unit/store/getters.spec.js | 49 ++++
tests/Integration/Db/MailAccountTest.php | 4 +
tests/Unit/Listener/MoveJunkListenerTest.php | 226 ++++++++++++++++++
19 files changed, 755 insertions(+), 8 deletions(-)
create mode 100644 lib/Listener/MoveJunkListener.php
create mode 100644 lib/Migration/Version3001Date20230307113544.php
create mode 100644 src/components/JunkSettings.vue
create mode 100644 tests/Unit/Listener/MoveJunkListenerTest.php
diff --git a/appinfo/info.xml b/appinfo/info.xml
index 6fdbee9847..2a3df1ff08 100644
--- a/appinfo/info.xml
+++ b/appinfo/info.xml
@@ -22,7 +22,7 @@ Positive:
Learn more about the Nextcloud Ethical AI Rating [in our blog](https://nextcloud.com/blog/nextcloud-ethical-ai-rating/).
]]>
- 3.4.0-alpha.2
+ 3.4.0-alpha.3
agpl
Greta Doçi
Nextcloud Groupware Team
diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php
index 018728b7a3..39216789b3 100644
--- a/lib/AppInfo/Application.php
+++ b/lib/AppInfo/Application.php
@@ -60,6 +60,7 @@
use OCA\Mail\Listener\MailboxesSynchronizedSpecialMailboxesUpdater;
use OCA\Mail\Listener\MessageCacheUpdaterListener;
use OCA\Mail\Listener\MessageKnownSinceListener;
+use OCA\Mail\Listener\MoveJunkListener;
use OCA\Mail\Listener\NewMessageClassificationListener;
use OCA\Mail\Listener\OauthTokenRefreshListener;
use OCA\Mail\Listener\SaveSentMessageListener;
@@ -123,6 +124,7 @@ public function register(IRegistrationContext $context): void {
$context->registerEventListener(MessageFlaggedEvent::class, MessageCacheUpdaterListener::class);
$context->registerEventListener(MessageFlaggedEvent::class, SpamReportListener::class);
$context->registerEventListener(MessageFlaggedEvent::class, HamReportListener::class);
+ $context->registerEventListener(MessageFlaggedEvent::class, MoveJunkListener::class);
$context->registerEventListener(MessageDeletedEvent::class, MessageCacheUpdaterListener::class);
$context->registerEventListener(MessageSentEvent::class, AddressCollectionListener::class);
$context->registerEventListener(MessageSentEvent::class, FlagRepliedMessageListener::class);
diff --git a/lib/Controller/AccountsController.php b/lib/Controller/AccountsController.php
index ff9ae9122f..e5a4c895f3 100644
--- a/lib/Controller/AccountsController.php
+++ b/lib/Controller/AccountsController.php
@@ -242,7 +242,9 @@ public function patchAccount(int $id,
int $trashMailboxId = null,
int $archiveMailboxId = null,
bool $signatureAboveQuote = null,
- int $trashRetentionDays = null): JSONResponse {
+ int $trashRetentionDays = null,
+ int $junkMailboxId = null,
+ bool $moveJunk = null): JSONResponse {
$account = $this->accountService->find($this->currentUserId, $id);
$dbAccount = $account->getMailAccount();
@@ -279,6 +281,13 @@ public function patchAccount(int $id,
// Passing 0 (or lower) disables retention
$dbAccount->setTrashRetentionDays($trashRetentionDays <= 0 ? null : $trashRetentionDays);
}
+ if ($junkMailboxId !== null) {
+ $this->mailManager->getMailbox($this->currentUserId, $junkMailboxId);
+ $dbAccount->setJunkMailboxId($junkMailboxId);
+ }
+ if ($moveJunk !== null) {
+ $dbAccount->setMoveJunk($moveJunk);
+ }
return new JSONResponse(
$this->accountService->save($dbAccount)
);
diff --git a/lib/Db/MailAccount.php b/lib/Db/MailAccount.php
index ce6422eb1c..b3fd9122e3 100644
--- a/lib/Db/MailAccount.php
+++ b/lib/Db/MailAccount.php
@@ -110,6 +110,10 @@
* @method void setQuotaPercentage(int $quota);
* @method int|null getTrashRetentionDays()
* @method void setTrashRetentionDays(int|null $trashRetentionDays)
+ * @method int|null getJunkMailboxId()
+ * @method void setJunkMailboxId(?int $id)
+ * @method bool isMoveJunk()
+ * @method void setMoveJunk(bool $moveJunk)
*/
class MailAccount extends Entity {
public const SIGNATURE_MODE_PLAIN = 0;
@@ -181,6 +185,9 @@ class MailAccount extends Entity {
/** @var int|null */
protected $trashRetentionDays;
+ protected ?int $junkMailboxId = null;
+ protected bool $moveJunk = false;
+
/**
* @param array $params
*/
@@ -254,6 +261,8 @@ public function __construct(array $params = []) {
$this->addType('smimeCertificateId', 'integer');
$this->addType('quotaPercentage', 'integer');
$this->addType('trashRetentionDays', 'integer');
+ $this->addType('junkMailboxId', 'integer');
+ $this->addType('moveJunk', 'boolean');
}
/**
@@ -285,6 +294,8 @@ public function toJson() {
'smimeCertificateId' => $this->getSmimeCertificateId(),
'quotaPercentage' => $this->getQuotaPercentage(),
'trashRetentionDays' => $this->getTrashRetentionDays(),
+ 'junkMailboxId' => $this->getJunkMailboxId(),
+ 'moveJunk' => ($this->isMoveJunk() === true),
];
if (!is_null($this->getOutboundHost())) {
diff --git a/lib/Listener/MailboxesSynchronizedSpecialMailboxesUpdater.php b/lib/Listener/MailboxesSynchronizedSpecialMailboxesUpdater.php
index 34b8d5718d..41bfb89728 100644
--- a/lib/Listener/MailboxesSynchronizedSpecialMailboxesUpdater.php
+++ b/lib/Listener/MailboxesSynchronizedSpecialMailboxesUpdater.php
@@ -112,6 +112,15 @@ public function handle(Event $event): void {
$mailAccount->setArchiveMailboxId(null);
}
}
+ if ($mailAccount->getJunkMailboxId() === null || !array_key_exists($mailAccount->getJunkMailboxId(), $mailboxes)) {
+ try {
+ $junkMailbox = $this->findSpecial($mailboxes, 'junk');
+ $mailAccount->setJunkMailboxId($junkMailbox->getId());
+ } catch (DoesNotExistException) {
+ $this->logger->info("Account " . $account->getId() . " does not have an junk mailbox");
+ $mailAccount->setJunkMailboxId(null);
+ }
+ }
$this->mailAccountMapper->update($mailAccount);
}
diff --git a/lib/Listener/MoveJunkListener.php b/lib/Listener/MoveJunkListener.php
new file mode 100644
index 0000000000..44dfeb8aed
--- /dev/null
+++ b/lib/Listener/MoveJunkListener.php
@@ -0,0 +1,102 @@
+
+ *
+ * @author Daniel Kesselberg
+ *
+ * @license AGPL-3.0-or-later
+ *
+ * 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
+ *
+ */
+
+namespace OCA\Mail\Listener;
+
+use OCA\Mail\Contracts\IMailManager;
+use OCA\Mail\Events\MessageFlaggedEvent;
+use OCA\Mail\Exception\ClientException;
+use OCA\Mail\Exception\ServiceException;
+use OCP\EventDispatcher\Event;
+use OCP\EventDispatcher\IEventListener;
+use Psr\Log\LoggerInterface;
+
+/**
+ * @template-implements IEventListener
+ */
+class MoveJunkListener implements IEventListener {
+ public function __construct(
+ private IMailManager $mailManager,
+ private LoggerInterface $logger,
+ ) {
+ }
+
+ public function handle(Event $event): void {
+ if (!$event instanceof MessageFlaggedEvent || $event->getFlag() !== '$junk') {
+ return;
+ }
+
+ $account = $event->getAccount();
+ $mailAccount = $account->getMailAccount();
+
+ if (!$mailAccount->isMoveJunk()) {
+ return;
+ }
+
+ $mailbox = $event->getMailbox();
+
+ if ($event->isSet() && $mailAccount->getJunkMailboxId() !== $mailbox->getId()) {
+ try {
+ $junkMailbox = $this->mailManager->getMailbox($account->getUserId(), $mailAccount->getJunkMailboxId());
+ } catch (ClientException) {
+ $this->logger->debug('move to junk enabled, but junk mailbox does not exist. account_id: {account_id}, junk_mailbox_id: {junk_mailbox_id}', [
+ 'account_id' => $account->getId(),
+ 'junk_mailbox_id' => $mailAccount->getJunkMailboxId(),
+ ]);
+ return;
+ }
+
+ try {
+ $this->mailManager->moveMessage(
+ $account,
+ $mailbox->getName(),
+ $event->getUid(),
+ $account,
+ $junkMailbox->getName(),
+ );
+ } catch (ServiceException $e) {
+ $this->logger->error('move message to junk mailbox failed. account_id: {account_id}', [
+ 'exception' => $e,
+ 'account_id' => $account->getId(),
+ ]);
+ }
+ } elseif (!$event->isSet() && 'INBOX' !== $mailbox->getName()) {
+ try {
+ $this->mailManager->moveMessage(
+ $account,
+ $mailbox->getName(),
+ $event->getUid(),
+ $account,
+ 'INBOX',
+ );
+ } catch (ServiceException $e) {
+ $this->logger->error('move message to inbox failed. account_id: {account_id}', [
+ 'exception' => $e,
+ 'account_id' => $account->getId(),
+ ]);
+ }
+ }
+ }
+}
diff --git a/lib/Migration/Version3001Date20230307113544.php b/lib/Migration/Version3001Date20230307113544.php
new file mode 100644
index 0000000000..3e072cf1f4
--- /dev/null
+++ b/lib/Migration/Version3001Date20230307113544.php
@@ -0,0 +1,52 @@
+
+ *
+ * @author Daniel Kesselberg
+ *
+ * @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 .
+ *
+ */
+
+namespace OCA\Mail\Migration;
+
+use Closure;
+use OCP\DB\ISchemaWrapper;
+use OCP\Migration\IOutput;
+use OCP\Migration\SimpleMigrationStep;
+
+class Version3001Date20230307113544 extends SimpleMigrationStep {
+ public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
+ /** @var ISchemaWrapper $schema */
+ $schema = $schemaClosure();
+
+ $accountsTable = $schema->getTable('mail_accounts');
+ $accountsTable->addColumn('junk_mailbox_id', 'integer', [
+ 'notnull' => false,
+ 'default' => null,
+ 'length' => 20,
+ ]);
+ $accountsTable->addColumn('move_junk', 'boolean', [
+ 'notnull' => false,
+ 'default' => false,
+ ]);
+
+ return $schema;
+ }
+}
diff --git a/src/components/AccountDefaultsSettings.vue b/src/components/AccountDefaultsSettings.vue
index 9f002a44ae..0aee963216 100644
--- a/src/components/AccountDefaultsSettings.vue
+++ b/src/components/AccountDefaultsSettings.vue
@@ -41,6 +41,11 @@
+
+
+ {{ t('mail', 'Junk messages are saved in:') }}
+
+
@@ -173,6 +178,33 @@ export default {
}
},
},
+ junkMailbox: {
+ get() {
+ const mb = this.$store.getters.getMailbox(this.account.junkMailboxId)
+ if (!mb) {
+ return
+ }
+ return mb.databaseId
+ },
+ async set(junkMailboxId) {
+ logger.debug('setting junk mailbox to ' + junkMailboxId)
+ this.saving = true
+ try {
+ await this.$store.dispatch('patchAccount', {
+ account: this.account,
+ data: {
+ junkMailboxId,
+ },
+ })
+ } catch (error) {
+ logger.error('could not set junk mailbox', {
+ error,
+ })
+ } finally {
+ this.saving = false
+ }
+ },
+ },
},
}
diff --git a/src/components/AccountSettings.vue b/src/components/AccountSettings.vue
index d1e22377f6..9c923a84c5 100644
--- a/src/components/AccountSettings.vue
+++ b/src/components/AccountSettings.vue
@@ -54,7 +54,7 @@
{{
- t('mail', 'The folders to use for drafts, sent messages, deleted messages and archived messages.')
+ t('mail', 'The folders to use for drafts, sent messages, deleted messages, archived messages and junk messages.')
}}
@@ -65,6 +65,9 @@
+
+
+
+ -
+ - @author 2023 Daniel Kesselberg
+ -
+ - @license AGPL-3.0-or-later
+ -
+ - 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 .
+ -->
+
+
+
+
+ {{ t('mail', 'Move messages to Junk folder') }}
+
+
+
+
+
diff --git a/src/components/MenuEnvelope.vue b/src/components/MenuEnvelope.vue
index aad5b4461e..84aa5d7507 100644
--- a/src/components/MenuEnvelope.vue
+++ b/src/components/MenuEnvelope.vue
@@ -381,8 +381,30 @@ export default {
onToggleSeen() {
this.$store.dispatch('toggleEnvelopeSeen', { envelope: this.envelope })
},
- onToggleJunk() {
- this.$store.dispatch('toggleEnvelopeJunk', this.envelope)
+ async onToggleJunk() {
+ const removeEnvelope = await this.$store.dispatch('moveEnvelopeToJunk', this.envelope)
+
+ /**
+ * moveEnvelopeToJunk returns true if the envelope should be moved to a different mailbox.
+ *
+ * Our backend (MessageMapper.move) implemented move as copy and delete.
+ * The message is copied to another mailbox and gets a new UID; the message in the current folder is deleted.
+ *
+ * Trigger the delete event here to open the next envelope and remove the current envelope from the list.
+ * The delete event bubbles up to MailboxThread.deleteMessage and is forwarded to Mailbox.onDelete to the actual implementation.
+ *
+ * In Mailbox.onDelete, fetchNextEnvelopes requires the current envelope to find the next envelope.
+ * Therefore, it must run before removing the envelope.
+ */
+
+ if (removeEnvelope) {
+ await this.$emit('delete', this.envelope.databaseId)
+ }
+
+ await this.$store.dispatch('toggleEnvelopeJunk', {
+ envelope: this.envelope,
+ removeEnvelope,
+ })
},
toggleSelected() {
this.$emit('update:selected')
diff --git a/src/components/NavigationMailbox.vue b/src/components/NavigationMailbox.vue
index ec1c6dc719..81e16a6aa5 100644
--- a/src/components/NavigationMailbox.vue
+++ b/src/components/NavigationMailbox.vue
@@ -56,6 +56,8 @@
:size="20" />
+
{
// Change immediately and switch back on error
const oldState = envelope.flags.$junk
@@ -976,6 +976,10 @@ export default {
value: oldState,
})
+ if (removeEnvelope) {
+ commit('removeEnvelope', { id: envelope.databaseId })
+ }
+
try {
await setEnvelopeFlags(envelope.databaseId, {
$junk: !oldState,
@@ -984,6 +988,10 @@ export default {
} catch (error) {
console.error('could not toggle message junk state', error)
+ if (removeEnvelope) {
+ commit('addEnvelope', envelope)
+ }
+
// Revert change
commit('flagEnvelope', {
envelope,
@@ -1361,4 +1369,28 @@ export default {
commit('patchAccount', { account, data: { smimeCertificateId } })
})
},
+
+ /**
+ * Should the envelope moved to the junk (or back to inbox)
+ *
+ * @param {object} context Vuex store context
+ * @param {object} context.getters Vuex store getters
+ * @param {object} envelope envelope object@
+ * @return {boolean}
+ */
+ async moveEnvelopeToJunk({ getters }, envelope) {
+ const account = getters.getAccount(envelope.accountId)
+ if (account.moveJunk === false) {
+ return false
+ }
+
+ if (!envelope.flags.$junk) {
+ // move message to junk
+ return account.junkMailboxId && envelope.mailboxId !== account.junkMailboxId
+ }
+
+ const inbox = getters.getInbox(account.id)
+ // move message to inbox
+ return inbox && envelope.mailboxId !== inbox.databaseId
+ },
}
diff --git a/src/store/getters.js b/src/store/getters.js
index ec8ac153fb..01eecc1bab 100644
--- a/src/store/getters.js
+++ b/src/store/getters.js
@@ -144,4 +144,10 @@ export const getters = {
},
getNcVersion: (state) => state.preferences?.ncVersion,
getAppVersion: (state) => state.preferences?.version,
+ findMailboxBySpecialRole: (state, getters) => (accountId, specialRole) => {
+ return getters.getMailboxes(accountId).find(mailbox => mailbox.specialRole === specialRole)
+ },
+ getInbox: (state, getters) => (accountId) => {
+ return getters.findMailboxBySpecialRole(accountId, 'inbox')
+ },
}
diff --git a/src/tests/unit/store/actions.spec.js b/src/tests/unit/store/actions.spec.js
index 1acc9f3abc..5853f69ab2 100644
--- a/src/tests/unit/store/actions.spec.js
+++ b/src/tests/unit/store/actions.spec.js
@@ -47,6 +47,8 @@ describe('Vuex store actions', () => {
dispatch: jest.fn(),
getters: {
accounts: [],
+ getAccount: jest.fn(),
+ getInbox: jest.fn(),
getMailbox: jest.fn(),
getMailboxes: jest.fn(),
getEnvelope: jest.fn(),
@@ -513,4 +515,87 @@ describe('Vuex store actions', () => {
expect(NotificationService.showNewMessagesNotification).toHaveBeenCalled
})
})
+
+ it('should move message to junk', async() => {
+ context.getters.getAccount.mockReturnValueOnce({
+ moveJunk: true,
+ junkMailboxId: 10
+ })
+
+ const removeEnvelope = await actions.moveEnvelopeToJunk(context, {
+ flags: {
+ $junk: false
+ },
+ mailboxId: 1
+ })
+
+ expect(removeEnvelope).toBeTruthy()
+ })
+
+ it('should move message to junk, no mailbox configured', async() => {
+ context.getters.getAccount.mockReturnValueOnce({
+ moveJunk: true,
+ junkMailboxId: null
+ })
+
+ const removeEnvelope = await actions.moveEnvelopeToJunk(context, {
+ flags: {
+ $junk: false
+ },
+ mailboxId: 1
+ })
+
+ expect(removeEnvelope).toBeFalsy()
+ })
+
+ it('should move message to inbox', async() => {
+ context.getters.getAccount.mockReturnValueOnce({
+ moveJunk: true,
+ junkMailboxId: 10
+ })
+ context.getters.getInbox.mockReturnValueOnce({
+ databaseId: 1
+ })
+
+ const removeEnvelope = await actions.moveEnvelopeToJunk(context, {
+ flags: {
+ $junk: true
+ },
+ mailboxId: 10
+ })
+
+ expect(removeEnvelope).toBeTruthy()
+ })
+
+ it('should move message to inbox, inbox not found', async() => {
+ context.getters.getAccount.mockReturnValueOnce({
+ moveJunk: true,
+ junkMailboxId: 10
+ })
+ context.getters.getInbox.mockReturnValueOnce(undefined)
+
+ const removeEnvelope = await actions.moveEnvelopeToJunk(context, {
+ flags: {
+ $junk: true
+ },
+ mailboxId: 10
+ })
+
+ expect(removeEnvelope).toBeFalsy()
+ })
+
+ it('should not move messages', async() => {
+ context.getters.getAccount.mockReturnValueOnce({
+ moveJunk: false
+ })
+
+ const removeEnvelope = await actions.moveEnvelopeToJunk(context, {
+ flags: {
+ $junk: true
+ },
+ mailboxId: 10
+ })
+
+ expect(removeEnvelope).toBeFalsy()
+ })
})
diff --git a/src/tests/unit/store/getters.spec.js b/src/tests/unit/store/getters.spec.js
index f8af1ecf61..419c44c29a 100644
--- a/src/tests/unit/store/getters.spec.js
+++ b/src/tests/unit/store/getters.spec.js
@@ -199,4 +199,53 @@ describe('Vuex store getters', () => {
const envelopesB = getters.getEnvelopesByThreadRootId('345-678-901')
expect(envelopesB.length).toEqual(0)
})
+
+ it('find mailbox by special role: inbox', () => {
+ const mockedGetters = {
+ getMailboxes: () => [
+ {
+ name: 'Test',
+ specialRole: 0,
+ },
+ {
+ name: 'INBOX',
+ specialRole: 'inbox',
+ },
+ {
+ name: 'Trash',
+ specialRole: 'trash',
+ }
+ ]
+ }
+
+ const result = getters.findMailboxBySpecialRole(state, mockedGetters)('100', 'inbox')
+
+ expect(result).toEqual({
+ name: 'INBOX',
+ specialRole: 'inbox'
+ });
+ })
+
+ it('find mailbox by special role: undefined', () => {
+ const mockedGetters = {
+ getMailboxes: () => [
+ {
+ name: 'Test',
+ specialRole: 0,
+ },
+ {
+ name: 'INBOX',
+ specialRole: 'inbox',
+ },
+ {
+ name: 'Trash',
+ specialRole: 'trash',
+ }
+ ]
+ }
+
+ const result = getters.findMailboxBySpecialRole(state, mockedGetters)('100', 'drafts')
+
+ expect(result).toEqual(undefined);
+ })
})
diff --git a/tests/Integration/Db/MailAccountTest.php b/tests/Integration/Db/MailAccountTest.php
index 323f616d70..51b9d70181 100644
--- a/tests/Integration/Db/MailAccountTest.php
+++ b/tests/Integration/Db/MailAccountTest.php
@@ -78,6 +78,8 @@ public function testToAPI() {
'smimeCertificateId' => null,
'quotaPercentage' => 10,
'trashRetentionDays' => 60,
+ 'junkMailboxId' => null,
+ 'moveJunk' => false
], $a->toJson());
}
@@ -111,6 +113,8 @@ public function testMailAccountConstruct() {
'smimeCertificateId' => null,
'quotaPercentage' => null,
'trashRetentionDays' => 60,
+ 'junkMailboxId' => null,
+ 'moveJunk' => false,
];
$a = new MailAccount($expected);
// TODO: fix inconsistency
diff --git a/tests/Unit/Listener/MoveJunkListenerTest.php b/tests/Unit/Listener/MoveJunkListenerTest.php
new file mode 100644
index 0000000000..55f505ab0b
--- /dev/null
+++ b/tests/Unit/Listener/MoveJunkListenerTest.php
@@ -0,0 +1,226 @@
+
+ *
+ * @author Daniel Kesselberg
+ *
+ * @license AGPL-3.0-or-later
+ *
+ * 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
+ *
+ */
+namespace Unit\Listener;
+
+use ChristophWurst\Nextcloud\Testing\TestCase;
+use OCA\Mail\Account;
+use OCA\Mail\Contracts\IMailManager;
+use OCA\Mail\Db\MailAccount;
+use OCA\Mail\Db\Mailbox;
+use OCA\Mail\Events\MessageFlaggedEvent;
+use OCA\Mail\Exception\ClientException;
+use OCA\Mail\Exception\ServiceException;
+use OCA\Mail\Listener\MoveJunkListener;
+use Psr\Log\LoggerInterface;
+use Psr\Log\Test\TestLogger;
+
+class MoveJunkListenerTest extends TestCase {
+ private IMailManager $mailManager;
+ private LoggerInterface $logger;
+ private MoveJunkListener $listener;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->mailManager = $this->createMock(IMailManager::class);
+ $this->logger = new TestLogger();
+
+ $this->listener = new MoveJunkListener(
+ $this->mailManager,
+ $this->logger
+ );
+ }
+
+ public function testIgnoreOtherFlags(): void {
+ $event = $this->createMock(MessageFlaggedEvent::class);
+ $event->method('getFlag')
+ ->willReturn('test');
+
+ $event->expects($this->never())
+ ->method('getAccount');
+
+ $this->listener->handle($event);
+ }
+
+ public function testMoveJunkDisabled(): void {
+ $mailAccount = new MailAccount();
+ $mailAccount->setMoveJunk(false);
+ $account = new Account($mailAccount);
+
+ $event = $this->createMock(MessageFlaggedEvent::class);
+ $event->method('getFlag')
+ ->willReturn('$junk');
+ $event->method('getAccount')
+ ->willReturn($account);
+
+ $event->expects($this->never())
+ ->method('getMailbox');
+
+ $this->listener->handle($event);
+ }
+
+ public function testMoveJunkMailboxNotFound(): void {
+ $mailAccount = new MailAccount();
+ $mailAccount->setJunkMailboxId(200);
+ $mailAccount->setMoveJunk(true);
+ $mailAccount->setUserId('bob');
+ $account = new Account($mailAccount);
+
+ $mailbox = new Mailbox();
+ $mailbox->setId(100);
+
+ $this->mailManager->method('getMailbox')
+ ->willThrowException(new ClientException('Computer says no'));
+
+ $event = new MessageFlaggedEvent(
+ $account,
+ $mailbox,
+ 100,
+ '$junk',
+ true
+ );
+
+ $this->listener->handle($event);
+
+ $this->assertCount(1, $this->logger->records);
+ }
+
+ public function testMoveJunkAlreadyInJunk(): void {
+ $mailAccount = new MailAccount();
+ $mailAccount->setJunkMailboxId(200);
+ $mailAccount->setMoveJunk(true);
+ $mailAccount->setUserId('bob');
+ $account = new Account($mailAccount);
+
+ $mailbox = new Mailbox();
+ $mailbox->setId(200);
+
+ $this->mailManager->expects($this->never())
+ ->method('moveMessage');
+
+ $event = new MessageFlaggedEvent(
+ $account,
+ $mailbox,
+ 100,
+ '$junk',
+ true
+ );
+
+ $this->listener->handle($event);
+ }
+
+ public function testMoveJunkFailed(): void {
+ $mailAccount = new MailAccount();
+ $mailAccount->setJunkMailboxId(200);
+ $mailAccount->setMoveJunk(true);
+ $mailAccount->setUserId('bob');
+ $account = new Account($mailAccount);
+
+ $mailbox = new Mailbox();
+ $mailbox->setId(100);
+ $mailbox->setName('INBOX');
+
+ $junkMailbox = new Mailbox();
+ $junkMailbox->setId(200);
+ $junkMailbox->setName('Junk');
+
+ $this->mailManager->method('getMailbox')
+ ->willReturn($junkMailbox);
+
+ $this->mailManager->method('moveMessage')
+ ->willThrowException(new ServiceException('Computer says no'));
+
+ $event = new MessageFlaggedEvent(
+ $account,
+ $mailbox,
+ 100,
+ '$junk',
+ true
+ );
+
+ $this->listener->handle($event);
+
+ $this->assertCount(1, $this->logger->records);
+ }
+
+ public function testMoveJunkAlreadyInInbox(): void {
+ $mailAccount = new MailAccount();
+ $mailAccount->setJunkMailboxId(200);
+ $mailAccount->setMoveJunk(true);
+ $mailAccount->setUserId('bob');
+ $account = new Account($mailAccount);
+
+ $mailbox = new Mailbox();
+ $mailbox->setId(100);
+ $mailbox->setName('INBOX');
+
+ $this->mailManager->expects($this->never())
+ ->method('moveMessage');
+
+ $event = new MessageFlaggedEvent(
+ $account,
+ $mailbox,
+ 100,
+ '$junk',
+ false
+ );
+
+ $this->listener->handle($event);
+ }
+
+ public function testMoveJunkToInboxFailed(): void {
+ $mailAccount = new MailAccount();
+ $mailAccount->setJunkMailboxId(200);
+ $mailAccount->setMoveJunk(true);
+ $mailAccount->setUserId('bob');
+ $account = new Account($mailAccount);
+
+ $mailbox = new Mailbox();
+ $mailbox->setId(200);
+ $mailbox->setName('Junk');
+
+ $junkMailbox = new Mailbox();
+ $junkMailbox->setId(200);
+ $junkMailbox->setName('Junk');
+
+ $this->mailManager->method('getMailbox')
+ ->willReturn($junkMailbox);
+
+ $this->mailManager->method('moveMessage')
+ ->willThrowException(new ServiceException('Computer says no'));
+
+ $event = new MessageFlaggedEvent(
+ $account,
+ $mailbox,
+ 100,
+ '$junk',
+ false
+ );
+
+ $this->listener->handle($event);
+
+ $this->assertCount(1, $this->logger->records);
+ }
+}