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

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


The following commit(s) were added to refs/heads/master by this push:
     new bf100353ac [IMPROVEMENT] Zstd: Contract and test coverage for 
ZstdBlobStoreDAO atop file and memory implementations (#3199)
bf100353ac is described below

commit bf100353ac3c4fc76d9ecced7472528b43e1ca21
Author: Hesanda Liyanage <[email protected]>
AuthorDate: Thu Sep 24 11:59:04 2026 +0530

    [IMPROVEMENT] Zstd: Contract and test coverage for ZstdBlobStoreDAO atop 
file and memory implementations (#3199)
    
    Following PMC feedback regarding ZstdBlobStoreDAO working out of the box 
atop the file implementation:
    
    - Extract ZstdBlobStoreDAOContract interface extending BlobStoreDAOContract 
and MetadataAwareBlobStoreDAOContract.
    - Add ZstdFileBlobStoreDAOTest verifying ZstdBlobStoreDAO works out of the 
box atop FileBlobStoreDAO.
    - Add ZstdMemoryBlobStoreDAOTest for lightweight in-memory contract 
verification without requiring Docker.
    - Refactor ZstdBlobStoreDAOTest to implement ZstdBlobStoreDAOContract 
against S3BlobStoreDAO.
    - Add File and Postgres BlobStore module choosing tests in 
BlobStoreModulesChooserTest.
---
 server/blob/blob-zstd/pom.xml                      |  10 +
 ...eDAOTest.java => ZstdBlobStoreDAOContract.java} | 200 ++++--------
 .../james/blob/zstd/ZstdBlobStoreDAOTest.java      | 356 +--------------------
 .../james/blob/zstd/ZstdFileBlobStoreDAOTest.java  |  62 ++++
 .../blob/zstd/ZstdMemoryBlobStoreDAOTest.java      |  60 ++++
 .../blobstore/BlobStoreModulesChooserTest.java     |  22 ++
 6 files changed, 230 insertions(+), 480 deletions(-)

diff --git a/server/blob/blob-zstd/pom.xml b/server/blob/blob-zstd/pom.xml
index 053921f2cb..ccf440ac00 100644
--- a/server/blob/blob-zstd/pom.xml
+++ b/server/blob/blob-zstd/pom.xml
@@ -41,6 +41,16 @@
             <type>test-jar</type>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
+            <artifactId>blob-file</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
+            <artifactId>blob-memory</artifactId>
+            <scope>test</scope>
+        </dependency>
         <dependency>
             <groupId>${james.groupId}</groupId>
             <artifactId>blob-s3</artifactId>
diff --git 
a/server/blob/blob-zstd/src/test/java/org/apache/james/blob/zstd/ZstdBlobStoreDAOTest.java
 
b/server/blob/blob-zstd/src/test/java/org/apache/james/blob/zstd/ZstdBlobStoreDAOContract.java
similarity index 69%
copy from 
server/blob/blob-zstd/src/test/java/org/apache/james/blob/zstd/ZstdBlobStoreDAOTest.java
copy to 
server/blob/blob-zstd/src/test/java/org/apache/james/blob/zstd/ZstdBlobStoreDAOContract.java
index 99ddc51af2..245b00ad80 100644
--- 
a/server/blob/blob-zstd/src/test/java/org/apache/james/blob/zstd/ZstdBlobStoreDAOTest.java
+++ 
b/server/blob/blob-zstd/src/test/java/org/apache/james/blob/zstd/ZstdBlobStoreDAOContract.java
@@ -23,8 +23,6 @@ import static 
org.apache.james.blob.api.BlobStoreDAOFixture.ELEVEN_KILOBYTES;
 import static org.apache.james.blob.api.BlobStoreDAOFixture.SHORT_BYTEARRAY;
 import static org.apache.james.blob.api.BlobStoreDAOFixture.TEST_BLOB_ID;
 import static org.apache.james.blob.api.BlobStoreDAOFixture.TEST_BUCKET_NAME;
-import static 
org.apache.james.blob.objectstorage.aws.JamesS3MetricPublisher.DEFAULT_S3_METRICS_PREFIX;
-import static 
org.apache.james.blob.objectstorage.aws.S3BlobStoreConfiguration.UPLOAD_RETRY_EXCEPTION_PREDICATE;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.assertj.core.api.SoftAssertions.assertSoftly;
@@ -32,32 +30,14 @@ import static 
org.assertj.core.api.SoftAssertions.assertSoftly;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
-import java.time.Duration;
-import java.util.Optional;
 import java.util.Random;
 import java.util.stream.Stream;
 
 import org.apache.james.blob.api.BlobStoreDAO;
 import org.apache.james.blob.api.BlobStoreDAOContract;
-import org.apache.james.blob.api.BucketName;
 import org.apache.james.blob.api.MetadataAwareBlobStoreDAOContract;
-import org.apache.james.blob.api.TestBlobId;
-import org.apache.james.blob.objectstorage.aws.AwsS3AuthConfiguration;
-import org.apache.james.blob.objectstorage.aws.DockerAwsS3Container;
-import org.apache.james.blob.objectstorage.aws.DockerAwsS3Extension;
-import org.apache.james.blob.objectstorage.aws.JamesS3MetricPublisher;
-import org.apache.james.blob.objectstorage.aws.S3BlobStoreConfiguration;
-import org.apache.james.blob.objectstorage.aws.S3BlobStoreDAO;
-import org.apache.james.blob.objectstorage.aws.S3ClientFactory;
-import org.apache.james.blob.objectstorage.aws.S3RequestOption;
-import org.apache.james.metrics.api.NoopGaugeRegistry;
 import org.apache.james.metrics.tests.RecordingMetricFactory;
-import org.junit.jupiter.api.AfterAll;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeAll;
-import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.Arguments;
 import org.junit.jupiter.params.provider.MethodSource;
@@ -65,77 +45,31 @@ import org.junit.jupiter.params.provider.MethodSource;
 import com.google.common.io.ByteSource;
 
 import reactor.core.publisher.Mono;
-import reactor.util.retry.Retry;
-
-@ExtendWith(DockerAwsS3Extension.class)
-class ZstdBlobStoreDAOTest implements BlobStoreDAOContract, 
MetadataAwareBlobStoreDAOContract {
-    private static final CompressionConfiguration 
DEFAULT_COMPRESSION_CONFIGURATION = CompressionConfiguration.builder()
-        .enabled(true)
-        .build();
-    private static final BucketName FALLBACK_BUCKET = 
BucketName.of("fallback");
-
-    private static S3BlobStoreDAO underlying;
-    private static S3ClientFactory s3ClientFactory;
-
-    private RecordingMetricFactory metricFactory;
-    private ZstdBlobStoreDAO testee;
-
-    @BeforeAll
-    static void setUp(DockerAwsS3Container dockerAwsS3) {
-        AwsS3AuthConfiguration authConfiguration = 
AwsS3AuthConfiguration.builder()
-            .endpoint(dockerAwsS3.getEndpoint())
-            .accessKeyId(DockerAwsS3Container.ACCESS_KEY_ID)
-            .secretKey(DockerAwsS3Container.SECRET_ACCESS_KEY)
-            .build();
-
-        S3BlobStoreConfiguration s3Configuration = 
S3BlobStoreConfiguration.builder()
-            .authConfiguration(authConfiguration)
-            .region(dockerAwsS3.dockerAwsS3().region())
-            .uploadRetrySpec(Optional.of(Retry.backoff(3, 
Duration.ofSeconds(1))
-                .filter(UPLOAD_RETRY_EXCEPTION_PREDICATE)))
-            .defaultBucketName(BucketName.DEFAULT)
-            .fallbackBucketName(Optional.of(FALLBACK_BUCKET))
-            .build();
 
-        s3ClientFactory = new S3ClientFactory(s3Configuration, () -> new 
JamesS3MetricPublisher(new RecordingMetricFactory(),
-            new NoopGaugeRegistry(), DEFAULT_S3_METRICS_PREFIX));
-        underlying = new S3BlobStoreDAO(s3ClientFactory, s3Configuration, new 
TestBlobId.Factory(), S3RequestOption.DEFAULT);
-    }
-
-    @AfterAll
-    static void tearDownClass() {
-        if (s3ClientFactory != null) {
-            s3ClientFactory.close();
-        }
-    }
+public interface ZstdBlobStoreDAOContract extends BlobStoreDAOContract, 
MetadataAwareBlobStoreDAOContract {
 
-    @BeforeEach
-    void setUp() {
-        metricFactory = new RecordingMetricFactory();
-        testee = new ZstdBlobStoreDAO(underlying, 
DEFAULT_COMPRESSION_CONFIGURATION, metricFactory);
-    }
+    BlobStoreDAO underlying();
 
-    @AfterEach
-    void tearDown() {
-        if (underlying != null) {
-            underlying.deleteAllBuckets().block();
-        }
-    }
+    RecordingMetricFactory metricFactory();
 
     @Override
-    public BlobStoreDAO testee() {
-        return testee;
+    ZstdBlobStoreDAO testee();
+
+    default CompressionConfiguration defaultCompressionConfiguration() {
+        return CompressionConfiguration.builder()
+            .enabled(true)
+            .build();
     }
 
     @Override
     @Test
-    public void retrieveContentTransferEncodingShouldSucceed() {
-        ZstdBlobStoreDAO localTestee = new ZstdBlobStoreDAO(underlying,
+    default void retrieveContentTransferEncodingShouldSucceed() {
+        ZstdBlobStoreDAO localTestee = new ZstdBlobStoreDAO(underlying(),
             CompressionConfiguration.builder()
                 .enabled(true)
                 .threshold(1)
                 .build(),
-            metricFactory);
+            metricFactory());
 
         // should compress and append content-transfer-encoding metadata, when 
threshold is met.
         Mono.from(localTestee.save(TEST_BUCKET_NAME, TEST_BLOB_ID, 
ELEVEN_KILOBYTES)).block();
@@ -145,13 +79,13 @@ class ZstdBlobStoreDAOTest implements 
BlobStoreDAOContract, MetadataAwareBlobSto
     }
 
     @Test
-    void shouldPreserveCallerMetadataWhenCompressionHappens() {
-        ZstdBlobStoreDAO localTestee = new ZstdBlobStoreDAO(underlying,
+    default void shouldPreserveCallerMetadataWhenCompressionHappens() {
+        ZstdBlobStoreDAO localTestee = new ZstdBlobStoreDAO(underlying(),
             CompressionConfiguration.builder()
                 .enabled(true)
                 .threshold(1)
                 .build(),
-            metricFactory);
+            metricFactory());
         BlobStoreDAO.BlobMetadata metadata = BlobStoreDAO.BlobMetadata.empty()
             .withMetadata(new BlobStoreDAO.BlobMetadataName("name"), new 
BlobStoreDAO.BlobMetadataValue("value"))
             .withMetadata(new BlobStoreDAO.BlobMetadataName("type"), new 
BlobStoreDAO.BlobMetadataValue("attachment"));
@@ -172,25 +106,25 @@ class ZstdBlobStoreDAOTest implements 
BlobStoreDAOContract, MetadataAwareBlobSto
     }
 
     @Test
-    void readExistingNonCompressedBlobShouldSucceed() {
-        Mono.from(underlying.save(TEST_BUCKET_NAME, TEST_BLOB_ID, 
ELEVEN_KILOBYTES)).block();
+    default void readExistingNonCompressedBlobShouldSucceed() {
+        Mono.from(underlying().save(TEST_BUCKET_NAME, TEST_BLOB_ID, 
ELEVEN_KILOBYTES)).block();
 
-        assertThat(Mono.from(testee.readBytes(TEST_BUCKET_NAME, 
TEST_BLOB_ID)).block())
+        assertThat(Mono.from(testee().readBytes(TEST_BUCKET_NAME, 
TEST_BLOB_ID)).block())
             .isEqualTo(ELEVEN_KILOBYTES);
     }
 
     @Test
-    void shouldNotCompressBlobIfThresholdIsNotMet() {
-        ZstdBlobStoreDAO localTestee = new ZstdBlobStoreDAO(underlying,
+    default void shouldNotCompressBlobIfThresholdIsNotMet() {
+        ZstdBlobStoreDAO localTestee = new ZstdBlobStoreDAO(underlying(),
             CompressionConfiguration.builder()
                 .enabled(true)
                 .threshold(16 * 1024)
                 .build(),
-            metricFactory);
+            metricFactory());
 
         Mono.from(localTestee.save(TEST_BUCKET_NAME, TEST_BLOB_ID, 
SHORT_BYTEARRAY)).block();
 
-        BlobStoreDAO.BytesBlob storedBlob = 
Mono.from(underlying.readBytes(TEST_BUCKET_NAME, TEST_BLOB_ID)).block();
+        BlobStoreDAO.BytesBlob storedBlob = 
Mono.from(underlying().readBytes(TEST_BUCKET_NAME, TEST_BLOB_ID)).block();
 
         assertSoftly(softly -> {
             
softly.assertThat(storedBlob.payload()).isEqualTo(SHORT_BYTEARRAY.payload());
@@ -200,13 +134,13 @@ class ZstdBlobStoreDAOTest implements 
BlobStoreDAOContract, MetadataAwareBlobSto
     }
 
     @Test
-    void readShouldDecompressCompressedBlob() throws IOException {
-        ZstdBlobStoreDAO localTestee = new ZstdBlobStoreDAO(underlying,
+    default void readShouldDecompressCompressedBlob() throws IOException {
+        ZstdBlobStoreDAO localTestee = new ZstdBlobStoreDAO(underlying(),
             CompressionConfiguration.builder()
                 .enabled(true)
                 .threshold(1)
                 .build(),
-            metricFactory);
+            metricFactory());
 
         Mono.from(localTestee.save(TEST_BUCKET_NAME, TEST_BLOB_ID, 
ELEVEN_KILOBYTES)).block();
 
@@ -222,13 +156,13 @@ class ZstdBlobStoreDAOTest implements 
BlobStoreDAOContract, MetadataAwareBlobSto
     }
 
     @Test
-    void readReactiveShouldDecompressCompressedBlob() throws IOException {
-        ZstdBlobStoreDAO localTestee = new ZstdBlobStoreDAO(underlying,
+    default void readReactiveShouldDecompressCompressedBlob() throws 
IOException {
+        ZstdBlobStoreDAO localTestee = new ZstdBlobStoreDAO(underlying(),
             CompressionConfiguration.builder()
                 .enabled(true)
                 .threshold(1)
                 .build(),
-            metricFactory);
+            metricFactory());
 
         Mono.from(localTestee.save(TEST_BUCKET_NAME, TEST_BLOB_ID, 
ELEVEN_KILOBYTES)).block();
 
@@ -244,18 +178,18 @@ class ZstdBlobStoreDAOTest implements 
BlobStoreDAOContract, MetadataAwareBlobSto
     }
 
     @Test
-    void shouldNotCompressBlobWhenMinRatioIsZero() {
-        ZstdBlobStoreDAO localTestee = new ZstdBlobStoreDAO(underlying,
+    default void shouldNotCompressBlobWhenMinRatioIsZero() {
+        ZstdBlobStoreDAO localTestee = new ZstdBlobStoreDAO(underlying(),
             CompressionConfiguration.builder()
                 .enabled(true)
                 .threshold(1)
                 .minRatio(0F)
                 .build(),
-            metricFactory);
+            metricFactory());
 
         Mono.from(localTestee.save(TEST_BUCKET_NAME, TEST_BLOB_ID, 
ELEVEN_KILOBYTES)).block();
 
-        BlobStoreDAO.BytesBlob storedBlob = 
Mono.from(underlying.readBytes(TEST_BUCKET_NAME, TEST_BLOB_ID)).block();
+        BlobStoreDAO.BytesBlob storedBlob = 
Mono.from(underlying().readBytes(TEST_BUCKET_NAME, TEST_BLOB_ID)).block();
 
         assertSoftly(softly -> {
             
softly.assertThat(storedBlob.payload()).isEqualTo(ELEVEN_KILOBYTES.payload());
@@ -265,20 +199,20 @@ class ZstdBlobStoreDAOTest implements 
BlobStoreDAOContract, MetadataAwareBlobSto
     }
 
     @Test
-    void shouldStillDecompressBlobWhenMinRatioIsZero() {
-        ZstdBlobStoreDAO compressingTestee = new ZstdBlobStoreDAO(underlying,
+    default void shouldStillDecompressBlobWhenMinRatioIsZero() {
+        ZstdBlobStoreDAO compressingTestee = new ZstdBlobStoreDAO(underlying(),
             CompressionConfiguration.builder()
                 .enabled(true)
                 .threshold(1)
                 .build(),
-            metricFactory);
-        ZstdBlobStoreDAO uncompressingOnlyTestee = new 
ZstdBlobStoreDAO(underlying,
+            metricFactory());
+        ZstdBlobStoreDAO uncompressingOnlyTestee = new 
ZstdBlobStoreDAO(underlying(),
             CompressionConfiguration.builder()
                 .enabled(true)
                 .threshold(1)
                 .minRatio(0F)
                 .build(),
-            metricFactory);
+            metricFactory());
 
         Mono.from(compressingTestee.save(TEST_BUCKET_NAME, TEST_BLOB_ID, 
ELEVEN_KILOBYTES)).block();
 
@@ -293,21 +227,21 @@ class ZstdBlobStoreDAOTest implements 
BlobStoreDAOContract, MetadataAwareBlobSto
     }
 
     @Test
-    void shouldNotCompressBlobWhenMinRatioIsNotMet() {
-        ZstdBlobStoreDAO localTestee = new ZstdBlobStoreDAO(underlying,
+    default void shouldNotCompressBlobWhenMinRatioIsNotMet() {
+        ZstdBlobStoreDAO localTestee = new ZstdBlobStoreDAO(underlying(),
             CompressionConfiguration.builder()
                 .enabled(true)
                 .threshold(1)
                 .minRatio(0.5F)
                 .build(),
-            metricFactory);
+            metricFactory());
         byte[] randomPayload = new byte[4096];
         new Random(1).nextBytes(randomPayload);
         BlobStoreDAO.BytesBlob randomBlob = 
BlobStoreDAO.BytesBlob.of(randomPayload);
 
         Mono.from(localTestee.save(TEST_BUCKET_NAME, TEST_BLOB_ID, 
randomBlob)).block();
 
-        BlobStoreDAO.BytesBlob storedBlob = 
Mono.from(underlying.readBytes(TEST_BUCKET_NAME, TEST_BLOB_ID)).block();
+        BlobStoreDAO.BytesBlob storedBlob = 
Mono.from(underlying().readBytes(TEST_BUCKET_NAME, TEST_BLOB_ID)).block();
 
         assertSoftly(softly -> {
             softly.assertThat(storedBlob.payload()).isEqualTo(randomPayload);
@@ -317,53 +251,53 @@ class ZstdBlobStoreDAOTest implements 
BlobStoreDAOContract, MetadataAwareBlobSto
     }
 
     @Test
-    void shouldRecordMetrics() {
-        ZstdBlobStoreDAO localTestee = new ZstdBlobStoreDAO(underlying,
+    default void shouldRecordMetrics() {
+        ZstdBlobStoreDAO localTestee = new ZstdBlobStoreDAO(underlying(),
             CompressionConfiguration.builder()
                 .enabled(true)
                 .threshold(1)
                 .build(),
-            metricFactory);
+            metricFactory());
 
         Mono.from(localTestee.save(TEST_BUCKET_NAME, TEST_BLOB_ID, 
ELEVEN_KILOBYTES)).block();
         Mono.from(localTestee.readBytes(TEST_BUCKET_NAME, 
TEST_BLOB_ID)).block();
-        BlobStoreDAO.BytesBlob storedBlob = 
Mono.from(underlying.readBytes(TEST_BUCKET_NAME, TEST_BLOB_ID)).block();
+        BlobStoreDAO.BytesBlob storedBlob = 
Mono.from(underlying().readBytes(TEST_BUCKET_NAME, TEST_BLOB_ID)).block();
 
         assertSoftly(softly -> {
-            
softly.assertThat(metricFactory.countFor(ZstdBlobStoreDAO.MetricRecorder.BLOB_ZSTD_COMPRESS_SAVE_COUNT_METRIC_NAME)).isEqualTo(1);
-            
softly.assertThat(metricFactory.countFor(ZstdBlobStoreDAO.MetricRecorder.BLOB_ZSTD_DECOMPRESS_COUNT_METRIC_NAME)).isEqualTo(1);
-            
softly.assertThat(metricFactory.countFor(ZstdBlobStoreDAO.MetricRecorder.BLOB_ZSTD_SAVED_BYTES_METRIC_NAME))
+            
softly.assertThat(metricFactory().countFor(ZstdBlobStoreDAO.MetricRecorder.BLOB_ZSTD_COMPRESS_SAVE_COUNT_METRIC_NAME)).isEqualTo(1);
+            
softly.assertThat(metricFactory().countFor(ZstdBlobStoreDAO.MetricRecorder.BLOB_ZSTD_DECOMPRESS_COUNT_METRIC_NAME)).isEqualTo(1);
+            
softly.assertThat(metricFactory().countFor(ZstdBlobStoreDAO.MetricRecorder.BLOB_ZSTD_SAVED_BYTES_METRIC_NAME))
                 .isEqualTo(ELEVEN_KILOBYTES.payload().length - 
storedBlob.payload().length);
-            
softly.assertThat(metricFactory.executionTimesFor(ZstdBlobStoreDAO.MetricRecorder.BLOB_ZSTD_COMPRESS_LATENCY_METRIC_NAME)).hasSize(1);
-            
softly.assertThat(metricFactory.executionTimesFor(ZstdBlobStoreDAO.MetricRecorder.BLOB_ZSTD_DECOMPRESS_LATENCY_METRIC_NAME)).hasSize(1);
+            
softly.assertThat(metricFactory().executionTimesFor(ZstdBlobStoreDAO.MetricRecorder.BLOB_ZSTD_COMPRESS_LATENCY_METRIC_NAME)).hasSize(1);
+            
softly.assertThat(metricFactory().executionTimesFor(ZstdBlobStoreDAO.MetricRecorder.BLOB_ZSTD_DECOMPRESS_LATENCY_METRIC_NAME)).hasSize(1);
         });
     }
 
     @Test
-    void shouldRecordThresholdSkipMetricIfThresholdNotMatch() {
-        Mono.from(testee.save(TEST_BUCKET_NAME, TEST_BLOB_ID, 
SHORT_BYTEARRAY)).block();
+    default void shouldRecordThresholdSkipMetricIfThresholdNotMatch() {
+        Mono.from(testee().save(TEST_BUCKET_NAME, TEST_BLOB_ID, 
SHORT_BYTEARRAY)).block();
 
-        
assertThat(metricFactory.countFor(ZstdBlobStoreDAO.MetricRecorder.BLOB_ZSTD_THRESHOLD_SKIP_COUNT_METRIC_NAME))
+        
assertThat(metricFactory().countFor(ZstdBlobStoreDAO.MetricRecorder.BLOB_ZSTD_THRESHOLD_SKIP_COUNT_METRIC_NAME))
             .isEqualTo(1);
     }
 
     @ParameterizedTest
     @MethodSource("blobsWithReservedCompressionMetadata")
-    void saveShouldRejectReservedCompressionMetadata(BlobStoreDAO.Blob blob) {
-        assertThatThrownBy(() -> Mono.from(testee.save(TEST_BUCKET_NAME, 
TEST_BLOB_ID, blob)).block())
+    default void saveShouldRejectReservedCompressionMetadata(BlobStoreDAO.Blob 
blob) {
+        assertThatThrownBy(() -> Mono.from(testee().save(TEST_BUCKET_NAME, 
TEST_BLOB_ID, blob)).block())
             .isInstanceOf(IllegalArgumentException.class)
             .hasMessageContaining("Reserved zstd metadata are not allowed");
     }
 
     @ParameterizedTest
     @MethodSource("compressionSamples")
-    void readBytesShouldRoundTripCompressedResourcesWithoutCorruption(String 
resourcePath) throws IOException {
-        ZstdBlobStoreDAO localTestee = new ZstdBlobStoreDAO(underlying,
+    default void 
readBytesShouldRoundTripCompressedResourcesWithoutCorruption(String 
resourcePath) throws IOException {
+        ZstdBlobStoreDAO localTestee = new ZstdBlobStoreDAO(underlying(),
             CompressionConfiguration.builder()
                 .enabled(true)
                 .threshold(1)
                 .build(),
-            metricFactory);
+            metricFactory());
         byte[] resourceBytes = readResource(resourcePath);
 
         Mono.from(localTestee.save(TEST_BUCKET_NAME, TEST_BLOB_ID, 
BlobStoreDAO.BytesBlob.of(resourceBytes))).block();
@@ -380,13 +314,13 @@ class ZstdBlobStoreDAOTest implements 
BlobStoreDAOContract, MetadataAwareBlobSto
 
     @ParameterizedTest
     @MethodSource("compressionSamples")
-    void readShouldRoundTripCompressedResourcesWithoutCorruption(String 
resourcePath) throws IOException {
-        ZstdBlobStoreDAO localTestee = new ZstdBlobStoreDAO(underlying,
+    default void 
readShouldRoundTripCompressedResourcesWithoutCorruption(String resourcePath) 
throws IOException {
+        ZstdBlobStoreDAO localTestee = new ZstdBlobStoreDAO(underlying(),
             CompressionConfiguration.builder()
                 .enabled(true)
                 .threshold(1)
                 .build(),
-            metricFactory);
+            metricFactory());
         byte[] resourceBytes = readResource(resourcePath);
 
         Mono.from(localTestee.save(TEST_BUCKET_NAME, TEST_BLOB_ID, 
BlobStoreDAO.BytesBlob.of(resourceBytes))).block();
@@ -404,13 +338,13 @@ class ZstdBlobStoreDAOTest implements 
BlobStoreDAOContract, MetadataAwareBlobSto
 
     @ParameterizedTest
     @MethodSource("compressionSamples")
-    void 
readReactiveShouldRoundTripCompressedResourcesWithoutCorruption(String 
resourcePath) throws IOException {
-        ZstdBlobStoreDAO localTestee = new ZstdBlobStoreDAO(underlying,
+    default void 
readReactiveShouldRoundTripCompressedResourcesWithoutCorruption(String 
resourcePath) throws IOException {
+        ZstdBlobStoreDAO localTestee = new ZstdBlobStoreDAO(underlying(),
             CompressionConfiguration.builder()
                 .enabled(true)
                 .threshold(1)
                 .build(),
-            metricFactory);
+            metricFactory());
         byte[] resourceBytes = readResource(resourcePath);
 
         Mono.from(localTestee.save(TEST_BUCKET_NAME, TEST_BLOB_ID, 
BlobStoreDAO.BytesBlob.of(resourceBytes))).block();
@@ -426,7 +360,7 @@ class ZstdBlobStoreDAOTest implements BlobStoreDAOContract, 
MetadataAwareBlobSto
         });
     }
 
-    private static Stream<Arguments> compressionSamples() {
+    static Stream<Arguments> compressionSamples() {
         return Stream.of(
             Arguments.of("zstd/text.txt"),
             Arguments.of("zstd/james-logo.jpg"),
@@ -434,7 +368,7 @@ class ZstdBlobStoreDAOTest implements BlobStoreDAOContract, 
MetadataAwareBlobSto
             Arguments.of("zstd/document.pdf"));
     }
 
-    private static Stream<Arguments> blobsWithReservedCompressionMetadata() {
+    static Stream<Arguments> blobsWithReservedCompressionMetadata() {
         BlobStoreDAO.BlobMetadata reservedMetadata = 
BlobStoreDAO.BlobMetadata.empty()
             .withMetadata(BlobStoreDAO.ContentEncoding.NAME, 
BlobStoreDAO.ContentEncoding.ZSTD.asValue());
 
@@ -444,7 +378,7 @@ class ZstdBlobStoreDAOTest implements BlobStoreDAOContract, 
MetadataAwareBlobSto
             
Arguments.of(BlobStoreDAO.ByteSourceBlob.of(ByteSource.wrap(ELEVEN_KILOBYTES.payload()),
 reservedMetadata)));
     }
 
-    private byte[] readResource(String resourcePath) throws IOException {
+    default byte[] readResource(String resourcePath) throws IOException {
         try (InputStream inputStream = 
ClassLoader.getSystemClassLoader().getResourceAsStream(resourcePath)) {
             assertThat(inputStream).describedAs("resource %s should exist", 
resourcePath).isNotNull();
             return inputStream.readAllBytes();
diff --git 
a/server/blob/blob-zstd/src/test/java/org/apache/james/blob/zstd/ZstdBlobStoreDAOTest.java
 
b/server/blob/blob-zstd/src/test/java/org/apache/james/blob/zstd/ZstdBlobStoreDAOTest.java
index 99ddc51af2..bc2177a3bc 100644
--- 
a/server/blob/blob-zstd/src/test/java/org/apache/james/blob/zstd/ZstdBlobStoreDAOTest.java
+++ 
b/server/blob/blob-zstd/src/test/java/org/apache/james/blob/zstd/ZstdBlobStoreDAOTest.java
@@ -19,28 +19,14 @@
 
 package org.apache.james.blob.zstd;
 
-import static org.apache.james.blob.api.BlobStoreDAOFixture.ELEVEN_KILOBYTES;
-import static org.apache.james.blob.api.BlobStoreDAOFixture.SHORT_BYTEARRAY;
-import static org.apache.james.blob.api.BlobStoreDAOFixture.TEST_BLOB_ID;
-import static org.apache.james.blob.api.BlobStoreDAOFixture.TEST_BUCKET_NAME;
 import static 
org.apache.james.blob.objectstorage.aws.JamesS3MetricPublisher.DEFAULT_S3_METRICS_PREFIX;
 import static 
org.apache.james.blob.objectstorage.aws.S3BlobStoreConfiguration.UPLOAD_RETRY_EXCEPTION_PREDICATE;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.assertj.core.api.SoftAssertions.assertSoftly;
 
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
 import java.time.Duration;
 import java.util.Optional;
-import java.util.Random;
-import java.util.stream.Stream;
 
 import org.apache.james.blob.api.BlobStoreDAO;
-import org.apache.james.blob.api.BlobStoreDAOContract;
 import org.apache.james.blob.api.BucketName;
-import org.apache.james.blob.api.MetadataAwareBlobStoreDAOContract;
 import org.apache.james.blob.api.TestBlobId;
 import org.apache.james.blob.objectstorage.aws.AwsS3AuthConfiguration;
 import org.apache.james.blob.objectstorage.aws.DockerAwsS3Container;
@@ -56,22 +42,12 @@ import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
-import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.Arguments;
-import org.junit.jupiter.params.provider.MethodSource;
 
-import com.google.common.io.ByteSource;
-
-import reactor.core.publisher.Mono;
 import reactor.util.retry.Retry;
 
 @ExtendWith(DockerAwsS3Extension.class)
-class ZstdBlobStoreDAOTest implements BlobStoreDAOContract, 
MetadataAwareBlobStoreDAOContract {
-    private static final CompressionConfiguration 
DEFAULT_COMPRESSION_CONFIGURATION = CompressionConfiguration.builder()
-        .enabled(true)
-        .build();
+class ZstdBlobStoreDAOTest implements ZstdBlobStoreDAOContract {
     private static final BucketName FALLBACK_BUCKET = 
BucketName.of("fallback");
 
     private static S3BlobStoreDAO underlying;
@@ -112,7 +88,7 @@ class ZstdBlobStoreDAOTest implements BlobStoreDAOContract, 
MetadataAwareBlobSto
     @BeforeEach
     void setUp() {
         metricFactory = new RecordingMetricFactory();
-        testee = new ZstdBlobStoreDAO(underlying, 
DEFAULT_COMPRESSION_CONFIGURATION, metricFactory);
+        testee = new ZstdBlobStoreDAO(underlying, 
defaultCompressionConfiguration(), metricFactory);
     }
 
     @AfterEach
@@ -123,331 +99,17 @@ class ZstdBlobStoreDAOTest implements 
BlobStoreDAOContract, MetadataAwareBlobSto
     }
 
     @Override
-    public BlobStoreDAO testee() {
-        return testee;
+    public BlobStoreDAO underlying() {
+        return underlying;
     }
 
     @Override
-    @Test
-    public void retrieveContentTransferEncodingShouldSucceed() {
-        ZstdBlobStoreDAO localTestee = new ZstdBlobStoreDAO(underlying,
-            CompressionConfiguration.builder()
-                .enabled(true)
-                .threshold(1)
-                .build(),
-            metricFactory);
-
-        // should compress and append content-transfer-encoding metadata, when 
threshold is met.
-        Mono.from(localTestee.save(TEST_BUCKET_NAME, TEST_BLOB_ID, 
ELEVEN_KILOBYTES)).block();
-
-        assertThat(Mono.from(localTestee.readBytes(TEST_BUCKET_NAME, 
TEST_BLOB_ID)).block().metadata().contentEncoding())
-            .contains(BlobStoreDAO.ContentEncoding.ZSTD);
-    }
-
-    @Test
-    void shouldPreserveCallerMetadataWhenCompressionHappens() {
-        ZstdBlobStoreDAO localTestee = new ZstdBlobStoreDAO(underlying,
-            CompressionConfiguration.builder()
-                .enabled(true)
-                .threshold(1)
-                .build(),
-            metricFactory);
-        BlobStoreDAO.BlobMetadata metadata = BlobStoreDAO.BlobMetadata.empty()
-            .withMetadata(new BlobStoreDAO.BlobMetadataName("name"), new 
BlobStoreDAO.BlobMetadataValue("value"))
-            .withMetadata(new BlobStoreDAO.BlobMetadataName("type"), new 
BlobStoreDAO.BlobMetadataValue("attachment"));
-        BlobStoreDAO.BytesBlob blob = 
BlobStoreDAO.BytesBlob.of(ELEVEN_KILOBYTES.payload(), metadata);
-
-        Mono.from(localTestee.save(TEST_BUCKET_NAME, TEST_BLOB_ID, 
blob)).block();
-
-        BlobStoreDAO.BytesBlob readBlob = 
Mono.from(localTestee.readBytes(TEST_BUCKET_NAME, TEST_BLOB_ID)).block();
-
-        assertSoftly(softly -> {
-            softly.assertThat(readBlob.metadata().underlyingMap())
-                .containsEntry(new BlobStoreDAO.BlobMetadataName("name"), new 
BlobStoreDAO.BlobMetadataValue("value"))
-                .containsEntry(new BlobStoreDAO.BlobMetadataName("type"), new 
BlobStoreDAO.BlobMetadataValue("attachment"))
-                .containsEntry(BlobStoreDAO.ContentEncoding.NAME, 
BlobStoreDAO.ContentEncoding.ZSTD.asValue())
-                .containsEntry(ZstdBlobStoreDAO.CONTENT_ORIGINAL_SIZE,
-                    new 
BlobStoreDAO.BlobMetadataValue(String.valueOf(ELEVEN_KILOBYTES.payload().length)));
-        });
-    }
-
-    @Test
-    void readExistingNonCompressedBlobShouldSucceed() {
-        Mono.from(underlying.save(TEST_BUCKET_NAME, TEST_BLOB_ID, 
ELEVEN_KILOBYTES)).block();
-
-        assertThat(Mono.from(testee.readBytes(TEST_BUCKET_NAME, 
TEST_BLOB_ID)).block())
-            .isEqualTo(ELEVEN_KILOBYTES);
-    }
-
-    @Test
-    void shouldNotCompressBlobIfThresholdIsNotMet() {
-        ZstdBlobStoreDAO localTestee = new ZstdBlobStoreDAO(underlying,
-            CompressionConfiguration.builder()
-                .enabled(true)
-                .threshold(16 * 1024)
-                .build(),
-            metricFactory);
-
-        Mono.from(localTestee.save(TEST_BUCKET_NAME, TEST_BLOB_ID, 
SHORT_BYTEARRAY)).block();
-
-        BlobStoreDAO.BytesBlob storedBlob = 
Mono.from(underlying.readBytes(TEST_BUCKET_NAME, TEST_BLOB_ID)).block();
-
-        assertSoftly(softly -> {
-            
softly.assertThat(storedBlob.payload()).isEqualTo(SHORT_BYTEARRAY.payload());
-            
softly.assertThat(storedBlob.metadata().contentEncoding()).isEmpty();
-            
softly.assertThat(storedBlob.metadata().get(ZstdBlobStoreDAO.CONTENT_ORIGINAL_SIZE)).isEmpty();
-        });
-    }
-
-    @Test
-    void readShouldDecompressCompressedBlob() throws IOException {
-        ZstdBlobStoreDAO localTestee = new ZstdBlobStoreDAO(underlying,
-            CompressionConfiguration.builder()
-                .enabled(true)
-                .threshold(1)
-                .build(),
-            metricFactory);
-
-        Mono.from(localTestee.save(TEST_BUCKET_NAME, TEST_BLOB_ID, 
ELEVEN_KILOBYTES)).block();
-
-        BlobStoreDAO.InputStreamBlob readBlob = 
localTestee.read(TEST_BUCKET_NAME, TEST_BLOB_ID);
-        byte[] payload = readBlob.payload().readAllBytes();
-
-        assertSoftly(softly -> {
-            softly.assertThat(payload).isEqualTo(ELEVEN_KILOBYTES.payload());
-            
softly.assertThat(readBlob.metadata().contentEncoding()).contains(BlobStoreDAO.ContentEncoding.ZSTD);
-            
softly.assertThat(readBlob.metadata().get(ZstdBlobStoreDAO.CONTENT_ORIGINAL_SIZE))
-                .contains(new 
BlobStoreDAO.BlobMetadataValue(String.valueOf(ELEVEN_KILOBYTES.payload().length)));
-        });
-    }
-
-    @Test
-    void readReactiveShouldDecompressCompressedBlob() throws IOException {
-        ZstdBlobStoreDAO localTestee = new ZstdBlobStoreDAO(underlying,
-            CompressionConfiguration.builder()
-                .enabled(true)
-                .threshold(1)
-                .build(),
-            metricFactory);
-
-        Mono.from(localTestee.save(TEST_BUCKET_NAME, TEST_BLOB_ID, 
ELEVEN_KILOBYTES)).block();
-
-        BlobStoreDAO.InputStreamBlob readBlob = 
Mono.from(localTestee.readReactive(TEST_BUCKET_NAME, TEST_BLOB_ID)).block();
-        byte[] payload = readBlob.payload().readAllBytes();
-
-        assertSoftly(softly -> {
-            softly.assertThat(payload).isEqualTo(ELEVEN_KILOBYTES.payload());
-            
softly.assertThat(readBlob.metadata().contentEncoding()).contains(BlobStoreDAO.ContentEncoding.ZSTD);
-            
softly.assertThat(readBlob.metadata().get(ZstdBlobStoreDAO.CONTENT_ORIGINAL_SIZE))
-                .contains(new 
BlobStoreDAO.BlobMetadataValue(String.valueOf(ELEVEN_KILOBYTES.payload().length)));
-        });
-    }
-
-    @Test
-    void shouldNotCompressBlobWhenMinRatioIsZero() {
-        ZstdBlobStoreDAO localTestee = new ZstdBlobStoreDAO(underlying,
-            CompressionConfiguration.builder()
-                .enabled(true)
-                .threshold(1)
-                .minRatio(0F)
-                .build(),
-            metricFactory);
-
-        Mono.from(localTestee.save(TEST_BUCKET_NAME, TEST_BLOB_ID, 
ELEVEN_KILOBYTES)).block();
-
-        BlobStoreDAO.BytesBlob storedBlob = 
Mono.from(underlying.readBytes(TEST_BUCKET_NAME, TEST_BLOB_ID)).block();
-
-        assertSoftly(softly -> {
-            
softly.assertThat(storedBlob.payload()).isEqualTo(ELEVEN_KILOBYTES.payload());
-            
softly.assertThat(storedBlob.metadata().contentEncoding()).isEmpty();
-            
softly.assertThat(storedBlob.metadata().get(ZstdBlobStoreDAO.CONTENT_ORIGINAL_SIZE)).isEmpty();
-        });
-    }
-
-    @Test
-    void shouldStillDecompressBlobWhenMinRatioIsZero() {
-        ZstdBlobStoreDAO compressingTestee = new ZstdBlobStoreDAO(underlying,
-            CompressionConfiguration.builder()
-                .enabled(true)
-                .threshold(1)
-                .build(),
-            metricFactory);
-        ZstdBlobStoreDAO uncompressingOnlyTestee = new 
ZstdBlobStoreDAO(underlying,
-            CompressionConfiguration.builder()
-                .enabled(true)
-                .threshold(1)
-                .minRatio(0F)
-                .build(),
-            metricFactory);
-
-        Mono.from(compressingTestee.save(TEST_BUCKET_NAME, TEST_BLOB_ID, 
ELEVEN_KILOBYTES)).block();
-
-        BlobStoreDAO.BytesBlob readBlob = 
Mono.from(uncompressingOnlyTestee.readBytes(TEST_BUCKET_NAME, 
TEST_BLOB_ID)).block();
-
-        assertSoftly(softly -> {
-            
softly.assertThat(readBlob.payload()).isEqualTo(ELEVEN_KILOBYTES.payload());
-            
softly.assertThat(readBlob.metadata().contentEncoding()).contains(BlobStoreDAO.ContentEncoding.ZSTD);
-            
softly.assertThat(readBlob.metadata().get(ZstdBlobStoreDAO.CONTENT_ORIGINAL_SIZE))
-                .contains(new 
BlobStoreDAO.BlobMetadataValue(String.valueOf(ELEVEN_KILOBYTES.payload().length)));
-        });
+    public RecordingMetricFactory metricFactory() {
+        return metricFactory;
     }
 
-    @Test
-    void shouldNotCompressBlobWhenMinRatioIsNotMet() {
-        ZstdBlobStoreDAO localTestee = new ZstdBlobStoreDAO(underlying,
-            CompressionConfiguration.builder()
-                .enabled(true)
-                .threshold(1)
-                .minRatio(0.5F)
-                .build(),
-            metricFactory);
-        byte[] randomPayload = new byte[4096];
-        new Random(1).nextBytes(randomPayload);
-        BlobStoreDAO.BytesBlob randomBlob = 
BlobStoreDAO.BytesBlob.of(randomPayload);
-
-        Mono.from(localTestee.save(TEST_BUCKET_NAME, TEST_BLOB_ID, 
randomBlob)).block();
-
-        BlobStoreDAO.BytesBlob storedBlob = 
Mono.from(underlying.readBytes(TEST_BUCKET_NAME, TEST_BLOB_ID)).block();
-
-        assertSoftly(softly -> {
-            softly.assertThat(storedBlob.payload()).isEqualTo(randomPayload);
-            
softly.assertThat(storedBlob.metadata().contentEncoding()).isEmpty();
-            
softly.assertThat(storedBlob.metadata().get(ZstdBlobStoreDAO.CONTENT_ORIGINAL_SIZE)).isEmpty();
-        });
-    }
-
-    @Test
-    void shouldRecordMetrics() {
-        ZstdBlobStoreDAO localTestee = new ZstdBlobStoreDAO(underlying,
-            CompressionConfiguration.builder()
-                .enabled(true)
-                .threshold(1)
-                .build(),
-            metricFactory);
-
-        Mono.from(localTestee.save(TEST_BUCKET_NAME, TEST_BLOB_ID, 
ELEVEN_KILOBYTES)).block();
-        Mono.from(localTestee.readBytes(TEST_BUCKET_NAME, 
TEST_BLOB_ID)).block();
-        BlobStoreDAO.BytesBlob storedBlob = 
Mono.from(underlying.readBytes(TEST_BUCKET_NAME, TEST_BLOB_ID)).block();
-
-        assertSoftly(softly -> {
-            
softly.assertThat(metricFactory.countFor(ZstdBlobStoreDAO.MetricRecorder.BLOB_ZSTD_COMPRESS_SAVE_COUNT_METRIC_NAME)).isEqualTo(1);
-            
softly.assertThat(metricFactory.countFor(ZstdBlobStoreDAO.MetricRecorder.BLOB_ZSTD_DECOMPRESS_COUNT_METRIC_NAME)).isEqualTo(1);
-            
softly.assertThat(metricFactory.countFor(ZstdBlobStoreDAO.MetricRecorder.BLOB_ZSTD_SAVED_BYTES_METRIC_NAME))
-                .isEqualTo(ELEVEN_KILOBYTES.payload().length - 
storedBlob.payload().length);
-            
softly.assertThat(metricFactory.executionTimesFor(ZstdBlobStoreDAO.MetricRecorder.BLOB_ZSTD_COMPRESS_LATENCY_METRIC_NAME)).hasSize(1);
-            
softly.assertThat(metricFactory.executionTimesFor(ZstdBlobStoreDAO.MetricRecorder.BLOB_ZSTD_DECOMPRESS_LATENCY_METRIC_NAME)).hasSize(1);
-        });
-    }
-
-    @Test
-    void shouldRecordThresholdSkipMetricIfThresholdNotMatch() {
-        Mono.from(testee.save(TEST_BUCKET_NAME, TEST_BLOB_ID, 
SHORT_BYTEARRAY)).block();
-
-        
assertThat(metricFactory.countFor(ZstdBlobStoreDAO.MetricRecorder.BLOB_ZSTD_THRESHOLD_SKIP_COUNT_METRIC_NAME))
-            .isEqualTo(1);
-    }
-
-    @ParameterizedTest
-    @MethodSource("blobsWithReservedCompressionMetadata")
-    void saveShouldRejectReservedCompressionMetadata(BlobStoreDAO.Blob blob) {
-        assertThatThrownBy(() -> Mono.from(testee.save(TEST_BUCKET_NAME, 
TEST_BLOB_ID, blob)).block())
-            .isInstanceOf(IllegalArgumentException.class)
-            .hasMessageContaining("Reserved zstd metadata are not allowed");
-    }
-
-    @ParameterizedTest
-    @MethodSource("compressionSamples")
-    void readBytesShouldRoundTripCompressedResourcesWithoutCorruption(String 
resourcePath) throws IOException {
-        ZstdBlobStoreDAO localTestee = new ZstdBlobStoreDAO(underlying,
-            CompressionConfiguration.builder()
-                .enabled(true)
-                .threshold(1)
-                .build(),
-            metricFactory);
-        byte[] resourceBytes = readResource(resourcePath);
-
-        Mono.from(localTestee.save(TEST_BUCKET_NAME, TEST_BLOB_ID, 
BlobStoreDAO.BytesBlob.of(resourceBytes))).block();
-
-        BlobStoreDAO.BytesBlob readBlob = 
Mono.from(localTestee.readBytes(TEST_BUCKET_NAME, TEST_BLOB_ID)).block();
-
-        assertSoftly(softly -> {
-            softly.assertThat(readBlob.payload()).isEqualTo(resourceBytes);
-            
softly.assertThat(readBlob.metadata().contentEncoding()).contains(BlobStoreDAO.ContentEncoding.ZSTD);
-            
softly.assertThat(readBlob.metadata().get(ZstdBlobStoreDAO.CONTENT_ORIGINAL_SIZE))
-                .contains(new 
BlobStoreDAO.BlobMetadataValue(String.valueOf(resourceBytes.length)));
-        });
-    }
-
-    @ParameterizedTest
-    @MethodSource("compressionSamples")
-    void readShouldRoundTripCompressedResourcesWithoutCorruption(String 
resourcePath) throws IOException {
-        ZstdBlobStoreDAO localTestee = new ZstdBlobStoreDAO(underlying,
-            CompressionConfiguration.builder()
-                .enabled(true)
-                .threshold(1)
-                .build(),
-            metricFactory);
-        byte[] resourceBytes = readResource(resourcePath);
-
-        Mono.from(localTestee.save(TEST_BUCKET_NAME, TEST_BLOB_ID, 
BlobStoreDAO.BytesBlob.of(resourceBytes))).block();
-
-        BlobStoreDAO.InputStreamBlob readBlob = 
localTestee.read(TEST_BUCKET_NAME, TEST_BLOB_ID);
-        byte[] payload = readBlob.payload().readAllBytes();
-
-        assertSoftly(softly -> {
-            softly.assertThat(payload).isEqualTo(resourceBytes);
-            
softly.assertThat(readBlob.metadata().contentEncoding()).contains(BlobStoreDAO.ContentEncoding.ZSTD);
-            
softly.assertThat(readBlob.metadata().get(ZstdBlobStoreDAO.CONTENT_ORIGINAL_SIZE))
-                .contains(new 
BlobStoreDAO.BlobMetadataValue(String.valueOf(resourceBytes.length)));
-        });
-    }
-
-    @ParameterizedTest
-    @MethodSource("compressionSamples")
-    void 
readReactiveShouldRoundTripCompressedResourcesWithoutCorruption(String 
resourcePath) throws IOException {
-        ZstdBlobStoreDAO localTestee = new ZstdBlobStoreDAO(underlying,
-            CompressionConfiguration.builder()
-                .enabled(true)
-                .threshold(1)
-                .build(),
-            metricFactory);
-        byte[] resourceBytes = readResource(resourcePath);
-
-        Mono.from(localTestee.save(TEST_BUCKET_NAME, TEST_BLOB_ID, 
BlobStoreDAO.BytesBlob.of(resourceBytes))).block();
-
-        BlobStoreDAO.InputStreamBlob readBlob = 
Mono.from(localTestee.readReactive(TEST_BUCKET_NAME, TEST_BLOB_ID)).block();
-        byte[] payload = readBlob.payload().readAllBytes();
-
-        assertSoftly(softly -> {
-            softly.assertThat(payload).isEqualTo(resourceBytes);
-            
softly.assertThat(readBlob.metadata().contentEncoding()).contains(BlobStoreDAO.ContentEncoding.ZSTD);
-            
softly.assertThat(readBlob.metadata().get(ZstdBlobStoreDAO.CONTENT_ORIGINAL_SIZE))
-                .contains(new 
BlobStoreDAO.BlobMetadataValue(String.valueOf(resourceBytes.length)));
-        });
-    }
-
-    private static Stream<Arguments> compressionSamples() {
-        return Stream.of(
-            Arguments.of("zstd/text.txt"),
-            Arguments.of("zstd/james-logo.jpg"),
-            Arguments.of("zstd/mail1.eml"),
-            Arguments.of("zstd/document.pdf"));
-    }
-
-    private static Stream<Arguments> blobsWithReservedCompressionMetadata() {
-        BlobStoreDAO.BlobMetadata reservedMetadata = 
BlobStoreDAO.BlobMetadata.empty()
-            .withMetadata(BlobStoreDAO.ContentEncoding.NAME, 
BlobStoreDAO.ContentEncoding.ZSTD.asValue());
-
-        return Stream.of(
-            Arguments.of(BlobStoreDAO.BytesBlob.of(ELEVEN_KILOBYTES.payload(), 
reservedMetadata)),
-            Arguments.of(BlobStoreDAO.InputStreamBlob.of(new 
ByteArrayInputStream(ELEVEN_KILOBYTES.payload()), reservedMetadata)),
-            
Arguments.of(BlobStoreDAO.ByteSourceBlob.of(ByteSource.wrap(ELEVEN_KILOBYTES.payload()),
 reservedMetadata)));
-    }
-
-    private byte[] readResource(String resourcePath) throws IOException {
-        try (InputStream inputStream = 
ClassLoader.getSystemClassLoader().getResourceAsStream(resourcePath)) {
-            assertThat(inputStream).describedAs("resource %s should exist", 
resourcePath).isNotNull();
-            return inputStream.readAllBytes();
-        }
+    @Override
+    public ZstdBlobStoreDAO testee() {
+        return testee;
     }
 }
diff --git 
a/server/blob/blob-zstd/src/test/java/org/apache/james/blob/zstd/ZstdFileBlobStoreDAOTest.java
 
b/server/blob/blob-zstd/src/test/java/org/apache/james/blob/zstd/ZstdFileBlobStoreDAOTest.java
new file mode 100644
index 0000000000..6532c33c08
--- /dev/null
+++ 
b/server/blob/blob-zstd/src/test/java/org/apache/james/blob/zstd/ZstdFileBlobStoreDAOTest.java
@@ -0,0 +1,62 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.blob.zstd;
+
+import org.apache.james.blob.api.BlobStoreDAO;
+import org.apache.james.blob.api.PlainBlobId;
+import org.apache.james.blob.file.FileBlobStoreDAO;
+import org.apache.james.metrics.tests.RecordingMetricFactory;
+import org.apache.james.server.core.filesystem.FileSystemImpl;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
+
+class ZstdFileBlobStoreDAOTest implements ZstdBlobStoreDAOContract {
+
+    private FileBlobStoreDAO underlying;
+    private RecordingMetricFactory metricFactory;
+    private ZstdBlobStoreDAO testee;
+
+    @BeforeEach
+    void setUp() throws Exception {
+        underlying = new FileBlobStoreDAO(FileSystemImpl.forTesting(), new 
PlainBlobId.Factory());
+        metricFactory = new RecordingMetricFactory();
+        testee = new ZstdBlobStoreDAO(underlying, 
defaultCompressionConfiguration(), metricFactory);
+    }
+
+    @Override
+    public BlobStoreDAO underlying() {
+        return underlying;
+    }
+
+    @Override
+    public RecordingMetricFactory metricFactory() {
+        return metricFactory;
+    }
+
+    @Override
+    public ZstdBlobStoreDAO testee() {
+        return testee;
+    }
+
+    @Override
+    @Disabled("Not supported on local filesystem due to concurrent file 
locking")
+    public void mixingSaveReadAndDeleteShouldReturnConsistentState() {
+    }
+}
diff --git 
a/server/blob/blob-zstd/src/test/java/org/apache/james/blob/zstd/ZstdMemoryBlobStoreDAOTest.java
 
b/server/blob/blob-zstd/src/test/java/org/apache/james/blob/zstd/ZstdMemoryBlobStoreDAOTest.java
new file mode 100644
index 0000000000..e980644f2a
--- /dev/null
+++ 
b/server/blob/blob-zstd/src/test/java/org/apache/james/blob/zstd/ZstdMemoryBlobStoreDAOTest.java
@@ -0,0 +1,60 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.blob.zstd;
+
+import org.apache.james.blob.api.BlobStoreDAO;
+import org.apache.james.blob.memory.MemoryBlobStoreDAO;
+import org.apache.james.metrics.tests.RecordingMetricFactory;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
+
+class ZstdMemoryBlobStoreDAOTest implements ZstdBlobStoreDAOContract {
+
+    private MemoryBlobStoreDAO underlying;
+    private RecordingMetricFactory metricFactory;
+    private ZstdBlobStoreDAO testee;
+
+    @BeforeEach
+    void setUp() {
+        underlying = new MemoryBlobStoreDAO();
+        metricFactory = new RecordingMetricFactory();
+        testee = new ZstdBlobStoreDAO(underlying, 
defaultCompressionConfiguration(), metricFactory);
+    }
+
+    @Override
+    public BlobStoreDAO underlying() {
+        return underlying;
+    }
+
+    @Override
+    public RecordingMetricFactory metricFactory() {
+        return metricFactory;
+    }
+
+    @Override
+    public ZstdBlobStoreDAO testee() {
+        return testee;
+    }
+
+    @Override
+    @Disabled("Not supported")
+    public void listBucketsShouldReturnBucketsWithNoBlob() {
+    }
+}
diff --git 
a/server/container/guice/distributed/src/test/java/org/apache/james/modules/blobstore/BlobStoreModulesChooserTest.java
 
b/server/container/guice/distributed/src/test/java/org/apache/james/modules/blobstore/BlobStoreModulesChooserTest.java
index 53040161e2..8cffb43cbf 100644
--- 
a/server/container/guice/distributed/src/test/java/org/apache/james/modules/blobstore/BlobStoreModulesChooserTest.java
+++ 
b/server/container/guice/distributed/src/test/java/org/apache/james/modules/blobstore/BlobStoreModulesChooserTest.java
@@ -49,6 +49,28 @@ class BlobStoreModulesChooserTest {
             .hasSize(1);
     }
 
+    @Test
+    void provideBlobStoreShouldReturnFileBlobStoreWhenFileConfigured() {
+        
assertThat(BlobStoreModulesChooser.chooseModules(BlobStoreConfiguration.builder()
+                .file()
+                .disableCache()
+                .passthrough()
+                .noCryptoConfig()))
+            .filteredOn(module -> module instanceof 
BlobStoreModulesChooser.FileBlobStoreDAODeclarationModule)
+            .hasSize(1);
+    }
+
+    @Test
+    void provideBlobStoreShouldReturnPostgresBlobStoreWhenPostgresConfigured() 
{
+        
assertThat(BlobStoreModulesChooser.chooseModules(BlobStoreConfiguration.builder()
+                .postgres()
+                .disableCache()
+                .passthrough()
+                .noCryptoConfig()))
+            .filteredOn(module -> module instanceof 
BlobStoreModulesChooser.PostgresBlobStoreDAODeclarationModule)
+            .hasSize(1);
+    }
+
     @Test
     void provideBlobStoreShouldReturnNoEncryptionWhenNoneConfigured() {
         
assertThat(BlobStoreModulesChooser.chooseModules(BlobStoreConfiguration.builder()


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

Reply via email to