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

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

commit 3dbd07c8cb60d9676fd6437dbc08c9ce4d9d35e2
Author: Rene Cordier <[email protected]>
AuthorDate: Mon May 18 13:15:18 2026 +0700

    JAMES-4204 Restore messages from backup zip file
---
 .../james/mailbox/backup/MessageArchiveEntry.java  |  43 +-------
 .../james/mailbox/backup/UnknownArchiveEntry.java  |  12 +--
 .../mailbox/backup/ZipMailArchiveRestorer.java     |  70 +++++++------
 .../mailbox/backup/zip/ExtraFieldExtractor.java    |  33 ++++++
 .../james/mailbox/backup/zip/FlagsExtraField.java  |  24 +++++
 .../mailbox/backup/zip/ZipArchivesLoader.java      |   3 +-
 .../backup/zip/ZippedMailAccountIterator.java      |  77 +++++++++++---
 .../james/webadmin/service/RestoreServiceTest.java | 116 ++++++++++++++++++---
 8 files changed, 266 insertions(+), 112 deletions(-)

diff --git 
a/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/MessageArchiveEntry.java
 
b/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/MessageArchiveEntry.java
index 4ac3bee8a3..6fbd68d773 100644
--- 
a/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/MessageArchiveEntry.java
+++ 
b/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/MessageArchiveEntry.java
@@ -23,47 +23,8 @@ import java.util.Date;
 
 import jakarta.mail.Flags;
 
