blob: 110fb48024095fadd9cc99e72326fa05638b7bf4 [file] [log] [blame]
/****************************************************************
* Licensed to the Apache Software Foundation (ASF) under one *
* or more contributor license agreements. See the NOTICE file *
* distributed with this work for additional information *
* regarding copyright ownership. The ASF licenses this file *
* to you under the Apache License, Version 2.0 (the *
* "License"); you may not use this file except in compliance *
* with the License. You may obtain a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, *
* software distributed under the License is distributed on an *
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
* KIND, either express or implied. See the License for the *
* specific language governing permissions and limitations *
* under the License. *
****************************************************************/
package org.apache.james.rspamd;
import static org.apache.james.mailbox.events.MailboxEvents.Added.IS_APPENDED;
import static org.apache.james.mailbox.events.MailboxEvents.Added.IS_DELIVERY;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import jakarta.mail.Flags;
import org.apache.james.core.Username;
import org.apache.james.events.Group;
import org.apache.james.mailbox.DefaultMailboxes;
import org.apache.james.mailbox.MailboxSession;
import org.apache.james.mailbox.MailboxSessionUtil;
import org.apache.james.mailbox.events.MailboxEvents;
import org.apache.james.mailbox.events.MessageMoveEvent;
import org.apache.james.mailbox.exception.MailboxException;
import org.apache.james.mailbox.inmemory.manager.InMemoryIntegrationResources;
import org.apache.james.mailbox.model.ByteContent;
import org.apache.james.mailbox.model.Mailbox;
import org.apache.james.mailbox.model.MailboxId;
import org.apache.james.mailbox.model.MailboxPath;
import org.apache.james.mailbox.model.MessageMetaData;
import org.apache.james.mailbox.model.MessageMoves;
import org.apache.james.mailbox.model.TestMessageId;
import org.apache.james.mailbox.model.ThreadId;
import org.apache.james.mailbox.model.UidValidity;
import org.apache.james.mailbox.store.MailboxSessionMapperFactory;
import org.apache.james.mailbox.store.StoreMailboxManager;
import org.apache.james.mailbox.store.SystemMailboxesProviderImpl;
import org.apache.james.mailbox.store.event.EventFactory;
import org.apache.james.mailbox.store.mail.MailboxMapper;
import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder;
import org.apache.james.mailbox.store.mail.model.impl.SimpleMailboxMessage;
import org.apache.james.rspamd.client.RspamdClientConfiguration;
import org.apache.james.rspamd.client.RspamdHttpClient;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono;
public class RspamdListenerTest {
static final Username USER = Username.of("user");
static final MailboxSession MAILBOX_SESSION = MailboxSessionUtil.create(USER);
static final UidValidity UID_VALIDITY = UidValidity.of(43);
static final TestMessageId MESSAGE_ID = TestMessageId.of(45);
static final ThreadId THREAD_ID = ThreadId.fromBaseMessageId(MESSAGE_ID);
private RspamdHttpClient rspamdHttpClient;
private RspamdListener listener;
private MailboxSessionMapperFactory mapperFactory;
private Mailbox inbox;
private Mailbox mailbox1;
private Mailbox mailbox2;
private MailboxId mailboxId1;
private MailboxId mailboxId2;
private MailboxId spamMailboxId;
private MailboxId spamCapitalMailboxId;
private MailboxId trashMailboxId;
private StoreMailboxManager mailboxManager;
private SystemMailboxesProviderImpl systemMailboxesProvider;
@BeforeEach
void setup() {
rspamdHttpClient = mock(RspamdHttpClient.class);
RspamdClientConfiguration configuration = mock(RspamdClientConfiguration.class);
when(configuration.usePerUserBayes()).thenReturn(true);
when(rspamdHttpClient.reportAsHam(any(), any())).thenReturn(Mono.empty());
when(rspamdHttpClient.reportAsHam(any())).thenReturn(Mono.empty());
when(rspamdHttpClient.reportAsSpam(any(), any())).thenReturn(Mono.empty());
when(rspamdHttpClient.reportAsSpam(any())).thenReturn(Mono.empty());
mailboxManager = spy(InMemoryIntegrationResources.defaultResources().getMailboxManager());
systemMailboxesProvider = new SystemMailboxesProviderImpl(mailboxManager);
when(mailboxManager.createSystemSession(USER))
.thenReturn(MAILBOX_SESSION);
mapperFactory = mailboxManager.getMapperFactory();
MailboxMapper mailboxMapper = mapperFactory.createMailboxMapper(MAILBOX_SESSION);
inbox = mailboxMapper.create(MailboxPath.forUser(USER, DefaultMailboxes.INBOX), UID_VALIDITY).block();
mailbox1 = mailboxMapper.create(MailboxPath.forUser(USER, "mailbox1"), UID_VALIDITY).block();
mailbox2 = mailboxMapper.create(MailboxPath.forUser(USER, "mailbox2"), UID_VALIDITY).block();
mailboxId1 = mailbox1.getMailboxId();
mailboxId2 = mailbox2.getMailboxId();
spamMailboxId = mailboxMapper.create(MailboxPath.forUser(USER, "Spam"), UID_VALIDITY).block().getMailboxId();
spamCapitalMailboxId = mailboxMapper.create(MailboxPath.forUser(USER, "SPAM"), UID_VALIDITY).block().getMailboxId();
trashMailboxId = mailboxMapper.create(MailboxPath.forUser(USER, "Trash"), UID_VALIDITY).block().getMailboxId();
listener = new RspamdListener(rspamdHttpClient, mailboxManager, mapperFactory, systemMailboxesProvider, configuration,
RspamdListener.RspamdListenerConfiguration.DEFAULT);
}
@Test
void deserializeListenerGroup() throws Exception {
assertThat(Group.deserialize("org.apache.james.rspamd.RspamdListener$RspamdListenerGroup"))
.isEqualTo(new RspamdListener.RspamdListenerGroup());
}
@Test
void isEventOnSpamMailboxShouldReturnFalseWhenMessageIsMovedToANonSpamMailbox() {
MessageMoveEvent messageMoveEvent = MessageMoveEvent.builder()
.session(MAILBOX_SESSION)
.messageMoves(MessageMoves.builder()
.previousMailboxIds(mailboxId1)
.targetMailboxIds(mailboxId2)
.build())
.messageId(MESSAGE_ID)
.build();
assertThat(listener.isMessageMovedToSpamMailbox(messageMoveEvent).block()).isFalse();
}
@Test
void isEventOnSpamMailboxShouldReturnTrueWhenMailboxIsSpam() {
MessageMoveEvent messageMoveEvent = MessageMoveEvent.builder()
.session(MAILBOX_SESSION)
.messageMoves(MessageMoves.builder()
.previousMailboxIds(mailboxId1)
.targetMailboxIds(spamMailboxId)
.build())
.messageId(MESSAGE_ID)
.build();
assertThat(listener.isMessageMovedToSpamMailbox(messageMoveEvent).block()).isTrue();
}
@Test
void isEventOnSpamMailboxShouldReturnFalseWhenMailboxIsSpamOtherCase() {
MessageMoveEvent messageMoveEvent = MessageMoveEvent.builder()
.session(MAILBOX_SESSION)
.messageMoves(MessageMoves.builder()
.previousMailboxIds(mailboxId1)
.targetMailboxIds(spamCapitalMailboxId)
.build())
.messageId(MESSAGE_ID)
.build();
assertThat(listener.isMessageMovedToSpamMailbox(messageMoveEvent).block()).isFalse();
}
@Test
void isMessageMovedOutOfSpamMailboxShouldReturnFalseWhenMessageMovedBetweenNonSpamMailboxes() {
MessageMoveEvent messageMoveEvent = MessageMoveEvent.builder()
.session(MAILBOX_SESSION)
.messageMoves(MessageMoves.builder()
.previousMailboxIds(mailboxId1)
.targetMailboxIds(mailboxId2)
.build())
.messageId(MESSAGE_ID)
.build();
assertThat(listener.isMessageMovedOutOfSpamMailbox(messageMoveEvent).block()).isFalse();
}
@Test
void isMessageMovedOutOfSpamMailboxShouldReturnFalseWhenMessageMovedOutOfCapitalSpamMailbox() {
MessageMoveEvent messageMoveEvent = MessageMoveEvent.builder()
.session(MAILBOX_SESSION)
.messageMoves(MessageMoves.builder()
.previousMailboxIds(spamCapitalMailboxId)
.targetMailboxIds(mailboxId2)
.build())
.messageId(MESSAGE_ID)
.build();
assertThat(listener.isMessageMovedOutOfSpamMailbox(messageMoveEvent).block()).isFalse();
}
@Test
void isMessageMovedOutOfSpamMailboxShouldReturnTrueWhenMessageMovedOutOfSpamMailbox() {
MessageMoveEvent messageMoveEvent = MessageMoveEvent.builder()
.session(MAILBOX_SESSION)
.messageMoves(MessageMoves.builder()
.previousMailboxIds(spamMailboxId)
.targetMailboxIds(mailboxId2)
.build())
.messageId(MESSAGE_ID)
.build();
assertThat(listener.isMessageMovedOutOfSpamMailbox(messageMoveEvent).block()).isTrue();
}
@Test
void isMessageMovedOutOfSpamMailboxShouldReturnFalseWhenMessageMovedToTrash() {
MessageMoveEvent messageMoveEvent = MessageMoveEvent.builder()
.session(MAILBOX_SESSION)
.messageMoves(MessageMoves.builder()
.previousMailboxIds(spamMailboxId)
.targetMailboxIds(trashMailboxId)
.build())
.messageId(MESSAGE_ID)
.build();
assertThat(listener.isMessageMovedOutOfSpamMailbox(messageMoveEvent).block()).isFalse();
}
@Test
void eventShouldCallReportPerUserSpamLearningWhenTheMovedEventMatchesAndPerUserBayesIsEnabled() throws Exception {
RspamdClientConfiguration configuration = mock(RspamdClientConfiguration.class);
when(configuration.usePerUserBayes()).thenReturn(true);
listener = new RspamdListener(rspamdHttpClient, mailboxManager, mapperFactory, systemMailboxesProvider, configuration,
RspamdListener.RspamdListenerConfiguration.DEFAULT);
createMessage(inbox);
MessageMoveEvent messageMoveEvent = MessageMoveEvent.builder()
.session(MAILBOX_SESSION)
.messageMoves(MessageMoves.builder()
.previousMailboxIds(mailboxId1)
.targetMailboxIds(spamMailboxId)
.build())
.messageId(MESSAGE_ID)
.build();
listener.event(messageMoveEvent);
verify(rspamdHttpClient).reportAsSpam(any(), any());
verify(rspamdHttpClient, never()).reportAsSpam(any());
}
@Test
void eventShouldCallReportGlobalSpamLearningWhenTheMovedEventMatchesAndPerUserBayesIsDisabled() throws Exception {
RspamdClientConfiguration configuration = mock(RspamdClientConfiguration.class);
when(configuration.usePerUserBayes()).thenReturn(false);
listener = new RspamdListener(rspamdHttpClient, mailboxManager, mapperFactory, systemMailboxesProvider, configuration,
RspamdListener.RspamdListenerConfiguration.DEFAULT);
createMessage(inbox);
MessageMoveEvent messageMoveEvent = MessageMoveEvent.builder()
.session(MAILBOX_SESSION)
.messageMoves(MessageMoves.builder()
.previousMailboxIds(mailboxId1)
.targetMailboxIds(spamMailboxId)
.build())
.messageId(MESSAGE_ID)
.build();
listener.event(messageMoveEvent);
verify(rspamdHttpClient).reportAsSpam(any());
verify(rspamdHttpClient, never()).reportAsSpam(any(), any());
}
@Test
void eventShouldCallHamLearningWhenTheMovedEventMatches() throws Exception {
createMessage(inbox);
MessageMoveEvent messageMoveEvent = MessageMoveEvent.builder()
.session(MAILBOX_SESSION)
.messageMoves(MessageMoves.builder()
.previousMailboxIds(spamMailboxId)
.targetMailboxIds(mailboxId1)
.build())
.messageId(MESSAGE_ID)
.build();
listener.event(messageMoveEvent);
verify(rspamdHttpClient).reportAsHam(any(), any());
}
@Test
void eventShouldCallReportPerUserHamLearningWhenTheMessageIsAddedInInboxAndPerUserBayesIsEnabled() throws Exception {
RspamdClientConfiguration configuration = mock(RspamdClientConfiguration.class);
when(configuration.usePerUserBayes()).thenReturn(true);
listener = new RspamdListener(rspamdHttpClient, mailboxManager, mapperFactory, systemMailboxesProvider, configuration,
RspamdListener.RspamdListenerConfiguration.DEFAULT);
SimpleMailboxMessage message = createMessage(inbox);
MailboxEvents.Added addedEvent = EventFactory.added()
.randomEventId()
.mailboxSession(MAILBOX_SESSION)
.mailbox(inbox)
.addMetaData(message.metaData())
.isDelivery(!IS_DELIVERY)
.isAppended(!IS_APPENDED)
.build();
listener.event(addedEvent);
verify(rspamdHttpClient).reportAsHam(any(), any());
verify(rspamdHttpClient, never()).reportAsHam(any());
}
@Test
void eventShouldCallReportGlobalHamLearningWhenTheMessageIsAddedInInboxAndPerUserBayesIsDisabled() throws Exception {
RspamdClientConfiguration configuration = mock(RspamdClientConfiguration.class);
when(configuration.usePerUserBayes()).thenReturn(false);
listener = new RspamdListener(rspamdHttpClient, mailboxManager, mapperFactory, systemMailboxesProvider, configuration,
RspamdListener.RspamdListenerConfiguration.DEFAULT);
SimpleMailboxMessage message = createMessage(inbox);
MailboxEvents.Added addedEvent = EventFactory.added()
.randomEventId()
.mailboxSession(MAILBOX_SESSION)
.mailbox(inbox)
.addMetaData(message.metaData())
.isDelivery(!IS_DELIVERY)
.isAppended(!IS_APPENDED)
.build();
listener.event(addedEvent);
verify(rspamdHttpClient).reportAsHam(any());
verify(rspamdHttpClient, never()).reportAsHam(any(), any());
}
@Test
void eventShouldNotCallReportHamLearningWhenTheMessageIsAddedInAMailboxOtherThanInbox() throws Exception {
SimpleMailboxMessage message = createMessage(mailbox1);
MailboxEvents.Added addedEvent = EventFactory.added()
.randomEventId()
.mailboxSession(MAILBOX_SESSION)
.mailbox(mailbox1)
.addMetaData(message.metaData())
.isDelivery(!IS_DELIVERY)
.isAppended(!IS_APPENDED)
.build();
listener.event(addedEvent);
verify(rspamdHttpClient, never()).reportAsHam(any());
verify(rspamdHttpClient, never()).reportAsHam(any(), any());
}
private SimpleMailboxMessage createMessage(Mailbox mailbox) throws MailboxException {
int size = 45;
int bodyStartOctet = 25;
byte[] content = "Subject: test\r\n\r\nBody\r\n".getBytes(StandardCharsets.UTF_8);
SimpleMailboxMessage message = new SimpleMailboxMessage(MESSAGE_ID, THREAD_ID, new Date(),
size, bodyStartOctet, new ByteContent(content), new Flags(), new PropertyBuilder().build(),
mailbox.getMailboxId());
MessageMetaData messageMetaData = mapperFactory.createMessageMapper(null).add(mailbox, message);
message.setUid(messageMetaData.getUid());
return message;
}
}