You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by bt...@apache.org on 2023/05/30 01:56:04 UTC

[james-project] branch master updated: JAMES-3908 Storage directive with several folder (#1575)

This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git


The following commit(s) were added to refs/heads/master by this push:
     new a7d19b05f5 JAMES-3908 Storage directive with several folder (#1575)
a7d19b05f5 is described below

commit a7d19b05f5bee38aace2653ef323d9b83adc50be
Author: Benoit TELLIER <bt...@linagora.com>
AuthorDate: Tue May 30 08:55:58 2023 +0700

    JAMES-3908 Storage directive with several folder (#1575)
---
 .../java/org/apache/mailet/AttributeValue.java     |   3 -
 .../java/org/apache/mailet/StorageDirective.java   |  48 ++++--
 .../ROOT/partials/WithStorageDirective.adoc        |  22 ++-
 .../WithStorageDirectiveIntegrationTest.java       |  22 +++
 .../transport/mailets/WithStorageDirective.java    |  23 ++-
 .../mailets/delivery/MailboxAppenderImpl.java      |  34 +++-
 .../mailets/delivery/SimpleMailStore.java          |   2 +-
 .../mailets/WithStorageDirectiveTest.java          |  37 ++--
 .../mailets/delivery/SieveIntegrationTest.java     | 190 ++++++++++++---------
 .../james/jmap/mailet/filter/ActionApplier.java    |   8 +-
 .../jmap/mailet/filter/JMAPFilteringTest.java      |  68 +++++---
 11 files changed, 311 insertions(+), 146 deletions(-)

diff --git a/mailet/api/src/main/java/org/apache/mailet/AttributeValue.java b/mailet/api/src/main/java/org/apache/mailet/AttributeValue.java
index b0290e68f4..208537c154 100644
--- a/mailet/api/src/main/java/org/apache/mailet/AttributeValue.java
+++ b/mailet/api/src/main/java/org/apache/mailet/AttributeValue.java
@@ -29,8 +29,6 @@ import java.util.Optional;
 
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.james.mailbox.model.MessageIdDto;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
@@ -46,7 +44,6 @@ import com.google.common.collect.ImmutableMap;
  * @since Mailet API v3.2
  */
 public class AttributeValue<T> {
-    private static final Logger LOGGER = LoggerFactory.getLogger(AttributeValue.class);
     private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
 
     public static AttributeValue<Boolean> of(Boolean value) {
diff --git a/mailet/api/src/main/java/org/apache/mailet/StorageDirective.java b/mailet/api/src/main/java/org/apache/mailet/StorageDirective.java
index 2d0fe928de..3506f319e7 100644
--- a/mailet/api/src/main/java/org/apache/mailet/StorageDirective.java
+++ b/mailet/api/src/main/java/org/apache/mailet/StorageDirective.java
@@ -19,6 +19,7 @@
 package org.apache.mailet;
 
 import java.util.Collection;
+import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.stream.Stream;
@@ -28,6 +29,7 @@ import javax.mail.Flags;
 import org.apache.james.core.Username;
 
 import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.primitives.Booleans;
 
@@ -54,7 +56,7 @@ import com.google.common.primitives.Booleans;
  */
 public class StorageDirective {
     public static class Builder {
-        private Optional<String> targetFolder = Optional.empty();
+        private ImmutableList.Builder<String> targetFolders = ImmutableList.builder();
         private Optional<Boolean> seen = Optional.empty();
         private Optional<Boolean> important = Optional.empty();
         private Optional<Collection<String>> keywords = Optional.empty();
@@ -70,12 +72,22 @@ public class StorageDirective {
         }
 
         public Builder targetFolder(Optional<String> value) {
-            this.targetFolder = value;
+            value.ifPresent(targetFolders::add);
             return this;
         }
 
         public Builder targetFolder(String value) {
-            this.targetFolder = Optional.of(value);
+            targetFolders.add(value);
+            return this;
+        }
+
+        public Builder targetFolders(Collection<String> values) {
+            targetFolders.addAll(values);
+            return this;
+        }
+
+        public Builder targetFolders(Optional<List<String>> values) {
+            values.ifPresent(this::targetFolders);
             return this;
         }
 
@@ -88,14 +100,15 @@ public class StorageDirective {
             Preconditions.checkState(hasChanges(),
                 "Expecting one of the storage directives to be specified: [targetFolder, seen, important, keywords]");
 
-            return new StorageDirective(targetFolder, seen, important, keywords);
+            Optional<Collection<String>> targetFolders = Optional.of(this.targetFolders.build()).filter(c -> !c.isEmpty()).map(c -> (Collection<String>) c);
+            return new StorageDirective(targetFolders, seen, important, keywords);
         }
 
         private boolean hasChanges() {
             return Booleans.countTrue(
                 seen.isPresent(),
                 important.isPresent(),
-                targetFolder.isPresent(),
+                !targetFolders.build().isEmpty(),
                 keywords.isPresent()) > 0;
         }
 
@@ -108,6 +121,7 @@ public class StorageDirective {
     }
 
     private static final String DELIVERY_PATH_PREFIX = "DeliveryPath_";
+    private static final String DELIVERY_PATHS_PREFIX = "DeliveryPaths_";
     private static final String SEEN_PREFIX = "Seen_";
     private static final String IMPORTANT_PREFIX = "Important_";
     private static final String KEYWORDS_PREFIX = "Keywords_";
@@ -155,17 +169,27 @@ public class StorageDirective {
             .map(Boolean.class::cast);
     }
 
-    private static Optional<String> locateFolder(Username username, Mail mail) {
+    private static Optional<Collection<String>> locateFolder(Username username, Mail mail) {
+        AttributeName foldersAttribute = AttributeName.of(DELIVERY_PATHS_PREFIX + username.asString());
+        if (mail.getAttribute(foldersAttribute).isPresent()) {
+            return AttributeUtils.getValueAndCastFromMail(mail, foldersAttribute, Collection.class)
+                .map(collection -> (Collection<AttributeValue>) collection)
+                .map(collection -> collection.stream()
+                    .map(AttributeValue::getValue)
+                    .map(String.class::cast)
+                    .collect(ImmutableList.toImmutableList()));
+        }
         return AttributeUtils
-            .getValueAndCastFromMail(mail, AttributeName.of(DELIVERY_PATH_PREFIX + username.asString()), String.class);
+            .getValueAndCastFromMail(mail, AttributeName.of(DELIVERY_PATH_PREFIX + username.asString()), String.class)
+            .map(ImmutableList::of);
     }
 
-    private final Optional<String> targetFolder;
+    private final Optional<Collection<String>> targetFolder;
     private final Optional<Boolean> seen;
     private final Optional<Boolean> important;
     private final Optional<Collection<String>> keywords;
 
-    private StorageDirective(Optional<String> targetFolder,
+    private StorageDirective(Optional<Collection<String>> targetFolder,
                             Optional<Boolean> seen,
                             Optional<Boolean> important,
                             Optional<Collection<String>> keywords) {
@@ -197,7 +221,7 @@ public class StorageDirective {
 
     public Stream<Attribute> encodeAsAttributes(Username username) {
         return Stream.of(
-            targetFolder.map(value -> new Attribute(AttributeName.of(DELIVERY_PATH_PREFIX + username.asString()), AttributeValue.of(value))),
+            targetFolder.map(value -> new Attribute(AttributeName.of(DELIVERY_PATHS_PREFIX + username.asString()), asAttributeValue(value))),
             seen.map(value -> new Attribute(AttributeName.of(SEEN_PREFIX + username.asString()), AttributeValue.of(value))),
             important.map(value -> new Attribute(AttributeName.of(IMPORTANT_PREFIX + username.asString()), AttributeValue.of(value))),
             keywords.map(value -> new Attribute(AttributeName.of(KEYWORDS_PREFIX + username.asString()), asAttributeValue(value))))
@@ -212,12 +236,12 @@ public class StorageDirective {
 
     public StorageDirective withDefaultFolder(String folder) {
         if (targetFolder.isEmpty()) {
-            return new StorageDirective(Optional.of(folder), seen, important, keywords);
+            return new StorageDirective(Optional.of(ImmutableList.of(folder)), seen, important, keywords);
         }
         return this;
     }
 
-    public Optional<String> getTargetFolder() {
+    public Optional<Collection<String>> getTargetFolders() {
         return targetFolder;
     }
 
diff --git a/server/apps/distributed-app/docs/modules/ROOT/partials/WithStorageDirective.adoc b/server/apps/distributed-app/docs/modules/ROOT/partials/WithStorageDirective.adoc
index 2aac9d18cb..6b5f9b9e46 100644
--- a/server/apps/distributed-app/docs/modules/ROOT/partials/WithStorageDirective.adoc
+++ b/server/apps/distributed-app/docs/modules/ROOT/partials/WithStorageDirective.adoc
@@ -6,12 +6,30 @@ These directives are used by <strong>LocalDelivery</strong> mailet when adding t
 
 The following storage directives can be set:
 
-* targetFolderName: the folder to append the email in. (compulsory)
+* targetFolderNames: the folders to append the email in. Defaults to none (INBOX). Coma separated list of folder names. Fallback to targetFolderName.
+* targetFolderName: the folder to append the email in. Defaults to none (INBOX).
+* seen: boolean, whether the message should be automatically marked as seen. Defaults to false.
+* important: boolean, whether the message should be automatically marked as important. Defaults to false.
+* keywords: set of string, encoded as a string (value are coma separated). IMAP user flags to set for the message. Defaults to none
 
 Example:
 
 ....
 <mailet match="IsMarkedAsSpam" class="WithStorageDirective">
-  <targetFolderName>Spam</targetFolderName>
+    <targetFolderName>Spam</targetFolderName>
+    <seen>true</seen>
+    <important>true</important>
+    <keywords>keyword1,keyword2</targetFolderName>
+</mailet>
+....
+
+Alternatively, several target folders can be specified:
+
+....
+<mailet match="IsMarkedAsSpam" class="WithStorageDirective">
+  <targetFolderNames>Important, INBOX</targetFolderNames>
+  <seen>true</seen>
+  <important>true</important>
+  <keywords>keyword1,keyword2</targetFolderName>
 </mailet>
 ....
\ No newline at end of file
diff --git a/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/WithStorageDirectiveIntegrationTest.java b/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/WithStorageDirectiveIntegrationTest.java
index 506162e41f..066d000064 100644
--- a/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/WithStorageDirectiveIntegrationTest.java
+++ b/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/WithStorageDirectiveIntegrationTest.java
@@ -77,6 +77,28 @@ class WithStorageDirectiveIntegrationTest {
             .awaitMessage(awaitAtMostOneMinute);
     }
 
+    @Test
+    void targetFolderNamesShouldWork(@TempDir File temporaryFolder) throws Exception {
+        setUp(temporaryFolder, MailetConfiguration.builder()
+            .matcher(SenderIsLocal.class)
+            .mailet(WithStorageDirective.class)
+            .addProperty("targetFolderNames", "target1, target2"));
+
+        testIMAPClient.connect(LOCALHOST_IP, jamesServer.getProbe(ImapGuiceProbe.class).getImapPort())
+            .login(RECIPIENT, PASSWORD)
+            .create("target1")
+            .create("target2");
+
+        messageSender.connect(LOCALHOST_IP, jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort())
+            .authenticate(FROM, PASSWORD)
+            .sendMessage(FROM, RECIPIENT);
+
+        testIMAPClient.select("target1")
+            .awaitMessage(awaitAtMostOneMinute);
+        testIMAPClient.select("target2")
+            .awaitMessage(awaitAtMostOneMinute);
+    }
+
     @Test
     void seenShouldWork(@TempDir File temporaryFolder) throws Exception {
         setUp(temporaryFolder, MailetConfiguration.builder()
diff --git a/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/WithStorageDirective.java b/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/WithStorageDirective.java
index 4cc7a2d1c2..e9a79f4892 100644
--- a/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/WithStorageDirective.java
+++ b/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/WithStorageDirective.java
@@ -20,6 +20,7 @@
 package org.apache.james.transport.mailets;
 
 import java.util.Collection;
+import java.util.List;
 import java.util.Optional;
 
 import javax.inject.Inject;
@@ -34,6 +35,7 @@ import org.apache.mailet.base.GenericMailet;
 import com.github.fge.lambdas.consumers.ThrowingConsumer;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableList;
 
 /**
  * WithStorageDirective position storage directive for the recipients of this email.
@@ -41,6 +43,8 @@ import com.google.common.base.Splitter;
  * These directives are used by <strong>LocalDelivery</strong> mailet when adding the email to the recipients mailboxes.
  *
  * The following storage directives can be set:
+ *  - targetFolderNames: the folders to append the email in. Defaults to none (INBOX). Coma separated list of folder names.
+ *    Fallback to targetFolderName.
  *  - targetFolderName: the folder to append the email in. Defaults to none (INBOX).
  *  - seen: boolean, whether the message should be automatically marked as seen. Defaults to false.
  *  - important: boolean, whether the message should be automatically marked as important. Defaults to false.
@@ -56,9 +60,20 @@ import com.google.common.base.Splitter;
  *      <important>true</important>
  *      <keywords>keyword1,keyword2</targetFolderName>
  *  </mailet>
+ *
+ *  Alternatively, several target folders can be specified:
+ *
+ *  <mailet match="IsMarkedAsSpam" class="WithStorageDirective">
+ *    <targetFolderNames>Important, INBOX</targetFolderNames>
+ *    <seen>true</seen>
+ *    <important>true</important>
+ *    <keywords>keyword1,keyword2</targetFolderName>
+ *  </mailet>
+ *
  */
 public class WithStorageDirective extends GenericMailet {
     static final String TARGET_FOLDER_NAME = "targetFolderName";
+    static final String TARGET_FOLDER_NAMES = "targetFolderNames";
     static final String SEEN = "seen";
     static final String IMPORTANT = "important";
     static final String KEYWORDS = "keywords";
@@ -81,13 +96,19 @@ public class WithStorageDirective extends GenericMailet {
         Preconditions.checkState(validBooleanParameter(getInitParameterAsOptional(IMPORTANT)), "'%s' needs to be a boolean", IMPORTANT);
 
         storageDirective = StorageDirective.builder()
-            .targetFolder(getInitParameterAsOptional(TARGET_FOLDER_NAME))
+            .targetFolders(targetFolders())
             .seen(getInitParameterAsOptional(SEEN).map(Boolean::parseBoolean))
             .important(getInitParameterAsOptional(IMPORTANT).map(Boolean::parseBoolean))
             .keywords(getInitParameterAsOptional(KEYWORDS).map(this::parseKeywords))
             .build();
     }
 
+    private Optional<List<String>> targetFolders() {
+        return getInitParameterAsOptional(TARGET_FOLDER_NAMES)
+            .map(names -> Splitter.on(',').omitEmptyStrings().trimResults().splitToList(names))
+            .or(() -> getInitParameterAsOptional(TARGET_FOLDER_NAME).map(ImmutableList::of));
+    }
+
     private Collection<String> parseKeywords(String s) {
         return KEYWORD_SPLITTER
             .splitToList(s);
diff --git a/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/delivery/MailboxAppenderImpl.java b/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/delivery/MailboxAppenderImpl.java
index 910279bd54..5e793b8f4a 100644
--- a/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/delivery/MailboxAppenderImpl.java
+++ b/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/delivery/MailboxAppenderImpl.java
@@ -21,6 +21,7 @@ package org.apache.james.transport.mailets.delivery;
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.Collection;
 import java.util.Optional;
 
 import javax.mail.Flags;
@@ -38,15 +39,18 @@ import org.apache.james.mailbox.exception.MailboxNotFoundException;
 import org.apache.james.mailbox.model.ComposedMessageId;
 import org.apache.james.mailbox.model.Content;
 import org.apache.james.mailbox.model.MailboxPath;
+import org.apache.james.mailbox.model.MessageRange;
 import org.apache.james.server.core.MimeMessageInputStream;
 import org.apache.james.server.core.MimeMessageUtil;
 import org.apache.mailet.StorageDirective;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.github.fge.lambdas.Throwing;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Strings;
 
+import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
 
 public class MailboxAppenderImpl implements MailboxAppender {
@@ -59,11 +63,35 @@ public class MailboxAppenderImpl implements MailboxAppender {
     }
 
     public Mono<ComposedMessageId> append(MimeMessage mail, Username user, StorageDirective storageDirective) throws MessagingException {
-        Preconditions.checkArgument(storageDirective.getTargetFolder().isPresent(), "'targetFolder' field is needed");
+        Preconditions.checkArgument(storageDirective.getTargetFolders().isPresent(), "'targetFolders' field is needed");
 
         MailboxSession session = createMailboxSession(user);
-        return append(mail, user, useSlashAsSeparator(storageDirective.getTargetFolder().get(), session), storageDirective.getFlags(), session)
-            .map(AppendResult::getId);
+        String urlPath = storageDirective.getTargetFolders().flatMap(collection -> collection.stream().findFirst()).get();
+        String targetFolder = useSlashAsSeparator(urlPath, session);
+
+        return append(mail, user, targetFolder, storageDirective.getFlags(), session)
+            .map(AppendResult::getId)
+            .flatMap(id -> copyToExtraMailboxes(storageDirective, session, targetFolder, id));
+    }
+
+    // Avoids using the MessageIdManager for JPA compatibility
+    private Mono<ComposedMessageId> copyToExtraMailboxes(StorageDirective storageDirective, MailboxSession session, String targetFolder, ComposedMessageId id) {
+        Collection<String> folders = storageDirective.getTargetFolders().get();
+
+        if (folders.size() > 1) {
+            return Flux.fromIterable(folders)
+                .skip(1)
+                .flatMap(Throwing.function(extraTargetFolder -> {
+                    MailboxPath targetMailboxPath = MailboxPath.forUser(session.getUser(), targetFolder);
+                    MailboxPath destinationMailboxPath = MailboxPath.forUser(session.getUser(), useSlashAsSeparator(extraTargetFolder, session));
+
+                    return mailboxManager.copyMessagesReactive(MessageRange.one(id.getUid()),
+                        targetMailboxPath, destinationMailboxPath, session);
+                }))
+                .then(Mono.fromRunnable(() -> LOGGER.info("{} copied to {} extra mailboxes", id.getMessageId(), folders)))
+                .thenReturn(id);
+        }
+        return Mono.just(id);
     }
 
     private String useSlashAsSeparator(String urlPath, MailboxSession session) throws MessagingException {
diff --git a/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/delivery/SimpleMailStore.java b/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/delivery/SimpleMailStore.java
index c9e1620487..c2c8a5eec1 100644
--- a/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/delivery/SimpleMailStore.java
+++ b/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/delivery/SimpleMailStore.java
@@ -101,7 +101,7 @@ public class SimpleMailStore implements MailStore {
                 .doOnSuccess(ids -> {
                     metric.increment();
                     LOGGER.info("Local delivered mail {} with messageId {} successfully from {} to {} in folder {} with composedMessageId {}",
-                        mail.getName(), getMessageId(mail), mail.getMaybeSender().asString(), recipient.asPrettyString(), storageDirective.getTargetFolder().get(), ids);
+                        mail.getName(), getMessageId(mail), mail.getMaybeSender().asString(), recipient.asPrettyString(), storageDirective.getTargetFolders().get(), ids);
                 })
                 .then();
         } catch (MessagingException e) {
diff --git a/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/WithStorageDirectiveTest.java b/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/WithStorageDirectiveTest.java
index 6ba7bf81c7..c5a99600b9 100644
--- a/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/WithStorageDirectiveTest.java
+++ b/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/WithStorageDirectiveTest.java
@@ -22,6 +22,9 @@ package org.apache.james.transport.mailets;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
+import java.util.Collection;
+
+import org.apache.commons.lang3.tuple.Pair;
 import org.apache.james.domainlist.api.DomainList;
 import org.apache.james.user.memory.MemoryUsersRepository;
 import org.apache.mailet.Attribute;
@@ -30,9 +33,9 @@ import org.apache.mailet.AttributeValue;
 import org.apache.mailet.base.MailAddressFixture;
 import org.apache.mailet.base.test.FakeMail;
 import org.apache.mailet.base.test.FakeMailetConfig;
-import org.assertj.core.api.SoftAssertions;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import org.testcontainers.shaded.com.google.common.collect.ImmutableList;
 import org.testcontainers.shaded.com.google.common.collect.ImmutableSet;
 
 class WithStorageDirectiveTest {
@@ -74,12 +77,18 @@ class WithStorageDirectiveTest {
 
         testee.service(mail);
 
-        AttributeName recipient1 = AttributeName.of("DeliveryPath_recipient1@localhost");
-        AttributeName recipient2 = AttributeName.of("DeliveryPath_recipient2@localhost");
-        assertThat(mail.attributes())
+        AttributeName recipient1 = AttributeName.of("DeliveryPaths_recipient1@localhost");
+        AttributeName recipient2 = AttributeName.of("DeliveryPaths_recipient2@localhost");
+
+        assertThat(mail.attributes().map(this::unbox))
             .containsOnly(
-                new Attribute(recipient1, AttributeValue.of(targetFolderName)),
-                new Attribute(recipient2, AttributeValue.of(targetFolderName)));
+                Pair.of(recipient1, targetFolderName),
+                Pair.of(recipient2, targetFolderName));
+    }
+
+    Pair<AttributeName, String> unbox(Attribute attribute) {
+        Collection<AttributeValue> collection = (Collection<AttributeValue>) attribute.getValue().getValue();
+        return Pair.of(attribute.getName(), (String) collection.stream().findFirst().get().getValue());
     }
 
     @Test
@@ -198,13 +207,13 @@ class WithStorageDirectiveTest {
 
     @Test
     void serviceShouldOverridePreviousStorageDirectives() throws Exception {
-        AttributeName name1 = AttributeName.of("DeliveryPath_recipient1@localhost");
-        AttributeName name2 = AttributeName.of("DeliveryPath_recipient2@localhost");
-        AttributeValue<String> targetFolderName = AttributeValue.of("Spam");
+        AttributeName name1 = AttributeName.of("DeliveryPaths_recipient1@localhost");
+        AttributeName name2 = AttributeName.of("DeliveryPaths_recipient2@localhost");
+        AttributeValue<Collection<AttributeValue<?>>> targetFolderName = AttributeValue.of(ImmutableList.of(AttributeValue.of("Spam")));
         Attribute attribute1 = new Attribute(name1, targetFolderName);
         Attribute attribute2 = new Attribute(name2, targetFolderName);
         testee.init(FakeMailetConfig.builder()
-            .setProperty(WithStorageDirective.TARGET_FOLDER_NAME, targetFolderName.value())
+            .setProperty(WithStorageDirective.TARGET_FOLDER_NAMES, "Spam")
             .build());
 
         FakeMail mail = FakeMail.builder()
@@ -215,12 +224,8 @@ class WithStorageDirectiveTest {
 
         testee.service(mail);
 
-        SoftAssertions.assertSoftly(softly -> {
-            softly.assertThat(mail.attributes())
-                .containsExactlyInAnyOrder(attribute1, attribute2);
-            softly.assertThat(mail.getAttribute(name1)).contains(attribute1);
-            softly.assertThat(mail.getAttribute(name2)).contains(attribute2);
-        });
+        assertThat(mail.attributes().map(this::unbox))
+            .containsExactlyInAnyOrder(Pair.of(name1, "Spam"), Pair.of(name2, "Spam"));
     }
 
 }
\ No newline at end of file
diff --git a/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/delivery/SieveIntegrationTest.java b/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/delivery/SieveIntegrationTest.java
index c2eeffbb95..92e525aa80 100644
--- a/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/delivery/SieveIntegrationTest.java
+++ b/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/delivery/SieveIntegrationTest.java
@@ -25,10 +25,13 @@ import static org.mockito.Mockito.when;
 
 import java.io.IOException;
 import java.time.ZonedDateTime;
+import java.util.Collection;
+import java.util.Optional;
 
 import javax.mail.MessagingException;
 import javax.mail.internet.MimeBodyPart;
 
+import org.apache.commons.lang3.tuple.Pair;
 import org.apache.james.core.MailAddress;
 import org.apache.james.core.Username;
 import org.apache.james.core.builder.MimeMessageBuilder;
@@ -51,6 +54,8 @@ import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.slf4j.Logger;
 
+import com.google.common.collect.ImmutableList;
+
 class SieveIntegrationTest {
 
     private static final String LOCAL_PART = "receiver";
@@ -66,19 +71,40 @@ class SieveIntegrationTest {
     private static final MailboxPath INBOX = MailboxPath.inbox(LOCAL_USER);
     private static final MailboxPath INBOX_ANY = MailboxPath.forUser(LOCAL_USER, "INBOX.any");
 
-    private static final AttributeName ATTRIBUTE_NAME = AttributeName.of("DeliveryPath_" + LOCAL_PART);
-    private static final Attribute ATTRIBUTE_INBOX = new Attribute(ATTRIBUTE_NAME, AttributeValue.of(expressMailboxNameWithSlash(INBOX.getName())));
-    private static final Attribute ATTRIBUTE_INBOX_ANY = new Attribute(ATTRIBUTE_NAME, AttributeValue.of(expressMailboxNameWithSlash(INBOX_ANY.getName())));
-    private static final Attribute ATTRIBUTE_SELECTED_MAILBOX = new Attribute(ATTRIBUTE_NAME, AttributeValue.of(expressMailboxNameWithSlash(SELECTED_MAILBOX.getName())));
-    private static final Attribute ATTRIBUTE_NOT_SELECTED_MAILBOX = new Attribute(ATTRIBUTE_NAME, AttributeValue.of(expressMailboxNameWithSlash(NOT_SELECTED_MAILBOX.getName())));
-    private static final AttributeName ATTRIBUTE_NAME_DOMAIN = AttributeName.of("DeliveryPath_" + RECEIVER_DOMAIN_COM);
-    private static final Attribute ATTRIBUTE_INBOX_DOMAIN = new Attribute(ATTRIBUTE_NAME_DOMAIN, AttributeValue.of(expressMailboxNameWithSlash(INBOX.getName())));
+    private static final AttributeName ATTRIBUTE_NAME = AttributeName.of("DeliveryPaths_" + LOCAL_PART);
+    private static final Attribute ATTRIBUTE_INBOX = new Attribute(ATTRIBUTE_NAME, AttributeValue.of(ImmutableList.of(AttributeValue.of(expressMailboxNameWithSlash(INBOX.getName())))));
+    private static final Attribute ATTRIBUTE_INBOX_ANY = new Attribute(ATTRIBUTE_NAME, AttributeValue.of(ImmutableList.of(AttributeValue.of(expressMailboxNameWithSlash(INBOX_ANY.getName())))));
+    private static final Attribute ATTRIBUTE_SELECTED_MAILBOX = new Attribute(ATTRIBUTE_NAME, AttributeValue.of(ImmutableList.of(AttributeValue.of(expressMailboxNameWithSlash(SELECTED_MAILBOX.getName())))));
+    private static final Attribute ATTRIBUTE_NOT_SELECTED_MAILBOX = new Attribute(ATTRIBUTE_NAME, AttributeValue.of(ImmutableList.of(AttributeValue.of(expressMailboxNameWithSlash(NOT_SELECTED_MAILBOX.getName())))));
+    private static final AttributeName ATTRIBUTE_NAME_DOMAIN = AttributeName.of("DeliveryPaths_" + RECEIVER_DOMAIN_COM);
+    private static final Attribute ATTRIBUTE_INBOX_DOMAIN = new Attribute(ATTRIBUTE_NAME_DOMAIN, AttributeValue.of(ImmutableList.of(AttributeValue.of(expressMailboxNameWithSlash(INBOX.getName())))));
 
     private Sieve testee;
     private UsersRepository usersRepository;
     private ResourceLocator resourceLocator;
     private FakeMailContext fakeMailContext;
 
+    @FunctionalInterface
+    interface AttributeEquals {
+        void isEqualTo(Attribute other);
+    }
+
+    public static AttributeEquals assertThatAttribute(Attribute attribute) {
+        return other -> {
+            assertThat(attribute.getName()).isEqualTo(other.getName());
+            assertThat(unbox(attribute)).isEqualTo(unbox(other));
+        };
+    }
+
+    public static AttributeEquals assertThatAttribute(Optional<Attribute> attribute) {
+        return assertThatAttribute(attribute.get());
+    }
+
+    static Pair<AttributeName, String> unbox(Attribute attribute) {
+        Collection<AttributeValue> collection = (Collection<AttributeValue>) attribute.getValue().getValue();
+        return Pair.of(attribute.getName(), (String) collection.stream().findFirst().get().getValue());
+    }
+
     @BeforeEach
     void setUp() throws Exception {
         resourceLocator = mock(ResourceLocator.class);
@@ -111,7 +137,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMail();
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME_DOMAIN)).contains(ATTRIBUTE_INBOX_DOMAIN);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME_DOMAIN)).isEqualTo(ATTRIBUTE_INBOX_DOMAIN);
     }
 
     @Test
@@ -123,7 +149,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMail();
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_INBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_INBOX);
     }
 
     @Test
@@ -133,7 +159,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMail();
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_INBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_INBOX);
     }
 
     @Test
@@ -153,7 +179,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMail();
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_INBOX_ANY);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_INBOX_ANY);
     }
 
     @Test
@@ -163,7 +189,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMail();
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_NOT_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_NOT_SELECTED_MAILBOX);
     }
 
     @Test
@@ -173,7 +199,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMail();
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_NOT_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_NOT_SELECTED_MAILBOX);
     }
 
     @Test
@@ -183,7 +209,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMail();
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_SELECTED_MAILBOX);
     }
 
     @Test
@@ -193,7 +219,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMail();
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_NOT_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_NOT_SELECTED_MAILBOX);
     }
 
     @Test
@@ -203,7 +229,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMail();
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_NOT_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_NOT_SELECTED_MAILBOX);
     }
 
     @Test
@@ -213,7 +239,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMail();
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_SELECTED_MAILBOX);
     }
 
     @Test
