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

commit fd8f3728a9d1167dd62add7cd62cdcb95a6c4e44
Author: Quan Tran <[email protected]>
AuthorDate: Wed Mar 18 13:29:11 2026 +0700

    JAMES-4191 Deleted message vault: Add more share mailbox integration tests
---
 .../vault/DeletedMessageVaultIntegrationTest.java  | 162 ++++++++++++++++++++-
 1 file changed, 160 insertions(+), 2 deletions(-)

diff --git 
a/server/protocols/webadmin-integration-test/webadmin-integration-test-common/src/main/java/org/apache/james/webadmin/integration/vault/DeletedMessageVaultIntegrationTest.java
 
b/server/protocols/webadmin-integration-test/webadmin-integration-test-common/src/main/java/org/apache/james/webadmin/integration/vault/DeletedMessageVaultIntegrationTest.java
index 8a41961e9c..ebac0a2aef 100644
--- 
a/server/protocols/webadmin-integration-test/webadmin-integration-test-common/src/main/java/org/apache/james/webadmin/integration/vault/DeletedMessageVaultIntegrationTest.java
+++ 
b/server/protocols/webadmin-integration-test/webadmin-integration-test-common/src/main/java/org/apache/james/webadmin/integration/vault/DeletedMessageVaultIntegrationTest.java
@@ -56,13 +56,15 @@ import java.util.Map;
 
 import org.apache.james.GuiceJamesServer;
 import org.apache.james.GuiceModuleTestExtension;
+import org.apache.james.core.Username;
 import org.apache.james.jmap.JmapGuiceProbe;
 import org.apache.james.junit.categories.BasicFeature;
 import org.apache.james.mailbox.DefaultMailboxes;
+import org.apache.james.mailbox.MessageUid;
 import org.apache.james.mailbox.Role;
 import org.apache.james.mailbox.backup.ZipAssert;
 import org.apache.james.mailbox.model.MailboxId;
-import org.apache.james.mailbox.probe.MailboxProbe;
+import org.apache.james.mailbox.model.MailboxPath;
 import org.apache.james.modules.MailboxProbeImpl;
 import org.apache.james.modules.protocols.ImapGuiceProbe;
 import org.apache.james.probe.DataProbe;
