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 99a8eac04ff211f27be5f299bf051062f3af1d0b
Author: Quan Tran <[email protected]>
AuthorDate: Tue Sep 30 17:07:06 2025 +0700

    JAMES-4148 Webadmin rules triage task should support moveTo action
---
 .../data/jmap/RunRulesOnMailboxService.java        |  33 ++-
 .../data/jmap/RunRulesOnMailboxRoutesTest.java     | 303 +++++++++++++++++++++
 2 files changed, 327 insertions(+), 9 deletions(-)

diff --git 
a/server/protocols/webadmin/webadmin-jmap/src/main/java/org/apache/james/webadmin/data/jmap/RunRulesOnMailboxService.java
 
b/server/protocols/webadmin/webadmin-jmap/src/main/java/org/apache/james/webadmin/data/jmap/RunRulesOnMailboxService.java
index 0a5190dd03..1a64ccfeb3 100644
--- 
a/server/protocols/webadmin/webadmin-jmap/src/main/java/org/apache/james/webadmin/data/jmap/RunRulesOnMailboxService.java
+++ 
b/server/protocols/webadmin/webadmin-jmap/src/main/java/org/apache/james/webadmin/data/jmap/RunRulesOnMailboxService.java
@@ -33,6 +33,7 @@ import org.apache.james.jmap.mailet.filter.RuleMatcher;
 import org.apache.james.mailbox.MailboxManager;
 import org.apache.james.mailbox.MailboxSession;
 import org.apache.james.mailbox.MessageIdManager;
+import org.apache.james.mailbox.MessageManager;
 import org.apache.james.mailbox.exception.MailboxException;
 import org.apache.james.mailbox.model.FetchGroup;
 import org.apache.james.mailbox.model.MailboxId;
@@ -46,6 +47,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.github.fge.lambdas.Throwing;
+import com.google.common.collect.ImmutableList;
 
 import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
