blob: 70b7aa130b15903c26a41d1a595b7c335ada36b6 [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.task;
import static org.apache.james.rspamd.DockerRSpamD.PASSWORD;
import static org.apache.james.rspamd.task.FeedSpamToRSpamDTask.RunningOptions.DEFAULT_MESSAGES_PER_SECOND;
import static org.apache.james.rspamd.task.FeedSpamToRSpamDTask.RunningOptions.DEFAULT_PERIOD;
import static org.apache.james.rspamd.task.FeedSpamToRSpamDTask.RunningOptions.DEFAULT_SAMPLING_PROBABILITY;
import static org.apache.james.rspamd.task.FeedSpamToRSpamDTask.SPAM_MAILBOX_NAME;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import java.io.ByteArrayInputStream;
import java.time.Clock;
import java.time.Instant;
import java.time.ZonedDateTime;
import java.util.Date;
import java.util.Optional;
import java.util.stream.IntStream;
import javax.mail.Flags;
import org.apache.james.core.Domain;
import org.apache.james.core.Username;
import org.apache.james.domainlist.api.DomainList;
import org.apache.james.mailbox.MailboxSession;
import org.apache.james.mailbox.MessageIdManager;
import org.apache.james.mailbox.exception.MailboxException;
import org.apache.james.mailbox.inmemory.InMemoryMailboxManager;
import org.apache.james.mailbox.inmemory.manager.InMemoryIntegrationResources;
import org.apache.james.mailbox.model.MailboxPath;
import org.apache.james.mailbox.store.MailboxSessionMapperFactory;
import org.apache.james.rspamd.DockerRSpamDExtension;
import org.apache.james.rspamd.client.RSpamDClientConfiguration;
import org.apache.james.rspamd.client.RSpamDHttpClient;
import org.apache.james.task.Task;
import org.apache.james.user.api.UsersRepository;
import org.apache.james.user.memory.MemoryUsersRepository;
import org.apache.james.utils.UpdatableTickingClock;
import org.assertj.core.api.SoftAssertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.mockito.Mockito;
import com.github.fge.lambdas.Throwing;
public class FeedSpamToRSpamDTaskTest {
@RegisterExtension
static DockerRSpamDExtension rSpamDExtension = new DockerRSpamDExtension();
public static final Domain DOMAIN = Domain.of("domain.tld");
public static final Username BOB = Username.fromLocalPartWithDomain("bob", DOMAIN);
public static final Username ALICE = Username.fromLocalPartWithDomain("alice", DOMAIN);
public static final MailboxPath BOB_SPAM_MAILBOX = MailboxPath.forUser(BOB, SPAM_MAILBOX_NAME);
public static final MailboxPath ALICE_SPAM_MAILBOX = MailboxPath.forUser(ALICE, SPAM_MAILBOX_NAME);
public static final long THREE_DAYS_IN_SECOND = 259200;
public static final long TWO_DAYS_IN_SECOND = 172800;
public static final long ONE_DAY_IN_SECOND = 86400;
public static final Instant NOW = ZonedDateTime.now().toInstant();
private InMemoryMailboxManager mailboxManager;
private MessageIdManager messageIdManager;
private MailboxSessionMapperFactory mapperFactory;
private UsersRepository usersRepository;
private Clock clock;
private RSpamDHttpClient client;
private FeedSpamToRSpamDTask task;
@BeforeEach
void setup() throws Exception {
InMemoryIntegrationResources inMemoryIntegrationResources = InMemoryIntegrationResources.defaultResources();
mailboxManager = inMemoryIntegrationResources.getMailboxManager();
DomainList domainList = mock(DomainList.class);
Mockito.when(domainList.containsDomain(any())).thenReturn(true);
usersRepository = MemoryUsersRepository.withVirtualHosting(domainList);
usersRepository.addUser(BOB, "anyPassword");
usersRepository.addUser(ALICE, "anyPassword");
mailboxManager.createMailbox(BOB_SPAM_MAILBOX, mailboxManager.createSystemSession(BOB));
mailboxManager.createMailbox(ALICE_SPAM_MAILBOX, mailboxManager.createSystemSession(ALICE));
clock = new UpdatableTickingClock(NOW);
client = new RSpamDHttpClient(new RSpamDClientConfiguration(rSpamDExtension.getBaseUrl(), PASSWORD, Optional.empty()));
messageIdManager = inMemoryIntegrationResources.getMessageIdManager();
mapperFactory = mailboxManager.getMapperFactory();
task = new FeedSpamToRSpamDTask(mailboxManager, usersRepository, messageIdManager, mapperFactory, client, FeedSpamToRSpamDTask.RunningOptions.DEFAULT, clock);
}
@Test
void shouldReturnDefaultInformationWhenDataIsEmpty() {
Task.Result result = task.run();
assertThat(result).isEqualTo(Task.Result.COMPLETED);
assertThat(task.snapshot())
.isEqualTo(FeedSpamToRSpamDTask.Context.Snapshot.builder()
.spamMessageCount(0)
.reportedSpamMessageCount(0)
.errorCount(0)
.messagesPerSecond(DEFAULT_MESSAGES_PER_SECOND)
.period(DEFAULT_PERIOD)
.samplingProbability(DEFAULT_SAMPLING_PROBABILITY)
.build());
}
@Test
void taskShouldReportAllSpamMessagesOfAllUsersByDefault() throws MailboxException {
appendSpamMessage(BOB_SPAM_MAILBOX, Date.from(NOW));
appendSpamMessage(ALICE_SPAM_MAILBOX, Date.from(NOW));
Task.Result result = task.run();
assertThat(result).isEqualTo(Task.Result.COMPLETED);
assertThat(task.snapshot())
.isEqualTo(FeedSpamToRSpamDTask.Context.Snapshot.builder()
.spamMessageCount(2)
.reportedSpamMessageCount(2)
.errorCount(0)
.messagesPerSecond(DEFAULT_MESSAGES_PER_SECOND)
.period(DEFAULT_PERIOD)
.samplingProbability(DEFAULT_SAMPLING_PROBABILITY)
.build());
}
@Test
void taskShouldReportSpamMessageInPeriod() throws MailboxException {
FeedSpamToRSpamDTask.RunningOptions runningOptions = new FeedSpamToRSpamDTask.RunningOptions(Optional.of(TWO_DAYS_IN_SECOND),
DEFAULT_MESSAGES_PER_SECOND, DEFAULT_SAMPLING_PROBABILITY);
task = new FeedSpamToRSpamDTask(mailboxManager, usersRepository, messageIdManager, mapperFactory, client, runningOptions, clock);
appendSpamMessage(BOB_SPAM_MAILBOX, Date.from(NOW.minusSeconds(ONE_DAY_IN_SECOND)));
Task.Result result = task.run();
assertThat(result).isEqualTo(Task.Result.COMPLETED);
assertThat(task.snapshot())
.isEqualTo(FeedSpamToRSpamDTask.Context.Snapshot.builder()
.spamMessageCount(1)
.reportedSpamMessageCount(1)
.errorCount(0)
.messagesPerSecond(DEFAULT_MESSAGES_PER_SECOND)
.period(Optional.of(TWO_DAYS_IN_SECOND))
.samplingProbability(DEFAULT_SAMPLING_PROBABILITY)
.build());
}
@Test
void taskShouldNotReportSpamMessageNotInPeriod() throws MailboxException {
FeedSpamToRSpamDTask.RunningOptions runningOptions = new FeedSpamToRSpamDTask.RunningOptions(Optional.of(TWO_DAYS_IN_SECOND),
DEFAULT_MESSAGES_PER_SECOND, DEFAULT_SAMPLING_PROBABILITY);
task = new FeedSpamToRSpamDTask(mailboxManager, usersRepository, messageIdManager, mapperFactory, client, runningOptions, clock);
appendSpamMessage(BOB_SPAM_MAILBOX, Date.from(NOW.minusSeconds(THREE_DAYS_IN_SECOND)));
Task.Result result = task.run();
assertThat(result).isEqualTo(Task.Result.COMPLETED);
assertThat(task.snapshot())
.isEqualTo(FeedSpamToRSpamDTask.Context.Snapshot.builder()
.spamMessageCount(1)
.reportedSpamMessageCount(0)
.errorCount(0)
.messagesPerSecond(DEFAULT_MESSAGES_PER_SECOND)
.period(Optional.of(TWO_DAYS_IN_SECOND))
.samplingProbability(DEFAULT_SAMPLING_PROBABILITY)
.build());
}
@Test
void mixedInternalDateCase() throws MailboxException {
FeedSpamToRSpamDTask.RunningOptions runningOptions = new FeedSpamToRSpamDTask.RunningOptions(Optional.of(TWO_DAYS_IN_SECOND),
DEFAULT_MESSAGES_PER_SECOND, DEFAULT_SAMPLING_PROBABILITY);
task = new FeedSpamToRSpamDTask(mailboxManager, usersRepository, messageIdManager, mapperFactory, client, runningOptions, clock);
appendSpamMessage(BOB_SPAM_MAILBOX, Date.from(NOW.minusSeconds(THREE_DAYS_IN_SECOND)));
appendSpamMessage(BOB_SPAM_MAILBOX, Date.from(NOW.minusSeconds(ONE_DAY_IN_SECOND)));
Task.Result result = task.run();
assertThat(result).isEqualTo(Task.Result.COMPLETED);
assertThat(task.snapshot())
.isEqualTo(FeedSpamToRSpamDTask.Context.Snapshot.builder()
.spamMessageCount(2)
.reportedSpamMessageCount(1)
.errorCount(0)
.messagesPerSecond(DEFAULT_MESSAGES_PER_SECOND)
.period(Optional.of(TWO_DAYS_IN_SECOND))
.samplingProbability(DEFAULT_SAMPLING_PROBABILITY)
.build());
}
@Test
void taskWithSamplingProbabilityIsZeroShouldReportNonSpamMessage() {
FeedSpamToRSpamDTask.RunningOptions runningOptions = new FeedSpamToRSpamDTask.RunningOptions(Optional.empty(),
DEFAULT_MESSAGES_PER_SECOND, 0);
task = new FeedSpamToRSpamDTask(mailboxManager, usersRepository, messageIdManager, mapperFactory, client, runningOptions, clock);
IntStream.range(0, 10)
.forEach(Throwing.intConsumer(any -> appendSpamMessage(BOB_SPAM_MAILBOX, Date.from(NOW.minusSeconds(ONE_DAY_IN_SECOND)))));
Task.Result result = task.run();
assertThat(result).isEqualTo(Task.Result.COMPLETED);
assertThat(task.snapshot())
.isEqualTo(FeedSpamToRSpamDTask.Context.Snapshot.builder()
.spamMessageCount(10)
.reportedSpamMessageCount(0)
.errorCount(0)
.messagesPerSecond(DEFAULT_MESSAGES_PER_SECOND)
.period(DEFAULT_PERIOD)
.samplingProbability(0)
.build());
}
@Test
void taskWithDefaultSamplingProbabilityShouldReportAllSpamMessages() {
IntStream.range(0, 10)
.forEach(Throwing.intConsumer(any -> appendSpamMessage(BOB_SPAM_MAILBOX, Date.from(NOW.minusSeconds(ONE_DAY_IN_SECOND)))));
Task.Result result = task.run();
assertThat(result).isEqualTo(Task.Result.COMPLETED);
assertThat(task.snapshot())
.isEqualTo(FeedSpamToRSpamDTask.Context.Snapshot.builder()
.spamMessageCount(10)
.reportedSpamMessageCount(10)
.errorCount(0)
.messagesPerSecond(DEFAULT_MESSAGES_PER_SECOND)
.period(DEFAULT_PERIOD)
.samplingProbability(DEFAULT_SAMPLING_PROBABILITY)
.build());
}
@Test
void taskWithVeryLowSamplingProbabilityShouldReportNotAllSpamMessages() {
FeedSpamToRSpamDTask.RunningOptions runningOptions = new FeedSpamToRSpamDTask.RunningOptions(Optional.empty(),
DEFAULT_MESSAGES_PER_SECOND, 0.01);
task = new FeedSpamToRSpamDTask(mailboxManager, usersRepository, messageIdManager, mapperFactory, client, runningOptions, clock);
IntStream.range(0, 10)
.forEach(Throwing.intConsumer(any -> appendSpamMessage(BOB_SPAM_MAILBOX, Date.from(NOW.minusSeconds(ONE_DAY_IN_SECOND)))));
Task.Result result = task.run();
SoftAssertions.assertSoftly(softly -> {
assertThat(result).isEqualTo(Task.Result.COMPLETED);
assertThat(task.snapshot().getSpamMessageCount()).isEqualTo(10);
assertThat(task.snapshot().getReportedSpamMessageCount()).isLessThan(10);
assertThat(task.snapshot().getErrorCount()).isZero();
});
}
@Test
void taskWithVeryHighSamplingProbabilityShouldReportMoreThanZeroMessage() {
FeedSpamToRSpamDTask.RunningOptions runningOptions = new FeedSpamToRSpamDTask.RunningOptions(Optional.empty(),
DEFAULT_MESSAGES_PER_SECOND, 0.99);
task = new FeedSpamToRSpamDTask(mailboxManager, usersRepository, messageIdManager, mapperFactory, client, runningOptions, clock);
IntStream.range(0, 10)
.forEach(Throwing.intConsumer(any -> appendSpamMessage(BOB_SPAM_MAILBOX, Date.from(NOW.minusSeconds(ONE_DAY_IN_SECOND)))));
Task.Result result = task.run();
SoftAssertions.assertSoftly(softly -> {
assertThat(result).isEqualTo(Task.Result.COMPLETED);
assertThat(task.snapshot().getSpamMessageCount()).isEqualTo(10);
assertThat(task.snapshot().getReportedSpamMessageCount()).isPositive();
assertThat(task.snapshot().getErrorCount()).isZero();
});
}
@Test
void taskWithAverageSamplingProbabilityShouldReportSomeMessages() {
FeedSpamToRSpamDTask.RunningOptions runningOptions = new FeedSpamToRSpamDTask.RunningOptions(Optional.empty(),
DEFAULT_MESSAGES_PER_SECOND, 0.5);
task = new FeedSpamToRSpamDTask(mailboxManager, usersRepository, messageIdManager, mapperFactory, client, runningOptions, clock);
IntStream.range(0, 10)
.forEach(Throwing.intConsumer(any -> appendSpamMessage(BOB_SPAM_MAILBOX, Date.from(NOW.minusSeconds(ONE_DAY_IN_SECOND)))));
Task.Result result = task.run();
SoftAssertions.assertSoftly(softly -> {
assertThat(result).isEqualTo(Task.Result.COMPLETED);
assertThat(task.snapshot().getSpamMessageCount()).isEqualTo(10);
assertThat(task.snapshot().getReportedSpamMessageCount()).isBetween(1L, 9L); // skip 0 and 10 cases cause their probability is very low (0.5^10)
assertThat(task.snapshot().getErrorCount()).isZero();
});
}
private void appendSpamMessage(MailboxPath mailboxPath, Date internalDate) throws MailboxException {
MailboxSession session = mailboxManager.createSystemSession(mailboxPath.getUser());
mailboxManager.getMailbox(mailboxPath, session)
.appendMessage(new ByteArrayInputStream(String.format("random content %4.3f", Math.random()).getBytes()),
internalDate,
session,
true,
new Flags());
}
}