@@ -128,6 +130,7 @@ public abstract class DeletedMessageVaultIntegrationTest {
     private static final ConditionFactory WAIT_TWO_MINUTES = 
calmlyAwait.atMost(TWO_MINUTES);
     private static final String SUBJECT = "This mail will be restored from the 
vault!!";
     private static final String MAILBOX_NAME = "toBeDeleted";
+    private static final String OWNER_ONLY_MAILBOX_NAME = "ownerOnly";
     private static final String MATCH_ALL_QUERY = "{" +
         "\"combinator\": \"and\"," +
         "\"criteria\": []" +
@@ -144,6 +147,8 @@ public abstract class DeletedMessageVaultIntegrationTest {
     private TestIMAPClient testIMAPClient;
     private RequestSpecification webAdminApi;
     private MailboxId otherMailboxId;
+    private MailboxId ownerOnlyMailboxId;
+    private MailboxProbeImpl mailboxProbe;
 
     private UserCredential homerCredential;
     private UserCredential bartCredential;
@@ -151,7 +156,7 @@ public abstract class DeletedMessageVaultIntegrationTest {
 
     @BeforeEach
     void setup(GuiceJamesServer jmapServer) throws Throwable {
-        MailboxProbe mailboxProbe = 
jmapServer.getProbe(MailboxProbeImpl.class);
+        mailboxProbe = jmapServer.getProbe(MailboxProbeImpl.class);
         DataProbe dataProbe = jmapServer.getProbe(DataProbeImpl.class);
 
         Port jmapPort = 
jmapServer.getProbe(JmapGuiceProbe.class).getJmapPort();
@@ -167,6 +172,7 @@ public abstract class DeletedMessageVaultIntegrationTest {
         dataProbe.addUser(JACK, PASSWORD);
         mailboxProbe.createMailbox("#private", HOMER, DefaultMailboxes.INBOX);
         otherMailboxId = mailboxProbe.createMailbox("#private", HOMER, 
MAILBOX_NAME);
+        ownerOnlyMailboxId = mailboxProbe.createMailbox("#private", HOMER, 
OWNER_ONLY_MAILBOX_NAME);
 
         homerCredential = getUserCredential(HOMER, PASSWORD);
         bartCredential = getUserCredential(BART, BOB_PASSWORD);
@@ -436,6 +442,7 @@ public abstract class DeletedMessageVaultIntegrationTest {
 
     @Test
     void vaultEndpointShouldNotRestoreMessageForSharee() {
+        // GIVEN a message in Homer's mailbox shared with Bart
         bartSendMessageToHomer();
         WAIT_TWO_MINUTES.untilAsserted(() -> 
assertThat(listMessageIdsForAccount(homerCredential)).hasSize(1));
         WAIT_TWO_MINUTES.untilAsserted(() -> 
assertThat(listMessageIdsForAccount(bartCredential)).hasSize(1));
@@ -445,9 +452,11 @@ public abstract class DeletedMessageVaultIntegrationTest {
 
         homerSharesHisMailboxWithBart();
 
+        // WHEN Bart deletes the shared message
         bartDeletesMessages(ImmutableList.of(messageId));
         WAIT_TWO_MINUTES.untilAsserted(() -> 
assertThat(listMessageIdsForAccount(homerCredential)).hasSize(0));
 
+        // THEN Bart should not restore anything from his own DMV
         restoreMessagesFor(BART);
         awaitSearchUpToDate();
 
@@ -458,6 +467,7 @@ public abstract class DeletedMessageVaultIntegrationTest {
 
     @Test
     void vaultEndpointShouldRestoreMessageForSharer() {
+        // GIVEN a message in Homer's mailbox shared with Bart
         bartSendMessageToHomer();
         WAIT_TWO_MINUTES.untilAsserted(() -> 
assertThat(listMessageIdsForAccount(homerCredential)).hasSize(1));
 
@@ -466,9 +476,11 @@ public abstract class DeletedMessageVaultIntegrationTest {
 
         homerSharesHisMailboxWithBart();
 
+        // WHEN Bart deletes the shared message
         bartDeletesMessages(ImmutableList.of(messageId));
         WAIT_TWO_MINUTES.untilAsserted(() -> 
assertThat(listMessageIdsForAccount(homerCredential)).hasSize(0));
 
+        // THEN Homer should be able to restore it from his DMV
         restoreAllMessagesOfHomer();
         WAIT_TWO_MINUTES.untilAsserted(() -> 
assertThat(listMessageIdsForAccount(homerCredential)).hasSize(1));
 
@@ -478,6 +490,112 @@ public abstract class DeletedMessageVaultIntegrationTest {
             
.getString("methodResponses[0][1].list[0].subject")).isEqualTo(SUBJECT);
     }
 
+    @Test
+    void 
vaultEndpointShouldRestoreMessageForOwnerWhenShareeCopiedSharedMessageToOwnMailbox()
 {
+        // GIVEN a message in Homer's mailbox shared with Bart
+        bartSendMessageToHomer();
+        WAIT_TWO_MINUTES.untilAsserted(() -> 
assertThat(listMessageIdsForAccount(homerCredential)).hasSize(1));
+
+        String messageId = listMessageIdsForAccount(homerCredential).get(0);
+        homerMovesTheMailInAnotherMailbox(messageId);
+        homerSharesHisMailboxWithBart();
+
+        bartCopiesSharedMessageToOwnInbox();
+
+        // WHEN Homer deletes the original shared-mailbox reference
+        homerDeletesMessages(ImmutableList.of(messageId));
+        WAIT_TWO_MINUTES.untilAsserted(() -> 
assertThat(listMessageIdsForAccount(homerCredential)).hasSize(0));
+
+        // THEN Homer should still get a DMV entry for the message he lost 
access to
+        restoreAllMessagesOfHomer();
+        WAIT_TWO_MINUTES.untilAsserted(() -> 
assertThat(listMessageIdsForAccount(homerCredential)).hasSize(1));
+
+        String restoredMessageId = getLatestMessageId(homerCredential, 
Role.RESTORED_MESSAGES);
+        assertThat(getMessageContent(homerCredential, restoredMessageId)
+            
.getString("methodResponses[0][1].list[0].subject")).isEqualTo(SUBJECT);
+    }
+
+    @Test
+    void 
vaultEndpointShouldNotRestoreMessageForOwnerWhenOwnerStillHasAnotherReference() 
{
+        // GIVEN a message in Homer's mailbox shared with Bart
+        bartSendMessageToHomer();
+        WAIT_TWO_MINUTES.untilAsserted(() -> 
assertThat(listMessageIdsForAccount(homerCredential)).hasSize(1));
+
+        String messageId = listMessageIdsForAccount(homerCredential).get(0);
+        homerMovesTheMailInAnotherMailbox(messageId);
+        homerSharesHisMailboxWithBart();
+
+        // Homer copies the shared message to his own mailbox
+        homerCopiesSharedMessageToOwnerOnlyMailbox();
+
+        // WHEN Bart deletes the shared message
+        bartDeletesMessages(ImmutableList.of(messageId));
+        WAIT_TWO_MINUTES.untilAsserted(() -> 
assertThat(listMessageIdsInMailbox(homerCredential, 
ownerOnlyMailboxId.serialize())).hasSize(1));
+
+        // THEN neither Homer nor Bart should restore anything from DMV
+        restoreAllMessagesOfHomer();
+        restoreMessagesFor(BART);
+        awaitSearchUpToDate();
+
+        assertThat(restoredMessagesCount(homerCredential)).isEqualTo(0);
+        assertThat(restoredMessagesCount(bartCredential)).isEqualTo(0);
+    }
+
+    @Test
+    void 
vaultEndpointShouldRestoreMailboxDeletionMessagesForOwnerAndNotForSharee(GuiceJamesServer
 jmapServer) throws Exception {
+        // GIVEN a message in Homer's mailbox shared with Bart
+        bartSendMessageToHomer();
+        WAIT_TWO_MINUTES.untilAsserted(() -> 
assertThat(listMessageIdsForAccount(homerCredential)).hasSize(1));
+
+        String messageId = listMessageIdsForAccount(homerCredential).get(0);
+        homerMovesTheMailInAnotherMailbox(messageId);
+        homerSharesHisMailboxWithBart();
+
+        // WHEN Homer deletes the shared mailbox
+        homerDeletesMailbox(jmapServer);
+        WAIT_TWO_MINUTES.untilAsserted(() -> 
assertThat(listMessageIdsForAccount(homerCredential)).hasSize(0));
+
+        // THEN Homer should be able to restore the message, but Bart should 
not
+        restoreAllMessagesOfHomer();
+        restoreMessagesFor(BART);
+        awaitSearchUpToDate();
+
+        assertThat(restoredMessagesCount(homerCredential)).isEqualTo(1);
+        assertThat(restoredMessagesCount(bartCredential)).isEqualTo(0);
+
+        String restoredMessageId = getLatestMessageId(homerCredential, 
Role.RESTORED_MESSAGES);
+        assertThat(getMessageContent(homerCredential, restoredMessageId)
+            
.getString("methodResponses[0][1].list[0].subject")).isEqualTo(SUBJECT);
+    }
+
+    @Test
+    void 
vaultEndpointShouldRestoreMailboxDeletionMessageForOwnerWhenShareeStillHasAnotherReference(GuiceJamesServer
 jmapServer) throws Exception {
+        // GIVEN a message in Homer's mailbox shared with Bart
+        bartSendMessageToHomer();
+        WAIT_TWO_MINUTES.untilAsserted(() -> 
assertThat(listMessageIdsForAccount(homerCredential)).hasSize(1));
+
+        String messageId = listMessageIdsForAccount(homerCredential).get(0);
+        homerMovesTheMailInAnotherMailbox(messageId);
+        homerSharesHisMailboxWithBart();
+        bartCopiesSharedMessageToOwnInbox();
+
+        // WHEN Homer deletes the shared mailbox
+        homerDeletesMailbox(jmapServer);
+        WAIT_TWO_MINUTES.untilAsserted(() -> 
assertThat(listMessageIdsForAccount(homerCredential)).hasSize(0));
+
+        // THEN Homer should be able to restore the message, but Bart should 
not
+        restoreAllMessagesOfHomer();
+        restoreMessagesFor(BART);
+        awaitSearchUpToDate();
+
+        assertThat(restoredMessagesCount(homerCredential)).isEqualTo(1);
+        assertThat(restoredMessagesCount(bartCredential)).isEqualTo(0);
+
+        String restoredMessageId = getLatestMessageId(homerCredential, 
Role.RESTORED_MESSAGES);
+        assertThat(getMessageContent(homerCredential, restoredMessageId)
+            
.getString("methodResponses[0][1].list[0].subject")).isEqualTo(SUBJECT);
+    }
+
     @Tag(BasicFeature.TAG)
     @Test
     void 
vaultExportShouldExportZipContainsVaultMessagesToShareeWhenJmapDeleteMessage() 
throws Exception {
@@ -1040,6 +1158,38 @@ public abstract class DeletedMessageVaultIntegrationTest 
{
         restoreMessagesForUserWithQuery(webAdminApi, user, MATCH_ALL_QUERY);
     }
 
+    private int restoredMessagesCount(UserCredential credential) {
+        return getAllMailboxesIds(credential).stream()
+            .filter(mailbox -> 
Role.RESTORED_MESSAGES.serialize().equals(mailbox.get("role")))
+            .findFirst()
+            .map(mailbox -> listMessageIdsInMailbox(credential, 
mailbox.get("id")).size())
+            .orElse(0);
+    }
+
+    private void bartCopiesSharedMessageToOwnInbox() {
+        try {
+            mailboxProbe.copy(
+                Username.of(BART),
+                new MailboxPath("#private", Username.of(HOMER), MAILBOX_NAME),
+                MailboxPath.forUser(Username.of(BART), DefaultMailboxes.INBOX),
+                MessageUid.of(1));
+        } catch (Exception e) {
+            throw new RuntimeException("Failed to copy shared message to Bart 
inbox", e);
+        }
+    }
+
+    private void homerCopiesSharedMessageToOwnerOnlyMailbox() {
+        try {
+            mailboxProbe.copy(
+                Username.of(HOMER),
+                new MailboxPath("#private", Username.of(HOMER), MAILBOX_NAME),
+                new MailboxPath("#private", Username.of(HOMER), 
OWNER_ONLY_MAILBOX_NAME),
+                MessageUid.of(1));
+        } catch (Exception e) {
+            throw new RuntimeException("Failed to copy shared message to Homer 
owner-only mailbox", e);
+        }
+    }
+
     private void homerMovesTheMailInAnotherMailbox(String messageId) {
         given()
             .auth().basic(homerCredential.username().asString(), 
homerCredential.password())
@@ -1063,6 +1213,14 @@ public abstract class DeletedMessageVaultIntegrationTest 
{
             .contentType(JSON);
     }
 
+    private void homerDeletesMailbox(GuiceJamesServer jmapServer) throws 
Exception {
+        testIMAPClient.connect(LOCALHOST_IP, 
jmapServer.getProbe(ImapGuiceProbe.class).getImapPort())
+            .login(HOMER, PASSWORD)
+            .select(TestIMAPClient.INBOX);
+
+        testIMAPClient.delete(MAILBOX_NAME);
+    }
+
 
     private boolean homerHasMailboxWithRole(Role role) {
         return getAllMailboxesIds(homerCredential).stream()


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to