@@ -103,24 +105,37 @@ public class RunRulesOnMailboxService {
             throw new NotImplementedException("Only action on moving messages 
is supported for now");
         }
 
-        if (action.getAppendInMailboxes().getMailboxIds().isEmpty()) {
+        if (action.getAppendInMailboxes().getMailboxIds().isEmpty() && 
action.getMoveTo().isEmpty()) {
             throw new IllegalArgumentException("Move action should not be 
empty");
         }
     }
 
     private Mono<Task.Result> appendInMailboxes(MessageId messageId, 
Rule.Action action, MailboxSession mailboxSession, 
RunRulesOnMailboxTask.Context context) {
-        List<MailboxId> mailboxIds = action.getAppendInMailboxes()
+        ImmutableList.Builder<MailboxId> mailboxIdsBuilder = 
ImmutableList.builder();
+        List<MailboxId> appendInMailboxIds = action.getAppendInMailboxes()
             .getMailboxIds()
             .stream()
             .map(mailboxIdFactory::fromString)
             .toList();
+        mailboxIdsBuilder.addAll(appendInMailboxIds);
+
+        return Mono.justOrEmpty(action.getMoveTo())
+            .flatMap(moveTo -> getMailboxId(mailboxSession, 
MailboxPath.forUser(mailboxSession.getUser(), moveTo.getMailboxName())))
+            .map(moveToMailboxId -> 
mailboxIdsBuilder.add(moveToMailboxId).build())
+            .switchIfEmpty(Mono.fromCallable(mailboxIdsBuilder::build))
+            .flatMap(mailboxIds -> {
+                if (mailboxIds.isEmpty()) {
+                    return Mono.just(Task.Result.COMPLETED);
+                }
+
+                return 
Mono.from(messageIdManager.setInMailboxesReactive(messageId, mailboxIds, 
mailboxSession))
+                    .doOnSuccess(next -> context.incrementSuccesses())
+                    .then(Mono.just(Task.Result.COMPLETED));
+            });
+    }
 
-        if (mailboxIds.isEmpty()) {
-            return Mono.just(Task.Result.COMPLETED);
-        }
-
-        return Mono.from(messageIdManager.setInMailboxesReactive(messageId, 
mailboxIds, mailboxSession))
-            .doOnSuccess(next -> context.incrementSuccesses())
-            .then(Mono.just(Task.Result.COMPLETED));
+    private Mono<MailboxId> getMailboxId(MailboxSession mailboxSession, 
MailboxPath mailboxPath) {
+        return Mono.from(mailboxManager.getMailboxReactive(mailboxPath, 
mailboxSession))
+            .map(MessageManager::getId);
     }
 }
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 3b6d0626c3..2950d2b5f8 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
@@ -72,6 +72,7 @@ public class RunRulesOnMailboxRoutesTest {
     private static final Username USERNAME = Username.of("username");
     private static final String MAILBOX_NAME = "myMailboxName";
     private static final String OTHER_MAILBOX_NAME = "myOtherMailboxName";
+    private static final String MOVE_TO_MAILBOX_NAME = "moveToMailbox";
     private static final String ERROR_TYPE_NOTFOUND = "notFound";
     private static final String ERROR_TYPE_INVALIDARGUMENT = "InvalidArgument";
     private static final String RULE_PAYLOAD = """
@@ -311,6 +312,308 @@ public class RunRulesOnMailboxRoutesTest {
         );
     }
 
+    @Test
+    void runRulesOnMailboxShouldSupportMoveToMailboxNameWhenMatchingMessage() 
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()
+                    .build(Message.Builder.of()
+                        .setSubject("plop")
+                        .setFrom("[email protected]")
+                        .setBody("body", StandardCharsets.UTF_8)),
+                systemSession);
+
+        String taskId = given()
+            .queryParam("action", "triage")
+            .body("""
+                {
+                  "id": "1",
+                  "name": "rule 1",
+                  "action": {
+                    "appendIn": {
+                      "mailboxIds": []
+                    },
+                    "moveTo": {
+                      "mailboxName": "%s"
+                    },
+                    "important": false,
+                    "keyworkds": [],
+                    "reject": false,
+                    "seen": false
+                  },
+                  "conditionGroup": {
+                    "conditionCombiner": "OR",
+                    "conditions": [
+                      {
+                        "comparator": "contains",
+                        "field": "subject",
+                        "value": "plop"
+                      },
+                      {
+                        "comparator": "exactly-equals",
+                        "field": "from",
+                        "value": "[email protected]"
+                      }
+                    ]
+                  }
+                }"""
+                .formatted(OTHER_MAILBOX_NAME))
+            .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(0);
+                softly.assertThat(Throwing.supplier(() -> 
mailboxManager.getMailbox(otherMailboxPath, 
systemSession).getMailboxCounters(systemSession).getCount()).get())
+                    .isEqualTo(1);
+            }
+        );
+    }
+
+    @Test
+    void runRulesOnMailboxShouldNotMoveToMailboxNameWhenNonMatchingMessage() 
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()
+                    .build(Message.Builder.of()
+                        .setSubject("not match rules")
+                        .setFrom("[email protected]")
+                        .setBody("body", StandardCharsets.UTF_8)),
+                systemSession);
+
+        String taskId = given()
+            .queryParam("action", "triage")
+            .body("""
+                {
+                  "id": "1",
+                  "name": "rule 1",
+                  "action": {
+                    "appendIn": {
+                      "mailboxIds": []
+                    },
+                    "moveTo": {
+                      "mailboxName": "%s"
+                    },
+                    "important": false,
+                    "keyworkds": [],
+                    "reject": false,
+                    "seen": false
+                  },
+                  "conditionGroup": {
+                    "conditionCombiner": "OR",
+                    "conditions": [
+                      {
+                        "comparator": "contains",
+                        "field": "subject",
+                        "value": "plop"
+                      },
+                      {
+                        "comparator": "exactly-equals",
+                        "field": "from",
+                        "value": "[email protected]"
+                      }
+                    ]
+                  }
+                }"""
+                .formatted(OTHER_MAILBOX_NAME))
+            .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(0);
+            }
+        );
+    }
+
+    @Test
+    void bothMoveToAndAppendInMailboxesShouldWork() throws Exception {
+        MailboxPath mailboxPath = MailboxPath.forUser(USERNAME, MAILBOX_NAME);
+        MailboxPath appendIdMailboxPath = MailboxPath.forUser(USERNAME, 
OTHER_MAILBOX_NAME);
+        MailboxPath moveToMailboxPath = MailboxPath.forUser(USERNAME, 
MOVE_TO_MAILBOX_NAME);
+        MailboxSession systemSession = 
mailboxManager.createSystemSession(USERNAME);
+
+        mailboxManager.createMailbox(mailboxPath, systemSession);
+        mailboxManager.createMailbox(appendIdMailboxPath, systemSession);
+        mailboxManager.createMailbox(moveToMailboxPath, systemSession);
+        MailboxId appendIdMailboxId = 
mailboxManager.getMailbox(appendIdMailboxPath, systemSession).getId();
+
+        mailboxManager.getMailbox(mailboxPath, systemSession)
+            .appendMessage(MessageManager.AppendCommand.builder()
+                    .build(Message.Builder.of()
+                        .setSubject("plop")
+                        .setFrom("[email protected]")
+                        .setBody("body", StandardCharsets.UTF_8)),
+                systemSession);
+
+        String taskId = given()
+            .queryParam("action", "triage")
+            .body("""
+                {
+                  "id": "1",
+                  "name": "rule 1",
+                  "action": {
+                    "appendIn": {
+                      "mailboxIds": ["%s"]
+                    },
+                    "moveTo": {
+                      "mailboxName": "%s"
+                    },
+                    "important": false,
+                    "keyworkds": [],
+                    "reject": false,
+                    "seen": false
+                  },
+                  "conditionGroup": {
+                    "conditionCombiner": "OR",
+                    "conditions": [
+                      {
+                        "comparator": "contains",
+                        "field": "subject",
+                        "value": "plop"
+                      },
+                      {
+                        "comparator": "exactly-equals",
+                        "field": "from",
+                        "value": "[email protected]"
+                      }
+                    ]
+                  }
+                }"""
+                .formatted(appendIdMailboxId.serialize(), 
MOVE_TO_MAILBOX_NAME))
+            .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(0);
+                softly.assertThat(Throwing.supplier(() -> 
mailboxManager.getMailbox(appendIdMailboxPath, 
systemSession).getMailboxCounters(systemSession).getCount()).get())
+                    .isEqualTo(1);
+                softly.assertThat(Throwing.supplier(() -> 
mailboxManager.getMailbox(moveToMailboxPath, 
systemSession).getMailboxCounters(systemSession).getCount()).get())
+                    .isEqualTo(1);
+            }
+        );
+    }
+
+    @Test
+    void 
bothMoveToAndAppendInMailboxesShouldNotDuplicateMessageWhenTheSameTargetMailbox()
 throws Exception {
+        MailboxPath mailboxPath = MailboxPath.forUser(USERNAME, MAILBOX_NAME);
+        MailboxPath targetMailboxPath = MailboxPath.forUser(USERNAME, 
OTHER_MAILBOX_NAME);
+        MailboxSession systemSession = 
mailboxManager.createSystemSession(USERNAME);
+
+        mailboxManager.createMailbox(mailboxPath, systemSession);
+        mailboxManager.createMailbox(targetMailboxPath, systemSession);
+        MailboxId targetMailboxId = 
mailboxManager.getMailbox(targetMailboxPath, systemSession).getId();
+
+        mailboxManager.getMailbox(mailboxPath, systemSession)
+            .appendMessage(MessageManager.AppendCommand.builder()
+                    .build(Message.Builder.of()
+                        .setSubject("plop")
+                        .setFrom("[email protected]")
+                        .setBody("body", StandardCharsets.UTF_8)),
+                systemSession);
+
+        String taskId = given()
+            .queryParam("action", "triage")
+            .body("""
+                {
+                  "id": "1",
+                  "name": "rule 1",
+                  "action": {
+                    "appendIn": {
+                      "mailboxIds": ["%s"]
+                    },
+                    "moveTo": {
+                      "mailboxName": "%s"
+                    },
+                    "important": false,
+                    "keyworkds": [],
+                    "reject": false,
+                    "seen": false
+                  },
+                  "conditionGroup": {
+                    "conditionCombiner": "OR",
+                    "conditions": [
+                      {
+                        "comparator": "contains",
+                        "field": "subject",
+                        "value": "plop"
+                      },
+                      {
+                        "comparator": "exactly-equals",
+                        "field": "from",
+                        "value": "[email protected]"
+                      }
+                    ]
+                  }
+                }"""
+                .formatted(targetMailboxId.serialize(), OTHER_MAILBOX_NAME))
+            .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(0);
+                softly.assertThat(Throwing.supplier(() -> 
mailboxManager.getMailbox(targetMailboxPath, 
systemSession).getMailboxCounters(systemSession).getCount()).get())
+                    .isEqualTo(1);
+            }
+        );
+    }
+
     @Test
     void runRulesShouldApplyDateCrieria() 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