@@ -223,7 +249,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMail();
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_SELECTED_MAILBOX);
     }
 
     @Test
@@ -233,7 +259,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMail();
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_NOT_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_NOT_SELECTED_MAILBOX);
     }
 
     @Test
@@ -243,7 +269,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMail();
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_SELECTED_MAILBOX);
     }
 
     @Test
@@ -253,7 +279,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMail();
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_SELECTED_MAILBOX);
     }
 
     @Test
@@ -263,7 +289,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMail();
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_INBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_INBOX);
     }
 
     @Test
@@ -273,7 +299,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMail();
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_NOT_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_NOT_SELECTED_MAILBOX);
     }
 
     @Test
@@ -290,8 +316,8 @@ class SieveIntegrationTest {
             .build();
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME))
-            .contains(ATTRIBUTE_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME))
+            .isEqualTo(ATTRIBUTE_SELECTED_MAILBOX);
     }
 
 
@@ -302,7 +328,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMailWithSubject("JAMES-1620 revolution");
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_SELECTED_MAILBOX);
     }
 
     @Test
@@ -313,7 +339,7 @@ class SieveIntegrationTest {
         mail.setMessageSize(100);
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_NOT_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_NOT_SELECTED_MAILBOX);
     }
 
     @Test
