You are viewing a plain text version of this content. The canonical link for it is here.
Posted to server-dev@james.apache.org by ro...@apache.org on 2019/03/28 11:14:30 UTC

[james-project] branch master updated (08ef402 -> 4e1561f)

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

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


    from 08ef402  MAILBOX-373 deadLetter::remove operation was not bound
     new c7a9890  JAMES-2688 ZipAssert `hasSameContentWith` impl
     new 9738e5d  JAMES-2688 DeletedMessageVault Export API Integration Tests
     new 83b7a56  JAMES-2686 grafana board for predeletion hooks execution times
     new 4e1561f  Merge remote-tracking branch 'Arsnael/james-2686-grafana-predeletionhooks'

The 4 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../PreDeletionHooks-1553684324244-dashboard.json  | 187 +++++++++++++++
 grafana-reporting/README.md                        |   1 +
 .../org/apache/james/mailbox/backup/ZipAssert.java | 109 ++++++++-
 .../apache/james/mailbox/backup/ZipAssertTest.java | 256 +++++++++++++++++----
 .../cassandra-jmap-integration-testing/pom.xml     |   5 +
 .../CassandraDeletedMessageVaultTest.java          |   6 +-
 .../jmap-integration-testing-common/pom.xml        |   6 +
 .../integration/DeletedMessagesVaultTest.java      | 229 +++++++++++++++++-
 .../memory-jmap-integration-testing/pom.xml        |   5 +
 ...FileMailRepositoryDeletedMessagesVaultTest.java |   6 +-
 .../memory/MemoryDeletedMessagesVaultTest.java     |   6 +-
 .../rabbitmq-jmap-integration-testing/pom.xml      |   5 +
 .../rabbitmq/RabbitMQDeletedMessagesVaultTest.java |   6 +-
 src/site/xdoc/server/metrics.xml                   |   1 +
 14 files changed, 771 insertions(+), 57 deletions(-)
 create mode 100644 grafana-reporting/PreDeletionHooks-1553684324244-dashboard.json


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


[james-project] 04/04: Merge remote-tracking branch 'Arsnael/james-2686-grafana-predeletionhooks'

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 4e1561feffc8ff6266860741b3d23424a7160a28
Merge: 9738e5d 83b7a56
Author: Raphael Ouazana <ra...@linagora.com>
AuthorDate: Thu Mar 28 12:13:47 2019 +0100

    Merge remote-tracking branch 'Arsnael/james-2686-grafana-predeletionhooks'

 .../PreDeletionHooks-1553684324244-dashboard.json  | 187 +++++++++++++++++++++
 grafana-reporting/README.md                        |   1 +
 src/site/xdoc/server/metrics.xml                   |   1 +
 3 files changed, 189 insertions(+)


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


[james-project] 02/04: JAMES-2688 ZipAssert `hasSameContentWith` impl

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit c7a98909d58375deeef47389b453007b48b65bb8
Author: Tran Tien Duc <dt...@linagora.com>
AuthorDate: Mon Mar 25 15:34:07 2019 +0700

    JAMES-2688 ZipAssert `hasSameContentWith` impl
---
 .../org/apache/james/mailbox/backup/ZipAssert.java |  96 +++++++-
 .../apache/james/mailbox/backup/ZipAssertTest.java | 256 +++++++++++++++++----
 2 files changed, 306 insertions(+), 46 deletions(-)

diff --git a/mailbox/backup/src/test/java/org/apache/james/mailbox/backup/ZipAssert.java b/mailbox/backup/src/test/java/org/apache/james/mailbox/backup/ZipAssert.java
index 7c46fad..9f0c9b0 100644
--- a/mailbox/backup/src/test/java/org/apache/james/mailbox/backup/ZipAssert.java
+++ b/mailbox/backup/src/test/java/org/apache/james/mailbox/backup/ZipAssert.java
@@ -20,13 +20,17 @@
 package org.apache.james.mailbox.backup;
 
 import static org.apache.james.mailbox.backup.ZipArchiveEntryAssert.assertThatZipEntry;
+import static org.assertj.core.api.Assertions.assertThat;
 
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
+import java.util.stream.Stream;
 
 import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
 import org.apache.commons.compress.archivers.zip.ZipExtraField;
@@ -34,8 +38,10 @@ import org.apache.commons.compress.archivers.zip.ZipFile;
 import org.apache.commons.compress.utils.SeekableInMemoryByteChannel;
 import org.assertj.core.api.AbstractAssert;
 import org.assertj.core.error.BasicErrorMessageFactory;
+import org.assertj.core.error.ErrorMessageFactory;
 
 import com.github.steveash.guavate.Guavate;
+import com.google.common.collect.ImmutableList;
 
 public class ZipAssert extends AbstractAssert<ZipAssert, ZipFile> implements AutoCloseable {
     interface EntryCheck {
@@ -77,7 +83,7 @@ public class ZipAssert extends AbstractAssert<ZipAssert, ZipFile> implements Aut
         }
     }
 
-    static ZipAssert assertThatZip(ZipFile zipFile) {
+    public static ZipAssert assertThatZip(ZipFile zipFile) {
         return new ZipAssert(zipFile);
     }
 
@@ -93,6 +99,29 @@ public class ZipAssert extends AbstractAssert<ZipAssert, ZipFile> implements Aut
         return new BasicErrorMessageFactory("%nExpecting %s to be empty", zipFile);
     }
 
+    private static BasicErrorMessageFactory shouldBeNonNull() {
+        return new BasicErrorMessageFactory("%nExpecting zipFile to be non-null");
+    }
+
+    private static BasicErrorMessageFactory shouldHaveSameEntriesSize(List<ZipArchiveEntry> entries, List<ZipArchiveEntry> expectedEntries) {
+        return new BasicErrorMessageFactory("%nExpecting zipFile entries has size (%s) but actually (%s)", expectedEntries.size(), entries.size());
+    }
+
+    private static ErrorMessageFactory entriesShouldHaveSameContentAt(int entryIndex, String entryContentAssertionError) {
+        return new BasicErrorMessageFactory("%nExpecting zipFile entry at index %s have same content. Details: \n%s",
+            entryIndex, entryContentAssertionError);
+    }
+
+    private static ErrorMessageFactory entriesShouldHaveSameName(ZipArchiveEntry entry, ZipArchiveEntry expectedEntry, int entryIndex) {
+        return new BasicErrorMessageFactory("%nExpecting zipFile entry name (%s) at index %s but actually (%s)",
+            expectedEntry.getName(), entryIndex, entry.getName());
+    }
+
+    private static ErrorMessageFactory entriesShouldHaveSameExtraFields(ZipArchiveEntry entry, ZipArchiveEntry expectedEntry, int entryIndex) {
+        return new BasicErrorMessageFactory("%nExpecting zipFile entry at index %s has extra fields (%s) but actually (%s)", entryIndex,
+            expectedEntry.getExtraFields(), entry.getExtraFields());
+    }
+
     private final ZipFile zipFile;
 
     private ZipAssert(ZipFile zipFile) {
@@ -130,4 +159,69 @@ public class ZipAssert extends AbstractAssert<ZipAssert, ZipFile> implements Aut
     public void close() throws Exception {
         zipFile.close();
     }
+
+    public ZipAssert hasSameContentWith(ZipFile anotherZipFile) throws IOException {
+        validateNonNull(zipFile);
+        validateNonNull(anotherZipFile);
+
+        ArrayList<ZipArchiveEntry> entries = Collections.list(zipFile.getEntries());
+        ArrayList<ZipArchiveEntry> entriesOfAnother = Collections.list(anotherZipFile.getEntries());
+
+        if (entries.size() != entriesOfAnother.size()) {
+            throwAssertionError(shouldHaveSameEntriesSize(entries, entriesOfAnother));
+        }
+
+        for (int entryIndex = 0; entryIndex < entries.size(); entryIndex++) {
+            ZipArchiveEntry entry = entries.get(entryIndex);
+            ZipArchiveEntry entryOfAnother = entriesOfAnother.get(entryIndex);
+            haveSameName(entry, entryOfAnother, entryIndex);
+            haveSameExtraFields(entry, entryOfAnother, entryIndex);
+            haveSameContentAt(zipFile.getInputStream(entry), anotherZipFile.getInputStream(entryOfAnother), entryIndex);
+        }
+
+        return myself;
+    }
+
+    private void haveSameName(ZipArchiveEntry entry, ZipArchiveEntry expectedEntry, int entryIndex) {
+        try {
+            assertThat(entry.getName()).isEqualTo(expectedEntry.getName());
+        } catch (AssertionError assertionError) {
+            throwAssertionError(entriesShouldHaveSameName(entry, expectedEntry, entryIndex));
+        }
+    }
+
+    private void haveSameExtraFields(ZipArchiveEntry entry, ZipArchiveEntry expectedEntry, int entryIndex) {
+        try {
+            assertThat(extractJamesExtraFields(entry))
+                .containsExactlyElementsOf(extractJamesExtraFields(expectedEntry));
+        } catch (AssertionError assertionError) {
+            throwAssertionError(entriesShouldHaveSameExtraFields(entry, expectedEntry, entryIndex));
+        }
+    }
+
+    private void haveSameContentAt(InputStream entryContent, InputStream expectingEntryContent, int entryIndex) {
+        try {
+            assertThat(entryContent)
+                .hasSameContentAs(expectingEntryContent);
+        } catch (AssertionError assertionError) {
+            throwAssertionError(entriesShouldHaveSameContentAt(entryIndex, assertionError.getMessage()));
+        }
+    }
+
+    private void validateNonNull(ZipFile zipFile) {
+        if (zipFile == null) {
+            throwAssertionError(shouldBeNonNull());
+        }
+    }
+
+    /**
+     * Because there are always some extra fields not belong to James, and their equals() method doesn't work
+     * @param entry
+     * @return
+     */
+    private ImmutableList<ZipExtraField> extractJamesExtraFields(ZipArchiveEntry entry) {
+        return Stream.of(entry.getExtraFields())
+            .filter(field -> field instanceof WithZipHeader)
+            .collect(Guavate.toImmutableList());
+    }
 }
