feat: move messages to junk folder

Signed-off-by: Daniel Kesselberg <mail@danielkesselberg.de>
This commit is contained in:
Daniel Kesselberg 2023-03-07 21:56:18 +01:00
Родитель 2507be59e9
Коммит 45c5ab9277
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 36E3664E099D0614
19 изменённых файлов: 755 добавлений и 8 удалений

Просмотреть файл

@ -22,7 +22,7 @@ Positive:
Learn more about the Nextcloud Ethical AI Rating [in our blog](https://nextcloud.com/blog/nextcloud-ethical-ai-rating/).
]]></description>
<version>3.4.0-alpha.2</version>
<version>3.4.0-alpha.3</version>
<licence>agpl</licence>
<author>Greta Doçi</author>
<author homepage="https://github.com/nextcloud/groupware">Nextcloud Groupware Team</author>

Просмотреть файл

@ -60,6 +60,7 @@ use OCA\Mail\Listener\InteractionListener;
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 @@ class Application extends App implements IBootstrap {
$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);

Просмотреть файл

@ -242,7 +242,9 @@ class AccountsController extends Controller {
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 @@ class AccountsController extends Controller {
// 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)
);

Просмотреть файл

@ -110,6 +110,10 @@ use OCP\AppFramework\Db\Entity;
* @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 @@ class MailAccount extends Entity {
$this->addType('smimeCertificateId', 'integer');
$this->addType('quotaPercentage', 'integer');
$this->addType('trashRetentionDays', 'integer');
$this->addType('junkMailboxId', 'integer');
$this->addType('moveJunk', 'boolean');
}
/**
@ -285,6 +294,8 @@ class MailAccount extends Entity {
'smimeCertificateId' => $this->getSmimeCertificateId(),
'quotaPercentage' => $this->getQuotaPercentage(),
'trashRetentionDays' => $this->getTrashRetentionDays(),
'junkMailboxId' => $this->getJunkMailboxId(),
'moveJunk' => ($this->isMoveJunk() === true),
];
if (!is_null($this->getOutboundHost())) {

Просмотреть файл

@ -112,6 +112,15 @@ class MailboxesSynchronizedSpecialMailboxesUpdater implements IEventListener {
$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);
}

Просмотреть файл

@ -0,0 +1,102 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2023 Daniel Kesselberg <mail@danielkesselberg.de>
*
* @author Daniel Kesselberg <mail@danielkesselberg.de>
*
* @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 <http://www.gnu.org/licenses/>
*
*/
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<Event|MessageFlaggedEvent>
*/
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(),
]);
}
}
}
}

Просмотреть файл

@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2023 Daniel Kesselberg <mail@danielkesselberg.de>
*
* @author Daniel Kesselberg <mail@danielkesselberg.de>
*
* @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\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;
}
}

Просмотреть файл

@ -41,6 +41,11 @@
</p>
<MailboxInlinePicker v-model="archiveMailbox" :account="account" :disabled="saving" />
<p>
{{ t('mail', 'Junk messages are saved in:') }}
</p>
<MailboxInlinePicker v-model="junkMailbox" :account="account" :disabled="saving" />
</div>
</template>
@ -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
}
},
},
},
}
</script>

Просмотреть файл