@@ -324,7 +350,7 @@ class SieveIntegrationTest {
         mail.setMessageSize(100);
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_SELECTED_MAILBOX);
     }
 
     @Test
@@ -335,7 +361,7 @@ class SieveIntegrationTest {
         mail.setMessageSize(1000);
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_SELECTED_MAILBOX);
     }
 
     @Test
@@ -346,7 +372,7 @@ class SieveIntegrationTest {
         mail.setMessageSize(1000);
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_NOT_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_NOT_SELECTED_MAILBOX);
     }
 
     @Test
@@ -356,7 +382,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMailWithSubjectAndHeaders("Default", new Header("Cc", "source@any.com"));
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_SELECTED_MAILBOX);
     }
 
 
@@ -367,7 +393,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMailWithSubjectAndHeaders("Default", new Header("Cc", "source1@domain.com"));
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_NOT_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_NOT_SELECTED_MAILBOX);
     }
 
 
@@ -378,7 +404,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMailWithSubjectAndHeaders("Default", new Header("Cc", "source1@domain.com"));
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_SELECTED_MAILBOX);
     }
 
     @Test
@@ -388,7 +414,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMailWithSubjectAndHeaders("Default", new Header("Cc", "source@domain.org"));
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_NOT_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_NOT_SELECTED_MAILBOX);
     }
 
     @Test