diff --git a/mailbox/backup/src/test/java/org/apache/james/mailbox/backup/ZipAssertTest.java b/mailbox/backup/src/test/java/org/apache/james/mailbox/backup/ZipAssertTest.java
index 4e391fa..b000831 100644
--- a/mailbox/backup/src/test/java/org/apache/james/mailbox/backup/ZipAssertTest.java
+++ b/mailbox/backup/src/test/java/org/apache/james/mailbox/backup/ZipAssertTest.java
@@ -21,26 +21,81 @@ package org.apache.james.mailbox.backup;
 
 import static org.apache.james.mailbox.backup.ZipAssert.EntryChecks.hasName;
 import static org.apache.james.mailbox.backup.ZipAssert.assertThatZip;
+import static org.apache.james.mailbox.backup.ZipAssertTest.ZipEntryWithContent.entryBuilder;
 import static org.assertj.core.api.Assertions.assertThatCode;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
 import java.io.ByteArrayInputStream;
 import java.io.File;
+import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
 import java.util.AbstractMap.SimpleImmutableEntry;
 
 import org.apache.commons.compress.archivers.zip.ExtraFieldUtils;
 import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
 import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
+import org.apache.commons.compress.archivers.zip.ZipExtraField;
 import org.apache.commons.compress.archivers.zip.ZipFile;
 import org.apache.commons.io.IOUtils;
 import org.apache.james.junit.TemporaryFolderExtension;
 import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
