173 lines
9.2 KiB
Groovy
173 lines
9.2 KiB
Groovy
// JPA persistence adapter — merged RDBMS base + PostgreSQL vendor module.
|
|
// Owns JPA entities, Spring Data repositories, mappers, transaction/audit/lock/outbox port
|
|
// implementations, and the vendor-neutral SPI interfaces (OutboxClaimRepository /
|
|
// SqlStateErrorMapping). The PostgreSQL driver, flyway-database-postgresql dialect, and vendor
|
|
// Flyway migrations live only under the .postgresql subpackage (ArchUnit keeps the base neutral).
|
|
sourceSets {
|
|
postgresqlIntegrationTest {
|
|
java.setSrcDirs(['src/postgresqlIntegrationTest/java'])
|
|
resources.setSrcDirs(['src/postgresqlIntegrationTest/resources'])
|
|
compileClasspath += sourceSets.main.output
|
|
runtimeClasspath += output + compileClasspath
|
|
}
|
|
}
|
|
|
|
configurations {
|
|
postgresqlIntegrationTestImplementation.extendsFrom testImplementation
|
|
postgresqlIntegrationTestCompileOnly.extendsFrom testCompileOnly
|
|
postgresqlIntegrationTestRuntimeOnly.extendsFrom testRuntimeOnly
|
|
postgresqlIntegrationTestAnnotationProcessor.extendsFrom testAnnotationProcessor
|
|
}
|
|
|
|
ext.jpaPostgreSqlEvidenceImage = 'postgres:16-alpine'
|
|
|
|
dependencies {
|
|
implementation project(':application-core')
|
|
implementation project(':shared-contract')
|
|
|
|
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
|
// feature-distributed-lock-contract: Spring Integration JDBC LockRegistry backs the
|
|
// multi-instance distributedLockProvider. Version managed by Spring Boot BOM.
|
|
implementation 'org.springframework.integration:spring-integration-jdbc'
|
|
|
|
// Vendor (PostgreSQL): Flyway migration API + PostgreSQL driver/dialect. Used only by the
|
|
// .postgresql subpackage; the RDBMS base stays vendor-neutral (PERSISTENCE_RDBMS_STAYS_VENDOR_NEUTRAL).
|
|
implementation 'org.springframework.boot:spring-boot-starter-flyway'
|
|
runtimeOnly 'org.postgresql:postgresql'
|
|
runtimeOnly 'org.flywaydb:flyway-database-postgresql'
|
|
|
|
// Vendor (H2): the local-profile driver. Used only by the .h2 subpackage, which reaches it
|
|
// through JDBC/JPA rather than by importing org.h2 types — the same shape as the PostgreSQL
|
|
// driver above. Not `developmentOnly`: local is a deployable profile of this artifact, and the
|
|
// vendor selector, not the packaging, decides which driver a deployment loads.
|
|
runtimeOnly 'com.h2database:h2'
|
|
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
|
|
|
|
postgresqlIntegrationTestImplementation 'org.testcontainers:testcontainers-postgresql'
|
|
postgresqlIntegrationTestImplementation 'org.testcontainers:testcontainers-junit-jupiter'
|
|
postgresqlIntegrationTestRuntimeOnly 'org.postgresql:postgresql'
|
|
}
|
|
|
|
tasks.withType(JavaCompile).configureEach { options.encoding = 'UTF-8' }
|
|
|
|
def registerPostgreSqlReadinessTest = { String taskName, String testClass ->
|
|
tasks.register(taskName, Test) {
|
|
group = 'verification'
|
|
description = "Runs the no-skip real PostgreSQL readiness scenario ${testClass}."
|
|
testClassesDirs = sourceSets.postgresqlIntegrationTest.output.classesDirs
|
|
classpath = sourceSets.postgresqlIntegrationTest.runtimeClasspath
|
|
useJUnitPlatform()
|
|
filter {
|
|
includeTestsMatching testClass
|
|
}
|
|
failOnNoDiscoveredTests = true
|
|
outputs.upToDateWhen { false }
|
|
jvmArgs(
|
|
'-Duser.timezone=UTC',
|
|
"-Djpa.evidence.postgresql.image=${jpaPostgreSqlEvidenceImage}")
|
|
}
|
|
}
|
|
|
|
def postgresqlLifecycleIntegrationTest = registerPostgreSqlReadinessTest(
|
|
'postgresqlLifecycleIntegrationTest',
|
|
'dev.caskeleton.adapter.outbound.persistence.readiness.PostgreSqlLifecycleIntegrationTest')
|
|
def postgresqlSecurityBaselineIntegrationTest = registerPostgreSqlReadinessTest(
|
|
'postgresqlSecurityBaselineIntegrationTest',
|
|
'dev.caskeleton.adapter.outbound.persistence.readiness.PostgreSqlSecurityBaselineIntegrationTest')
|
|
def postgresqlMigrationIntegrationTest = registerPostgreSqlReadinessTest(
|
|
'postgresqlMigrationIntegrationTest',
|
|
'dev.caskeleton.adapter.outbound.persistence.readiness.PostgreSqlMigrationIntegrationTest')
|
|
def postgresqlTransactionIntegrationTest = registerPostgreSqlReadinessTest(
|
|
'postgresqlTransactionIntegrationTest',
|
|
'dev.caskeleton.adapter.outbound.persistence.readiness.PostgreSqlTransactionIntegrationTest')
|
|
def postgresqlAggregateIntegrationTest = registerPostgreSqlReadinessTest(
|
|
'postgresqlAggregateIntegrationTest',
|
|
'dev.caskeleton.adapter.outbound.persistence.readiness.PostgreSqlAggregateIntegrationTest')
|
|
def postgresqlQueryIntegrationTest = registerPostgreSqlReadinessTest(
|
|
'postgresqlQueryIntegrationTest',
|
|
'dev.caskeleton.adapter.outbound.persistence.readiness.PostgreSqlQueryIntegrationTest')
|
|
def postgresqlIdempotencyIntegrationTest = registerPostgreSqlReadinessTest(
|
|
'postgresqlIdempotencyIntegrationTest',
|
|
'dev.caskeleton.adapter.outbound.persistence.readiness.PostgreSqlIdempotencyIntegrationTest')
|
|
def postgresqlOutboxStorageIntegrationTest = registerPostgreSqlReadinessTest(
|
|
'postgresqlOutboxStorageIntegrationTest',
|
|
'dev.caskeleton.adapter.outbound.persistence.readiness.PostgreSqlOutboxStorageIntegrationTest')
|
|
def postgresqlOutboxPollingIntegrationTest = registerPostgreSqlReadinessTest(
|
|
'postgresqlOutboxPollingIntegrationTest',
|
|
'dev.caskeleton.adapter.outbound.persistence.readiness.PostgreSqlOutboxPollingIntegrationTest')
|
|
def postgresqlInboxIntegrationTest = registerPostgreSqlReadinessTest(
|
|
'postgresqlInboxIntegrationTest',
|
|
'dev.caskeleton.adapter.outbound.persistence.readiness.PostgreSqlInboxIntegrationTest')
|
|
def postgresqlFileserverMigrationIntegrationTest = registerPostgreSqlReadinessTest(
|
|
'postgresqlFileserverMigrationIntegrationTest',
|
|
'dev.caskeleton.adapter.outbound.persistence.readiness.PostgreSqlFileserverMigrationIntegrationTest')
|
|
def postgresqlFileserverMetadataIntegrationTest = registerPostgreSqlReadinessTest(
|
|
'postgresqlFileserverMetadataIntegrationTest',
|
|
'dev.caskeleton.adapter.outbound.persistence.readiness.PostgreSqlFileserverMetadataStoreIntegrationTest')
|
|
def postgresqlFileserverReclamationIntegrationTest = registerPostgreSqlReadinessTest(
|
|
'postgresqlFileserverReclamationIntegrationTest',
|
|
'dev.caskeleton.adapter.outbound.persistence.readiness.PostgreSqlFileserverReclamationIntegrationTest')
|
|
|
|
def verifyJpaSqlConstructionSafety = tasks.register('verifyJpaSqlConstructionSafety') {
|
|
group = 'verification'
|
|
description = 'Rejects concatenated SQL construction and non-parameterized PostgreSQL timeout configuration.'
|
|
File vendorSource = file('src/main/java/dev/caskeleton/adapter/outbound/persistence/postgresql')
|
|
inputs.dir(vendorSource)
|
|
doLast {
|
|
List<String> violations = []
|
|
vendorSource.eachFileRecurse { File source ->
|
|
if (!source.name.endsWith('.java')) {
|
|
return
|
|
}
|
|
String text = source.getText('UTF-8')
|
|
def concatenatedSql = text =~ /(?s)(createNativeQuery|queryForObject|update)\s*\([^;]*"\s*\+/
|
|
if (concatenatedSql.find()) {
|
|
violations << "${source}: concatenated SQL construction"
|
|
}
|
|
source.readLines().eachWithIndex { String line, int index ->
|
|
if (line.contains("set_config('") && !line.contains('?')) {
|
|
violations << "${source}:${index + 1}: set_config value is not parameterized"
|
|
}
|
|
}
|
|
}
|
|
if (!violations.isEmpty()) {
|
|
throw new GradleException(
|
|
"verifyJpaSqlConstructionSafety: ${violations.size()} violation(s):\n " +
|
|
violations.join('\n '))
|
|
}
|
|
logger.lifecycle(
|
|
'verifyJpaSqlConstructionSafety: OK — no concatenated SQL construction and all set_config values are parameterized.')
|
|
}
|
|
}
|
|
|
|
def verifyJpaSecurityFixtures = tasks.register('verifyJpaSecurityFixtures') {
|
|
group = 'verification'
|
|
description = 'Verifies the no-skip PostgreSQL security fixture covers runtime-role namespace denial.'
|
|
File fixture = file(
|
|
'src/postgresqlIntegrationTest/java/dev/caskeleton/adapter/outbound/persistence/readiness/PostgreSqlSecurityBaselineIntegrationTest.java')
|
|
inputs.file(fixture)
|
|
doLast {
|
|
if (!fixture.isFile()) {
|
|
throw new GradleException("verifyJpaSecurityFixtures: missing ${fixture}")
|
|
}
|
|
String text = fixture.getText('UTF-8')
|
|
['runtimeRoleCannotCreateInApplicationSchema', 'assertDockerAvailable', '42501'].each {
|
|
String required ->
|
|
if (!text.contains(required)) {
|
|
throw new GradleException(
|
|
"verifyJpaSecurityFixtures: ${fixture} is missing '${required}'")
|
|
}
|
|
}
|
|
logger.lifecycle(
|
|
'verifyJpaSecurityFixtures: OK — no-skip Docker and runtime-role namespace denial fixtures are present.')
|
|
}
|
|
}
|
|
|
|
postgresqlSecurityBaselineIntegrationTest.configure {
|
|
dependsOn verifyJpaSqlConstructionSafety
|
|
dependsOn verifyJpaSecurityFixtures
|
|
dependsOn project(':adapter:inbound:web').tasks.named('jpaPersistenceRedactionContractTest')
|
|
}
|
|
|
|
apply from: rootProject.file('gradle/jpa-evidence.gradle')
|