@@ -398,7 +424,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMailWithSubjectAndHeaders("Default", new Header("Cc", "source@domain.com"));
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_NOT_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_NOT_SELECTED_MAILBOX);
     }
 
     @Test
@@ -408,7 +434,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMailWithSubjectAndHeaders("Default", new Header("Bcc", "source@domain.com"));
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_SELECTED_MAILBOX);
     }
 
     @Test
@@ -418,7 +444,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMailWithSubjectAndHeaders("Default", new Header("Bcc", "source2@domain.com"));
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_NOT_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_NOT_SELECTED_MAILBOX);
     }
 
     @Test
@@ -428,7 +454,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMailWithSubjectAndHeaders("Default", new Header("Bcc", "source@domain.org"));
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_NOT_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_NOT_SELECTED_MAILBOX);
     }
 
     @Test
@@ -455,7 +481,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMailWithSubjectAndHeaders("Default", new Header("Resend-From", "source@domain.com"));
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_NOT_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_NOT_SELECTED_MAILBOX);
     }
 
     @Test
@@ -465,7 +491,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMailWithSubjectAndHeaders("Default", new Header("Cc", "source@domain.com"));
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_SELECTED_MAILBOX);
     }
 
     @Test
@@ -475,7 +501,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMailWithSubjectAndHeaders("Default", new Header("Cc", "source2@domain.com"));
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_NOT_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_NOT_SELECTED_MAILBOX);
     }
 
     @Test
