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]

Reply via email to