This is an automated email from the ASF dual-hosted git repository. btellier pushed a commit to branch 3.9.x in repository https://gitbox.apache.org/repos/asf/james-project.git
commit 7a64de63bf96c6ba8dcdeb84f8d05889ab14d1cf Author: Benoit TELLIER <[email protected]> AuthorDate: Mon Sep 29 17:19:32 2025 +0200 JAMES-4148 JMAP rule filtering: add support for internalDate, savedDate and sentDate --- .../james/core/builder/MimeMessageBuilder.java | 11 +++ .../james/jmap/cassandra/filtering/DTOTest.java | 4 +- .../src/test/resources/json/eventComplex-v4.json | 20 +++++ .../org/apache/james/jmap/api/filtering/Rule.java | 7 +- .../james/jmap/api/filtering/RuleFixture.java | 28 +++++++ .../james/jmap/mailet/filter/ContentMatcher.java | 29 ++++++++ .../james/jmap/mailet/filter/FilteringHeaders.java | 34 ++++++++- .../james/jmap/mailet/filter/HeaderExtractor.java | 4 + .../jmap/mailet/filter/JMAPFilteringTest.java | 65 ++++++++++++++++- .../data/jmap/RunRulesOnMailboxRoutesTest.java | 85 ++++++++++++++++++++++ 10 files changed, 282 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/org/apache/james/core/builder/MimeMessageBuilder.java b/core/src/main/java/org/apache/james/core/builder/MimeMessageBuilder.java index 03e3626b27..1402636720 100644 --- a/core/src/main/java/org/apache/james/core/builder/MimeMessageBuilder.java +++ b/core/src/main/java/org/apache/james/core/builder/MimeMessageBuilder.java @@ -22,8 +22,10 @@ package org.apache.james.core.builder; import java.io.ByteArrayInputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.time.Instant; import java.util.Arrays; import java.util.Collection; +import java.util.Date; import java.util.List; import java.util.Objects; import java.util.Optional; @@ -249,6 +251,7 @@ public class MimeMessageBuilder { private Optional<String> text = Optional.empty(); private Optional<String> textContentType = Optional.empty(); private Optional<String> subject = Optional.empty(); + private Optional<Instant> date = Optional.empty(); private Optional<InternetAddress> sender = Optional.empty(); private Optional<MimeMultipart> content = Optional.empty(); private ImmutableList.Builder<InternetAddress> from = ImmutableList.builder(); @@ -278,6 +281,11 @@ public class MimeMessageBuilder { return this; } + public MimeMessageBuilder setDate(Instant instant) { + this.date = Optional.ofNullable(instant); + return this; + } + public MimeMessageBuilder setSender(String sender) throws AddressException { this.sender = Optional.of(new InternetAddress(sender)); return this; @@ -403,6 +411,9 @@ public class MimeMessageBuilder { if (subject.isPresent()) { mimeMessage.setSubject(subject.get()); } + if (date.isPresent()) { + mimeMessage.setSentDate(Date.from(date.get())); + } ImmutableList<InternetAddress> fromAddresses = from.build(); if (!fromAddresses.isEmpty()) { mimeMessage.addFrom(fromAddresses.toArray(InternetAddress[]::new)); diff --git a/server/data/data-jmap-cassandra/src/test/java/org/apache/james/jmap/cassandra/filtering/DTOTest.java b/server/data/data-jmap-cassandra/src/test/java/org/apache/james/jmap/cassandra/filtering/DTOTest.java index fc0bfa9aa7..9c3d6985e4 100644 --- a/server/data/data-jmap-cassandra/src/test/java/org/apache/james/jmap/cassandra/filtering/DTOTest.java +++ b/server/data/data-jmap-cassandra/src/test/java/org/apache/james/jmap/cassandra/filtering/DTOTest.java @@ -26,7 +26,7 @@ import static org.apache.james.jmap.api.filtering.RuleFixture.RULE_1; import static org.apache.james.jmap.api.filtering.RuleFixture.RULE_2; import static org.apache.james.jmap.api.filtering.RuleFixture.RULE_4; import static org.apache.james.jmap.api.filtering.RuleFixture.RULE_CC; -import static org.apache.james.jmap.api.filtering.RuleFixture.RULE_CC_2; +import static org.apache.james.jmap.api.filtering.RuleFixture.RULE_CC_DATES; import static org.apache.james.jmap.api.filtering.RuleFixture.RULE_FROM; import static org.apache.james.jmap.api.filtering.RuleFixture.RULE_FROM_2; import static org.apache.james.jmap.api.filtering.RuleFixture.RULE_RECIPIENT; @@ -93,7 +93,7 @@ class DTOTest { static final RuleSetDefined COMPLEX_RULE_2 = new RuleSetDefined( new FilteringAggregateId(Username.of("Bart")), EventId.first(), - ImmutableList.of(RULE_CC_2, RULE_FROM_2, RULE_RECIPIENT_2, RULE_SUBJECT_2, RULE_TO_2)); + ImmutableList.of(RULE_CC_DATES, RULE_FROM_2, RULE_RECIPIENT_2, RULE_SUBJECT_2, RULE_TO_2)); static final IncrementalRuleChange INCREMENT_2 = new IncrementalRuleChange( new FilteringAggregateId(Username.of("Bart")), EventId.first(), diff --git a/server/data/data-jmap-cassandra/src/test/resources/json/eventComplex-v4.json b/server/data/data-jmap-cassandra/src/test/resources/json/eventComplex-v4.json index 1eb83c3c8b..437eaeff39 100644 --- a/server/data/data-jmap-cassandra/src/test/resources/json/eventComplex-v4.json +++ b/server/data/data-jmap-cassandra/src/test/resources/json/eventComplex-v4.json @@ -13,6 +13,26 @@ "field": "cc", "comparator": "start-with", "value": "A value to match 5" + }, + { + "field": "sentDate", + "comparator": "isOlderThan", + "value": "15d" + }, + { + "field": "sentDate", + "comparator": "isNewerThan", + "value": "30d" + }, + { + "field": "savedDate", + "comparator": "isNewerThan", + "value": "45d" + }, + { + "field": "internalDate", + "comparator": "isNewerThan", + "value": "2d" } ] }, diff --git a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/filtering/Rule.java b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/filtering/Rule.java index 8fe31b98b8..c892df422a 100644 --- a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/filtering/Rule.java +++ b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/filtering/Rule.java @@ -148,7 +148,10 @@ public class Rule { public static Field CC = new FixedField("cc"); public static Field SUBJECT = new FixedField("subject"); public static Field RECIPIENT = new FixedField("recipient"); - public static final ImmutableList<Field> VALUES = ImmutableList.of(FROM, TO, CC, SUBJECT, RECIPIENT); + public static Field SENT_DATE = new FixedField("sentDate"); + public static Field SAVED_DATE = new FixedField("savedDate"); + public static Field INTERNAL_DATE = new FixedField("internalDate"); + public static final ImmutableList<Field> VALUES = ImmutableList.of(FROM, TO, CC, SUBJECT, RECIPIENT, SENT_DATE, SAVED_DATE, INTERNAL_DATE); public static Optional<Field> find(String fieldName) { return VALUES.stream() @@ -178,6 +181,8 @@ public class Rule { public enum Comparator { CONTAINS("contains"), + IS_OLDER_THAN("isOlderThan"), + IS_NEWER_THAN("isNewerThan"), NOT_CONTAINS("not-contains"), EXACTLY_EQUALS("exactly-equals"), NOT_EXACTLY_EQUALS("not-exactly-equals"), diff --git a/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/filtering/RuleFixture.java b/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/filtering/RuleFixture.java index 5160a086cd..e027ca94f3 100644 --- a/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/filtering/RuleFixture.java +++ b/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/filtering/RuleFixture.java @@ -112,6 +112,34 @@ public interface RuleFixture { "A value to match 5")) .build(); + Rule RULE_CC_DATES = Rule.builder() + .id(Rule.Id.of("id-cc")) + .name(NAME) + .action(ACTION_2) + .conditionGroup(Rule.ConditionCombiner.AND, + Rule.Condition.of( + Rule.Condition.FixedField.CC, + Rule.Condition.Comparator.START_WITH, + "A value to match 5"), + + Rule.Condition.of( + Rule.Condition.FixedField.SENT_DATE, + Rule.Condition.Comparator.IS_OLDER_THAN, + "15d"), + Rule.Condition.of( + Rule.Condition.FixedField.SENT_DATE, + Rule.Condition.Comparator.IS_NEWER_THAN, + "30d"), + Rule.Condition.of( + Rule.Condition.FixedField.SAVED_DATE, + Rule.Condition.Comparator.IS_NEWER_THAN, + "45d"), + Rule.Condition.of( + Rule.Condition.FixedField.INTERNAL_DATE, + Rule.Condition.Comparator.IS_NEWER_THAN, + "2d")) + .build(); + Rule RULE_TO_2 = Rule.builder() .id(Rule.Id.of("id-to")) .name(NAME) diff --git a/server/protocols/jmap-rfc-8621/src/main/java/org/apache/james/jmap/mailet/filter/ContentMatcher.java b/server/protocols/jmap-rfc-8621/src/main/java/org/apache/james/jmap/mailet/filter/ContentMatcher.java index ddd8f71b8e..8369caeb95 100644 --- a/server/protocols/jmap-rfc-8621/src/main/java/org/apache/james/jmap/mailet/filter/ContentMatcher.java +++ b/server/protocols/jmap-rfc-8621/src/main/java/org/apache/james/jmap/mailet/filter/ContentMatcher.java @@ -19,6 +19,9 @@ package org.apache.james.jmap.mailet.filter; +import java.time.Clock; +import java.time.Duration; +import java.time.Instant; import java.util.Map; import java.util.Optional; import java.util.stream.Stream; @@ -28,6 +31,10 @@ import jakarta.mail.internet.InternetAddress; import org.apache.commons.lang3.StringUtils; import org.apache.james.jmap.api.filtering.Rule; +import org.apache.james.mime4j.codec.DecodeMonitor; +import org.apache.james.mime4j.field.DateTimeFieldLenientImpl; +import org.apache.james.mime4j.stream.RawField; +import org.apache.james.util.DurationParser; import org.apache.james.util.OptionalUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -92,6 +99,20 @@ public interface ContentMatcher { } ContentMatcher STRING_CONTAINS_MATCHER = (contents, valueToMatch) -> contents.anyMatch(content -> StringUtils.contains(content, valueToMatch)); + ContentMatcher IS_OLDER_THAN_MATCHER = (contents, valueToMatch) -> { + Duration duration = DurationParser.parse(valueToMatch); + Instant horizon = Clock.systemUTC().instant().minus(duration); + return contents + .map(dateField -> DateTimeFieldLenientImpl.PARSER.parse(new RawField("Date", dateField), DecodeMonitor.SILENT).getDate().toInstant()) + .anyMatch(date -> date.isBefore(horizon)); + }; + ContentMatcher IS_NEWER_THAN_MATCHER = (contents, valueToMatch) -> { + Duration duration = DurationParser.parse(valueToMatch); + Instant horizon = Clock.systemUTC().instant().minus(duration); + return contents + .map(dateField -> DateTimeFieldLenientImpl.PARSER.parse(new RawField("Date", dateField), DecodeMonitor.SILENT).getDate().toInstant()) + .anyMatch(date -> date.isAfter(horizon)); + }; ContentMatcher STRING_NOT_CONTAINS_MATCHER = negate(STRING_CONTAINS_MATCHER); ContentMatcher STRING_EXACTLY_EQUALS_MATCHER = (contents, valueToMatch) -> contents.anyMatch(content -> StringUtils.equals(content, valueToMatch)); ContentMatcher STRING_NOT_EXACTLY_EQUALS_MATCHER = negate(STRING_EXACTLY_EQUALS_MATCHER); @@ -106,6 +127,11 @@ public interface ContentMatcher { .map(ContentMatcher::asAddressHeader) .anyMatch(addressHeader -> addressHeader.fullAddress.startsWith(valueToMatch)); + Map<Rule.Condition.Comparator, ContentMatcher> DATE_MATCHER_REGISTRY = ImmutableMap.<Rule.Condition.Comparator, ContentMatcher>builder() + .put(Rule.Condition.Comparator.IS_NEWER_THAN, IS_NEWER_THAN_MATCHER) + .put(Rule.Condition.Comparator.IS_OLDER_THAN, IS_OLDER_THAN_MATCHER) + .build(); + Map<Rule.Condition.Comparator, ContentMatcher> HEADER_ADDRESS_MATCHER_REGISTRY = ImmutableMap.<Rule.Condition.Comparator, ContentMatcher>builder() .put(Rule.Condition.Comparator.CONTAINS, ADDRESS_CONTAINS_MATCHER) .put(Rule.Condition.Comparator.NOT_CONTAINS, ADDRESS_NOT_CONTAINS_MATCHER) @@ -128,6 +154,9 @@ public interface ContentMatcher { .put(Rule.Condition.FixedField.CC, HEADER_ADDRESS_MATCHER_REGISTRY) .put(Rule.Condition.FixedField.RECIPIENT, HEADER_ADDRESS_MATCHER_REGISTRY) .put(Rule.Condition.FixedField.FROM, HEADER_ADDRESS_MATCHER_REGISTRY) + .put(Rule.Condition.FixedField.SENT_DATE, DATE_MATCHER_REGISTRY) + .put(Rule.Condition.FixedField.INTERNAL_DATE, DATE_MATCHER_REGISTRY) + .put(Rule.Condition.FixedField.SAVED_DATE, DATE_MATCHER_REGISTRY) .build(); static ContentMatcher negate(ContentMatcher contentMatcher) { diff --git a/server/protocols/jmap-rfc-8621/src/main/java/org/apache/james/jmap/mailet/filter/FilteringHeaders.java b/server/protocols/jmap-rfc-8621/src/main/java/org/apache/james/jmap/mailet/filter/FilteringHeaders.java index 264847e74b..1997b142ba 100644 --- a/server/protocols/jmap-rfc-8621/src/main/java/org/apache/james/jmap/mailet/filter/FilteringHeaders.java +++ b/server/protocols/jmap-rfc-8621/src/main/java/org/apache/james/jmap/mailet/filter/FilteringHeaders.java @@ -21,15 +21,20 @@ package org.apache.james.jmap.mailet.filter; import java.util.ArrayList; import java.util.Arrays; +import java.util.Date; import java.util.Iterator; import java.util.List; +import java.util.TimeZone; +import java.util.stream.Stream; import jakarta.mail.MessagingException; +import org.apache.commons.lang3.NotImplementedException; import org.apache.james.mailbox.exception.MailboxException; import org.apache.james.mailbox.model.Header; import org.apache.james.mailbox.model.Headers; import org.apache.james.mailbox.model.MessageResult; +import org.apache.james.mime4j.util.MimeUtil; import org.apache.mailet.Mail; public interface FilteringHeaders { @@ -49,13 +54,25 @@ public interface FilteringHeaders { public String getSubject() throws MessagingException { return mail.getMessage().getSubject(); } + + @Override + public Stream<String> getInternalDate() { + throw new NotImplementedException("Not implemented"); + } + + @Override + public Stream<String> getSavedDate() { + throw new NotImplementedException("Not implemented"); + } } class MessageResultFilteringHeaders implements FilteringHeaders { private final Headers headers; + private final MessageResult messageResult; public MessageResultFilteringHeaders(MessageResult messageResult) throws MailboxException { - this.headers = messageResult.getHeaders(); + this.messageResult = messageResult; + this.headers = this.messageResult.getHeaders(); } @Override @@ -84,9 +101,24 @@ public interface FilteringHeaders { } return results.toArray(new String[0]); } + + @Override + public Stream<String> getInternalDate() { + return Stream.of(MimeUtil.formatDate(messageResult.getInternalDate(), TimeZone.getDefault())); + } + + @Override + public Stream<String> getSavedDate() { + return Stream.of(messageResult.getSaveDate().map(date -> MimeUtil.formatDate(date, TimeZone.getDefault())) + .orElse(MimeUtil.formatDate(new Date(), TimeZone.getDefault()))); + } } String[] getHeader(String name) throws Exception; String getSubject() throws Exception; + + Stream<String> getInternalDate(); + + Stream<String> getSavedDate(); } diff --git a/server/protocols/jmap-rfc-8621/src/main/java/org/apache/james/jmap/mailet/filter/HeaderExtractor.java b/server/protocols/jmap-rfc-8621/src/main/java/org/apache/james/jmap/mailet/filter/HeaderExtractor.java index c5a57ffa11..e199bf9aea 100644 --- a/server/protocols/jmap-rfc-8621/src/main/java/org/apache/james/jmap/mailet/filter/HeaderExtractor.java +++ b/server/protocols/jmap-rfc-8621/src/main/java/org/apache/james/jmap/mailet/filter/HeaderExtractor.java @@ -47,12 +47,16 @@ public interface HeaderExtractor extends ThrowingFunction<FilteringHeaders, Stre StreamUtils.ofNullables(filteringHeaders.getSubject()); HeaderExtractor CC_EXTRACTOR = recipientExtractor(Message.RecipientType.CC); HeaderExtractor TO_EXTRACTOR = recipientExtractor(Message.RecipientType.TO); + HeaderExtractor SENT_EXTRACTOR = headers -> StreamUtils.ofNullables(headers.getHeader("Date")); HeaderExtractor RECIPIENT_EXTRACTOR = and(TO_EXTRACTOR, CC_EXTRACTOR); HeaderExtractor FROM_EXTRACTOR = addressExtractor(filteringHeaders -> filteringHeaders.getHeader(FROM), FROM); Map<Rule.Condition.Field, HeaderExtractor> HEADER_EXTRACTOR_REGISTRY = ImmutableMap.<Rule.Condition.Field, HeaderExtractor>builder() .put(Rule.Condition.FixedField.SUBJECT, SUBJECT_EXTRACTOR) .put(Rule.Condition.FixedField.RECIPIENT, RECIPIENT_EXTRACTOR) + .put(Rule.Condition.FixedField.SENT_DATE, SENT_EXTRACTOR) + .put(Rule.Condition.FixedField.SAVED_DATE, FilteringHeaders::getSavedDate) + .put(Rule.Condition.FixedField.INTERNAL_DATE, FilteringHeaders::getInternalDate) .put(Rule.Condition.FixedField.FROM, FROM_EXTRACTOR) .put(Rule.Condition.FixedField.CC, CC_EXTRACTOR) .put(Rule.Condition.FixedField.TO, TO_EXTRACTOR) diff --git a/server/protocols/jmap-rfc-8621/src/test/java/org/apache/james/jmap/mailet/filter/JMAPFilteringTest.java b/server/protocols/jmap-rfc-8621/src/test/java/org/apache/james/jmap/mailet/filter/JMAPFilteringTest.java index e050aa472a..7ea7ed1a5c 100644 --- a/server/protocols/jmap-rfc-8621/src/test/java/org/apache/james/jmap/mailet/filter/JMAPFilteringTest.java +++ b/server/protocols/jmap-rfc-8621/src/test/java/org/apache/james/jmap/mailet/filter/JMAPFilteringTest.java @@ -22,12 +22,15 @@ package org.apache.james.jmap.mailet.filter; import static org.apache.james.core.builder.MimeMessageBuilder.mimeMessageBuilder; import static org.apache.james.jmap.api.filtering.Rule.Condition.Comparator.CONTAINS; import static org.apache.james.jmap.api.filtering.Rule.Condition.Comparator.EXACTLY_EQUALS; +import static org.apache.james.jmap.api.filtering.Rule.Condition.Comparator.IS_NEWER_THAN; +import static org.apache.james.jmap.api.filtering.Rule.Condition.Comparator.IS_OLDER_THAN; import static org.apache.james.jmap.api.filtering.Rule.Condition.Comparator.NOT_CONTAINS; import static org.apache.james.jmap.api.filtering.Rule.Condition.Comparator.NOT_EXACTLY_EQUALS; import static org.apache.james.jmap.api.filtering.Rule.Condition.Comparator.START_WITH; import static org.apache.james.jmap.api.filtering.Rule.Condition.FixedField.CC; import static org.apache.james.jmap.api.filtering.Rule.Condition.FixedField.FROM; import static org.apache.james.jmap.api.filtering.Rule.Condition.FixedField.RECIPIENT; +import static org.apache.james.jmap.api.filtering.Rule.Condition.FixedField.SENT_DATE; import static org.apache.james.jmap.api.filtering.Rule.Condition.FixedField.SUBJECT; import static org.apache.james.jmap.api.filtering.Rule.Condition.FixedField.TO; import static org.apache.james.jmap.mailet.filter.JMAPFilteringFixture.BOU; @@ -54,6 +57,8 @@ import static org.apache.james.jmap.mailet.filter.JMAPFilteringFixture.USER_4_FU import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; +import java.time.Clock; +import java.time.Duration; import java.util.Collection; import java.util.Locale; import java.util.Optional; @@ -593,7 +598,7 @@ class JMAPFilteringTest { argumentBuilder().scrambledSubjectShouldNotMatchCaseSensitive().build(), argumentBuilder().unscrambledSubjectToMatch(SHOULD_NOT_MATCH).build(), argumentBuilder().unscrambledSubjectShouldNotMatchCaseSensitive().build()), - Rule.Condition.FixedField.VALUES.stream() + ImmutableList.of(FROM, TO, CC, SUBJECT, RECIPIENT).stream() .map(field -> argumentBuilder() .description("no header") .field(field) @@ -1128,6 +1133,64 @@ class JMAPFilteringTest { .isEqualTo(RECIPIENT_1_MAILBOX_1_ATTRIBUTE); } + @Test + void shouldSupportSentDateAndIsNewerThan(JMAPFilteringTestSystem testSystem) throws Exception { + Mono.from(testSystem.getFilteringManagement().defineRulesForUser(RECIPIENT_1_USERNAME, + Optional.empty(), + Rule.builder() + .id(Rule.Id.of("1")) + .name("rule 1") + .conditionGroup(Rule.Condition.of(SENT_DATE, IS_NEWER_THAN, "7d")) + .action(Rule.Action.of(Rule.Action.AppendInMailboxes.withMailboxIds(testSystem.getRecipient1MailboxId().serialize()))) + .build())).block(); + + FakeMail oldMail = testSystem.asMail(mimeMessageBuilder() + .addHeader(FROM.asString(), USER_2_ADDRESS) + .addHeader(SUBJECT.asString(), "abcdef") + .setDate(Clock.systemUTC().instant().minus(Duration.ofDays(10)))); + FakeMail newMail = testSystem.asMail(mimeMessageBuilder() + .addHeader(FROM.asString(), USER_2_ADDRESS) + .addHeader(SUBJECT.asString(), "abcdef") + .setDate(Clock.systemUTC().instant().minus(Duration.ofDays(5)))); + + testSystem.getJmapFiltering().service(oldMail); + testSystem.getJmapFiltering().service(newMail); + + assertThatAttribute(newMail.getAttribute(RECIPIENT_1_USERNAME_ATTRIBUTE_NAME)) + .isEqualTo(RECIPIENT_1_MAILBOX_1_ATTRIBUTE); + assertThat(oldMail.getAttribute(RECIPIENT_1_USERNAME_ATTRIBUTE_NAME)) + .isEmpty(); + } + + @Test + void shouldSupportSentDateAndIsOlderThan(JMAPFilteringTestSystem testSystem) throws Exception { + Mono.from(testSystem.getFilteringManagement().defineRulesForUser(RECIPIENT_1_USERNAME, + Optional.empty(), + Rule.builder() + .id(Rule.Id.of("1")) + .name("rule 1") + .conditionGroup(Rule.Condition.of(SENT_DATE, IS_OLDER_THAN, "7d")) + .action(Rule.Action.of(Rule.Action.AppendInMailboxes.withMailboxIds(testSystem.getRecipient1MailboxId().serialize()))) + .build())).block(); + + FakeMail oldMail = testSystem.asMail(mimeMessageBuilder() + .addHeader(FROM.asString(), USER_2_ADDRESS) + .addHeader(SUBJECT.asString(), "abcdef") + .setDate(Clock.systemUTC().instant().minus(Duration.ofDays(10)))); + FakeMail newMail = testSystem.asMail(mimeMessageBuilder() + .addHeader(FROM.asString(), USER_2_ADDRESS) + .addHeader(SUBJECT.asString(), "abcdef") + .setDate(Clock.systemUTC().instant().minus(Duration.ofDays(5)))); + + testSystem.getJmapFiltering().service(oldMail); + testSystem.getJmapFiltering().service(newMail); + + assertThatAttribute(oldMail.getAttribute(RECIPIENT_1_USERNAME_ATTRIBUTE_NAME)) + .isEqualTo(RECIPIENT_1_MAILBOX_1_ATTRIBUTE); + assertThat(newMail.getAttribute(RECIPIENT_1_USERNAME_ATTRIBUTE_NAME)) + .isEmpty(); + } + @Test void orShouldNotMatchWhenNoConditionsAreMet(JMAPFilteringTestSystem testSystem) throws Exception { Mono.from(testSystem.getFilteringManagement().defineRulesForUser(RECIPIENT_1_USERNAME, diff --git a/server/protocols/webadmin/webadmin-jmap/src/test/java/org/apache/james/webadmin/data/jmap/RunRulesOnMailboxRoutesTest.java b/server/protocols/webadmin/webadmin-jmap/src/test/java/org/apache/james/webadmin/data/jmap/RunRulesOnMailboxRoutesTest.java index 43cd0cc357..3b6d0626c3 100644 --- a/server/protocols/webadmin/webadmin-jmap/src/test/java/org/apache/james/webadmin/data/jmap/RunRulesOnMailboxRoutesTest.java +++ b/server/protocols/webadmin/webadmin-jmap/src/test/java/org/apache/james/webadmin/data/jmap/RunRulesOnMailboxRoutesTest.java @@ -32,6 +32,9 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.nio.charset.StandardCharsets; +import java.time.Clock; +import java.time.Duration; +import java.util.Date; import java.util.Map; import org.apache.james.core.Username; @@ -308,6 +311,88 @@ public class RunRulesOnMailboxRoutesTest { ); } + @Test + void runRulesShouldApplyDateCrieria() throws Exception { + MailboxPath mailboxPath = MailboxPath.forUser(USERNAME, MAILBOX_NAME); + MailboxPath otherMailboxPath = MailboxPath.forUser(USERNAME, OTHER_MAILBOX_NAME); + MailboxSession systemSession = mailboxManager.createSystemSession(USERNAME); + + mailboxManager.createMailbox(mailboxPath, systemSession); + mailboxManager.createMailbox(otherMailboxPath, systemSession); + + mailboxManager.getMailbox(mailboxPath, systemSession) + .appendMessage(MessageManager.AppendCommand.builder() + .withInternalDate(Date.from(Clock.systemUTC().instant().minus(Duration.ofDays(2)))) + .build(Message.Builder.of() + .setSubject("plop") + .setFrom("[email protected]") + .setBody("body", StandardCharsets.UTF_8)), + systemSession); + + mailboxManager.getMailbox(mailboxPath, systemSession) + .appendMessage(MessageManager.AppendCommand.builder() + .withInternalDate(Date.from(Clock.systemUTC().instant().minus(Duration.ofDays(20)))) + .build(Message.Builder.of() + .setSubject("plop") + .setFrom("[email protected]") + .setBody("body", StandardCharsets.UTF_8)), + systemSession); + + MailboxId otherMailboxId = mailboxManager.getMailbox(otherMailboxPath, systemSession).getId(); + + String taskId = given() + .queryParam("action", "triage") + .body(""" + { + "id": "1", + "name": "rule 1", + "action": { + "appendIn": { + "mailboxIds": ["%s"] + }, + "important": false, + "keyworkds": [], + "reject": false, + "seen": false + }, + "conditionGroup": { + "conditionCombiner": "AND", + "conditions": [ + { + "comparator": "contains", + "field": "subject", + "value": "plop" + }, + { + "comparator": "isOlderThan", + "field": "internalDate", + "value": "10d" + } + ] + } + }""".formatted(otherMailboxId.serialize())) + .post(MAILBOX_NAME + "/messages") + .then() + .statusCode(CREATED_201) + .extract() + .jsonPath() + .get("taskId"); + + given() + .basePath(TasksRoutes.BASE) + .when() + .get(taskId + "/await"); + + SoftAssertions.assertSoftly( + softly -> { + softly.assertThat(Throwing.supplier(() -> mailboxManager.getMailbox(mailboxPath, systemSession).getMailboxCounters(systemSession).getCount()).get()) + .isEqualTo(1); + softly.assertThat(Throwing.supplier(() -> mailboxManager.getMailbox(otherMailboxPath, systemSession).getMailboxCounters(systemSession).getCount()).get()) + .isEqualTo(1); + } + ); + } + @Test void runRulesOnMailboxShouldNotMoveNonMatchingMessage() throws Exception { MailboxPath mailboxPath = MailboxPath.forUser(USERNAME, MAILBOX_NAME); --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