@@ -485,7 +511,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMailWithSubjectAndHeaders("Default", new Header("Cc", "source@domain.org"));
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_NOT_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_NOT_SELECTED_MAILBOX);
     }
 
     @Test
@@ -495,7 +521,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMailWithSubjectAndHeaders("Default", new Header("Cc", "source@domain.com"));
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_NOT_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_NOT_SELECTED_MAILBOX);
     }
 
     @Test
@@ -505,7 +531,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMailWithSubjectAndHeaders("Default", new Header("From", "source@domain.com"));
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_SELECTED_MAILBOX);
     }
 
     @Test
@@ -515,7 +541,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMailWithSubjectAndHeaders("Default", new Header("From", "source2@domain.com"));
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_NOT_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_NOT_SELECTED_MAILBOX);
     }
 
     @Test
@@ -525,7 +551,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMailWithSubjectAndHeaders("Default", new Header("From", "source@domain.org"));
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_NOT_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_NOT_SELECTED_MAILBOX);
     }
 
     @Test
@@ -535,7 +561,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMailWithSubjectAndHeaders("Default", new Header("Resent-To", "source@domain.com"));
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_NOT_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_NOT_SELECTED_MAILBOX);
     }
 
     @Test
@@ -545,7 +571,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMailWithSubjectAndHeaders("Default", new Header("To", "source@domain.com"));
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_SELECTED_MAILBOX);
     }
 
     @Test
