chore: record pre-existing uncommitted repository state

Snapshot of the in-flight state that already existed, identically, in both
this worktree and the main checkout before this session began: the initial
HTTP Client platform implementation (previously untracked), the redis-lab
removal, and the JPA / object-storage / notification integration work.

Kept separate from this session's HTTP Client review response, which lands
in the following commit, so the two bodies of work stay reviewable apart.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
DongHyeonka
2026-08-11 16:48:43 +09:00
co-authored by Claude Opus 5
parent 1a3b560678
commit 5f10b791d3
1857 changed files with 130925 additions and 72491 deletions
+38 -20
View File
@@ -8,6 +8,8 @@
// root dependencyManagement block stays awssdk-free), mirroring the grpc module's grpc-bom import.
description = 'Outbound adapter: object storage (S3/MinIO + local filesystem)'
apply from: "${rootProject.projectDir}/gradle/strict-qualification-test.gradle"
sourceSets {
objectStorageMinioContractTest {
java.srcDir 'src/objectStorageMinioContractTest/java'
@@ -61,31 +63,47 @@ dependencies {
testImplementation 'org.testcontainers:testcontainers'
testImplementation 'org.testcontainers:testcontainers-junit-jupiter'
testImplementation 'org.testcontainers:testcontainers-toxiproxy'
testImplementation 'com.tngtech.archunit:archunit-junit5:1.3.0'
testImplementation 'net.jqwik:jqwik:1.9.1'
}
tasks.register('objectStorageMinioContractTest', Test) {
description = 'Runs the non-skipping exact-release MinIO managed object contract.'
group = 'verification'
testClassesDirs = sourceSets.objectStorageMinioContractTest.output.classesDirs
classpath = sourceSets.objectStorageMinioContractTest.runtimeClasspath
useJUnitPlatform()
def objectStorageReadinessRegistry = rootProject.projectDir.parentFile.toPath()
.resolve('docs/registries/object-storage-readiness.yaml').toFile()
tasks.named('test') {
inputs.file(objectStorageReadinessRegistry)
.withPathSensitivity(PathSensitivity.RELATIVE)
systemProperty 'objectstorage.readiness.registry', objectStorageReadinessRegistry.absolutePath
}
def objectStorageMinioContractQualification = registerStrictQualificationTest(
name: 'objectStorageMinioContractTest',
sourceSet: sourceSets.objectStorageMinioContractTest,
requiredClasses: [
'dev.caskeleton.adapter.outbound.objectstorage.qualification.MinioDirectTransferContractTest',
'dev.caskeleton.adapter.outbound.objectstorage.qualification.MinioManagedObjectContractTest'
],
description: 'Runs the non-skipping exact-release MinIO managed object contract.')
objectStorageMinioContractQualification.configure {
shouldRunAfter tasks.named('test')
}
tasks.register('objectStorageMinioFaultTest', Test) {
description = 'Runs the non-skipping digest-pinned MinIO/Toxiproxy fault contract.'
group = 'verification'
testClassesDirs = sourceSets.objectStorageMinioFaultTest.output.classesDirs
classpath = sourceSets.objectStorageMinioFaultTest.runtimeClasspath
useJUnitPlatform()
shouldRunAfter tasks.named('objectStorageMinioContractTest')
def objectStorageMinioFaultQualification = registerStrictQualificationTest(
name: 'objectStorageMinioFaultTest',
sourceSet: sourceSets.objectStorageMinioFaultTest,
requiredClasses: [
'dev.caskeleton.adapter.outbound.objectstorage.qualification.MinioDirectTransferFaultTest',
'dev.caskeleton.adapter.outbound.objectstorage.qualification.MinioManagedObjectFaultTest'
],
description: 'Runs the non-skipping digest-pinned MinIO/Toxiproxy fault contract.')
objectStorageMinioFaultQualification.configure {
shouldRunAfter objectStorageMinioContractQualification
}
tasks.register('objectStorageAwsQualificationTest', Test) {
description = 'Runs only with explicit protected AWS sandbox authority and exact inputs.'
group = 'verification'
testClassesDirs = sourceSets.objectStorageAwsQualificationTest.output.classesDirs
classpath = sourceSets.objectStorageAwsQualificationTest.runtimeClasspath
useJUnitPlatform()
}
registerStrictQualificationTest(
name: 'objectStorageAwsQualificationTest',
sourceSet: sourceSets.objectStorageAwsQualificationTest,
requiredClasses: [
'dev.caskeleton.adapter.outbound.objectstorage.qualification.AwsS3DirectTransferQualificationTest',
'dev.caskeleton.adapter.outbound.objectstorage.qualification.AwsS3ManagedCommonSubsetQualificationTest'
],
description: 'Runs only with explicit protected AWS sandbox authority and exact inputs.')
@@ -35,6 +35,11 @@ com.google.protobuf:protobuf-java:4.33.2=annotationProcessor,objectStorageAwsQua
com.h3xstream.findsecbugs:findsecbugs-plugin:1.14.0=spotbugsPlugins
com.jayway.jsonpath:json-path:2.9.0=objectStorageAwsQualificationTestCompileClasspath,objectStorageAwsQualificationTestRuntimeClasspath,objectStorageMinioContractTestCompileClasspath,objectStorageMinioContractTestRuntimeClasspath,objectStorageMinioFaultTestCompileClasspath,objectStorageMinioFaultTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
com.puppycrawl.tools:checkstyle:13.5.0=checkstyle
com.tngtech.archunit:archunit-junit5-api:1.3.0=objectStorageAwsQualificationTestCompileClasspath,objectStorageAwsQualificationTestRuntimeClasspath,objectStorageMinioContractTestCompileClasspath,objectStorageMinioContractTestRuntimeClasspath,objectStorageMinioFaultTestCompileClasspath,objectStorageMinioFaultTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
com.tngtech.archunit:archunit-junit5-engine-api:1.3.0=objectStorageAwsQualificationTestRuntimeClasspath,objectStorageMinioContractTestRuntimeClasspath,objectStorageMinioFaultTestRuntimeClasspath,testRuntimeClasspath
com.tngtech.archunit:archunit-junit5-engine:1.3.0=objectStorageAwsQualificationTestRuntimeClasspath,objectStorageMinioContractTestRuntimeClasspath,objectStorageMinioFaultTestRuntimeClasspath,testRuntimeClasspath
com.tngtech.archunit:archunit-junit5:1.3.0=objectStorageAwsQualificationTestCompileClasspath,objectStorageAwsQualificationTestRuntimeClasspath,objectStorageMinioContractTestCompileClasspath,objectStorageMinioContractTestRuntimeClasspath,objectStorageMinioFaultTestCompileClasspath,objectStorageMinioFaultTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
com.tngtech.archunit:archunit:1.3.0=objectStorageAwsQualificationTestCompileClasspath,objectStorageAwsQualificationTestRuntimeClasspath,objectStorageMinioContractTestCompileClasspath,objectStorageMinioContractTestRuntimeClasspath,objectStorageMinioFaultTestCompileClasspath,objectStorageMinioFaultTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
com.vaadin.external.google:android-json:0.0.20131108.vaadin1=objectStorageAwsQualificationTestCompileClasspath,objectStorageAwsQualificationTestRuntimeClasspath,objectStorageMinioContractTestCompileClasspath,objectStorageMinioContractTestRuntimeClasspath,objectStorageMinioFaultTestCompileClasspath,objectStorageMinioFaultTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
commons-beanutils:commons-beanutils:1.11.0=checkstyle
commons-codec:commons-codec:1.19.0=objectStorageAwsQualificationTestCompileClasspath,objectStorageAwsQualificationTestRuntimeClasspath,objectStorageMinioContractTestCompileClasspath,objectStorageMinioContractTestRuntimeClasspath,objectStorageMinioFaultTestCompileClasspath,objectStorageMinioFaultTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
@@ -48,20 +53,20 @@ io.github.eisop:dataflow-errorprone:3.41.0-eisop1=annotationProcessor,objectStor
io.github.java-diff-utils:java-diff-utils:4.12=annotationProcessor,objectStorageAwsQualificationTestAnnotationProcessor,objectStorageMinioContractTestAnnotationProcessor,objectStorageMinioFaultTestAnnotationProcessor,testAnnotationProcessor
io.micrometer:micrometer-commons:1.16.0=compileClasspath,objectStorageAwsQualificationTestCompileClasspath,objectStorageAwsQualificationTestRuntimeClasspath,objectStorageMinioContractTestCompileClasspath,objectStorageMinioContractTestRuntimeClasspath,objectStorageMinioFaultTestCompileClasspath,objectStorageMinioFaultTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.micrometer:micrometer-observation:1.16.0=compileClasspath,objectStorageAwsQualificationTestCompileClasspath,objectStorageAwsQualificationTestRuntimeClasspath,objectStorageMinioContractTestCompileClasspath,objectStorageMinioContractTestRuntimeClasspath,objectStorageMinioFaultTestCompileClasspath,objectStorageMinioFaultTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.netty:netty-buffer:4.2.7.Final=compileClasspath,objectStorageAwsQualificationTestCompileClasspath,objectStorageAwsQualificationTestRuntimeClasspath,objectStorageMinioContractTestCompileClasspath,objectStorageMinioContractTestRuntimeClasspath,objectStorageMinioFaultTestCompileClasspath,objectStorageMinioFaultTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.netty:netty-codec-base:4.2.7.Final=compileClasspath,objectStorageAwsQualificationTestCompileClasspath,objectStorageAwsQualificationTestRuntimeClasspath,objectStorageMinioContractTestCompileClasspath,objectStorageMinioContractTestRuntimeClasspath,objectStorageMinioFaultTestCompileClasspath,objectStorageMinioFaultTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.netty:netty-codec-compression:4.2.7.Final=compileClasspath,objectStorageAwsQualificationTestCompileClasspath,objectStorageAwsQualificationTestRuntimeClasspath,objectStorageMinioContractTestCompileClasspath,objectStorageMinioContractTestRuntimeClasspath,objectStorageMinioFaultTestCompileClasspath,objectStorageMinioFaultTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.netty:netty-codec-http2:4.2.7.Final=compileClasspath,objectStorageAwsQualificationTestCompileClasspath,objectStorageAwsQualificationTestRuntimeClasspath,objectStorageMinioContractTestCompileClasspath,objectStorageMinioContractTestRuntimeClasspath,objectStorageMinioFaultTestCompileClasspath,objectStorageMinioFaultTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.netty:netty-codec-http:4.2.7.Final=compileClasspath,objectStorageAwsQualificationTestCompileClasspath,objectStorageAwsQualificationTestRuntimeClasspath,objectStorageMinioContractTestCompileClasspath,objectStorageMinioContractTestRuntimeClasspath,objectStorageMinioFaultTestCompileClasspath,objectStorageMinioFaultTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.netty:netty-codec-marshalling:4.2.7.Final=compileClasspath,objectStorageAwsQualificationTestCompileClasspath,objectStorageAwsQualificationTestRuntimeClasspath,objectStorageMinioContractTestCompileClasspath,objectStorageMinioContractTestRuntimeClasspath,objectStorageMinioFaultTestCompileClasspath,objectStorageMinioFaultTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.netty:netty-codec-protobuf:4.2.7.Final=compileClasspath,objectStorageAwsQualificationTestCompileClasspath,objectStorageAwsQualificationTestRuntimeClasspath,objectStorageMinioContractTestCompileClasspath,objectStorageMinioContractTestRuntimeClasspath,objectStorageMinioFaultTestCompileClasspath,objectStorageMinioFaultTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.netty:netty-codec:4.2.7.Final=compileClasspath,objectStorageAwsQualificationTestCompileClasspath,objectStorageAwsQualificationTestRuntimeClasspath,objectStorageMinioContractTestCompileClasspath,objectStorageMinioContractTestRuntimeClasspath,objectStorageMinioFaultTestCompileClasspath,objectStorageMinioFaultTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.netty:netty-common:4.2.7.Final=compileClasspath,objectStorageAwsQualificationTestCompileClasspath,objectStorageAwsQualificationTestRuntimeClasspath,objectStorageMinioContractTestCompileClasspath,objectStorageMinioContractTestRuntimeClasspath,objectStorageMinioFaultTestCompileClasspath,objectStorageMinioFaultTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.netty:netty-handler:4.2.7.Final=compileClasspath,objectStorageAwsQualificationTestCompileClasspath,objectStorageAwsQualificationTestRuntimeClasspath,objectStorageMinioContractTestCompileClasspath,objectStorageMinioContractTestRuntimeClasspath,objectStorageMinioFaultTestCompileClasspath,objectStorageMinioFaultTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.netty:netty-resolver:4.2.7.Final=compileClasspath,objectStorageAwsQualificationTestCompileClasspath,objectStorageAwsQualificationTestRuntimeClasspath,objectStorageMinioContractTestCompileClasspath,objectStorageMinioContractTestRuntimeClasspath,objectStorageMinioFaultTestCompileClasspath,objectStorageMinioFaultTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.netty:netty-transport-classes-epoll:4.2.7.Final=compileClasspath,objectStorageAwsQualificationTestCompileClasspath,objectStorageAwsQualificationTestRuntimeClasspath,objectStorageMinioContractTestCompileClasspath,objectStorageMinioContractTestRuntimeClasspath,objectStorageMinioFaultTestCompileClasspath,objectStorageMinioFaultTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.netty:netty-transport-native-unix-common:4.2.7.Final=compileClasspath,objectStorageAwsQualificationTestCompileClasspath,objectStorageAwsQualificationTestRuntimeClasspath,objectStorageMinioContractTestCompileClasspath,objectStorageMinioContractTestRuntimeClasspath,objectStorageMinioFaultTestCompileClasspath,objectStorageMinioFaultTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.netty:netty-transport:4.2.7.Final=compileClasspath,objectStorageAwsQualificationTestCompileClasspath,objectStorageAwsQualificationTestRuntimeClasspath,objectStorageMinioContractTestCompileClasspath,objectStorageMinioContractTestRuntimeClasspath,objectStorageMinioFaultTestCompileClasspath,objectStorageMinioFaultTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.netty:netty-buffer:4.2.17.Final=compileClasspath,objectStorageAwsQualificationTestCompileClasspath,objectStorageAwsQualificationTestRuntimeClasspath,objectStorageMinioContractTestCompileClasspath,objectStorageMinioContractTestRuntimeClasspath,objectStorageMinioFaultTestCompileClasspath,objectStorageMinioFaultTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.netty:netty-codec-base:4.2.17.Final=compileClasspath,objectStorageAwsQualificationTestCompileClasspath,objectStorageAwsQualificationTestRuntimeClasspath,objectStorageMinioContractTestCompileClasspath,objectStorageMinioContractTestRuntimeClasspath,objectStorageMinioFaultTestCompileClasspath,objectStorageMinioFaultTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.netty:netty-codec-compression:4.2.17.Final=compileClasspath,objectStorageAwsQualificationTestCompileClasspath,objectStorageAwsQualificationTestRuntimeClasspath,objectStorageMinioContractTestCompileClasspath,objectStorageMinioContractTestRuntimeClasspath,objectStorageMinioFaultTestCompileClasspath,objectStorageMinioFaultTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.netty:netty-codec-http2:4.2.17.Final=compileClasspath,objectStorageAwsQualificationTestCompileClasspath,objectStorageAwsQualificationTestRuntimeClasspath,objectStorageMinioContractTestCompileClasspath,objectStorageMinioContractTestRuntimeClasspath,objectStorageMinioFaultTestCompileClasspath,objectStorageMinioFaultTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.netty:netty-codec-http:4.2.17.Final=compileClasspath,objectStorageAwsQualificationTestCompileClasspath,objectStorageAwsQualificationTestRuntimeClasspath,objectStorageMinioContractTestCompileClasspath,objectStorageMinioContractTestRuntimeClasspath,objectStorageMinioFaultTestCompileClasspath,objectStorageMinioFaultTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.netty:netty-codec-marshalling:4.2.17.Final=compileClasspath,objectStorageAwsQualificationTestCompileClasspath,objectStorageAwsQualificationTestRuntimeClasspath,objectStorageMinioContractTestCompileClasspath,objectStorageMinioContractTestRuntimeClasspath,objectStorageMinioFaultTestCompileClasspath,objectStorageMinioFaultTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.netty:netty-codec-protobuf:4.2.17.Final=compileClasspath,objectStorageAwsQualificationTestCompileClasspath,objectStorageAwsQualificationTestRuntimeClasspath,objectStorageMinioContractTestCompileClasspath,objectStorageMinioContractTestRuntimeClasspath,objectStorageMinioFaultTestCompileClasspath,objectStorageMinioFaultTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.netty:netty-codec:4.2.17.Final=compileClasspath,objectStorageAwsQualificationTestCompileClasspath,objectStorageAwsQualificationTestRuntimeClasspath,objectStorageMinioContractTestCompileClasspath,objectStorageMinioContractTestRuntimeClasspath,objectStorageMinioFaultTestCompileClasspath,objectStorageMinioFaultTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.netty:netty-common:4.2.17.Final=compileClasspath,objectStorageAwsQualificationTestCompileClasspath,objectStorageAwsQualificationTestRuntimeClasspath,objectStorageMinioContractTestCompileClasspath,objectStorageMinioContractTestRuntimeClasspath,objectStorageMinioFaultTestCompileClasspath,objectStorageMinioFaultTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.netty:netty-handler:4.2.17.Final=compileClasspath,objectStorageAwsQualificationTestCompileClasspath,objectStorageAwsQualificationTestRuntimeClasspath,objectStorageMinioContractTestCompileClasspath,objectStorageMinioContractTestRuntimeClasspath,objectStorageMinioFaultTestCompileClasspath,objectStorageMinioFaultTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.netty:netty-resolver:4.2.17.Final=compileClasspath,objectStorageAwsQualificationTestCompileClasspath,objectStorageAwsQualificationTestRuntimeClasspath,objectStorageMinioContractTestCompileClasspath,objectStorageMinioContractTestRuntimeClasspath,objectStorageMinioFaultTestCompileClasspath,objectStorageMinioFaultTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.netty:netty-transport-classes-epoll:4.2.17.Final=compileClasspath,objectStorageAwsQualificationTestCompileClasspath,objectStorageAwsQualificationTestRuntimeClasspath,objectStorageMinioContractTestCompileClasspath,objectStorageMinioContractTestRuntimeClasspath,objectStorageMinioFaultTestCompileClasspath,objectStorageMinioFaultTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.netty:netty-transport-native-unix-common:4.2.17.Final=compileClasspath,objectStorageAwsQualificationTestCompileClasspath,objectStorageAwsQualificationTestRuntimeClasspath,objectStorageMinioContractTestCompileClasspath,objectStorageMinioContractTestRuntimeClasspath,objectStorageMinioFaultTestCompileClasspath,objectStorageMinioFaultTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.netty:netty-transport:4.2.17.Final=compileClasspath,objectStorageAwsQualificationTestCompileClasspath,objectStorageAwsQualificationTestRuntimeClasspath,objectStorageMinioContractTestCompileClasspath,objectStorageMinioContractTestRuntimeClasspath,objectStorageMinioFaultTestCompileClasspath,objectStorageMinioFaultTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
jakarta.activation:jakarta.activation-api:2.1.4=objectStorageAwsQualificationTestCompileClasspath,objectStorageAwsQualificationTestRuntimeClasspath,objectStorageMinioContractTestCompileClasspath,objectStorageMinioContractTestRuntimeClasspath,objectStorageMinioFaultTestCompileClasspath,objectStorageMinioFaultTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
jakarta.annotation:jakarta.annotation-api:3.0.0=objectStorageAwsQualificationTestCompileClasspath,objectStorageAwsQualificationTestRuntimeClasspath,objectStorageMinioContractTestCompileClasspath,objectStorageMinioContractTestRuntimeClasspath,objectStorageMinioFaultTestCompileClasspath,objectStorageMinioFaultTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
jakarta.xml.bind:jakarta.xml.bind-api:4.0.4=objectStorageAwsQualificationTestCompileClasspath,objectStorageAwsQualificationTestRuntimeClasspath,objectStorageMinioContractTestCompileClasspath,objectStorageMinioContractTestRuntimeClasspath,objectStorageMinioFaultTestCompileClasspath,objectStorageMinioFaultTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
@@ -118,7 +123,7 @@ org.junit.platform:junit-platform-engine:6.0.1=objectStorageAwsQualificationTest
org.junit.platform:junit-platform-launcher:6.0.1=objectStorageAwsQualificationTestRuntimeClasspath,objectStorageMinioContractTestRuntimeClasspath,objectStorageMinioFaultTestRuntimeClasspath,testRuntimeClasspath
org.junit:junit-bom:6.0.1=objectStorageAwsQualificationTestCompileClasspath,objectStorageAwsQualificationTestRuntimeClasspath,objectStorageMinioContractTestCompileClasspath,objectStorageMinioContractTestRuntimeClasspath,objectStorageMinioFaultTestCompileClasspath,objectStorageMinioFaultTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
org.junit:junit-bom:6.1.0=spotbugs
org.mockito:mockito-core:5.20.0=objectStorageAwsQualificationTestCompileClasspath,objectStorageAwsQualificationTestRuntimeClasspath,objectStorageMinioContractTestCompileClasspath,objectStorageMinioContractTestRuntimeClasspath,objectStorageMinioFaultTestCompileClasspath,objectStorageMinioFaultTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
org.mockito:mockito-core:5.20.0=mockitoAgent,objectStorageAwsQualificationTestCompileClasspath,objectStorageAwsQualificationTestRuntimeClasspath,objectStorageMinioContractTestCompileClasspath,objectStorageMinioContractTestRuntimeClasspath,objectStorageMinioFaultTestCompileClasspath,objectStorageMinioFaultTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
org.mockito:mockito-junit-jupiter:5.20.0=objectStorageAwsQualificationTestCompileClasspath,objectStorageAwsQualificationTestRuntimeClasspath,objectStorageMinioContractTestCompileClasspath,objectStorageMinioContractTestRuntimeClasspath,objectStorageMinioFaultTestCompileClasspath,objectStorageMinioFaultTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
org.objenesis:objenesis:3.3=objectStorageAwsQualificationTestRuntimeClasspath,objectStorageMinioContractTestRuntimeClasspath,objectStorageMinioFaultTestRuntimeClasspath,testRuntimeClasspath
org.opentest4j:opentest4j:1.3.0=objectStorageAwsQualificationTestCompileClasspath,objectStorageAwsQualificationTestRuntimeClasspath,objectStorageMinioContractTestCompileClasspath,objectStorageMinioContractTestRuntimeClasspath,objectStorageMinioFaultTestCompileClasspath,objectStorageMinioFaultTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
@@ -19,6 +19,8 @@ import org.slf4j.LoggerFactory;
* port exposes no content type on read); it is echoed back in the {@link StoredObject} receipt
* only.
*/
// This exact compatibility adapter remains active until data/API migration is complete.
@SuppressWarnings("removal")
public class FilesystemObjectStorageAdapter implements ObjectStoragePort {
private static final String DEPENDENCY_NAME = "objectstorage";
@@ -27,6 +27,8 @@ import software.amazon.awssdk.services.s3.model.S3Exception;
* (wired in {@link ObjectStorageConfig}) make this same code work against real AWS S3 and MinIO.
* The {@code location} in the returned {@link StoredObject} is an {@code s3://bucket/key} URI.
*/
// This exact compatibility adapter remains active until data/API migration is complete.
@SuppressWarnings("removal")
public class S3ObjectStorageAdapter implements ObjectStoragePort {
private static final Logger log = LoggerFactory.getLogger(S3ObjectStorageAdapter.class);
@@ -7,7 +7,8 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
/** Explicit isolated legacy-adoption settings; normal runtime remains disabled. */
@ConfigurationProperties(prefix = "app.object-storage.legacy-adoption")
@SuppressWarnings("removal")
// The adoption settings remain active until data/API migration is complete.
@SuppressWarnings("deprecation")
public record LegacyObjectAdoptionSettings(
boolean enabled,
LegacyObjectAdoptionRequest.Mode mode,
@@ -21,7 +21,6 @@ import software.amazon.awssdk.services.s3.S3Client;
/** Disabled-by-default canonical composition root for normal object-storage semantic ports. */
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(ObjectStorageCapabilitySettings.class)
@SuppressWarnings("removal")
public class ObjectStorageCapabilityConfig {
@Bean
@@ -91,6 +90,8 @@ public class ObjectStorageCapabilityConfig {
prefix = "app.object-storage.legacy",
name = "enabled",
havingValue = "true")
// This exact legacy runtime assembly remains active until data/API migration is complete.
@SuppressWarnings("removal")
public LegacyRuntime canonicalLegacyObjectStorageRuntime(
ObjectStorageCapabilitySettings settings) {
ObjectStorageCapabilitySettings.Legacy legacy = settings.legacy();
@@ -114,10 +115,13 @@ public class ObjectStorageCapabilityConfig {
prefix = "app.object-storage.legacy",
name = "enabled",
havingValue = "true")
// This exact legacy port exposure remains active until data/API migration is complete.
@SuppressWarnings("removal")
public ObjectStoragePort canonicalLegacyObjectStoragePort(LegacyRuntime runtime) {
return runtime.port();
}
// This exact old-settings translation remains active until data/API migration is complete.
@SuppressWarnings("removal")
private static ObjectStorageSettings legacySettings(
ObjectStorageCapabilitySettings.Legacy source) {
@@ -134,6 +138,8 @@ public class ObjectStorageCapabilityConfig {
return target;
}
// This holder owns only the exact legacy port/client lifetime during migration.
@SuppressWarnings("removal")
public static final class LegacyRuntime implements AutoCloseable {
private final ObjectStoragePort port;
@@ -22,7 +22,8 @@ import org.springframework.context.annotation.Configuration;
prefix = "app.object-storage.legacy-adoption",
name = "enabled",
havingValue = "true")
@SuppressWarnings("removal")
// This explicit adoption wiring remains active until data/API migration is complete.
@SuppressWarnings("deprecation")
public class ObjectStorageLegacyMigrationConfig {
@Bean
@@ -3,6 +3,8 @@ package dev.caskeleton.adapter.outbound.objectstorage.control;
/** Conditional create or exact-version CAS conflict. */
public final class ObjectControlConflictException extends RuntimeException {
private static final long serialVersionUID = 1L;
public ObjectControlConflictException(String message) {
super(message);
}
@@ -3,6 +3,8 @@ package dev.caskeleton.adapter.outbound.objectstorage.control;
/** Malformed, non-canonical, oversized, or digest-mismatched control evidence. */
public final class ObjectControlCorruptionException extends RuntimeException {
private static final long serialVersionUID = 1L;
public ObjectControlCorruptionException(String message) {
super(message);
}
@@ -3,6 +3,8 @@ package dev.caskeleton.adapter.outbound.objectstorage.control;
/** A newer or unknown durable schema that must be quarantined rather than overwritten. */
public final class UnsupportedObjectControlSchemaException extends RuntimeException {
private static final long serialVersionUID = 1L;
public UnsupportedObjectControlSchemaException(String message) {
super(message);
}
@@ -17,7 +17,8 @@ import java.util.HexFormat;
import java.util.Map;
/** Fail-closed detached two-person Ed25519 approval verifier. */
@SuppressWarnings("removal")
// The adoption verifier remains active until data/API migration is complete.
@SuppressWarnings("deprecation")
public final class Ed25519LegacyAdoptionApprovalVerifier
implements LegacyObjectAdoptionApprovalVerifierPort {
@@ -13,7 +13,8 @@ import java.time.Clock;
import java.util.HexFormat;
/** Report-first legacy inspection and reviewed, operation-keyed adoption apply. */
@SuppressWarnings("removal")
// The adoption mechanism remains active until data/API migration is complete.
@SuppressWarnings("deprecation")
public final class LegacyObjectAdoptionService implements LegacyObjectAdoptionPort {
private final LegacyObjectInspector inspector;
@@ -4,6 +4,8 @@ import dev.caskeleton.application.storage.migration.LegacyObjectLocator;
/** Privileged legacy namespace reader used only by explicit migration composition. */
@FunctionalInterface
// The adoption mechanism remains active until data/API migration is complete.
@SuppressWarnings("deprecation")
public interface LegacyObjectInspector {
LegacyObjectInspection inspect(LegacyObjectLocator locator);
@@ -5,6 +5,7 @@ import java.util.Objects;
/** Bounded provider failure classification without physical locator disclosure. */
public final class ObjectStorageProviderException extends RuntimeException {
private static final long serialVersionUID = 1L;
private final Failure failure;
public ObjectStorageProviderException(Failure failure, String message) {
@@ -4,11 +4,11 @@ import java.util.Objects;
import software.amazon.awssdk.core.checksums.RequestChecksumCalculation;
import software.amazon.awssdk.core.checksums.ResponseChecksumValidation;
import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration;
import software.amazon.awssdk.core.retry.RetryPolicy;
import software.amazon.awssdk.core.retry.backoff.EqualJitterBackoffStrategy;
import software.amazon.awssdk.http.async.SdkAsyncHttpClient;
import software.amazon.awssdk.http.nio.netty.NettyNioAsyncHttpClient;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.retries.StandardRetryStrategy;
import software.amazon.awssdk.retries.api.BackoffStrategy;
import software.amazon.awssdk.services.s3.S3AsyncClient;
import software.amazon.awssdk.services.s3.S3AsyncClientBuilder;
@@ -29,22 +29,23 @@ public final class S3AsyncClientFactory {
.useIdleConnectionReaper(true)
.build();
try {
EqualJitterBackoffStrategy backoff =
EqualJitterBackoffStrategy.builder()
.baseDelay(policy.retryBaseDelay())
.maxBackoffTime(policy.retryMaximumBackoff())
.build();
RetryPolicy retry =
RetryPolicy.builder()
.numRetries(policy.maximumAttempts() - 1)
.backoffStrategy(backoff)
.throttlingBackoffStrategy(backoff)
BackoffStrategy normalBackoff =
BackoffStrategy.exponentialDelayHalfJitter(
policy.retryBaseDelay(), policy.retryMaximumBackoff());
BackoffStrategy throttlingBackoff =
BackoffStrategy.exponentialDelayHalfJitter(
policy.retryBaseDelay(), policy.retryMaximumBackoff());
StandardRetryStrategy retry =
StandardRetryStrategy.builder()
.maxAttempts(policy.maximumAttempts())
.backoffStrategy(normalBackoff)
.throttlingBackoffStrategy(throttlingBackoff)
.build();
ClientOverrideConfiguration override =
ClientOverrideConfiguration.builder()
.apiCallTimeout(policy.apiCallTimeout())
.apiCallAttemptTimeout(policy.apiCallAttemptTimeout())
.retryPolicy(retry)
.retryStrategy(retry)
.build();
S3AsyncClientBuilder builder =
S3AsyncClient.builder()
@@ -49,6 +49,9 @@ class MinioManagedObjectContractTest {
private static S3AsyncClient client;
@BeforeAll
// The container is assigned to a static field and stopped in the matching @AfterAll; the
// fluent withX chain reads to the compiler as a second, unclosed instance.
@SuppressWarnings("resource")
static void startExactMinio() {
String accessKey = "a" + UUID.randomUUID().toString().replace("-", "");
String secretKey = UUID.randomUUID().toString().replace("-", "") + UUID.randomUUID();
@@ -3,6 +3,9 @@ package dev.caskeleton.adapter.outbound.objectstorage.qualification;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import eu.rekawek.toxiproxy.Proxy;
import eu.rekawek.toxiproxy.ToxiproxyClient;
import eu.rekawek.toxiproxy.model.ToxicDirection;
import java.net.URI;
import java.nio.ByteBuffer;
import java.time.Duration;
@@ -11,7 +14,7 @@ import java.util.concurrent.CompletionException;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.Network;
import org.testcontainers.containers.ToxiproxyContainer;
import org.testcontainers.toxiproxy.ToxiproxyContainer;
import org.testcontainers.utility.DockerImageName;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
@@ -26,6 +29,11 @@ import software.amazon.awssdk.services.s3.model.PutObjectRequest;
class MinioManagedObjectFaultTest {
private static final int TOXIPROXY_CONTROL_PORT = 8474;
private static final int TOXIPROXY_PROXY_PORT = 8666;
private static final String CUT_CONNECTION_DOWNSTREAM = "CUT_CONNECTION_DOWNSTREAM";
private static final String CUT_CONNECTION_UPSTREAM = "CUT_CONNECTION_UPSTREAM";
private static final DockerImageName MINIO_IMAGE =
DockerImageName.parse(
"minio/minio@sha256:4c4a4876193f030c81f57aabb22bcb9a73462010eb61fcab66908e03e5484af8");
@@ -35,8 +43,11 @@ class MinioManagedObjectFaultTest {
+ "9378ed52a28bc50edc1350f936f518f31fa95f0d15917d6eb40b8e376d1a214e")
.asCompatibleSubstituteFor("shopify/toxiproxy");
// Every container below is declared in try-with-resources; the fluent `withX` chain returns the
// same instance, which the compiler reads as a second, unclosed resource.
@SuppressWarnings("resource")
@Test
void connectionCutProducesABoundedFailureAndRecoveryWithoutMutationReplay() {
void connectionCutProducesABoundedFailureAndRecoveryWithoutMutationReplay() throws Exception {
String accessKey = "a" + UUID.randomUUID().toString().replace("-", "");
String secretKey = UUID.randomUUID().toString().replace("-", "") + UUID.randomUUID();
try (Network network = Network.newNetwork();
@@ -49,15 +60,24 @@ class MinioManagedObjectFaultTest {
.withEnv("MINIO_ROOT_PASSWORD", secretKey)
.withCommand("server", "/data");
ToxiproxyContainer toxiproxy =
new ToxiproxyContainer(TOXIPROXY_IMAGE).withNetwork(network)) {
new ToxiproxyContainer(TOXIPROXY_IMAGE)
.withNetwork(network)
.withExposedPorts(TOXIPROXY_CONTROL_PORT, TOXIPROXY_PROXY_PORT)) {
minio.start();
toxiproxy.start();
ToxiproxyContainer.ContainerProxy proxy = toxiproxy.getProxy(minio, 9000);
ToxiproxyClient toxiproxyClient =
new ToxiproxyClient(toxiproxy.getHost(), toxiproxy.getControlPort());
Proxy proxy =
toxiproxyClient.createProxy(
"qualified-minio", "0.0.0.0:" + TOXIPROXY_PROXY_PORT, "qualified-minio:9000");
try (S3AsyncClient client =
S3AsyncClient.builder()
.endpointOverride(
URI.create(
"http://" + proxy.getContainerIpAddress() + ":" + proxy.getProxyPort()))
"http://"
+ toxiproxy.getHost()
+ ":"
+ toxiproxy.getMappedPort(TOXIPROXY_PROXY_PORT)))
.region(Region.US_EAST_1)
.credentialsProvider(
StaticCredentialsProvider.create(
@@ -92,7 +112,7 @@ class MinioManagedObjectFaultTest {
AsyncRequestBody.fromByteBuffer(ByteBuffer.wrap(body)))
.join();
proxy.setConnectionCut(true);
setConnectionCut(proxy, true);
long started = System.nanoTime();
assertThatThrownBy(
() ->
@@ -103,7 +123,7 @@ class MinioManagedObjectFaultTest {
.isInstanceOf(CompletionException.class);
assertThat(Duration.ofNanos(System.nanoTime() - started)).isLessThan(Duration.ofSeconds(5));
proxy.setConnectionCut(false);
setConnectionCut(proxy, false);
assertThat(
client
.headObject(
@@ -114,4 +134,14 @@ class MinioManagedObjectFaultTest {
}
}
}
private static void setConnectionCut(Proxy proxy, boolean shouldCutConnection) throws Exception {
if (shouldCutConnection) {
proxy.toxics().bandwidth(CUT_CONNECTION_DOWNSTREAM, ToxicDirection.DOWNSTREAM, 0);
proxy.toxics().bandwidth(CUT_CONNECTION_UPSTREAM, ToxicDirection.UPSTREAM, 0);
return;
}
proxy.toxics().get(CUT_CONNECTION_DOWNSTREAM).remove();
proxy.toxics().get(CUT_CONNECTION_UPSTREAM).remove();
}
}
@@ -24,6 +24,8 @@ class FilesystemObjectStorageAdapterTest {
}
@Test
// This single receipt assertion remains while the whole-byte migration is active.
@SuppressWarnings("removal")
void putThenGetReturnsSameBytes() {
byte[] content = "hello-object-storage".getBytes(StandardCharsets.UTF_8);
@@ -20,6 +20,8 @@ import software.amazon.awssdk.services.s3.model.PutObjectRequest;
* Characterizes the pre-migration blob API. These assertions preserve evidence of the unsafe legacy
* boundary; they are not requirements for the replacement capability.
*/
// This named characterization suite preserves the active seam until migration completes.
@SuppressWarnings("removal")
class LegacyObjectStorageBehaviorTest {
@TempDir Path tempDir;
@@ -18,6 +18,8 @@ import software.amazon.awssdk.services.s3.model.HeadBucketRequest;
import software.amazon.awssdk.services.s3.model.NoSuchBucketException;
/** Records the legacy configuration side effects that the canonical capability must remove. */
// This named characterization suite preserves the active seam until migration completes.
@SuppressWarnings("removal")
class LegacyObjectStorageConfigTest {
@TempDir Path tempDir;
@@ -0,0 +1,44 @@
package dev.caskeleton.adapter.outbound.objectstorage;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.methods;
import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.junit.AnalyzeClasses;
import com.tngtech.archunit.junit.ArchTest;
import com.tngtech.archunit.lang.ArchRule;
import org.junit.jupiter.api.Test;
@AnalyzeClasses(packages = "dev.caskeleton.adapter.outbound.objectstorage")
class ObjectStorageArchitectureTest {
@Test
void legacySettingsRemainAnExplicitRemovalBoundary() throws Exception {
Class<?> settings =
Class.forName("dev.caskeleton.adapter.outbound.objectstorage.ObjectStorageSettings");
Deprecated lifecycle = settings.getAnnotation(Deprecated.class);
org.assertj.core.api.Assertions.assertThat(lifecycle).isNotNull();
org.assertj.core.api.Assertions.assertThat(lifecycle.forRemoval()).isTrue();
}
@ArchTest
static final ArchRule OBJECT_STORAGE_ADAPTER_METHOD_RETURNS_ONLY_APPLICATION_OR_PRIMITIVES =
methods()
.that()
.areDeclaredInClassesThat()
.resideInAPackage("..adapter.outbound.objectstorage..")
.and()
.areDeclaredInClassesThat()
.haveSimpleNameEndingWith("Adapter")
.and()
.arePublic()
.and()
.areNotStatic()
.should()
.notHaveRawReturnType(
JavaClass.Predicates.resideInAnyPackage(
"..adapter.outbound..",
"..adapter.inbound.web..",
"..adapter.outbound.persistence.."))
.allowEmptyShould(false);
}
@@ -69,6 +69,8 @@ class S3ObjectStorageAdapterIT {
}
@Test
// This single receipt assertion remains while the whole-byte migration is active.
@SuppressWarnings("removal")
void putGetExistsDeleteRoundTrip() {
byte[] content = "minio-round-trip".getBytes(StandardCharsets.UTF_8);
@@ -31,6 +31,8 @@ class S3ObjectStorageAdapterTest {
private static final String BUCKET = "ca-skeleton-test";
@Test
// This single receipt assertion remains while the whole-byte migration is active.
@SuppressWarnings("removal")
void putMapsBucketKeyContentTypeAndReturnsS3Location() {
S3Client s3 = mock(S3Client.class);
S3ObjectStorageAdapter adapter = new S3ObjectStorageAdapter(s3, BUCKET);
@@ -139,16 +139,18 @@ class ObjectRequestFingerprintCodecTest {
new String(ObjectPolicySnapshotCodec.encode(snapshot), StandardCharsets.UTF_8);
assertThat(canonical)
.isEqualTo(
"object-policy-snapshot-v1\n"
+ "binding=10:binding-v1\n"
+ "policy=9:policy-v3\n"
+ "publication=18:INTEGRITY_VERIFIED\n"
+ "retention=4:NONE\n"
+ "encryption=16:PROVIDER_MANAGED\n"
+ "capabilities=37:IMMUTABLE_CREATE,SHA_256_VERIFICATION\n"
+ "max-object-bytes=8:10485760\n"
+ "chunk-bytes=5:65536\n"
+ "minimum-replay-seconds=6:604800\n")
"""
object-policy-snapshot-v1
binding=10:binding-v1
policy=9:policy-v3
publication=18:INTEGRITY_VERIFIED
retention=4:NONE
encryption=16:PROVIDER_MANAGED
capabilities=37:IMMUTABLE_CREATE,SHA_256_VERIFICATION
max-object-bytes=8:10485760
chunk-bytes=5:65536
minimum-replay-seconds=6:604800
""")
.doesNotContain("secret", "credential", "access-key", "endpoint");
}
@@ -33,7 +33,6 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
@SuppressWarnings("removal")
class ObjectStorageCapabilityConfigTest {
@TempDir Path root;
@@ -218,6 +217,8 @@ class ObjectStorageCapabilityConfigTest {
}
@Test
// This exact legacy-runtime characterization remains until data/API migration completes.
@SuppressWarnings("removal")
void canonicalLegacyOnlyIsExplicitAndPreservesExistingData() throws Exception {
Path legacyRoot = root.resolve("legacy-only");
Path existing = legacyRoot.resolve("posters/existing.bin");
@@ -247,6 +248,8 @@ class ObjectStorageCapabilityConfigTest {
}
@Test
// This exact dual-runtime characterization remains until data/API migration completes.
@SuppressWarnings("removal")
void namespaceSeparatedCanonicalCapabilityAndLegacyCanRunTogether() {
Path capabilityRoot = root.resolve("dual-v1");
Path legacyRoot = root.resolve("dual-legacy");
@@ -11,7 +11,8 @@ import java.time.Clock;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
@SuppressWarnings("removal")
// This named adoption wiring suite remains until data/API migration completes.
@SuppressWarnings("deprecation")
class ObjectStorageLegacyMigrationConfigTest {
@Test
@@ -29,7 +29,8 @@ import java.util.EnumSet;
import java.util.Map;
import org.junit.jupiter.api.Test;
@SuppressWarnings("removal")
// This named adoption verifier suite remains until data/API migration completes.
@SuppressWarnings("deprecation")
class LegacyAdoptionApprovalVerifierTest {
private static final Instant NOW = Instant.parse("2026-07-29T00:00:00Z");
@@ -27,7 +27,8 @@ import java.util.EnumSet;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.jupiter.api.Test;
@SuppressWarnings("removal")
// This named adoption characterization suite remains until data/API migration completes.
@SuppressWarnings("deprecation")
class LegacyObjectAdoptionServiceTest {
private static final Instant NOW = Instant.parse("2026-07-29T00:00:00Z");
@@ -12,7 +12,9 @@ import java.util.Map;
import java.util.Optional;
import java.util.Set;
import org.junit.jupiter.api.Test;
import org.yaml.snakeyaml.LoaderOptions;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.SafeConstructor;
class ObjectStorageReadinessRegistryTest {
@@ -117,22 +119,26 @@ class ObjectStorageReadinessRegistryTest {
@SuppressWarnings("unchecked")
private static Map<String, Object> loadRegistry() throws Exception {
Path path = registryPath();
LoaderOptions options = new LoaderOptions();
options.setAllowDuplicateKeys(false);
options.setMaxAliasesForCollections(0);
try (InputStream input = Files.newInputStream(path)) {
return new Yaml().load(input);
return new Yaml(new SafeConstructor(options)).load(input);
}
}
private static Path registryPath() {
Path directory = Path.of("").toAbsolutePath();
for (int depth = 0; depth < 8 && directory != null; depth++) {
Path candidate =
directory.resolve("docs/registries/object-storage-readiness.yaml").normalize();
if (Files.isRegularFile(candidate)) {
return candidate;
}
directory = directory.getParent();
String configured = System.getProperty("objectstorage.readiness.registry");
if (configured == null || configured.isBlank()) {
throw new IllegalStateException(
"objectstorage.readiness.registry must name the canonical tracked registry");
}
throw new IllegalStateException("object-storage readiness registry is absent");
Path registry = Path.of(configured).toAbsolutePath().normalize();
if (Files.isSymbolicLink(registry) || !Files.isRegularFile(registry)) {
throw new IllegalStateException(
"object-storage readiness registry must be a regular non-symlink file: " + registry);
}
return registry;
}
@SuppressWarnings("unchecked")
@@ -5,11 +5,15 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import java.lang.reflect.Field;
import java.net.URI;
import java.time.Duration;
import org.junit.jupiter.api.Test;
import org.mockito.InOrder;
import software.amazon.awssdk.http.async.SdkAsyncHttpClient;
import software.amazon.awssdk.retries.StandardRetryStrategy;
import software.amazon.awssdk.retries.api.BackoffStrategy;
import software.amazon.awssdk.retries.api.RetryStrategy;
import software.amazon.awssdk.services.s3.S3AsyncClient;
class S3AsyncClientFactoryTest {
@@ -35,6 +39,30 @@ class S3AsyncClientFactoryTest {
order.verifyNoMoreInteractions();
}
@Test
void assemblesRetriesV2WithExactAttemptsAndSeparateHalfJitterPaths() throws Exception {
S3ClientPolicy policy = validPolicy().build();
try (S3ClientLifecycle lifecycle = new S3AsyncClientFactory().create(policy)) {
RetryStrategy retry =
lifecycle
.client()
.serviceClientConfiguration()
.overrideConfiguration()
.retryStrategy()
.orElseThrow(() -> new AssertionError("retries-v2 strategy was not assembled"));
assertThat(retry).isInstanceOf(StandardRetryStrategy.class);
assertThat(retry.maxAttempts()).isEqualTo(policy.maximumAttempts());
BackoffStrategy normal = field(retry, "backoffStrategy", BackoffStrategy.class);
BackoffStrategy throttling = field(retry, "throttlingBackoffStrategy", BackoffStrategy.class);
assertThat(normal).isNotSameAs(throttling);
assertHalfJitterPolicy(normal, policy);
assertHalfJitterPolicy(throttling, policy);
}
}
@Test
void rejectsMissingNonPositiveAndContradictoryTimeoutPoolAndRetryPolicy() {
assertThatIllegalArgumentException()
@@ -127,4 +155,25 @@ class S3AsyncClientFactoryTest {
.shutdownGrace(Duration.ofSeconds(5))
.pathStyleAccess(false);
}
private static void assertHalfJitterPolicy(BackoffStrategy strategy, S3ClientPolicy policy) {
assertThat(strategy.getClass().getSimpleName()).isEqualTo("ExponentialDelayWithHalfJitter");
assertThat(strategy.toString())
.contains("baseDelay=" + policy.retryBaseDelay())
.contains("maxDelay=" + policy.retryMaximumBackoff());
}
private static <T> T field(Object owner, String name, Class<T> type) throws Exception {
Class<?> declaringType = owner.getClass();
while (declaringType != null) {
try {
Field field = declaringType.getDeclaredField(name);
field.setAccessible(true);
return type.cast(field.get(owner));
} catch (NoSuchFieldException ignored) {
declaringType = declaringType.getSuperclass();
}
}
throw new AssertionError(name + " was absent from " + owner.getClass().getName());
}
}
@@ -54,7 +54,8 @@ class S3ConditionalObjectControlStoreTest {
when(client.putObject(put.capture(), any(AsyncRequestBody.class)))
.thenReturn(
CompletableFuture.completedFuture(
PutObjectResponse.builder().eTag("\"etag-1\"").build()),
PutObjectResponse.builder().eTag("\"etag-1\"").build()))
.thenReturn(
CompletableFuture.completedFuture(
PutObjectResponse.builder().eTag("\"etag-2\"").build()));
S3ConditionalObjectControlStore store =
@@ -156,7 +157,6 @@ class S3ConditionalObjectControlStoreTest {
org.mockito.ArgumentMatchers.<AsyncResponseTransformer<GetObjectResponse, ?>>any()))
.thenAnswer(
invocation -> {
@SuppressWarnings("unchecked")
AsyncResponseTransformer<GetObjectResponse, Object> transformer =
invocation.getArgument(1);
CompletableFuture<Object> result = transformer.prepare();
@@ -9,7 +9,7 @@ import dev.caskeleton.adapter.outbound.objectstorage.control.ObjectDirectMultipa
import dev.caskeleton.adapter.outbound.objectstorage.direct.DirectGrantProvider;
import dev.caskeleton.application.objectstorage.identity.MultipartPartNumber;
import dev.caskeleton.application.objectstorage.model.ObjectDigest;
import java.net.URL;
import java.net.URI;
import java.time.Instant;
import java.util.List;
import java.util.Map;
@@ -27,7 +27,8 @@ class S3DirectMultipartProviderTest {
void presignsOneExactPartWithoutReturningTheProviderUploadIdentity() throws Exception {
S3Presigner presigner = mock(S3Presigner.class);
PresignedUploadPartRequest signed = mock(PresignedUploadPartRequest.class);
when(signed.url()).thenReturn(new URL("https://storage.example.test/part?signature=secret"));
when(signed.url())
.thenReturn(URI.create("https://storage.example.test/part?signature=secret").toURL());
when(signed.expiration()).thenReturn(Instant.parse("2026-07-28T00:05:00Z"));
when(signed.signedHeaders()).thenReturn(Map.of("x-amz-checksum-sha256", List.of("digest")));
ArgumentCaptor<UploadPartPresignRequest> request =
@@ -17,7 +17,7 @@ import dev.caskeleton.application.objectstorage.identity.ObjectOperationKey;
import dev.caskeleton.application.objectstorage.model.ObjectContentIdentity;
import dev.caskeleton.application.objectstorage.model.ObjectDigest;
import dev.caskeleton.application.objectstorage.model.ObjectMediaType;
import java.net.URL;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.List;
@@ -45,7 +45,8 @@ class S3DirectTransferProviderTest {
throws Exception {
S3Presigner presigner = mock(S3Presigner.class);
PresignedPutObjectRequest signed = mock(PresignedPutObjectRequest.class);
when(signed.url()).thenReturn(new URL("https://storage.example.test/key?signature=secret"));
when(signed.url())
.thenReturn(URI.create("https://storage.example.test/key?signature=secret").toURL());
when(signed.expiration()).thenReturn(NOW.plusSeconds(300));
when(signed.signedHeaders())
.thenReturn(Map.of("x-amz-checksum-sha256", List.of(CONTENT.fullDigest().base64Value())));
@@ -123,7 +123,6 @@ class S3ManagedObjectProviderTest {
.thenAnswer(
invocation -> {
GetObjectRequest actual = invocation.getArgument(0);
@SuppressWarnings("unchecked")
AsyncResponseTransformer<GetObjectResponse, GetObjectResponse> transformer =
invocation.getArgument(1);
CompletableFuture<GetObjectResponse> future = transformer.prepare();
@@ -131,9 +130,12 @@ class S3ManagedObjectProviderTest {
int end = content.length;
String contentRange = null;
if (actual.range() != null) {
String[] bounds = actual.range().substring("bytes=".length()).split("-");
start = Integer.parseInt(bounds[0]);
end = Integer.parseInt(bounds[1]) + 1;
String bounds = actual.range().substring("bytes=".length());
int boundary = bounds.indexOf('-');
assertThat(boundary).isPositive();
assertThat(bounds.indexOf('-', boundary + 1)).isEqualTo(-1);
start = Integer.parseInt(bounds.substring(0, boundary));
end = Integer.parseInt(bounds.substring(boundary + 1)) + 1;
contentRange = "bytes " + start + "-" + (end - 1) + "/" + content.length;
}
byte[] selected = java.util.Arrays.copyOfRange(content, start, end);