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:
co-authored by
Claude Opus 5
parent
1a3b560678
commit
5f10b791d3
@@ -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
|
||||
|
||||
+2
@@ -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";
|
||||
|
||||
+2
@@ -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);
|
||||
|
||||
+2
-1
@@ -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,
|
||||
|
||||
+7
-1
@@ -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;
|
||||
|
||||
+2
-1
@@ -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
|
||||
|
||||
+2
@@ -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);
|
||||
}
|
||||
|
||||
+2
@@ -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);
|
||||
}
|
||||
|
||||
+2
@@ -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);
|
||||
}
|
||||
|
||||
+2
-1
@@ -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 {
|
||||
|
||||
|
||||
+2
-1
@@ -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;
|
||||
|
||||
+2
@@ -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);
|
||||
|
||||
+1
@@ -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) {
|
||||
|
||||
+14
-13
@@ -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()
|
||||
|
||||
+3
@@ -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();
|
||||
|
||||
+37
-7
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
+2
@@ -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);
|
||||
|
||||
|
||||
+2
@@ -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;
|
||||
|
||||
+2
@@ -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;
|
||||
|
||||
+44
@@ -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);
|
||||
}
|
||||
+2
@@ -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);
|
||||
|
||||
|
||||
+2
@@ -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);
|
||||
|
||||
+12
-10
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
+4
-1
@@ -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");
|
||||
|
||||
+2
-1
@@ -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
|
||||
|
||||
+2
-1
@@ -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");
|
||||
|
||||
+2
-1
@@ -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");
|
||||
|
||||
+16
-10
@@ -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")
|
||||
|
||||
+49
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -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();
|
||||
|
||||
+3
-2
@@ -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 =
|
||||
|
||||
+3
-2
@@ -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())));
|
||||
|
||||
+6
-4
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user