-public class MessageArchiveEntry implements MailArchiveEntry {
-
-    private final SerializedMessageId messageId;
-    private final SerializedMailboxId mailboxId;
-    private final long size;
-    private final Date internalDate;
-    private final Flags flags;
-    private final InputStream content;
-
-    public MessageArchiveEntry(SerializedMessageId messageId, 
SerializedMailboxId mailboxId, long size, Date internalDate, Flags flags, 
InputStream content) {
-        this.messageId = messageId;
-        this.mailboxId = mailboxId;
-        this.size = size;
-        this.internalDate = internalDate;
-        this.flags = flags;
-        this.content = content;
-    }
-
-    public SerializedMessageId getMessageId() {
-        return messageId;
-    }
-
-    public SerializedMailboxId getMailboxId() {
-        return mailboxId;
-    }
-
-    public long getSize() {
-        return size;
-    }
-
-    public Date getInternalDate() {
-        return internalDate;
-    }
-
-    public Flags getFlags() {
-        return flags;
-    }
-
-    public InputStream getContent() {
-        return content;
-    }
+public record MessageArchiveEntry(SerializedMessageId messageId, 
SerializedMailboxId mailboxId, long size,
+                                  Date internalDate, Flags flags, InputStream 
content) implements MailArchiveEntry {
 
     @Override
     public ArchiveEntryType getType() {
diff --git 
a/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/UnknownArchiveEntry.java
 
b/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/UnknownArchiveEntry.java
index 88c858c70c..9690a68c41 100644
--- 
a/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/UnknownArchiveEntry.java
+++ 
b/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/UnknownArchiveEntry.java
@@ -18,17 +18,7 @@
  ****************************************************************/
 package org.apache.james.mailbox.backup;
 
-public class UnknownArchiveEntry implements MailArchiveEntry {
-
-    private final String entryName;
-
-    public UnknownArchiveEntry(String entryName) {
-        this.entryName = entryName;
-    }
-
-    public String getEntryName() {
-        return entryName;
-    }
+public record UnknownArchiveEntry(String entryName) implements 
MailArchiveEntry {
 
     @Override
     public ArchiveEntryType getType() {
diff --git 
a/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/ZipMailArchiveRestorer.java
 
b/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/ZipMailArchiveRestorer.java
index 3949e0f586..a7ca20e588 100644
--- 
a/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/ZipMailArchiveRestorer.java
+++ 
b/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/ZipMailArchiveRestorer.java
@@ -20,10 +20,9 @@ package org.apache.james.mailbox.backup;
 
 import java.io.IOException;
 import java.io.InputStream;
-import java.util.List;
+import java.util.HashMap;
 import java.util.Map;
 import java.util.Optional;
-import java.util.stream.Stream;
 
 import jakarta.inject.Inject;
 
@@ -32,15 +31,15 @@ import org.apache.james.core.Username;
 import org.apache.james.mailbox.MailboxManager;
 import org.apache.james.mailbox.MailboxSession;
 import org.apache.james.mailbox.MessageManager;
+import org.apache.james.mailbox.MessageManager.AppendResult;
 import org.apache.james.mailbox.exception.MailboxException;
+import org.apache.james.mailbox.model.ByteSourceContent;
 import org.apache.james.mailbox.model.MailboxId;
 import org.apache.james.mailbox.model.MailboxPath;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.github.fge.lambdas.Throwing;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
 
 public class ZipMailArchiveRestorer implements MailArchiveRestorer {
 
@@ -61,38 +60,49 @@ public class ZipMailArchiveRestorer implements 
MailArchiveRestorer {
         mailboxManager.endProcessingRequest(session);
     }
 
-    private void restoreEntries(InputStream source, MailboxSession session) 
throws IOException {
+    private void restoreEntries(InputStream source, MailboxSession session) 
throws IOException, MailboxException {
         try (MailArchiveIterator archiveIterator = archiveLoader.load(source)) 
{
-            List<MailboxWithAnnotationsArchiveEntry> mailboxes = 
readMailboxes(archiveIterator);
-            restoreMailboxes(session, mailboxes);
+            Map<SerializedMailboxId, MessageManager> restoredMailboxes = new 
HashMap<>();
+            while (archiveIterator.hasNext()) {
+                MailArchiveEntry entry = archiveIterator.next();
+                switch (entry.getType()) {
+                    case MAILBOX:
+                        MailboxWithAnnotationsArchiveEntry mailboxEntry = 
(MailboxWithAnnotationsArchiveEntry) entry;
+                        restoreMailboxEntry(session, mailboxEntry)
+                            .ifPresent(pair -> 
restoredMailboxes.put(pair.getKey(), pair.getValue()));
+                        break;
+                    case MESSAGE:
+                        MessageArchiveEntry messageEntry = 
(MessageArchiveEntry) entry;
+                        restoreMessage(session, messageEntry, 
restoredMailboxes);
+                        break;
+                    case UNKNOWN:
+                        String entryName = ((UnknownArchiveEntry) 
entry).entryName();
+                        LOGGER.warn("unknown entry found in zip :" + 
entryName);
+                        break;
+                }
+            }
         }
     }
 
-    private Map<SerializedMailboxId, MessageManager> 
restoreMailboxes(MailboxSession session, 
List<MailboxWithAnnotationsArchiveEntry> mailboxes) {
-        return mailboxes.stream()
-            .flatMap(Throwing.<MailboxWithAnnotationsArchiveEntry, 
Stream<ImmutablePair<SerializedMailboxId, MessageManager>>>function(
-                mailboxEntry -> restoreMailboxEntry(session, 
mailboxEntry).stream()).sneakyThrow())
-            .collect(ImmutableMap.toImmutableMap(Map.Entry::getKey, 
Map.Entry::getValue));
-    }
-
-    private List<MailboxWithAnnotationsArchiveEntry> 
readMailboxes(MailArchiveIterator iterator) {
-        ImmutableList.Builder<MailboxWithAnnotationsArchiveEntry> mailboxes = 
ImmutableList.builder();
-        while (iterator.hasNext()) {
-            MailArchiveEntry entry = iterator.next();
-            switch (entry.getType()) {
-                case MAILBOX:
-                    mailboxes.add((MailboxWithAnnotationsArchiveEntry) entry);
-                    break;
-                case MESSAGE:
-                    //Ignore for know, TODO: implementation
-                    break;
-                case UNKNOWN:
-                    String entryName = ((UnknownArchiveEntry) 
entry).getEntryName();
-                    LOGGER.warn("unknown entry found in zip :" + entryName);
-                    break;
+    private void restoreMessage(MailboxSession session, MessageArchiveEntry 
messageEntry, Map<SerializedMailboxId, MessageManager> mailboxes) {
+        try {
+            MessageManager messageManager = 
mailboxes.get(messageEntry.mailboxId());
+            if (messageManager == null) {
+                LOGGER.warn("Mailbox {} not found for message {}", 
messageEntry.mailboxId(), messageEntry.messageId());
+                return;
+            }
+            ByteSourceContent content = 
ByteSourceContent.of(messageEntry.content());
+            MessageManager.AppendCommand command = 
MessageManager.AppendCommand.builder()
+                .withInternalDate(messageEntry.internalDate())
+                .withFlags(messageEntry.flags())
+                .build(content);
+            AppendResult result = messageManager.appendMessage(command, 
session);
+            if (!result.getSize().equals(messageEntry.size())) {
+                LOGGER.warn("Size {} for message {} different from zip entry 
one {}", result.getSize(), messageEntry.messageId(), messageEntry.size());
             }
+        } catch (Exception e) {
+            LOGGER.error("Error restoring message {} to mailbox {}", 
messageEntry.messageId(), messageEntry.mailboxId(), e);
         }
-        return mailboxes.build();
     }
 
     private Optional<ImmutablePair<SerializedMailboxId, MessageManager>> 
restoreMailboxEntry(MailboxSession session,
diff --git 
a/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/zip/ExtraFieldExtractor.java
 
b/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/zip/ExtraFieldExtractor.java
index ceed635c20..57481817ab 100644
--- 
a/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/zip/ExtraFieldExtractor.java
+++ 
b/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/zip/ExtraFieldExtractor.java
@@ -19,11 +19,14 @@
 package org.apache.james.mailbox.backup.zip;
 
 import java.util.Arrays;
+import java.util.Date;
 import java.util.Optional;
 import java.util.function.Function;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipException;
 
+import jakarta.mail.Flags;
+
 import org.apache.commons.compress.archivers.zip.ExtraFieldUtils;
 import org.apache.commons.compress.archivers.zip.ZipExtraField;
 import org.apache.commons.compress.archivers.zip.ZipShort;
@@ -40,6 +43,36 @@ public class ExtraFieldExtractor {
             .flatMap(Function.identity());
     }
 
+    public static Optional<Long> getLongExtraField(ZipShort id, ZipEntry 
entry) throws ZipException {
+        ZipExtraField[] extraFields = ExtraFieldUtils.parse(entry.getExtra());
+        return Arrays.stream(extraFields)
+            .filter(field -> field.getHeaderId().equals(id))
+            .map(LongExtraField.class::cast)
+            .map(LongExtraField::getValue)
+            .findFirst()
+            .flatMap(Function.identity());
+    }
+
+    public static Optional<Date> getDateExtraField(ZipShort id, ZipEntry 
entry) throws ZipException {
+        ZipExtraField[] extraFields = ExtraFieldUtils.parse(entry.getExtra());
+        return Arrays.stream(extraFields)
+            .filter(field -> field.getHeaderId().equals(id))
+            .map(InternalDateExtraField.class::cast)
+            .map(InternalDateExtraField::getDateValue)
+            .findFirst()
+            .flatMap(Function.identity());
+    }
+
+    public static Optional<Flags> getFlagsExtraField(ZipShort id, ZipEntry 
entry) throws ZipException {
+        ZipExtraField[] extraFields = ExtraFieldUtils.parse(entry.getExtra());
+        return Arrays.stream(extraFields)
+            .filter(field -> field.getHeaderId().equals(id))
+            .map(FlagsExtraField.class::cast)
+            .map(FlagsExtraField::getFlagsValue)
+            .findFirst()
+            .flatMap(Function.identity());
+    }
+
     public static Optional<ZipEntryType> getEntryType(ZipEntry entry) {
         try {
             ZipExtraField[] extraFields = 
ExtraFieldUtils.parse(entry.getExtra());
diff --git 
a/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/zip/FlagsExtraField.java
 
b/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/zip/FlagsExtraField.java
index 7c9a5c2cee..0ee920b29c 100644
--- 
a/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/zip/FlagsExtraField.java
+++ 
b/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/zip/FlagsExtraField.java
@@ -48,6 +48,30 @@ public class FlagsExtraField extends StringExtraField 
implements WithZipHeader {
         super(Optional.of(serializeFlags(flags)));
     }
 
+    public Optional<Flags> getFlagsValue() {
+        return getValue().map(FlagsExtraField::parseFlags);
+    }
+
+    private static Flags parseFlags(String serialized) {
+        Flags flags = new Flags();
+        if (serialized == null || serialized.isEmpty()) {
+            return flags;
+        }
+        String[] parts = serialized.split("%");
+        for (String part : parts) {
+            switch (part) {
+                case "\\ANSWERED" -> flags.add(Flags.Flag.ANSWERED);
+                case "\\DELETED" -> flags.add(Flags.Flag.DELETED);
+                case "\\DRAFT" -> flags.add(Flags.Flag.DRAFT);
+                case "\\FLAGGED" -> flags.add(Flags.Flag.FLAGGED);
+                case "\\RECENT" -> flags.add(Flags.Flag.RECENT);
+                case "\\SEEN" -> flags.add(Flags.Flag.SEEN);
+                default -> flags.add(part);
+            }
+        }
+        return flags;
+    }
+
     @Override
     public ZipShort getHeaderId() {
         return ID_AP;
diff --git 
a/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/zip/ZipArchivesLoader.java
 
b/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/zip/ZipArchivesLoader.java
index 14e6838d2a..e7ab6d68ef 100644
--- 
a/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/zip/ZipArchivesLoader.java
+++ 
b/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/zip/ZipArchivesLoader.java
@@ -29,7 +29,6 @@ public class ZipArchivesLoader implements MailArchivesLoader {
     @Override
     public MailArchiveIterator load(InputStream inputStream) throws 
IOException {
         ZipInputStream zipInputStream = new ZipInputStream(inputStream);
-        ZipEntryIterator zipEntryIterator = new 
ZipEntryIterator(zipInputStream);
-        return new ZippedMailAccountIterator(zipEntryIterator);
+        return new ZippedMailAccountIterator(zipInputStream);
     }
 }
diff --git 
a/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/zip/ZippedMailAccountIterator.java
 
b/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/zip/ZippedMailAccountIterator.java
index 1688045aa6..d27b52ef1a 100644
--- 
a/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/zip/ZippedMailAccountIterator.java
+++ 
b/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/zip/ZippedMailAccountIterator.java
@@ -19,17 +19,23 @@
 package org.apache.james.mailbox.backup.zip;
 
 import java.io.IOException;
+import java.util.Date;
 import java.util.List;
 import java.util.NoSuchElementException;
 import java.util.Optional;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipException;
+import java.util.zip.ZipInputStream;
+
+import jakarta.mail.Flags;
 
 import org.apache.commons.lang3.StringUtils;
 import org.apache.james.mailbox.backup.MailArchiveEntry;
 import org.apache.james.mailbox.backup.MailArchiveIterator;
 import org.apache.james.mailbox.backup.MailboxWithAnnotationsArchiveEntry;
+import org.apache.james.mailbox.backup.MessageArchiveEntry;
 import org.apache.james.mailbox.backup.SerializedMailboxId;
+import org.apache.james.mailbox.backup.SerializedMessageId;
 import org.apache.james.mailbox.backup.UnknownArchiveEntry;
 import org.apache.james.mailbox.model.MailboxAnnotation;
 import org.slf4j.Logger;
@@ -41,37 +47,53 @@ import com.google.common.collect.ImmutableList;
 public class ZippedMailAccountIterator implements MailArchiveIterator {
     private static final Logger LOGGER = 
LoggerFactory.getLogger(ZippedMailAccountIterator.class);
     private static final List<MailboxAnnotation> NO_ANNOTATION = 
ImmutableList.of();
-    private final ZipEntryIterator zipEntryIterator;
-    private Optional<ZipEntry> next;
+    private final ZipInputStream zipInputStream;
+    private Optional<ZipEntry> currentEntry;
+    private boolean closed;
 
-    public ZippedMailAccountIterator(ZipEntryIterator zipEntryIterator) {
-        this.zipEntryIterator = zipEntryIterator;
-        next = Optional.ofNullable(zipEntryIterator.next());
+    public ZippedMailAccountIterator(ZipInputStream zipInputStream) {
+        this.zipInputStream = zipInputStream;
+        this.currentEntry = Optional.empty();
+        this.closed = false;
     }
 
     @Override
     public void close() throws IOException {
-        zipEntryIterator.close();
+        closed = true;
+        zipInputStream.close();
     }
 
     @Override
     public boolean hasNext() {
-        return next.isPresent();
+        if (closed) {
+            return false;
+        }
+        if (currentEntry.isPresent()) {
+            return true;
+        }
+        try {
+            closeCurrentEntryIfNeeded();
+            ZipEntry nextEntry = zipInputStream.getNextEntry();
+            currentEntry = Optional.ofNullable(nextEntry);
+            return currentEntry.isPresent();
+        } catch (IOException e) {
+            LOGGER.error("Error reading next zip entry", e);
+            return false;
+        }
     }
 
     @Override
     public MailArchiveEntry next() {
-        return next.map(this::doNext).orElseThrow(() -> new 
NoSuchElementException());
-    }
-
-    private MailArchiveEntry doNext(ZipEntry currentElement) {
-        next = Optional.ofNullable(zipEntryIterator.next());
+        if (!hasNext()) {
+            throw new NoSuchElementException();
+        }
+        ZipEntry entry = currentEntry.get();
+        currentEntry = Optional.empty();
         try {
-            return getMailArchiveEntry(currentElement);
+            return getMailArchiveEntry(entry);
         } catch (Exception e) {
-            LOGGER.error("Error when reading archive on entry : " + 
currentElement.getName(), e);
-            next = Optional.empty();
-            return new UnknownArchiveEntry(currentElement.getName());
+            LOGGER.error("Error when reading archive on entry : " + 
entry.getName(), e);
+            return new UnknownArchiveEntry(entry.getName());
         }
     }
 
@@ -84,6 +106,14 @@ public class ZippedMailAccountIterator implements 
MailArchiveIterator {
             .orElseGet(() -> new 
UnknownArchiveEntry(currentElement.getName()));
     }
 
+    private void closeCurrentEntryIfNeeded() {
+        try {
+            zipInputStream.closeEntry();
+        } catch (IOException e) {
+            LOGGER.warn("Error closing zip entry", e);
+        }
+    }
+
     private Optional<SerializedMailboxId> getMailBoxId(ZipEntry entry) throws 
ZipException {
         return 
ExtraFieldExtractor.getStringExtraField(MailboxIdExtraField.ID_AM, entry)
             .map(SerializedMailboxId::new);
@@ -97,10 +127,25 @@ public class ZippedMailAccountIterator implements 
MailArchiveIterator {
         return new MailboxWithAnnotationsArchiveEntry(getMailboxName(current), 
getMailBoxId(current).get(), NO_ANNOTATION);
     }
 
+    private MailArchiveEntry fromMessageEntry(ZipEntry current) throws 
ZipException {
+        SerializedMessageId messageId = 
ExtraFieldExtractor.getStringExtraField(MessageIdExtraField.ID_AL, current)
+            .map(SerializedMessageId::new)
+            .orElseThrow(() -> new ZipException("Message entry missing 
messageId"));
+        SerializedMailboxId mailboxId = getMailBoxId(current)
+            .orElseThrow(() -> new ZipException("Message entry missing 
mailboxId"));
+        long size = 
ExtraFieldExtractor.getLongExtraField(SizeExtraField.ID_AJ, current).orElse(0L);
+        Date internalDate = 
ExtraFieldExtractor.getDateExtraField(InternalDateExtraField.ID_AO, 
current).orElse(new Date());
+        Flags flags = 
ExtraFieldExtractor.getFlagsExtraField(FlagsExtraField.ID_AP, 
current).orElse(new Flags());
+
+        return new MessageArchiveEntry(messageId, mailboxId, size, 
internalDate, flags, zipInputStream);
+    }
+
     private MailArchiveEntry from(ZipEntry current, ZipEntryType 
currentEntryType) throws ZipException {
         switch (currentEntryType) {
             case MAILBOX:
                 return fromMailboxEntry(current);
+            case MESSAGE:
+                return fromMessageEntry(current);
             default:
                 return new UnknownArchiveEntry(current.getName());
         }
diff --git 
a/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/service/RestoreServiceTest.java
 
b/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/service/RestoreServiceTest.java
index 0ddc394ccb..a983e1382e 100644
--- 
a/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/service/RestoreServiceTest.java
+++ 
b/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/service/RestoreServiceTest.java
@@ -19,6 +19,7 @@
 
 package org.apache.james.webadmin.service;
 
+import static org.apache.james.mailbox.backup.MailboxMessageFixture.DATE_1;
 import static org.apache.james.webadmin.service.ExportServiceTestSystem.BOB;
 import static org.apache.james.webadmin.service.ExportServiceTestSystem.CEDRIC;
 import static org.assertj.core.api.Assertions.assertThat;
@@ -29,7 +30,9 @@ import static org.mockito.Mockito.doThrow;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.InputStream;
-import java.nio.charset.StandardCharsets;
+import java.util.Date;
+
+import jakarta.mail.Flags;
 
 import org.apache.james.blob.api.BlobId;
 import org.apache.james.blob.api.BlobStore;
@@ -51,13 +54,12 @@ import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.mockito.Mockito;
 
-import com.github.fge.lambdas.Throwing;
-
 import reactor.core.publisher.Mono;
 
 @ExtendWith(FileSystemExtension.class)
 class RestoreServiceTest {
     private static final int BUFFER_SIZE = 4096;
+    private static String OTHER_MAILBOX = "otherMailbox";
     private static final String MESSAGE_CONTENT = "MIME-Version: 1.0\r\n" +
         "Subject: test\r\n" +
         "Content-Type: text/plain; charset=UTF-8\r\n" +
@@ -87,7 +89,7 @@ class RestoreServiceTest {
 
     @Test
     void 
restoreShouldReturnCompleteWhenExistingUserWithoutDataAndNonEmptyZip() throws 
Exception {
-        createAMailboxWithAMail(MESSAGE_CONTENT);
+        createAMailboxWithAMail();
 
         ByteArrayOutputStream destination = new 
ByteArrayOutputStream(BUFFER_SIZE);
         testSystem.backup.backupAccount(BOB, destination);
@@ -101,7 +103,7 @@ class RestoreServiceTest {
 
     @Test
     void restoreShouldReturnPartialWhenNonEmptyAccount() throws Exception {
-        createAMailboxWithAMail(MESSAGE_CONTENT);
+        createAMailboxWithAMail();
 
         ByteArrayOutputStream destination = new 
ByteArrayOutputStream(BUFFER_SIZE);
         testSystem.backup.backupAccount(BOB, destination);
@@ -119,7 +121,7 @@ class RestoreServiceTest {
             .when(testSystem.blobStore)
             .read(any(), any());
 
-        createAMailboxWithAMail(MESSAGE_CONTENT);
+        createAMailboxWithAMail();
 
         ByteArrayOutputStream destination = new 
ByteArrayOutputStream(BUFFER_SIZE);
         testSystem.backup.backupAccount(BOB, destination);
@@ -148,7 +150,7 @@ class RestoreServiceTest {
 
     @Test
     void restoreShouldRestoreContentFromNonEmptyZip() throws Exception {
-        createAMailboxWithAMail(MESSAGE_CONTENT);
+        createAMailboxWithAMail();
 
         ByteArrayOutputStream destination = new 
ByteArrayOutputStream(BUFFER_SIZE);
         testSystem.backup.backupAccount(BOB, destination);
@@ -161,15 +163,99 @@ class RestoreServiceTest {
         MailboxSession cedricSession = 
testSystem.mailboxManager.createSystemSession(CEDRIC);
         MessageManager mailbox = 
testSystem.mailboxManager.getMailbox(MailboxPath.inbox(CEDRIC), cedricSession);
         MessageResultIterator resultIterator = 
mailbox.getMessages(MessageRange.all(), FetchGroup.FULL_CONTENT, cedricSession);
+        assertThat(resultIterator).toIterable()
+            .hasSize(1)
+            .first()
+            .satisfies(result -> assertThat(new 
String(result.getFullContent().getInputStream().readAllBytes())).isEqualTo(MESSAGE_CONTENT));
+    }
+
+    @Test
+    void restoreShouldPreserveFlags() throws Exception {
+        Flags messageFlags = new Flags("customFlag");
+        messageFlags.add(Flags.Flag.SEEN);
+        createAMailboxWithAMail(messageFlags, new Date());
+
+        ByteArrayOutputStream destination = new 
ByteArrayOutputStream(BUFFER_SIZE);
+        testSystem.backup.backupAccount(BOB, destination);
+
+        InputStream source = new 
ByteArrayInputStream(destination.toByteArray());
+        BlobId blobId = 
Mono.from(testSystem.blobStore.save(testSystem.blobStore.getDefaultBucketName(),
 source, BlobStore.StoragePolicy.LOW_COST)).block();
+
+        testee.restore(CEDRIC, blobId).block();
+
+        MailboxSession cedricSession = 
testSystem.mailboxManager.createSystemSession(CEDRIC);
+        MessageManager mailbox = 
testSystem.mailboxManager.getMailbox(MailboxPath.inbox(CEDRIC), cedricSession);
+        MessageResultIterator resultIterator = 
mailbox.getMessages(MessageRange.all(), FetchGroup.FULL_CONTENT, cedricSession);
+        assertThat(resultIterator).toIterable()
+            .hasSize(1)
+            .first()
+            .satisfies(result -> 
assertThat(result.getFlags()).isEqualTo(messageFlags));
+    }
+
+    @Test
+    void restoreShouldPreserveInternalDate() throws Exception {
+        Date internalDate = new Date(DATE_1.toEpochSecond());
+        createAMailboxWithAMail(new Flags(), internalDate);
+
+        ByteArrayOutputStream destination = new 
ByteArrayOutputStream(BUFFER_SIZE);
+        testSystem.backup.backupAccount(BOB, destination);
+
+        InputStream source = new 
ByteArrayInputStream(destination.toByteArray());
+        BlobId blobId = 
Mono.from(testSystem.blobStore.save(testSystem.blobStore.getDefaultBucketName(),
 source, BlobStore.StoragePolicy.LOW_COST)).block();
+
+        testee.restore(CEDRIC, blobId).block();
+
+        MailboxSession cedricSession = 
testSystem.mailboxManager.createSystemSession(CEDRIC);
+        MessageManager mailbox = 
testSystem.mailboxManager.getMailbox(MailboxPath.inbox(CEDRIC), cedricSession);
+        MessageResultIterator resultIterator = 
mailbox.getMessages(MessageRange.all(), FetchGroup.FULL_CONTENT, cedricSession);
+        assertThat(resultIterator).toIterable()
+            .hasSize(1)
+            .first()
+            .satisfies(result -> 
assertThat(result.getInternalDate()).isEqualTo(internalDate));
+    }
+
+    @Test
+    void restoreShouldRestoreMultipleMailboxesAndMessages() throws Exception {
+        MailboxPath bobInboxPath = MailboxPath.inbox(BOB);
+        MailboxPath otherBobMailboxPath = MailboxPath.forUser(BOB, 
OTHER_MAILBOX);
+        testSystem.mailboxManager.createMailbox(bobInboxPath, 
testSystem.bobSession);
+        testSystem.mailboxManager.createMailbox(otherBobMailboxPath, 
testSystem.bobSession);
+
+        testSystem.mailboxManager.getMailbox(bobInboxPath, 
testSystem.bobSession)
+            .appendMessage(MessageManager.AppendCommand.builder()
+                    .build(MESSAGE_CONTENT),
+                testSystem.bobSession);
+        testSystem.mailboxManager.getMailbox(bobInboxPath, 
testSystem.bobSession)
+            .appendMessage(MessageManager.AppendCommand.builder()
+                    .build(MESSAGE_CONTENT),
+                testSystem.bobSession);
+        testSystem.mailboxManager.getMailbox(otherBobMailboxPath, 
testSystem.bobSession)
+            .appendMessage(MessageManager.AppendCommand.builder()
+                    .build(MESSAGE_CONTENT),
+                testSystem.bobSession);
+
+        ByteArrayOutputStream destination = new 
ByteArrayOutputStream(BUFFER_SIZE);
+        testSystem.backup.backupAccount(BOB, destination);
+
+        InputStream source = new 
ByteArrayInputStream(destination.toByteArray());
+        BlobId blobId = 
Mono.from(testSystem.blobStore.save(testSystem.blobStore.getDefaultBucketName(),
 source, BlobStore.StoragePolicy.LOW_COST)).block();
+
+        testee.restore(CEDRIC, blobId).block();
+
+        MailboxSession cedricSession = 
testSystem.mailboxManager.createSystemSession(CEDRIC);
+        MessageManager inbox = 
testSystem.mailboxManager.getMailbox(MailboxPath.inbox(CEDRIC), cedricSession);
+        MessageManager otherMailbox = 
testSystem.mailboxManager.getMailbox(MailboxPath.forUser(CEDRIC, 
OTHER_MAILBOX), cedricSession);
+        MessageResultIterator inboxMessages = 
inbox.getMessages(MessageRange.all(), FetchGroup.FULL_CONTENT, cedricSession);
+        MessageResultIterator otherMailboxMessages = 
otherMailbox.getMessages(MessageRange.all(), FetchGroup.FULL_CONTENT, 
cedricSession);
         SoftAssertions.assertSoftly(softly -> {
-            softly.assertThat(resultIterator).toIterable().hasSize(1);
-            softly.assertThat(Throwing.supplier(() -> 
resultIterator.next().getBody().asBytesSequence())).isEqualTo(MESSAGE_CONTENT.getBytes(StandardCharsets.UTF_8));
+            softly.assertThat(inboxMessages).toIterable().hasSize(2);
+            softly.assertThat(otherMailboxMessages).toIterable().hasSize(1);
         });
     }
 
     @Test
     void restoreShouldDeleteBlobAfterCompletion() throws Exception {
-        createAMailboxWithAMail(MESSAGE_CONTENT);
+        createAMailboxWithAMail();
 
         ByteArrayOutputStream destination = new 
ByteArrayOutputStream(BUFFER_SIZE);
         testSystem.backup.backupAccount(BOB, destination);
@@ -183,12 +269,18 @@ class RestoreServiceTest {
             .isInstanceOf(ObjectNotFoundException.class);
     }
 
-    private ComposedMessageId createAMailboxWithAMail(String message) throws 
MailboxException {
+    private ComposedMessageId createAMailboxWithAMail() throws 
MailboxException {
+        return createAMailboxWithAMail(new Flags(), new Date());
+    }
+
+    private ComposedMessageId createAMailboxWithAMail(Flags flags, Date 
internalDate) throws MailboxException {
         MailboxPath bobInboxPath = MailboxPath.inbox(BOB);
         testSystem.mailboxManager.createMailbox(bobInboxPath, 
testSystem.bobSession);
         return testSystem.mailboxManager.getMailbox(bobInboxPath, 
testSystem.bobSession)
             .appendMessage(MessageManager.AppendCommand.builder()
-                    .build(message),
+                    .withFlags(flags)
+                    .withInternalDate(internalDate)
+                    .build(MESSAGE_CONTENT),
                 testSystem.bobSession)
             .getId();
     }


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

Reply via email to