@ -54,7 +54,7 @@
<AppSettingsSection id="default-folders" :title=" t('mail', 'Default folders')">
<p class="settings-hint">
{{
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.')
}}
</p>
<AccountDefaultsSettings :account="account" />
@ -65,6 +65,9 @@
</p>
<TrashRetentionSettings :account="account" />
</AppSettingsSection>
<AppSettingsSection id="junk-settings" :title="t('mail', 'Junk settings')">
<JunkSettings :account="account" />
</AppSettingsSection>
<AppSettingsSection
v-if="account"
id="out-of-office-replies"
@ -128,6 +131,7 @@ import SieveFilterForm from './SieveFilterForm'
import OutOfOfficeForm from './OutOfOfficeForm'
import CertificateSettings from './CertificateSettings'
import TrashRetentionSettings from './TrashRetentionSettings'
import JunkSettings from './JunkSettings'
export default {
name: 'AccountSettings',
@ -145,6 +149,7 @@ export default {
OutOfOfficeForm,
CertificateSettings,
TrashRetentionSettings,
JunkSettings,
},
props: {
account: {

Просмотреть файл

@ -586,8 +586,30 @@ export default {
onToggleSeen() {
this.$store.dispatch('toggleEnvelopeSeen', { envelope: this.data })
},
onToggleJunk() {
this.$store.dispatch('toggleEnvelopeJunk', this.data)
async onToggleJunk() {
const removeEnvelope = await this.$store.dispatch('moveEnvelopeToJunk', this.data)
/**
* 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 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.data.databaseId)
}
await this.$store.dispatch('toggleEnvelopeJunk', {
envelope: this.data,
removeEnvelope,
})
},
async onDelete() {
// Remove from selection first

Просмотреть файл

@ -0,0 +1,75 @@
<!--
- @copyright 2023 Daniel Kesselberg <mail@danielkesselberg.de>
-
- @author 2023 Daniel Kesselberg <mail@danielkesselberg.de>
-
- @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 <http://www.gnu.org/licenses/>.
-->
<template>
<div>
<NcCheckboxRadioSwitch
:checked="account.moveJunk"
:disabled="saving"
type="switch"
@update:checked="saveMoveJunk">
{{ t('mail', 'Move messages to Junk folder') }}
</NcCheckboxRadioSwitch>
</div>
</template>
<script>
import logger from '../logger'
import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch'
export default {
name: 'JunkSettings',
components: {
NcCheckboxRadioSwitch,
},
props: {
account: {
type: Object,
required: true,
},
},
data() {
return {
saving: false,
}
},
methods: {
async saveMoveJunk(moveJunk) {
logger.debug('setting move junk to mailbox to ' + moveJunk)
this.saving = true
try {
await this.$store.dispatch('patchAccount', {
account: this.account,
data: {
moveJunk,
},
})
} catch (error) {
logger.error('could not set move junk to mailbox', {
error,
})
} finally {
this.saving = false
}
},
},
}
</script>

Просмотреть файл

@ -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')

Просмотреть файл

@ -56,6 +56,8 @@
:size="20" />
<IconDelete v-else-if="mailbox.databaseId === account.trashMailboxId"
:size="20" />
<IconJunk v-else-if="mailbox.databaseId === account.junkMailboxId"
:size="20" />
<IconFolderShared v-else-if="mailbox.shared"
:size="20" />
<IconFolder v-else
@ -224,6 +226,7 @@ import IconInfo from 'vue-material-design-icons/Information'
import IconDraft from 'vue-material-design-icons/Pencil'
import IconArchive from 'vue-material-design-icons/PackageDown'
import IconInbox from 'vue-material-design-icons/Home'
import IconJunk from 'vue-material-design-icons/Fire'
import IconAllInboxes from 'vue-material-design-icons/InboxMultiple'
import EraserVariant from 'vue-material-design-icons/EraserVariant'
import ImportantIcon from './icons/ImportantIcon'
@ -263,6 +266,7 @@ export default {
IconFolderShared,
IconDraft,
IconArchive,
IconJunk,
IconInbox,
EraserVariant,
ImportantIcon,

Просмотреть файл

@ -961,7 +961,7 @@ export default {
}
})
},
async toggleEnvelopeJunk({ commit, getters }, envelope) {
async toggleEnvelopeJunk({ commit, getters }, { envelope, removeEnvelope }) {
return handleHttpAuthErrors(commit, async () => {
// 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
},
}

Просмотреть файл

@ -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')
},
}

Просмотреть файл

@ -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()
})
})

Просмотреть файл

@ -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);
})
})

Просмотреть файл

@ -78,6 +78,8 @@ class MailAccountTest extends TestCase {
'smimeCertificateId' => null,
'quotaPercentage' => 10,
'trashRetentionDays' => 60,
'junkMailboxId' => null,
'moveJunk' => false
], $a->toJson());
}
@ -111,6 +113,8 @@ class MailAccountTest extends TestCase {
'smimeCertificateId' => null,
'quotaPercentage' => null,
'trashRetentionDays' => 60,
'junkMailboxId' => null,
'moveJunk' => false,
];
$a = new MailAccount($expected);
// TODO: fix inconsistency

Просмотреть файл

@ -0,0 +1,226 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2023 Daniel Kesselberg <mail@danielkesselberg.de>
*
* @author Daniel Kesselberg <mail@danielkesselberg.de>
*
* @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 <http://www.gnu.org/licenses/>
*
*/
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);
}
}