@@ -555,7 +581,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMailWithSubjectAndHeaders("Default", new Header("To", "source2@domain.com"));
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_NOT_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_NOT_SELECTED_MAILBOX);
     }
 
     @Test
@@ -565,7 +591,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMailWithSubjectAndHeaders("Default", new Header("To", "source@domain.org"));
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_NOT_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_NOT_SELECTED_MAILBOX);
     }
 
     @Test
@@ -575,7 +601,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMailWithSubjectAndHeaders("Default", new Header("To", "source@domain.com"));
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_NOT_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_NOT_SELECTED_MAILBOX);
     }
 
     @Test
@@ -585,7 +611,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMailWithSubjectAndHeaders("Default", new Header("Sender", "source@domain.com"));
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_SELECTED_MAILBOX);
     }
 
     @Test
@@ -595,7 +621,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMailWithSubjectAndHeaders("Default", new Header("Sender", "source2@domain.com"));
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_NOT_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_NOT_SELECTED_MAILBOX);
     }
 
     @Test
@@ -605,7 +631,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMailWithSubjectAndHeaders("Default", new Header("Sender", "source@domain.org"));
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_NOT_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_NOT_SELECTED_MAILBOX);
     }
 
     @Test
@@ -615,7 +641,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMailWithSubjectAndHeaders("Default", new Header("From", "source@domain.com"));
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_NOT_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_NOT_SELECTED_MAILBOX);
     }
 
     @Test
@@ -625,7 +651,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMailWithSubjectAndHeaders("Default", new Header("Resend-From", "source@domain.com"));
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_SELECTED_MAILBOX);
     }
 
     @Test
@@ -635,7 +661,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMailWithSubjectAndHeaders("Default", new Header("Resend-From", "source2@domain.com"));
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_NOT_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_NOT_SELECTED_MAILBOX);
     }
 
     @Test
@@ -645,7 +671,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMailWithSubjectAndHeaders("Default", new Header("Resend-From", "source@domain.org"));
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_NOT_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_NOT_SELECTED_MAILBOX);
     }
 
     @Test
@@ -655,7 +681,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMailWithSubjectAndHeaders("Default", new Header("To", "source@domain.com"));
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_NOT_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_NOT_SELECTED_MAILBOX);
     }
 
     @Test
@@ -665,7 +691,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMailWithSubjectAndHeaders("Default", new Header("Resend-To", "source@domain.com"));
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_SELECTED_MAILBOX);
     }
 
     @Test
@@ -675,7 +701,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMailWithSubjectAndHeaders("Default", new Header("Resend-To", "source2@domain.com"));
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_NOT_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_NOT_SELECTED_MAILBOX);
     }
 
     @Test
@@ -685,7 +711,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMailWithSubjectAndHeaders("Default", new Header("Resend-To", "source@domain.org"));
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_NOT_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_NOT_SELECTED_MAILBOX);
     }
 
     @Test
@@ -695,7 +721,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMailWithSubjectAndHeaders("Default", new Header("From", "source@domain.com"));
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_SELECTED_MAILBOX);
     }
 
     @Test
@@ -705,7 +731,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMail();
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_NOT_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_NOT_SELECTED_MAILBOX);
     }
 
     @Test
@@ -715,7 +741,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMail();
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_SELECTED_MAILBOX);
     }
 
     @Test
@@ -725,7 +751,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMail();
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_NOT_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_NOT_SELECTED_MAILBOX);
     }
 
     @Test
@@ -735,7 +761,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMail();
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_NOT_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_NOT_SELECTED_MAILBOX);
     }
 
     @Test
@@ -745,7 +771,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMail();
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_SELECTED_MAILBOX);
     }
 
     @Test
@@ -755,7 +781,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMail();
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_SELECTED_MAILBOX);
     }
 
     @Test
@@ -765,7 +791,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMail();
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_NOT_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_NOT_SELECTED_MAILBOX);
     }
 
     @Test
@@ -775,7 +801,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMail();
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_NOT_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_NOT_SELECTED_MAILBOX);
     }
 
     @Test
@@ -785,7 +811,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMail();
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_NOT_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_NOT_SELECTED_MAILBOX);
     }
 
     @Test
@@ -795,7 +821,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMail();
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_SELECTED_MAILBOX);
     }
 
     @Test
@@ -823,7 +849,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMail();
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_INBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_INBOX);
 
         FakeMailContext.SentMail expectedSentMail = FakeMailContext.sentMailBuilder()
             .sender(new MailAddress(RECEIVER_DOMAIN_COM))
@@ -841,7 +867,7 @@ class SieveIntegrationTest {
 
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_INBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_INBOX);
         assertThat(fakeMailContext.getSentMails()).isEmpty();
     }
 
@@ -852,7 +878,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMail();
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_INBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_INBOX);
         assertThat(fakeMailContext.getSentMails()).isEmpty();
     }
 
@@ -863,7 +889,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMail();
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_SELECTED_MAILBOX);
         assertThat(fakeMailContext.getSentMails()).isEmpty();
     }
 
@@ -874,7 +900,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMail();
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_INBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_INBOX);
         assertThat(fakeMailContext.getSentMails()).isEmpty();
     }
 
@@ -885,7 +911,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMail();
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_INBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_INBOX);
         FakeMailContext.SentMail expectedSentMail = FakeMailContext.sentMailBuilder()
             .sender(new MailAddress(RECEIVER_DOMAIN_COM))
             .recipient(new MailAddress("sender@any.com"))
@@ -901,7 +927,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMail();
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_SELECTED_MAILBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_SELECTED_MAILBOX);
         FakeMailContext.SentMail expectedSentMail = FakeMailContext.sentMailBuilder()
             .sender(new MailAddress(RECEIVER_DOMAIN_COM))
             .recipient(new MailAddress("sender@any.com"))