+import org.testcontainers.shaded.com.google.common.collect.ImmutableList;
 
 @ExtendWith(TemporaryFolderExtension.class)
 public class ZipAssertTest {
+
+    static class ZipEntryWithContent {
+
+        static class Builder {
+            @FunctionalInterface
+            interface RequireName {
+                RequireContent name(String name);
+            }
+
+            @FunctionalInterface
+            interface RequireContent {
+                ReadyToBuild content(byte[] content);
+            }
+
+            static class ReadyToBuild {
+                private final String name;
+                private final byte[] content;
+                private final ImmutableList.Builder<ZipExtraField> extraFieldBuilder;
+
+                ReadyToBuild(String name, byte[] content) {
+                    this.name = name;
+                    this.content = content;
+                    this.extraFieldBuilder = new ImmutableList.Builder<>();
+                }
+
+                ReadyToBuild addField(ZipExtraField zipExtraField) {
+                    extraFieldBuilder.add(zipExtraField);
+                    return this;
+                }
+
+                public ZipEntryWithContent build() {
+                    return new ZipEntryWithContent(name, new ByteArrayInputStream(content), extraFieldBuilder.build());
+                }
+            }
+        }
+
+        static Builder.RequireName entryBuilder() {
+            return name -> content -> new Builder.ReadyToBuild(name, content);
+        }
+
+        private final String name;
+        private final InputStream content;
+        private final ImmutableList<ZipExtraField> extraFields;
+
+        ZipEntryWithContent(String name, InputStream content, ImmutableList<ZipExtraField> extraFields) {
+            this.name = name;
+            this.content = content;
+            this.extraFields = extraFields;
+        }
+    }
     private static final String ENTRY_NAME = "entryName";
     private static final String ENTRY_NAME_2 = "entryName2";
     private static final String DIRECTORY_NAME = "folder/";
@@ -53,32 +108,35 @@ public class ZipAssertTest {
     private static final SimpleImmutableEntry<String, byte[]> ENTRY = new SimpleImmutableEntry<>(ENTRY_NAME, ENTRY_CONTENT);
     private static final SimpleImmutableEntry<String, byte[]> ENTRY_2 = new SimpleImmutableEntry<>(ENTRY_NAME_2, ENTRY_CONTENT_2);
     private File destination;
+    private File destination2;
 
     @BeforeEach
     void beforeEach(TemporaryFolderExtension.TemporaryFolder temporaryFolder) throws Exception {
         destination = File.createTempFile("backup-test", ".zip", temporaryFolder.getTempDir());
+        destination2 = File.createTempFile("backup-test2", ".zip", temporaryFolder.getTempDir());
 
         ExtraFieldUtils.register(SizeExtraField.class);
+        ExtraFieldUtils.register(UidExtraField.class);
     }
 
     @SafeVarargs
-    private final void buildZipFile(SimpleImmutableEntry<String, byte[]>... entries) throws Exception {
+    private final ZipFile buildZipFile(SimpleImmutableEntry<String, byte[]>... entries) throws Exception {
         try (ZipArchiveOutputStream archiveOutputStream = new ZipArchiveOutputStream(destination)) {
-           for (SimpleImmutableEntry<String, byte[]> entry : entries) {
-                    ZipArchiveEntry archiveEntry = (ZipArchiveEntry) archiveOutputStream.createArchiveEntry(new File("any"), entry.getKey());
-                    archiveOutputStream.putArchiveEntry(archiveEntry);
-                    IOUtils.copy(new ByteArrayInputStream(entry.getValue()), archiveOutputStream);
-                    archiveOutputStream.closeArchiveEntry();
+            for (SimpleImmutableEntry<String, byte[]> entry : entries) {
+                ZipArchiveEntry archiveEntry = (ZipArchiveEntry) archiveOutputStream.createArchiveEntry(new File("any"), entry.getKey());
+                archiveOutputStream.putArchiveEntry(archiveEntry);
+                IOUtils.copy(new ByteArrayInputStream(entry.getValue()), archiveOutputStream);
+                archiveOutputStream.closeArchiveEntry();
             }
             archiveOutputStream.finish();
         }
+
+        return new ZipFile(destination);
     }
 
     @Test
     public void hasNoEntryShouldNotThrowWhenEmpty() throws Exception {
-       buildZipFile();
-
-        try (ZipFile zipFile = new ZipFile(destination)) {
+        try (ZipFile zipFile = buildZipFile()) {
             assertThatCode(() -> assertThatZip(zipFile)
                 .hasNoEntry())
                 .doesNotThrowAnyException();
@@ -87,9 +145,7 @@ public class ZipAssertTest {
 
     @Test
     public void hasNoEntryShouldThrowWhenNotEmpty() throws Exception {
-        buildZipFile(ENTRY);
-
-        try (ZipFile zipFile = new ZipFile(destination)) {
+        try (ZipFile zipFile = buildZipFile(ENTRY)) {
             assertThatThrownBy(() -> assertThatZip(zipFile)
                     .hasNoEntry())
                 .isInstanceOf(AssertionError.class);
@@ -98,9 +154,7 @@ public class ZipAssertTest {
 
     @Test
     public void containsExactlyEntriesMatchingShouldNotThrowWhenBothEmpty() throws Exception {
-        buildZipFile();
-
-        try (ZipFile zipFile = new ZipFile(destination)) {
+        try (ZipFile zipFile = buildZipFile()) {
             assertThatCode(() -> assertThatZip(zipFile)
                     .containsOnlyEntriesMatching())
                 .doesNotThrowAnyException();
@@ -109,9 +163,7 @@ public class ZipAssertTest {
 
     @Test
     public void containsExactlyEntriesMatchingShouldNotThrowWhenRightOrder() throws Exception {
-        buildZipFile(ENTRY, ENTRY_2);
-
-        try (ZipFile zipFile = new ZipFile(destination)) {
+        try (ZipFile zipFile = buildZipFile(ENTRY, ENTRY_2)) {
             assertThatCode(() -> assertThatZip(zipFile)
                     .containsOnlyEntriesMatching(
                         hasName(ENTRY_NAME),
@@ -122,9 +174,7 @@ public class ZipAssertTest {
 
     @Test
     public void hasNameShouldThrowWhenWrongName() throws Exception {
-        buildZipFile(ENTRY);
-
-        try (ZipFile zipFile = new ZipFile(destination)) {
+        try (ZipFile zipFile = buildZipFile(ENTRY)) {
             assertThatThrownBy(() -> assertThatZip(zipFile)
                     .containsOnlyEntriesMatching(
                         hasName(ENTRY_NAME_2)))
@@ -134,9 +184,7 @@ public class ZipAssertTest {
 
     @Test
     public void isDirectoryShouldThrowWhenNotADirectory() throws Exception {
-        buildZipFile(ENTRY);
-
-        try (ZipFile zipFile = new ZipFile(destination)) {
+        try (ZipFile zipFile = buildZipFile(ENTRY)) {
             assertThatThrownBy(() -> assertThatZip(zipFile)
                     .containsOnlyEntriesMatching(
                         hasName(ENTRY_NAME)
@@ -166,9 +214,7 @@ public class ZipAssertTest {
 
     @Test
     public void containsExactlyEntriesMatchingShouldNotThrowWhenWrongOrder() throws Exception {
-        buildZipFile(ENTRY, ENTRY_2);
-
-        try (ZipFile zipFile = new ZipFile(destination)) {
+        try (ZipFile zipFile = buildZipFile(ENTRY, ENTRY_2)) {
             assertThatCode(() -> assertThatZip(zipFile)
                     .containsOnlyEntriesMatching(
                         hasName(ENTRY_NAME),
@@ -179,9 +225,7 @@ public class ZipAssertTest {
 
     @Test
     public void containsExactlyEntriesMatchingShouldThrowWhenExpectingMoreEntries() throws Exception {
-        buildZipFile(ENTRY, ENTRY_2);
-
-        try (ZipFile zipFile = new ZipFile(destination)) {
+        try (ZipFile zipFile = buildZipFile(ENTRY, ENTRY_2)) {
             assertThatThrownBy(() -> assertThatZip(zipFile)
                     .containsOnlyEntriesMatching(
                         hasName(ENTRY_NAME),
@@ -193,9 +237,7 @@ public class ZipAssertTest {
 
     @Test
     public void containsExactlyEntriesMatchingShouldThrowWhenExpectingLessEntries() throws Exception {
-        buildZipFile(ENTRY, ENTRY_2);
-
-        try (ZipFile zipFile = new ZipFile(destination)) {
+        try (ZipFile zipFile = buildZipFile(ENTRY, ENTRY_2)) {
             assertThatThrownBy(() -> assertThatZip(zipFile)
                     .containsOnlyEntriesMatching(
                         hasName(ENTRY_NAME)))
@@ -205,9 +247,7 @@ public class ZipAssertTest {
 
     @Test
     public void hasStringContentShouldNotThrowWhenIdentical() throws Exception {
-        buildZipFile(ENTRY);
-
-        try (ZipFile zipFile = new ZipFile(destination)) {
+        try (ZipFile zipFile = buildZipFile(ENTRY)) {
             assertThatCode(() -> assertThatZip(zipFile)
                 .containsOnlyEntriesMatching(
                     hasName(ENTRY_NAME)
@@ -218,9 +258,7 @@ public class ZipAssertTest {
 
     @Test
     public void hasStringContentShouldThrowWhenDifferent() throws Exception {
-        buildZipFile(ENTRY);
-
-        try (ZipFile zipFile = new ZipFile(destination)) {
+        try (ZipFile zipFile = buildZipFile(ENTRY)) {
             assertThatThrownBy(() -> assertThatZip(zipFile)
                 .containsOnlyEntriesMatching(
                     hasName(ENTRY_NAME)
@@ -231,9 +269,7 @@ public class ZipAssertTest {
 
     @Test
     public void containsExactlyExtraFieldsShouldNotThrowWhenBothEmpty() throws Exception {
-        buildZipFile(ENTRY);
-
-        try (ZipFile zipFile = new ZipFile(destination)) {
+        try (ZipFile zipFile = buildZipFile(ENTRY)) {
             assertThatCode(() -> assertThatZip(zipFile)
                 .containsOnlyEntriesMatching(
                     hasName(ENTRY_NAME)
@@ -244,9 +280,7 @@ public class ZipAssertTest {
 
     @Test
     public void containsExactlyExtraFieldsShouldThrowWhenMissingExpectedField() throws Exception {
-        buildZipFile(ENTRY);
-
-        try (ZipFile zipFile = new ZipFile(destination)) {
+        try (ZipFile zipFile = buildZipFile(ENTRY)) {
             assertThatThrownBy(() -> assertThatZip(zipFile)
                 .containsOnlyEntriesMatching(
                     hasName(ENTRY_NAME)
@@ -298,4 +332,136 @@ public class ZipAssertTest {
                 .doesNotThrowAnyException();
         }
     }
+
+    @Nested
+    class HasSameContentTest {
+
+        @Test
+        void hasSameContentShouldThrowWhenExpectedZipFileIsNull() throws Exception {
+            try (ZipFile assertedZipFile = zipFile(destination, entryBuilder().name(ENTRY_NAME).content(ENTRY_CONTENT).build())) {
+                ZipFile expectedZipFile = null;
+                assertThatThrownBy(() -> assertThatZip(assertedZipFile)
+                    .hasSameContentWith(expectedZipFile))
+                    .isInstanceOf(AssertionError.class);
+            }
+        }
+
+        @Test
+        void hasSameContentShouldThrowWhenAssertedZipFileIsNull() throws Exception {
+            try (ZipFile expectedZipFile = zipFile(destination, entryBuilder().name(ENTRY_NAME).content(ENTRY_CONTENT).build())) {
+                ZipFile assertedZipFile = null;
+                assertThatThrownBy(() -> assertThatZip(assertedZipFile)
+                    .hasSameContentWith(expectedZipFile))
+                    .isInstanceOf(AssertionError.class);
+            }
+        }
+
+        @Test
+        void hasSameContentShouldThrowWhenAssertedZipFileHasDifferentSizeWithExpectedZipFile() throws Exception {
+            ZipEntryWithContent sameEntry = entryBuilder().name(ENTRY_NAME).content(ENTRY_CONTENT).build();
+            ZipEntryWithContent additionalEntry = entryBuilder().name(ENTRY_NAME_2).content(ENTRY_CONTENT).build();
+
+            try (ZipFile expectedZipFile = zipFile(destination, sameEntry);
+                    ZipFile assertedZipFile = zipFile(destination2, sameEntry, additionalEntry)) {
+                assertThatThrownBy(() -> assertThatZip(assertedZipFile)
+                    .hasSameContentWith(expectedZipFile))
+                    .isInstanceOf(AssertionError.class);
+            }
+        }
+
+        @Test
+        void hasSameContentShouldThrowWhenAssertedEntriesHaveDifferentContent() throws Exception {
+            try (ZipFile expectedZipFile = zipFile(destination, entryBuilder().name(ENTRY_NAME).content(ENTRY_CONTENT).build());
+                    ZipFile assertedZipFile = zipFile(destination2, entryBuilder().name(ENTRY_NAME).content(ENTRY_CONTENT_2).build())) {
+                assertThatThrownBy(() -> assertThatZip(assertedZipFile)
+                    .hasSameContentWith(expectedZipFile))
+                    .isInstanceOf(AssertionError.class);
+            }
+        }
+
+        @Test
+        void hasSameContentShouldThrowWhenAssertedEntriesHaveDifferentNames() throws Exception {
+            try (ZipFile expectedZipFile = zipFile(destination, entryBuilder().name(ENTRY_NAME).content(ENTRY_CONTENT).build());
+                    ZipFile assertedZipFile = zipFile(destination2, entryBuilder().name(ENTRY_NAME_2).content(ENTRY_CONTENT).build())) {
+                assertThatThrownBy(() -> assertThatZip(assertedZipFile)
+                    .hasSameContentWith(expectedZipFile))
+                    .isInstanceOf(AssertionError.class);
+            }
+        }
+
+        @Test
+        void hasSameContentShouldThrowWhenEntryHasDifferentExtraFieldsSize() throws Exception {
+            ZipEntryWithContent expectedEntry = entryBuilder().name(ENTRY_NAME).content(ENTRY_CONTENT)
+                .addField(new UidExtraField(1L))
+                .addField(new UidExtraField(2L))
+                .build();
+
+            ZipEntryWithContent assertedEntry = entryBuilder().name(ENTRY_NAME).content(ENTRY_CONTENT)
+                .addField(new UidExtraField(1L))
+                .build();
+
+            try (ZipFile expectedZipFile = zipFile(destination, expectedEntry);
+                    ZipFile assertedZipFile = zipFile(destination2, assertedEntry)) {
+                assertThatThrownBy(() -> assertThatZip(assertedZipFile)
+                    .hasSameContentWith(expectedZipFile))
+                    .isInstanceOf(AssertionError.class);
+            }
+        }
+
+        @Test
+        void hasSameContentShouldThrowWhenEntryHasSameExtraFieldsSizeButDifferentOrder() throws Exception {
+            ZipEntryWithContent expectedEntry = entryBuilder().name(ENTRY_NAME).content(ENTRY_CONTENT)
+                .addField(new UidExtraField(1L))
+                .addField(new SizeExtraField(2L))
+                .build();
+
+            ZipEntryWithContent assertedEntry = entryBuilder().name(ENTRY_NAME).content(ENTRY_CONTENT)
+                .addField(new SizeExtraField(2L))
+                .addField(new UidExtraField(1L))
+                .build();
+
+            try (ZipFile expectedZipFile = zipFile(destination, expectedEntry);
+                    ZipFile assertedZipFile = zipFile(destination2, assertedEntry)) {
+                assertThatThrownBy(() -> assertThatZip(assertedZipFile)
+                    .hasSameContentWith(expectedZipFile))
+                    .isInstanceOf(AssertionError.class);
+            }
+        }
+
+        @Test
+        void hasSameContentShouldThrowWhenEntryHasSameExtraFieldsSizeAndOrder() throws Exception {
+            ZipEntryWithContent expectedEntry = entryBuilder().name(ENTRY_NAME).content(ENTRY_CONTENT)
+                .addField(new UidExtraField(1L))
+                .addField(new SizeExtraField(2L))
+                .build();
+
+            ZipEntryWithContent assertedEntry = entryBuilder().name(ENTRY_NAME).content(ENTRY_CONTENT)
+                .addField(new UidExtraField(1L))
+                .addField(new SizeExtraField(2L))
+                .build();
+
+            try (ZipFile expectedZipFile = zipFile(destination, expectedEntry);
+                    ZipFile assertedZipFile = zipFile(destination2, assertedEntry)) {
+                assertThatCode(() -> assertThatZip(assertedZipFile)
+                    .hasSameContentWith(expectedZipFile))
+                    .doesNotThrowAnyException();
+            }
+        }
+
+        private ZipFile zipFile(File destination, ZipEntryWithContent...entries) throws Exception {
+            try (ZipArchiveOutputStream archiveOutputStream = new ZipArchiveOutputStream(destination)) {
+                for (ZipEntryWithContent entry : entries) {
+                    ZipArchiveEntry archiveEntry = (ZipArchiveEntry) archiveOutputStream.createArchiveEntry(new File("any"), entry.name);
+                    entry.extraFields.
+                        forEach(archiveEntry::addExtraField);
+                    archiveOutputStream.putArchiveEntry(archiveEntry);
+                    IOUtils.copy(entry.content, archiveOutputStream);
+                    archiveOutputStream.closeArchiveEntry();
+                }
+                archiveOutputStream.finish();
+            }
+
+            return new ZipFile(destination);
+        }
+    }
 }


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


[james-project] 01/04: JAMES-2686 grafana board for predeletion hooks execution times

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 83b7a56c86733c7c040e949b34cf65d0c8f406ce
Author: Rene Cordier <rc...@linagora.com>
AuthorDate: Thu Mar 28 10:21:16 2019 +0700

    JAMES-2686 grafana board for predeletion hooks execution times
---
 .../PreDeletionHooks-1553684324244-dashboard.json  | 187 +++++++++++++++++++++
 grafana-reporting/README.md                        |   1 +
 src/site/xdoc/server/metrics.xml                   |   1 +
 3 files changed, 189 insertions(+)

diff --git a/grafana-reporting/PreDeletionHooks-1553684324244-dashboard.json b/grafana-reporting/PreDeletionHooks-1553684324244-dashboard.json
new file mode 100644
index 0000000..71369c8
--- /dev/null
+++ b/grafana-reporting/PreDeletionHooks-1553684324244-dashboard.json
@@ -0,0 +1,187 @@
+{
+  "annotations": {
+    "list": [
+      {
+        "builtIn": 1,
+        "datasource": "-- Grafana --",
+        "enable": true,
+        "hide": true,
+        "iconColor": "rgba(0, 211, 255, 1)",
+        "name": "Annotations & Alerts",
+        "type": "dashboard"
+      }
+    ]
+  },
+  "editable": true,
+  "gnetId": null,
+  "graphTooltip": 0,
+  "id": 6,
+  "links": [],
+  "panels": [
+    {
+      "aliasColors": {},
+      "bars": false,
+      "dashLength": 10,
+      "dashes": false,
+      "datasource": "Elasticsearch",
+      "fill": 1,
+      "gridPos": {
+        "h": 8,
+        "w": 12,
+        "x": 0,
+        "y": 0
+      },
+      "id": 2,
+      "legend": {
+        "avg": false,
+        "current": false,
+        "max": false,
+        "min": false,
+        "show": true,
+        "total": false,
+        "values": false
+      },
+      "lines": true,
+      "linewidth": 1,
+      "links": [],
+      "nullPointMode": "null",
+      "paceLength": 10,
+      "percentage": false,
+      "pointradius": 2,
+      "points": false,
+      "renderer": "flot",
+      "seriesOverrides": [],
+      "stack": false,
+      "steppedLine": false,
+      "targets": [
+        {
+          "bucketAggs": [
+            {
+              "field": "@timestamp",
+              "id": "2",
+              "settings": {
+                "interval": "auto",
+                "min_doc_count": 0,
+                "trimEdges": 0
+              },
+              "type": "date_histogram"
+            }
+          ],
+          "metrics": [
+            {
+              "field": "p99",
+              "id": "1",
+              "meta": {},
+              "settings": {},
+              "type": "avg"
+            },
+            {
+              "field": "p95",
+              "id": "3",
+              "meta": {},
+              "settings": {},
+              "type": "avg"
+            },
+            {
+              "field": "p75",
+              "id": "4",
+              "meta": {},
+              "settings": {},
+              "type": "avg"
+            },
+            {
+              "field": "p50",
+              "id": "5",
+              "meta": {},
+              "settings": {},
+              "type": "avg"
+            }
+          ],
+          "query": "name:preDeletionHook",
+          "refId": "A",
+          "timeField": "@timestamp"
+        }
+      ],
+      "thresholds": [],
+      "timeFrom": null,
+      "timeRegions": [],
+      "timeShift": null,
+      "title": "PreDeletionHook",
+      "tooltip": {
+        "shared": true,
+        "sort": 0,
+        "value_type": "individual"
+      },
+      "type": "graph",
+      "xaxis": {
+        "buckets": null,
+        "mode": "time",
+        "name": null,
+        "show": true,
+        "values": []
+      },
+      "yaxes": [
+        {
+          "format": "ms",
+          "label": null,
+          "logBase": 1,
+          "max": null,
+          "min": null,
+          "show": true
+        },
+        {
+          "format": "short",
+          "label": null,
+          "logBase": 1,
+          "max": null,
+          "min": null,
+          "show": true
+        }
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
+    }
+  ],
+  "refresh": false,
+  "schemaVersion": 18,
+  "style": "dark",
+  "tags": [],
+  "templating": {
+    "list": []
+  },
+  "time": {
+    "from": "now-6h",
+    "to": "now"
+  },
+  "timepicker": {
+    "refresh_intervals": [
+      "5s",
+      "10s",
+      "30s",
+      "1m",
+      "5m",
+      "15m",
+      "30m",
+      "1h",
+      "2h",
+      "1d"
+    ],
+    "time_options": [
+      "5m",
+      "15m",
+      "1h",
+      "6h",
+      "12h",
+      "24h",
+      "2d",
+      "7d",
+      "30d"
+    ]
+  },
+  "timezone": "",
+  "title": "PreDeletionHooks",
+  "uid": "ntIRCz6mk",
+  "version": 2
+}
\ No newline at end of file
diff --git a/grafana-reporting/README.md b/grafana-reporting/README.md
index 73bac4c..7201b91 100644
--- a/grafana-reporting/README.md
+++ b/grafana-reporting/README.md
@@ -37,3 +37,4 @@ Note that you need to run a guice version of James.
  - Mailbox listeners statistics
  - MailQueue enqueue/dequeue timer & counter statistics
  - BlobStore timer statistics
+ - Statistics about pre-deletion hooks execution times
diff --git a/src/site/xdoc/server/metrics.xml b/src/site/xdoc/server/metrics.xml
index 433d62f..d9fa171 100644
--- a/src/site/xdoc/server/metrics.xml
+++ b/src/site/xdoc/server/metrics.xml
@@ -66,6 +66,7 @@
                     <li>Tika HTTP client statistics</li>
                     <li>SpamAssassin TCP client statistics</li>
                     <li>Mailbox listeners statistics time percentiles</li>
+                    <li>Pre-deletion hooks execution statistics time percentiles</li>
                 </ul>
 
                 Retrieve <a href="https://github.com/apache/james-project/tree/master/grafana-reporting">available boards</a> for Grafana.


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


[james-project] 03/04: JAMES-2688 DeletedMessageVault Export API Integration Tests

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 9738e5dd547076471912d3046888d86b2fa9e592
Author: Tran Tien Duc <dt...@linagora.com>
AuthorDate: Mon Mar 25 15:34:41 2019 +0700

    JAMES-2688 DeletedMessageVault Export API Integration Tests
---
 .../org/apache/james/mailbox/backup/ZipAssert.java |  23 ++-
 .../cassandra-jmap-integration-testing/pom.xml     |   5 +
 .../CassandraDeletedMessageVaultTest.java          |   6 +-
 .../jmap-integration-testing-common/pom.xml        |   6 +
 .../integration/DeletedMessagesVaultTest.java      | 229 ++++++++++++++++++++-
 .../memory-jmap-integration-testing/pom.xml        |   5 +
 ...FileMailRepositoryDeletedMessagesVaultTest.java |   6 +-
 .../memory/MemoryDeletedMessagesVaultTest.java     |   6 +-
 .../rabbitmq-jmap-integration-testing/pom.xml      |   5 +
 .../rabbitmq/RabbitMQDeletedMessagesVaultTest.java |   6 +-
 10 files changed, 281 insertions(+), 16 deletions(-)

diff --git a/mailbox/backup/src/test/java/org/apache/james/mailbox/backup/ZipAssert.java b/mailbox/backup/src/test/java/org/apache/james/mailbox/backup/ZipAssert.java
index 9f0c9b0..4721ffd 100644
--- a/mailbox/backup/src/test/java/org/apache/james/mailbox/backup/ZipAssert.java
+++ b/mailbox/backup/src/test/java/org/apache/james/mailbox/backup/ZipAssert.java
@@ -36,6 +36,7 @@ import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
 import org.apache.commons.compress.archivers.zip.ZipExtraField;
 import org.apache.commons.compress.archivers.zip.ZipFile;
 import org.apache.commons.compress.utils.SeekableInMemoryByteChannel;
+import org.apache.commons.io.IOUtils;
 import org.assertj.core.api.AbstractAssert;
 import org.assertj.core.error.BasicErrorMessageFactory;
 import org.assertj.core.error.ErrorMessageFactory;
@@ -91,6 +92,14 @@ public class ZipAssert extends AbstractAssert<ZipAssert, ZipFile> implements Aut
         return assertThatZip(new ZipFile(new SeekableInMemoryByteChannel(outputStream.toByteArray())));
     }
 
+    public static ZipAssert assertThatZip(InputStream inputStream) throws IOException {
+        return assertThatZip(zipFileFromInputStream(inputStream));
+    }
+
+    private static ZipFile zipFileFromInputStream(InputStream inputStream) throws IOException {
+        return new ZipFile(new SeekableInMemoryByteChannel(IOUtils.toByteArray(inputStream)));
+    }
+
     private static BasicErrorMessageFactory shouldHaveSize(ZipFile zipFile, int expected, int actual) {
         return new BasicErrorMessageFactory("%nExpecting %s to have size %s but was %s", zipFile, expected, actual);
     }
@@ -104,12 +113,12 @@ public class ZipAssert extends AbstractAssert<ZipAssert, ZipFile> implements Aut
     }
 
     private static BasicErrorMessageFactory shouldHaveSameEntriesSize(List<ZipArchiveEntry> entries, List<ZipArchiveEntry> expectedEntries) {
-        return new BasicErrorMessageFactory("%nExpecting zipFile entries has size (%s) but actually (%s)", expectedEntries.size(), entries.size());
+        return new BasicErrorMessageFactory("%nExpecting zipFile to contains %s entries (%s) but actually contains (%s)",
+            expectedEntries.size(), expectedEntries, entries);
     }
 
-    private static ErrorMessageFactory entriesShouldHaveSameContentAt(int entryIndex, String entryContentAssertionError) {
-        return new BasicErrorMessageFactory("%nExpecting zipFile entry at index %s have same content. Details: \n%s",
-            entryIndex, entryContentAssertionError);
+    private static ErrorMessageFactory entriesShouldHaveSameContentAt(int entryIndex) {
+        return new BasicErrorMessageFactory("%nExpecting zipFile entry at index %s has same content", entryIndex);
     }
 
     private static ErrorMessageFactory entriesShouldHaveSameName(ZipArchiveEntry entry, ZipArchiveEntry expectedEntry, int entryIndex) {
@@ -160,6 +169,10 @@ public class ZipAssert extends AbstractAssert<ZipAssert, ZipFile> implements Aut
         zipFile.close();
     }
 
+    public ZipAssert hasSameContentWith(InputStream inputStream) throws IOException {
+        return hasSameContentWith(zipFileFromInputStream(inputStream));
+    }
+
     public ZipAssert hasSameContentWith(ZipFile anotherZipFile) throws IOException {
         validateNonNull(zipFile);
         validateNonNull(anotherZipFile);
@@ -204,7 +217,7 @@ public class ZipAssert extends AbstractAssert<ZipAssert, ZipFile> implements Aut
             assertThat(entryContent)
                 .hasSameContentAs(expectingEntryContent);
         } catch (AssertionError assertionError) {
-            throwAssertionError(entriesShouldHaveSameContentAt(entryIndex, assertionError.getMessage()));
+            throwAssertionError(entriesShouldHaveSameContentAt(entryIndex));
         }
     }
 
diff --git a/server/protocols/jmap-integration-testing/cassandra-jmap-integration-testing/pom.xml b/server/protocols/jmap-integration-testing/cassandra-jmap-integration-testing/pom.xml
index fded2b6..82f0d44 100644
--- a/server/protocols/jmap-integration-testing/cassandra-jmap-integration-testing/pom.xml
+++ b/server/protocols/jmap-integration-testing/cassandra-jmap-integration-testing/pom.xml
@@ -82,6 +82,11 @@
         </dependency>
         <dependency>
             <groupId>${james.groupId}</groupId>
+            <artifactId>backup</artifactId>
+            <type>test-jar</type>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
             <artifactId>blob-objectstorage-guice</artifactId>
             <scope>test</scope>
         </dependency>
diff --git a/server/protocols/jmap-integration-testing/cassandra-jmap-integration-testing/src/test/java/org/apache/james/jmap/cassandra/CassandraDeletedMessageVaultTest.java b/server/protocols/jmap-integration-testing/cassandra-jmap-integration-testing/src/test/java/org/apache/james/jmap/cassandra/CassandraDeletedMessageVaultTest.java
index 1f331c4..9cbc0ec 100644
--- a/server/protocols/jmap-integration-testing/cassandra-jmap-integration-testing/src/test/java/org/apache/james/jmap/cassandra/CassandraDeletedMessageVaultTest.java
+++ b/server/protocols/jmap-integration-testing/cassandra-jmap-integration-testing/src/test/java/org/apache/james/jmap/cassandra/CassandraDeletedMessageVaultTest.java
@@ -24,6 +24,7 @@ import java.io.IOException;
 import org.apache.james.CassandraJmapTestRule;
 import org.apache.james.DockerCassandraRule;
 import org.apache.james.GuiceJamesServer;
+import org.apache.james.filesystem.api.FileSystem;
 import org.apache.james.jmap.methods.integration.DeletedMessagesVaultTest;
 import org.apache.james.mailrepository.api.MailRepositoryUrl;
 import org.apache.james.modules.mailbox.PreDeletionHookConfiguration;
@@ -42,14 +43,15 @@ public class CassandraDeletedMessageVaultTest extends DeletedMessagesVaultTest {
     public CassandraJmapTestRule rule = CassandraJmapTestRule.defaultTestRule();
     
     @Override
-    protected GuiceJamesServer createJmapServer() throws IOException {
+    protected GuiceJamesServer createJmapServer(FileSystem fileSystem) throws IOException {
         return rule.jmapServer(cassandra.getModule(),
             binder -> binder.bind(PreDeletionHooksConfiguration.class)
                 .toInstance(PreDeletionHooksConfiguration.forHooks(
                     PreDeletionHookConfiguration.forClass(DeletedMessageVaultHook.class))),
             binder -> binder.bind(WebAdminConfiguration.class).toInstance(WebAdminConfiguration.TEST_CONFIGURATION),
             binder -> binder.bind(MailRepositoryDeletedMessageVault.Configuration.class)
-                .toInstance(new MailRepositoryDeletedMessageVault.Configuration(MailRepositoryUrl.from("cassandra://var/deletedMessages/user"))));
+                .toInstance(new MailRepositoryDeletedMessageVault.Configuration(MailRepositoryUrl.from("cassandra://var/deletedMessages/user"))),
+            binder -> binder.bind(FileSystem.class).toInstance(fileSystem));
     }
 
     @Override
diff --git a/server/protocols/jmap-integration-testing/jmap-integration-testing-common/pom.xml b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/pom.xml
index c1bca3d..5925135 100644
--- a/server/protocols/jmap-integration-testing/jmap-integration-testing-common/pom.xml
+++ b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/pom.xml
@@ -52,6 +52,12 @@
         </dependency>
         <dependency>
             <groupId>${james.groupId}</groupId>
+            <artifactId>backup</artifactId>
+            <scope>test</scope>
+            <type>test-jar</type>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
             <artifactId>james-server-data-jmap</artifactId>
             <scope>test</scope>
         </dependency>
diff --git a/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/DeletedMessagesVaultTest.java b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/DeletedMessagesVaultTest.java
index 109fa2b..7807d7b 100644
--- a/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/DeletedMessagesVaultTest.java
+++ b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/DeletedMessagesVaultTest.java
@@ -22,6 +22,7 @@ package org.apache.james.jmap.methods.integration;
 import static io.restassured.RestAssured.given;
 import static io.restassured.RestAssured.with;
 import static org.apache.james.jmap.HttpJmapAuthentication.authenticateJamesUser;
+import static org.apache.james.jmap.JmapCommonRequests.getLastMessageId;
 import static org.apache.james.jmap.JmapCommonRequests.getOutboxId;
 import static org.apache.james.jmap.JmapCommonRequests.listMessageIdsForAccount;
 import static org.apache.james.jmap.JmapURIBuilder.baseUri;
@@ -30,6 +31,8 @@ import static org.apache.james.jmap.TestingConstants.DOMAIN;
 import static org.apache.james.jmap.TestingConstants.LOCALHOST_IP;
 import static org.apache.james.jmap.TestingConstants.calmlyAwait;
 import static org.apache.james.jmap.TestingConstants.jmapRequestSpecBuilder;
+import static org.apache.james.mailbox.backup.ZipAssert.EntryChecks.hasName;
+import static org.apache.james.mailbox.backup.ZipAssert.assertThatZip;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.hamcrest.Matchers.hasItem;
 import static org.hamcrest.Matchers.is;
@@ -39,14 +42,18 @@ import java.util.List;
 import java.util.stream.Collectors;
 
 import org.apache.james.GuiceJamesServer;
+import org.apache.james.filesystem.api.FileSystem;
 import org.apache.james.jmap.api.access.AccessToken;
 import org.apache.james.jmap.categories.BasicFeature;
 import org.apache.james.mailbox.DefaultMailboxes;
+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.modules.MailboxProbeImpl;
 import org.apache.james.modules.protocols.ImapGuiceProbe;
 import org.apache.james.probe.DataProbe;
+import org.apache.james.server.core.JamesServerResourceLoader;
+import org.apache.james.server.core.filesystem.FileSystemImpl;
 import org.apache.james.utils.DataProbeImpl;
 import org.apache.james.utils.IMAPMessageReader;
 import org.apache.james.utils.JmapGuiceProbe;
@@ -59,6 +66,7 @@ import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.experimental.categories.Category;
+import org.junit.rules.TemporaryFolder;
 
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableList;
@@ -68,6 +76,39 @@ import io.restassured.parsing.Parser;
 import io.restassured.specification.RequestSpecification;
 
 public abstract class DeletedMessagesVaultTest {
+
+    private static class ExportRequest {
+
+        private static class Builder {
+
+            @FunctionalInterface
+            interface RequireSharee {
+                RequireMatchingQuery exportTo(String sharee);
+            }
+
+            @FunctionalInterface
+            interface RequireMatchingQuery {
+                ExportRequest query(String query);
+            }
+        }
+
+        private static Builder.RequireSharee userExportFrom(String userExportFrom) {
+            return sharee -> query -> new ExportRequest(userExportFrom, sharee, query);
+        }
+
+        private final String userExportFrom;
+        private final String sharee;
+        private final String matchingQuery;
+
+        private ExportRequest(String userExportFrom, String sharee, String matchingQuery) {
+            this.userExportFrom = userExportFrom;
+            this.sharee = sharee;
+            this.matchingQuery = matchingQuery;
+        }
+    }
+
+    private static final String FIRST_SUBJECT = "first subject";
+    private static final String SECOND_SUBJECT = "second subject";
     private static final String HOMER = "homer@" + DOMAIN;
     private static final String BART = "bart@" + DOMAIN;
     private static final String PASSWORD = "password";
@@ -79,24 +120,32 @@ public abstract class DeletedMessagesVaultTest {
         "\"combinator\": \"and\"," +
         "\"criteria\": []" +
         "}";
+    private static final ExportRequest EXPORT_ALL_HOMER_MESSAGES_TO_BART = ExportRequest
+        .userExportFrom(HOMER)
+        .exportTo(BART)
+        .query(MATCH_ALL_QUERY);
 
     private MailboxId otherMailboxId;
 
-    protected abstract GuiceJamesServer createJmapServer() throws IOException;
+    protected abstract GuiceJamesServer createJmapServer(FileSystem fileSystem) throws IOException;
 
     protected abstract void awaitSearchUpToDate();
 
     @Rule
     public IMAPMessageReader imapMessageReader = new IMAPMessageReader();
+    @Rule
+    public TemporaryFolder tempFolder = new TemporaryFolder();
 
     private AccessToken homerAccessToken;
     private AccessToken bartAccessToken;
     private GuiceJamesServer jmapServer;
     private RequestSpecification webAdminApi;
+    private FileSystem fileSystem;
 
     @Before
     public void setup() throws Throwable {
-        jmapServer = createJmapServer();
+        fileSystem = new FileSystemImpl(new JamesServerResourceLoader(tempFolder.getRoot().getPath()));
+        jmapServer = createJmapServer(fileSystem);
         jmapServer.start();
         MailboxProbe mailboxProbe = jmapServer.getProbe(MailboxProbeImpl.class);
         DataProbe dataProbe = jmapServer.getProbe(DataProbeImpl.class);
@@ -118,7 +167,7 @@ public abstract class DeletedMessagesVaultTest {
     }
 
     @After
-    public void tearDown() {
+    public void tearDown() throws Exception {
         jmapServer.stop();
     }
 
@@ -405,6 +454,166 @@ public abstract class DeletedMessagesVaultTest {
             .body(ARGUMENTS + ".list.subject", hasItem(SUBJECT));
     }
 
+    @Category(BasicFeature.class)
+    @Test
+    public void vaultExportShouldExportZipContainsVaultMessagesToShareeWhenJmapDeleteMessage() throws Exception {
+        bartSendMessageToHomer();
+        WAIT_TWO_MINUTES.until(() -> listMessageIdsForAccount(homerAccessToken).size() == 1);
+        String messageIdOfHomer = listMessageIdsForAccount(homerAccessToken).get(0);
+
+        homerDeletesMessages(listMessageIdsForAccount(homerAccessToken));
+        WAIT_TWO_MINUTES.until(() -> listMessageIdsForAccount(homerAccessToken).size() == 0);
+
+        String fileLocation = exportAndGetFileLocationFromLastMail(EXPORT_ALL_HOMER_MESSAGES_TO_BART, bartAccessToken);
+
+        try (ZipAssert zipAssert = assertThatZip(fileSystem.getResource(fileLocation))) {
+            zipAssert.containsOnlyEntriesMatching(hasName(messageIdOfHomer));
+        }
+    }
+
+    @Category(BasicFeature.class)
+    @Test
+    public void vaultExportShouldExportZipContainsVaultMessagesToShareeWhenImapDeleteMessage() throws Exception {
+        bartSendMessageToHomer();
+        WAIT_TWO_MINUTES.until(() -> listMessageIdsForAccount(homerAccessToken).size() == 1);
+        String messageIdOfHomer = listMessageIdsForAccount(homerAccessToken).get(0);
+
+        imapMessageReader.connect(LOCALHOST_IP, jmapServer.getProbe(ImapGuiceProbe.class).getImapPort())
+            .login(HOMER, PASSWORD)
+            .select(IMAPMessageReader.INBOX)
+            .setFlagsForAllMessagesInMailbox("\\Deleted");
+        imapMessageReader.expunge();
+
+        WAIT_TWO_MINUTES.until(() -> listMessageIdsForAccount(homerAccessToken).size() == 0);
+
+        String fileLocation = exportAndGetFileLocationFromLastMail(EXPORT_ALL_HOMER_MESSAGES_TO_BART, bartAccessToken);
+
+        try (ZipAssert zipAssert = assertThatZip(fileSystem.getResource(fileLocation))) {
+            zipAssert.containsOnlyEntriesMatching(hasName(messageIdOfHomer));
+        }
+    }
+
+    @Category(BasicFeature.class)
+    @Test
+    public void vaultExportShouldExportZipContainsVaultMessagesToShareeWhenImapDeletedMailbox() throws Exception {
+        bartSendMessageToHomer();
+        WAIT_TWO_MINUTES.until(() -> listMessageIdsForAccount(homerAccessToken).size() == 1);
+        String messageIdOfHomer = listMessageIdsForAccount(homerAccessToken).get(0);
+
+        imapMessageReader.connect(LOCALHOST_IP, jmapServer.getProbe(ImapGuiceProbe.class).getImapPort())
+            .login(HOMER, PASSWORD)
+            .select(IMAPMessageReader.INBOX);
+
+        imapMessageReader.moveFirstMessage(MAILBOX_NAME);
+
+        imapMessageReader.delete(MAILBOX_NAME);
+
+        WAIT_TWO_MINUTES.until(() -> listMessageIdsForAccount(homerAccessToken).size() == 0);
+
+        String fileLocation = exportAndGetFileLocationFromLastMail(EXPORT_ALL_HOMER_MESSAGES_TO_BART, bartAccessToken);
+
+        try (ZipAssert zipAssert = assertThatZip(fileSystem.getResource(fileLocation))) {
+            zipAssert.containsOnlyEntriesMatching(hasName(messageIdOfHomer));
+        }
+    }
+
+    @Test
+    public void vaultExportShouldExportZipContainsOnlyMatchedMessages() throws Exception {
+        bartSendMessageToHomerWithSubject(FIRST_SUBJECT);
+        WAIT_TWO_MINUTES.until(() -> listMessageIdsForAccount(homerAccessToken).size() == 1);
+        String firstMessageIdOfHomer = listMessageIdsForAccount(homerAccessToken).get(0);
+
+        bartSendMessageToHomerWithSubject(SECOND_SUBJECT);
+        WAIT_TWO_MINUTES.until(() -> listMessageIdsForAccount(homerAccessToken).size() == 2);
+        String secondMessageIdOfHomer = listMessageIdsForAccount(homerAccessToken).get(1);
+
+        homerDeletesMessages(listMessageIdsForAccount(homerAccessToken));
+        WAIT_TWO_MINUTES.until(() -> listMessageIdsForAccount(homerAccessToken).size() == 0);
+
+        ExportRequest exportRequest = ExportRequest
+            .userExportFrom(HOMER)
+            .exportTo(BART)
+            .query("{" +
+                "  \"fieldName\": \"subject\"," +
+                "  \"operator\": \"equals\"," +
+                "  \"value\": \"" + FIRST_SUBJECT + "\"" +
+                "}");
+        String fileLocation = exportAndGetFileLocationFromLastMail(exportRequest, bartAccessToken);
+
+        try (ZipAssert zipAssert = assertThatZip(fileSystem.getResource(fileLocation))) {
+            zipAssert.containsOnlyEntriesMatching(hasName(firstMessageIdOfHomer));
+        }
+    }
+
+    @Test
+    public void vaultExportShouldExportEmptyZipWhenQueryDoesntMatch() throws Exception {
+        bartSendMessageToHomerWithSubject(FIRST_SUBJECT);
+        bartSendMessageToHomerWithSubject(SECOND_SUBJECT);
+        WAIT_TWO_MINUTES.until(() -> listMessageIdsForAccount(homerAccessToken).size() == 2);
+
+        homerDeletesMessages(listMessageIdsForAccount(homerAccessToken));
+        WAIT_TWO_MINUTES.until(() -> listMessageIdsForAccount(homerAccessToken).size() == 0);
+
+        ExportRequest exportRequest = ExportRequest
+            .userExportFrom(HOMER)
+            .exportTo(BART)
+            .query("{" +
+                "  \"fieldName\": \"subject\"," +
+                "  \"operator\": \"equals\"," +
+                "  \"value\": \"non matching\"" +
+                "}");
+        String fileLocation = exportAndGetFileLocationFromLastMail(exportRequest, bartAccessToken);
+
+        try (ZipAssert zipAssert = assertThatZip(fileSystem.getResource(fileLocation))) {
+            zipAssert.hasNoEntry();
+        }
+    }
+
+    @Test
+    public void vaultExportShouldExportEmptyZipWhenVaultIsEmpty() throws Exception {
+        String fileLocation = exportAndGetFileLocationFromLastMail(EXPORT_ALL_HOMER_MESSAGES_TO_BART, bartAccessToken);
+
+        try (ZipAssert zipAssert = assertThatZip(fileSystem.getResource(fileLocation))) {
+            zipAssert.hasNoEntry();
+        }
+    }
+
+    @Test
+    public void vaultExportShouldResponseIdempotentSideEffect() throws Exception {
+        bartSendMessageToHomer();
+        WAIT_TWO_MINUTES.until(() -> listMessageIdsForAccount(homerAccessToken).size() == 1);
+
+        homerDeletesMessages(listMessageIdsForAccount(homerAccessToken));
+        WAIT_TWO_MINUTES.until(() -> listMessageIdsForAccount(homerAccessToken).size() == 0);
+
+        String fileLocationFirstExport = exportAndGetFileLocationFromLastMail(EXPORT_ALL_HOMER_MESSAGES_TO_BART, bartAccessToken);
+        String fileLocationSecondExport = exportAndGetFileLocationFromLastMail(EXPORT_ALL_HOMER_MESSAGES_TO_BART, bartAccessToken);
+
+        try (ZipAssert zipAssert = assertThatZip(fileSystem.getResource(fileLocationFirstExport))) {
+            zipAssert.hasSameContentWith(fileSystem.getResource(fileLocationSecondExport));
+        }
+    }
+
+    private String exportAndGetFileLocationFromLastMail(ExportRequest exportRequest, AccessToken shareeAccessToken) {
+        int currentNumberOfMessages = listMessageIdsForAccount(shareeAccessToken).size();
+        exportVaultContent(exportRequest);
+
+        WAIT_TWO_MINUTES.until(() -> listMessageIdsForAccount(shareeAccessToken).size() == currentNumberOfMessages + 1);
+        String exportingMessageId = getLastMessageId(shareeAccessToken);
+        return exportedFileLocationFromMailHeader(exportingMessageId, shareeAccessToken);
+
+    }
+
+    private String exportedFileLocationFromMailHeader(String messageId, AccessToken accessToken) {
+        return with()
+                .header("Authorization", accessToken.serialize())
+                .body("[[\"getMessages\", {\"ids\": [\"" + messageId + "\"]}, \"#0\"]]")
+                .post("/jmap")
+            .jsonPath()
+                .getList(ARGUMENTS + ".list.headers.corresponding-file", String.class)
+                .get(0);
+    }
+
     private void homerSharesHisMailboxWithBart() {
         with()
             .header("Authorization", homerAccessToken.serialize())
@@ -518,4 +727,18 @@ public abstract class DeletedMessagesVaultTest {
             .when()
             .post("/jmap");
     }
+
+    private void exportVaultContent(ExportRequest exportRequest) {
+        String taskId =
+            webAdminApi.with()
+                .body(exportRequest.matchingQuery)
+                .post("/deletedMessages/users/" + exportRequest.userExportFrom + "?action=export&exportTo=" + exportRequest.sharee)
+            .jsonPath()
+                .get("taskId");
+
+        webAdminApi.given()
+                .get("/tasks/" + taskId + "/await")
+            .then()
+                .body("status", is("completed"));
+    }
 }
diff --git a/server/protocols/jmap-integration-testing/memory-jmap-integration-testing/pom.xml b/server/protocols/jmap-integration-testing/memory-jmap-integration-testing/pom.xml
index 02531d3..d2601dc 100644
--- a/server/protocols/jmap-integration-testing/memory-jmap-integration-testing/pom.xml
+++ b/server/protocols/jmap-integration-testing/memory-jmap-integration-testing/pom.xml
@@ -57,6 +57,11 @@
         </dependency>
         <dependency>
             <groupId>${james.groupId}</groupId>
+            <artifactId>backup</artifactId>
+            <type>test-jar</type>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
             <artifactId>james-server-dnsservice-test</artifactId>
         </dependency>
         <dependency>
diff --git a/server/protocols/jmap-integration-testing/memory-jmap-integration-testing/src/test/java/org/apache/james/jmap/memory/FileMailRepositoryDeletedMessagesVaultTest.java b/server/protocols/jmap-integration-testing/memory-jmap-integration-testing/src/test/java/org/apache/james/jmap/memory/FileMailRepositoryDeletedMessagesVaultTest.java
index ec7dd62..4656d9d 100644
--- a/server/protocols/jmap-integration-testing/memory-jmap-integration-testing/src/test/java/org/apache/james/jmap/memory/FileMailRepositoryDeletedMessagesVaultTest.java
+++ b/server/protocols/jmap-integration-testing/memory-jmap-integration-testing/src/test/java/org/apache/james/jmap/memory/FileMailRepositoryDeletedMessagesVaultTest.java
@@ -23,6 +23,7 @@ import java.io.IOException;
 
 import org.apache.james.GuiceJamesServer;
 import org.apache.james.MemoryJmapTestRule;
+import org.apache.james.filesystem.api.FileSystem;
 import org.apache.james.jmap.methods.integration.DeletedMessagesVaultTest;
 import org.apache.james.mailrepository.api.MailRepositoryUrl;
 import org.apache.james.modules.mailbox.PreDeletionHookConfiguration;
@@ -43,13 +44,14 @@ public class FileMailRepositoryDeletedMessagesVaultTest extends DeletedMessagesV
     }
 
     @Override
-    protected GuiceJamesServer createJmapServer() throws IOException {
+    protected GuiceJamesServer createJmapServer(FileSystem fileSystem) throws IOException {
         return memoryJmap.jmapServer(
             binder -> binder.bind(PreDeletionHooksConfiguration.class)
                 .toInstance(PreDeletionHooksConfiguration.forHooks(
                     PreDeletionHookConfiguration.forClass(DeletedMessageVaultHook.class))),
             binder -> binder.bind(WebAdminConfiguration.class).toInstance(WebAdminConfiguration.TEST_CONFIGURATION),
             binder -> binder.bind(MailRepositoryDeletedMessageVault.Configuration.class)
-                .toInstance(new MailRepositoryDeletedMessageVault.Configuration(MailRepositoryUrl.from("file://var/deletedMessages/user"))));
+                .toInstance(new MailRepositoryDeletedMessageVault.Configuration(MailRepositoryUrl.from("file://var/deletedMessages/user"))),
+            binder -> binder.bind(FileSystem.class).toInstance(fileSystem));
     }
 }
diff --git a/server/protocols/jmap-integration-testing/memory-jmap-integration-testing/src/test/java/org/apache/james/jmap/memory/MemoryDeletedMessagesVaultTest.java b/server/protocols/jmap-integration-testing/memory-jmap-integration-testing/src/test/java/org/apache/james/jmap/memory/MemoryDeletedMessagesVaultTest.java
index d83717d..62360ad 100644
--- a/server/protocols/jmap-integration-testing/memory-jmap-integration-testing/src/test/java/org/apache/james/jmap/memory/MemoryDeletedMessagesVaultTest.java
+++ b/server/protocols/jmap-integration-testing/memory-jmap-integration-testing/src/test/java/org/apache/james/jmap/memory/MemoryDeletedMessagesVaultTest.java
@@ -23,6 +23,7 @@ import java.io.IOException;
 
 import org.apache.james.GuiceJamesServer;
 import org.apache.james.MemoryJmapTestRule;
+import org.apache.james.filesystem.api.FileSystem;
 import org.apache.james.jmap.methods.integration.DeletedMessagesVaultTest;
 import org.apache.james.mailrepository.api.MailRepositoryUrl;
 import org.apache.james.modules.mailbox.PreDeletionHookConfiguration;
@@ -37,14 +38,15 @@ public class MemoryDeletedMessagesVaultTest extends DeletedMessagesVaultTest {
     public MemoryJmapTestRule memoryJmap = new MemoryJmapTestRule();
 
     @Override
-    protected GuiceJamesServer createJmapServer() throws IOException {
+    protected GuiceJamesServer createJmapServer(FileSystem fileSystem) throws IOException {
         return memoryJmap.jmapServer(
             binder -> binder.bind(PreDeletionHooksConfiguration.class)
                 .toInstance(PreDeletionHooksConfiguration.forHooks(
                     PreDeletionHookConfiguration.forClass(DeletedMessageVaultHook.class))),
             binder -> binder.bind(WebAdminConfiguration.class).toInstance(WebAdminConfiguration.TEST_CONFIGURATION),
             binder -> binder.bind(MailRepositoryDeletedMessageVault.Configuration.class)
-                .toInstance(new MailRepositoryDeletedMessageVault.Configuration(MailRepositoryUrl.from("memory://var/deletedMessages/user"))));
+                .toInstance(new MailRepositoryDeletedMessageVault.Configuration(MailRepositoryUrl.from("memory://var/deletedMessages/user"))),
+            binder -> binder.bind(FileSystem.class).toInstance(fileSystem));
     }
 
     @Override
diff --git a/server/protocols/jmap-integration-testing/rabbitmq-jmap-integration-testing/pom.xml b/server/protocols/jmap-integration-testing/rabbitmq-jmap-integration-testing/pom.xml
index 21fec29..a6c4482 100644
--- a/server/protocols/jmap-integration-testing/rabbitmq-jmap-integration-testing/pom.xml
+++ b/server/protocols/jmap-integration-testing/rabbitmq-jmap-integration-testing/pom.xml
@@ -53,6 +53,11 @@
         </dependency>
         <dependency>
             <groupId>${james.groupId}</groupId>
+            <artifactId>backup</artifactId>
+            <type>test-jar</type>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
             <artifactId>blob-objectstorage</artifactId>
             <type>test-jar</type>
             <scope>test</scope>
diff --git a/server/protocols/jmap-integration-testing/rabbitmq-jmap-integration-testing/src/test/java/org/apache/james/jmap/rabbitmq/RabbitMQDeletedMessagesVaultTest.java b/server/protocols/jmap-integration-testing/rabbitmq-jmap-integration-testing/src/test/java/org/apache/james/jmap/rabbitmq/RabbitMQDeletedMessagesVaultTest.java
index 840f45c..30f0a3a 100644
--- a/server/protocols/jmap-integration-testing/rabbitmq-jmap-integration-testing/src/test/java/org/apache/james/jmap/rabbitmq/RabbitMQDeletedMessagesVaultTest.java
+++ b/server/protocols/jmap-integration-testing/rabbitmq-jmap-integration-testing/src/test/java/org/apache/james/jmap/rabbitmq/RabbitMQDeletedMessagesVaultTest.java
@@ -24,6 +24,7 @@ import java.io.IOException;
 import org.apache.james.CassandraRabbitMQSwiftJmapTestRule;
 import org.apache.james.DockerCassandraRule;
 import org.apache.james.GuiceJamesServer;
+import org.apache.james.filesystem.api.FileSystem;
 import org.apache.james.jmap.methods.integration.DeletedMessagesVaultTest;
 import org.apache.james.mailrepository.api.MailRepositoryUrl;
 import org.apache.james.modules.mailbox.PreDeletionHookConfiguration;
@@ -42,14 +43,15 @@ public class RabbitMQDeletedMessagesVaultTest extends DeletedMessagesVaultTest {
     public CassandraRabbitMQSwiftJmapTestRule rule = CassandraRabbitMQSwiftJmapTestRule.defaultTestRule();
 
     @Override
-    protected GuiceJamesServer createJmapServer() throws IOException {
+    protected GuiceJamesServer createJmapServer(FileSystem fileSystem) throws IOException {
         return rule.jmapServer(cassandra.getModule(),
             binder -> binder.bind(PreDeletionHooksConfiguration.class)
                 .toInstance(PreDeletionHooksConfiguration.forHooks(
                     PreDeletionHookConfiguration.forClass(DeletedMessageVaultHook.class))),
             binder -> binder.bind(WebAdminConfiguration.class).toInstance(WebAdminConfiguration.TEST_CONFIGURATION),
             binder -> binder.bind(MailRepositoryDeletedMessageVault.Configuration.class)
-                .toInstance(new MailRepositoryDeletedMessageVault.Configuration(MailRepositoryUrl.from("cassandra://var/deletedMessages/user"))));
+                .toInstance(new MailRepositoryDeletedMessageVault.Configuration(MailRepositoryUrl.from("cassandra://var/deletedMessages/user"))),
+            binder -> binder.bind(FileSystem.class).toInstance(fileSystem));
     }
 
     @Override


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