@@ -917,7 +943,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMail();
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_INBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_INBOX);
         FakeMailContext.SentMail expectedSentMail = FakeMailContext.sentMailBuilder()
             .sender(new MailAddress("benwa@apache.org"))
             .recipient(new MailAddress("sender@any.com"))
@@ -933,7 +959,7 @@ class SieveIntegrationTest {
         FakeMail mail = createMail();
         testee.service(mail);
 
-        assertThat(mail.getAttribute(ATTRIBUTE_NAME)).contains(ATTRIBUTE_INBOX);
+        assertThatAttribute(mail.getAttribute(ATTRIBUTE_NAME)).isEqualTo(ATTRIBUTE_INBOX);
         FakeMailContext.SentMail expectedSentMail = FakeMailContext.sentMailBuilder()
             .sender(new MailAddress(RECEIVER_DOMAIN_COM))
             .recipient(new MailAddress("sender@any.com"))
diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/mailet/filter/ActionApplier.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/mailet/filter/ActionApplier.java
index 6f21a0a0cd..5dcbc2b432 100644
--- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/mailet/filter/ActionApplier.java
+++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/mailet/filter/ActionApplier.java
@@ -41,7 +41,6 @@ import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableList;
 
 public class ActionApplier {
-    static final String DELIVERY_PATH_PREFIX = "DeliveryPath_";
     public static final Logger LOGGER = LoggerFactory.getLogger(ActionApplier.class);
 
     @VisibleForTesting
@@ -102,13 +101,14 @@ public class ActionApplier {
                 .collect(ImmutableList.toImmutableList()));
             return;
         }
-        Optional<String> targetMailbox = action.getAppendInMailboxes().getMailboxIds()
+        Optional<ImmutableList<String>> targetMailboxes = Optional.of(action.getAppendInMailboxes().getMailboxIds()
             .stream()
             .flatMap(this::asMailboxName)
-            .reduce((first, second) -> second);
+            .collect(ImmutableList.toImmutableList()))
+            .filter(mailboxes -> !mailboxes.isEmpty());
 
         StorageDirective.Builder storageDirective = StorageDirective.builder();
-        targetMailbox.ifPresent(storageDirective::targetFolder);
+        targetMailboxes.ifPresent(storageDirective::targetFolders);
         storageDirective
             .seen(Optional.of(action.isMarkAsSeen()).filter(seen -> seen))
             .important(Optional.of(action.isMarkAsImportant()).filter(seen -> seen))
diff --git a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/mailet/filter/JMAPFilteringTest.java b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/mailet/filter/JMAPFilteringTest.java
index 86d60a78b3..a4c1390aa4 100644
--- a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/mailet/filter/JMAPFilteringTest.java
+++ b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/mailet/filter/JMAPFilteringTest.java
@@ -29,13 +29,11 @@ import static org.apache.james.jmap.api.filtering.Rule.Condition.Field.FROM;
 import static org.apache.james.jmap.api.filtering.Rule.Condition.Field.RECIPIENT;
 import static org.apache.james.jmap.api.filtering.Rule.Condition.Field.SUBJECT;
 import static org.apache.james.jmap.api.filtering.Rule.Condition.Field.TO;
-import static org.apache.james.jmap.mailet.filter.ActionApplier.DELIVERY_PATH_PREFIX;
 import static org.apache.james.jmap.mailet.filter.JMAPFilteringFixture.BOU;
 import static org.apache.james.jmap.mailet.filter.JMAPFilteringFixture.EMPTY;
 import static org.apache.james.jmap.mailet.filter.JMAPFilteringFixture.FRED_MARTIN_FULLNAME;
 import static org.apache.james.jmap.mailet.filter.JMAPFilteringFixture.FRED_MARTIN_FULL_SCRAMBLED_ADDRESS;
 import static org.apache.james.jmap.mailet.filter.JMAPFilteringFixture.GA_BOU_ZO_MEU_FULL_ADDRESS;
-import static org.apache.james.jmap.mailet.filter.JMAPFilteringFixture.RECIPIENT_1;
 import static org.apache.james.jmap.mailet.filter.JMAPFilteringFixture.RECIPIENT_1_MAILBOX_1;
 import static org.apache.james.jmap.mailet.filter.JMAPFilteringFixture.RECIPIENT_1_USERNAME;
 import static org.apache.james.jmap.mailet.filter.JMAPFilteringFixture.SCRAMBLED_SUBJECT;
@@ -54,12 +52,14 @@ 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.util.Collection;
 import java.util.Locale;
 import java.util.Optional;
 import java.util.stream.Stream;
 
 import javax.mail.Flags;
 
+import org.apache.commons.lang3.tuple.Pair;
 import org.apache.james.core.Username;
 import org.apache.james.core.builder.MimeMessageBuilder;
 import org.apache.james.jmap.api.filtering.Rule;
@@ -83,14 +83,15 @@ import org.junit.jupiter.params.provider.MethodSource;
 import com.github.fge.lambdas.Throwing;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 
 import reactor.core.publisher.Mono;
 
 @ExtendWith(JMAPFilteringExtension.class)
 class JMAPFilteringTest {
 
-    private static final AttributeName RECIPIENT_1_USERNAME_ATTRIBUTE_NAME = AttributeName.of(DELIVERY_PATH_PREFIX + RECIPIENT_1_USERNAME.asString());
-    private static final Attribute RECIPIENT_1_MAILBOX_1_ATTRIBUTE = new Attribute(RECIPIENT_1_USERNAME_ATTRIBUTE_NAME, RECIPIENT_1_MAILBOX_1);
+    private static final AttributeName RECIPIENT_1_USERNAME_ATTRIBUTE_NAME = AttributeName.of("DeliveryPaths_" + RECIPIENT_1_USERNAME.asString());
+    private static final Attribute RECIPIENT_1_MAILBOX_1_ATTRIBUTE = new Attribute(RECIPIENT_1_USERNAME_ATTRIBUTE_NAME, AttributeValue.of(ImmutableList.of(RECIPIENT_1_MAILBOX_1)));
 
     static class FilteringArgumentBuilder {
         private Optional<String> description;
@@ -221,6 +222,27 @@ class JMAPFilteringTest {
         }
     }
 
+    @FunctionalInterface
+    interface AttributeEquals {
+        void isEqualTo(Attribute other);
+    }
+
+    public static AttributeEquals assertThatAttribute(Attribute attribute) {
+        return other -> {
+            assertThat(attribute.getName()).isEqualTo(other.getName());
+            assertThat(unbox(attribute)).isEqualTo(unbox(other));
+        };
+    }
+
+    public static AttributeEquals assertThatAttribute(Optional<Attribute> attribute) {
+        return assertThatAttribute(attribute.get());
+    }
+
+    static Pair<AttributeName, String> unbox(Attribute attribute) {
+        Collection<AttributeValue> collection = (Collection<AttributeValue>) attribute.getValue().getValue();
+        return Pair.of(attribute.getName(), (String) collection.stream().findFirst().get().getValue());
+    }
+
     public static final ImmutableList<FieldAndHeader> ADDRESS_TESTING_COMBINATION = ImmutableList.of(
         new FieldAndHeader(Field.FROM, RFC2822Headers.FROM),
         new FieldAndHeader(Field.TO, RFC2822Headers.TO),
@@ -576,8 +598,8 @@ class JMAPFilteringTest {
         FakeMail mail = testSystem.asMail(mimeMessageBuilder);
         testSystem.getJmapFiltering().service(mail);
 
-        assertThat(mail.getAttribute(RECIPIENT_1_USERNAME_ATTRIBUTE_NAME))
-                .contains(RECIPIENT_1_MAILBOX_1_ATTRIBUTE);
+        assertThatAttribute(mail.getAttribute(RECIPIENT_1_USERNAME_ATTRIBUTE_NAME))
+                .isEqualTo(RECIPIENT_1_MAILBOX_1_ATTRIBUTE);
     }
 
     @ParameterizedTest(name = "CONTAINS should not match for field {1}: {0}")
@@ -607,8 +629,8 @@ class JMAPFilteringTest {
         FakeMail mail = testSystem.asMail(mimeMessageBuilder);
         testSystem.getJmapFiltering().service(mail);
 
-        assertThat(mail.getAttribute(RECIPIENT_1_USERNAME_ATTRIBUTE_NAME))
-            .contains(RECIPIENT_1_MAILBOX_1_ATTRIBUTE);
+        assertThatAttribute(mail.getAttribute(RECIPIENT_1_USERNAME_ATTRIBUTE_NAME))
+            .isEqualTo(RECIPIENT_1_MAILBOX_1_ATTRIBUTE);
     }
 
 
@@ -640,8 +662,8 @@ class JMAPFilteringTest {
         FakeMail mail = testSystem.asMail(mimeMessageBuilder);
         testSystem.getJmapFiltering().service(mail);
 
-        assertThat(mail.getAttribute(RECIPIENT_1_USERNAME_ATTRIBUTE_NAME))
-            .contains(RECIPIENT_1_MAILBOX_1_ATTRIBUTE);
+        assertThatAttribute(mail.getAttribute(RECIPIENT_1_USERNAME_ATTRIBUTE_NAME))
+            .isEqualTo(RECIPIENT_1_MAILBOX_1_ATTRIBUTE);
     }
 
     @ParameterizedTest(name = "EXACTLY-EQUALS should not match for field {1}: {0}")
@@ -671,8 +693,8 @@ class JMAPFilteringTest {
         FakeMail mail = testSystem.asMail(mimeMessageBuilder);
         testSystem.getJmapFiltering().service(mail);
 
-        assertThat(mail.getAttribute(RECIPIENT_1_USERNAME_ATTRIBUTE_NAME))
-            .contains(RECIPIENT_1_MAILBOX_1_ATTRIBUTE);
+        assertThatAttribute(mail.getAttribute(RECIPIENT_1_USERNAME_ATTRIBUTE_NAME))
+            .isEqualTo(RECIPIENT_1_MAILBOX_1_ATTRIBUTE);
     }
 
     @ParameterizedTest(name = "NOT_EXACTLY_EQUALS should not match for field {1}: {0}")
@@ -726,8 +748,8 @@ class JMAPFilteringTest {
 
             testSystem.getJmapFiltering().service(mail);
 
-            assertThat(mail.getAttribute(RECIPIENT_1_USERNAME_ATTRIBUTE_NAME))
-                .contains(new Attribute(RECIPIENT_1_USERNAME_ATTRIBUTE_NAME, AttributeValue.of("RECIPIENT_1_MAILBOX_3")));
+            assertThatAttribute(mail.getAttribute(RECIPIENT_1_USERNAME_ATTRIBUTE_NAME))
+                .isEqualTo(new Attribute(RECIPIENT_1_USERNAME_ATTRIBUTE_NAME, AttributeValue.of(ImmutableList.of(AttributeValue.of("RECIPIENT_1_MAILBOX_3")))));
         }
 
         @Test
@@ -753,8 +775,10 @@ class JMAPFilteringTest {
 
             testSystem.getJmapFiltering().service(mail);
 
-            assertThat(mail.getAttribute(RECIPIENT_1_USERNAME_ATTRIBUTE_NAME))
-                .contains(new Attribute(RECIPIENT_1_USERNAME_ATTRIBUTE_NAME, AttributeValue.of("RECIPIENT_1_MAILBOX_1")));
+            assertThat((ImmutableSet) mail.getAttribute(RECIPIENT_1_USERNAME_ATTRIBUTE_NAME).get().getValue().value())
+                .containsOnly(AttributeValue.of("RECIPIENT_1_MAILBOX_3"),
+                    AttributeValue.of("RECIPIENT_1_MAILBOX_2"),
+                    AttributeValue.of("RECIPIENT_1_MAILBOX_1"));
         }
 
         @Test
@@ -782,8 +806,8 @@ class JMAPFilteringTest {
 
             testSystem.getJmapFiltering().service(mail);
 
-            assertThat(mail.getAttribute(RECIPIENT_1_USERNAME_ATTRIBUTE_NAME))
-                .contains(new Attribute(RECIPIENT_1_USERNAME_ATTRIBUTE_NAME, AttributeValue.of("RECIPIENT_1_MAILBOX_1")));
+            assertThatAttribute(mail.getAttribute(RECIPIENT_1_USERNAME_ATTRIBUTE_NAME))
+                .isEqualTo(new Attribute(RECIPIENT_1_USERNAME_ATTRIBUTE_NAME, AttributeValue.of(ImmutableList.of(AttributeValue.of("RECIPIENT_1_MAILBOX_1")))));
         }
 
         @Test
@@ -892,8 +916,8 @@ class JMAPFilteringTest {
 
             testSystem.getJmapFiltering().service(mail);
 
-            assertThat(mail.getAttribute(RECIPIENT_1_USERNAME_ATTRIBUTE_NAME))
-                .contains(RECIPIENT_1_MAILBOX_1_ATTRIBUTE);
+            assertThatAttribute(mail.getAttribute(RECIPIENT_1_USERNAME_ATTRIBUTE_NAME))
+                .isEqualTo(RECIPIENT_1_MAILBOX_1_ATTRIBUTE);
         }
 
         @Test
@@ -916,8 +940,8 @@ class JMAPFilteringTest {
 
             testSystem.getJmapFiltering().service(mail);
 
-            assertThat(mail.getAttribute(RECIPIENT_1_USERNAME_ATTRIBUTE_NAME))
-                .contains(RECIPIENT_1_MAILBOX_1_ATTRIBUTE);
+            assertThatAttribute(mail.getAttribute(RECIPIENT_1_USERNAME_ATTRIBUTE_NAME))
+                .isEqualTo(RECIPIENT_1_MAILBOX_1_ATTRIBUTE);
         }
     }
 


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org