From d40e7323e477272e304534a17a2212581f6780c5 Mon Sep 17 00:00:00 2001 From: ifsantana Date: Wed, 15 Jul 2026 22:11:45 -0300 Subject: [PATCH 1/2] fix(security): tolerate API-key prefix collisions in validate() (#245) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The stored prefix is rawKey.take(12) = "sk_live_" + 4 hex chars (~65k values) and its index is non-unique by design, but findByPrefix returned a single row. Once two live keys shared a prefix, validate() threw IncorrectResultSizeDataAccessException and ApiKeyAuthFilter 401'd both keys until one was revoked — ~50% likely by ~300 active keys. Replace the port's findByPrefix with findAllByPrefix; validate() now bcrypt-matches the raw key against each non-revoked candidate. No schema change; Redis cache stays prefix-scoped. Adds a repository collision test and a service disambiguation test. Signed-off-by: ifsantana --- .../idem/core/security/ApiKeyRepository.kt | 7 ++- .../security/ApiKeyJpaRepository.kt | 2 +- .../security/ApiKeyRepositoryAdapter.kt | 2 +- .../infrastructure/security/ApiKeyService.kt | 14 ++--- .../security/ApiKeyRepositoryAdapterTest.kt | 54 ++++++++----------- .../security/ApiKeyServiceTest.kt | 28 +++++++--- 6 files changed, 59 insertions(+), 48 deletions(-) diff --git a/core/src/main/kotlin/finance/idem/core/security/ApiKeyRepository.kt b/core/src/main/kotlin/finance/idem/core/security/ApiKeyRepository.kt index 8040664..1442e96 100644 --- a/core/src/main/kotlin/finance/idem/core/security/ApiKeyRepository.kt +++ b/core/src/main/kotlin/finance/idem/core/security/ApiKeyRepository.kt @@ -5,7 +5,12 @@ import finance.idem.core.TenantId interface ApiKeyRepository { fun save(apiKey: ApiKey): ApiKey - fun findByPrefix(prefix: String): ApiKey? + /** + * Returns every key sharing this prefix. The prefix (first 12 chars of the raw + * key) is not unique — its index is non-unique by design — so callers must + * disambiguate by bcrypt-matching the raw key against each candidate's hash. + */ + fun findAllByPrefix(prefix: String): List fun findById( id: ApiKeyId, diff --git a/infrastructure/src/main/kotlin/finance/idem/infrastructure/security/ApiKeyJpaRepository.kt b/infrastructure/src/main/kotlin/finance/idem/infrastructure/security/ApiKeyJpaRepository.kt index 7393eae..ceda7ed 100644 --- a/infrastructure/src/main/kotlin/finance/idem/infrastructure/security/ApiKeyJpaRepository.kt +++ b/infrastructure/src/main/kotlin/finance/idem/infrastructure/security/ApiKeyJpaRepository.kt @@ -4,7 +4,7 @@ import org.springframework.data.jpa.repository.JpaRepository import java.util.UUID interface ApiKeyJpaRepository : JpaRepository { - fun findByPrefix(prefix: String): ApiKeyDataModel? + fun findAllByPrefix(prefix: String): List fun findByIdAndTenantId( id: UUID, diff --git a/infrastructure/src/main/kotlin/finance/idem/infrastructure/security/ApiKeyRepositoryAdapter.kt b/infrastructure/src/main/kotlin/finance/idem/infrastructure/security/ApiKeyRepositoryAdapter.kt index dcfd10e..f46950d 100644 --- a/infrastructure/src/main/kotlin/finance/idem/infrastructure/security/ApiKeyRepositoryAdapter.kt +++ b/infrastructure/src/main/kotlin/finance/idem/infrastructure/security/ApiKeyRepositoryAdapter.kt @@ -19,7 +19,7 @@ class ApiKeyRepositoryAdapter( } @Transactional(readOnly = true) - override fun findByPrefix(prefix: String): ApiKey? = jpaRepository.findByPrefix(prefix)?.toDomain() + override fun findAllByPrefix(prefix: String): List = jpaRepository.findAllByPrefix(prefix).map { it.toDomain() } @Transactional(readOnly = true) override fun findById( diff --git a/infrastructure/src/main/kotlin/finance/idem/infrastructure/security/ApiKeyService.kt b/infrastructure/src/main/kotlin/finance/idem/infrastructure/security/ApiKeyService.kt index cfe4d46..6d190e5 100644 --- a/infrastructure/src/main/kotlin/finance/idem/infrastructure/security/ApiKeyService.kt +++ b/infrastructure/src/main/kotlin/finance/idem/infrastructure/security/ApiKeyService.kt @@ -54,12 +54,14 @@ class ApiKeyService( deserializeFromCache(json)?.let { return it } } - val apiKey = apiKeyRepository.findByPrefix(prefix) ?: return null - if (apiKey.isRevoked) return null - if (!passwordEncoder.matches(rawKey, apiKey.keyHash)) { - log.warn("Hash mismatch for prefix={}***", prefix.take(6)) - return null - } + // The prefix is not unique (only ~65k values, non-unique index), so several + // keys can share it — bcrypt-match the raw key against each live candidate. + val candidates = apiKeyRepository.findAllByPrefix(prefix).filterNot { it.isRevoked } + val apiKey = + candidates.firstOrNull { passwordEncoder.matches(rawKey, it.keyHash) } ?: run { + if (candidates.isNotEmpty()) log.warn("Hash mismatch for prefix={}***", prefix.take(6)) + return null + } val validated = ValidatedApiKey(apiKey.tenantId, apiKey.scopes) redisTemplate.opsForValue().set(cacheKey, serializeForCache(validated), cacheTtl) diff --git a/infrastructure/src/test/kotlin/finance/idem/infrastructure/security/ApiKeyRepositoryAdapterTest.kt b/infrastructure/src/test/kotlin/finance/idem/infrastructure/security/ApiKeyRepositoryAdapterTest.kt index 1b7d9eb..727ce79 100644 --- a/infrastructure/src/test/kotlin/finance/idem/infrastructure/security/ApiKeyRepositoryAdapterTest.kt +++ b/infrastructure/src/test/kotlin/finance/idem/infrastructure/security/ApiKeyRepositoryAdapterTest.kt @@ -4,16 +4,12 @@ import finance.idem.core.TenantId import finance.idem.core.security.ApiKey import finance.idem.core.security.ApiKeyId import finance.idem.core.security.ApiScope +import finance.idem.infrastructure.SharedPostgresTestBase import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest import org.springframework.context.annotation.Import -import org.springframework.test.context.DynamicPropertyRegistry -import org.springframework.test.context.DynamicPropertySource -import org.testcontainers.containers.PostgreSQLContainer -import org.testcontainers.junit.jupiter.Container -import org.testcontainers.junit.jupiter.Testcontainers import java.time.Instant import java.util.concurrent.atomic.AtomicInteger import kotlin.test.assertEquals @@ -23,26 +19,10 @@ import kotlin.test.assertTrue @DataJpaTest @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) -@Testcontainers @Import(ApiKeyRepositoryAdapter::class) -class ApiKeyRepositoryAdapterTest { +class ApiKeyRepositoryAdapterTest : SharedPostgresTestBase() { companion object { private val prefixSeq = AtomicInteger(0) - - @Container - val postgres: PostgreSQLContainer<*> = - PostgreSQLContainer("postgres:16") - .withDatabaseName("idem_test") - .withUsername("idem") - .withPassword("idem") - - @DynamicPropertySource - @JvmStatic - fun props(registry: DynamicPropertyRegistry) { - registry.add("spring.datasource.url", postgres::getJdbcUrl) - registry.add("spring.datasource.username", postgres::getUsername) - registry.add("spring.datasource.password", postgres::getPassword) - } } @Autowired @@ -52,13 +32,12 @@ class ApiKeyRepositoryAdapterTest { private val now = Instant.now() @Test - fun `save and findByPrefix round-trip preserves all fields`() { + fun `save and findAllByPrefix round-trip preserves all fields`() { val key = apiKey(prefix = "sk_live_aabb") adapter.save(key) - val found = adapter.findByPrefix("sk_live_aabb") + val found = adapter.findAllByPrefix("sk_live_aabb").single() - assertNotNull(found) assertEquals(key.id, found.id) assertEquals(key.tenantId, found.tenantId) assertEquals(key.keyHash, found.keyHash) @@ -69,8 +48,20 @@ class ApiKeyRepositoryAdapterTest { } @Test - fun `findByPrefix returns null for unknown prefix`() { - assertNull(adapter.findByPrefix("sk_live_none")) + fun `findAllByPrefix returns empty for unknown prefix`() { + assertTrue(adapter.findAllByPrefix("sk_live_none").isEmpty()) + } + + @Test + fun `findAllByPrefix returns every key sharing a prefix`() { + val first = apiKey(prefix = "sk_live_coll") + val second = apiKey(prefix = "sk_live_coll").copy(id = ApiKeyId.generate()) + adapter.save(first) + adapter.save(second) + + val found = adapter.findAllByPrefix("sk_live_coll") + + assertEquals(setOf(first.id, second.id), found.map { it.id }.toSet()) } @Test @@ -79,8 +70,7 @@ class ApiKeyRepositoryAdapterTest { val key = apiKey().copy(revokedAt = revokedAt) adapter.save(key) - val found = adapter.findByPrefix(key.prefix) - assertNotNull(found) + val found = adapter.findAllByPrefix(key.prefix).single() assertTrue(found.isRevoked) assertEquals(revokedAt.epochSecond, found.revokedAt!!.epochSecond) } @@ -110,8 +100,7 @@ class ApiKeyRepositoryAdapterTest { val key = apiKey(scopes = scopes) adapter.save(key) - val found = adapter.findByPrefix(key.prefix) - assertNotNull(found) + val found = adapter.findAllByPrefix(key.prefix).single() assertEquals(scopes, found.scopes) } @@ -120,8 +109,7 @@ class ApiKeyRepositoryAdapterTest { val key = apiKey(scopes = emptySet()) adapter.save(key) - val found = adapter.findByPrefix(key.prefix) - assertNotNull(found) + val found = adapter.findAllByPrefix(key.prefix).single() assertTrue(found.scopes.isEmpty()) } diff --git a/infrastructure/src/test/kotlin/finance/idem/infrastructure/security/ApiKeyServiceTest.kt b/infrastructure/src/test/kotlin/finance/idem/infrastructure/security/ApiKeyServiceTest.kt index 13ccadf..4cb39c6 100644 --- a/infrastructure/src/test/kotlin/finance/idem/infrastructure/security/ApiKeyServiceTest.kt +++ b/infrastructure/src/test/kotlin/finance/idem/infrastructure/security/ApiKeyServiceTest.kt @@ -75,17 +75,17 @@ class ApiKeyServiceTest { @Test fun `validate returns null when prefix not in cache and not in DB`() { whenever(opsForValue.get(any())).thenReturn(null) - whenever(apiKeyRepository.findByPrefix(any())).thenReturn(null) + whenever(apiKeyRepository.findAllByPrefix(any())).thenReturn(emptyList()) assertNull(service.validate("sk_live_testabcd1234")) - verify(apiKeyRepository).findByPrefix("sk_live_test") + verify(apiKeyRepository).findAllByPrefix("sk_live_test") } @Test fun `validate returns null for revoked key without checking hash`() { val revokedKey = apiKey().copy(revokedAt = Instant.now()) whenever(opsForValue.get(any())).thenReturn(null) - whenever(apiKeyRepository.findByPrefix(any())).thenReturn(revokedKey) + whenever(apiKeyRepository.findAllByPrefix(any())).thenReturn(listOf(revokedKey)) assertNull(service.validate("sk_live_testabcd1234")) verify(passwordEncoder, never()).matches(any(), any()) @@ -94,7 +94,7 @@ class ApiKeyServiceTest { @Test fun `validate returns null when hash does not match`() { whenever(opsForValue.get(any())).thenReturn(null) - whenever(apiKeyRepository.findByPrefix(any())).thenReturn(apiKey()) + whenever(apiKeyRepository.findAllByPrefix(any())).thenReturn(listOf(apiKey())) whenever(passwordEncoder.matches(any(), any())).thenReturn(false) assertNull(service.validate("sk_live_testabcd1234")) @@ -110,14 +110,14 @@ class ApiKeyServiceTest { assertNotNull(result) assertEquals(tenantId, result.tenantId) assertTrue(result.scopes.contains(ApiScope.TRANSACTIONS_READ)) - verify(apiKeyRepository, never()).findByPrefix(any()) + verify(apiKeyRepository, never()).findAllByPrefix(any()) } @Test fun `validate caches result and returns ValidatedApiKey from DB`() { val key = apiKey() whenever(opsForValue.get(any())).thenReturn(null) - whenever(apiKeyRepository.findByPrefix(any())).thenReturn(key) + whenever(apiKeyRepository.findAllByPrefix(any())).thenReturn(listOf(key)) whenever(passwordEncoder.matches(any(), any())).thenReturn(true) val result = service.validate("sk_live_testabcd1234") @@ -127,6 +127,22 @@ class ApiKeyServiceTest { verify(opsForValue).set(eq("apikey:sk_live_test"), any(), eq(Duration.ofMinutes(5))) } + @Test + fun `validate disambiguates by hash when several keys share a prefix`() { + val other = apiKey().copy(id = ApiKeyId.generate(), keyHash = "\$2a\$12\$otherhash") + val matching = apiKey() + whenever(opsForValue.get(any())).thenReturn(null) + whenever(apiKeyRepository.findAllByPrefix(any())).thenReturn(listOf(other, matching)) + // Only the second candidate's hash matches the presented raw key. + whenever(passwordEncoder.matches(any(), eq("\$2a\$12\$otherhash"))).thenReturn(false) + whenever(passwordEncoder.matches(any(), eq("\$2a\$12\$fakehash"))).thenReturn(true) + + val result = service.validate("sk_live_testabcd1234") + + assertNotNull(result) + assertEquals(matching.tenantId, result.tenantId) + } + @Test fun `revoke returns false for key not found`() { val keyId = ApiKeyId.generate() From 094f702b898e540a914f183fc92f0587198cab2e Mon Sep 17 00:00:00 2001 From: ifsantana Date: Wed, 15 Jul 2026 22:12:22 -0300 Subject: [PATCH 2/2] test: share one Postgres per module + bounded parallelism (#242) Consolidate ~27 per-class Postgres Testcontainers in the infrastructure module onto the existing PostgresTestContainers singleton via a new SharedPostgresTestBase (@DynamicPropertySource); slice tests roll back and committing tests stay tenant-scoped, so the shared DB is safe. App module @ServiceConnection containers become JVM singletons shared across the ~5 @SpringBootTest contexts. Adds -T 1C (.mvn/maven.config) and class-level JUnit parallelism in the container-free modules (core, application, sdk-kotlin). Caps the Hikari pool to 2 and raises Postgres max_connections so the many cached Spring contexts sharing one container don't exhaust connections. Postgres container starts across the build: ~33 -> 6. Local clean verify: 5:31 -> 4:00, all suites green. Signed-off-by: ifsantana --- .mvn/maven.config | 1 + .../idem/TestcontainersConfiguration.kt | 16 ++++++++-- .../test/resources/junit-platform.properties | 4 +++ .../test/resources/junit-platform.properties | 4 +++ .../infrastructure/SharedPostgresTestBase.kt | 32 +++++++++++++++++++ .../ComplianceQueueRepositoryAdapterTest.kt | 26 ++------------- .../compliance/LgpdRetentionServiceTest.kt | 26 ++------------- .../TravelRuleRepositoryAdapterTest.kt | 26 ++------------- .../AccountRepositoryAdapterTest.kt | 26 ++------------- .../JournalLineRepositoryAdapterTest.kt | 26 ++------------- .../TransactionRepositoryAdapterTest.kt | 26 ++------------- .../audit/AgentAuditRepositoryAdapterTest.kt | 25 +++------------ .../audit/AuditExportRepositoryAdapterTest.kt | 26 ++------------- .../audit/AuditRepositoryAdapterTest.kt | 25 +++------------ .../ChainCheckpointRepositoryAdapterTest.kt | 26 ++------------- ...ailedChainTransferRepositoryAdapterTest.kt | 26 ++------------- .../WatchedAddressRepositoryAdapterTest.kt | 26 ++------------- .../PostgresIdempotencyStoreTest.kt | 26 ++------------- .../PostgresSettlementIdempotencyStoreTest.kt | 26 ++------------- .../WebhookOutboxRepositoryAdapterTest.kt | 26 ++------------- .../policy/PolicyRepositoryAdapterTest.kt | 26 ++------------- .../SettlementRepositoryAdapterTest.kt | 29 +++-------------- .../InstallationMetadataAdapterTest.kt | 26 ++------------- .../tenant/TenantRepositoryAdapterTest.kt | 26 ++------------- .../WorkflowPlanRepositoryAdapterTest.kt | 26 ++------------- .../security/ApiKeyServiceIntegrationTest.kt | 15 ++------- .../PostgresServiceIntegrationTestBase.kt | 15 ++------- .../service/PostgresTestContainers.kt | 3 ++ ...nantThresholdWarnServiceIntegrationTest.kt | 26 ++------------- .../test/resources/junit-platform.properties | 4 +++ 30 files changed, 115 insertions(+), 526 deletions(-) create mode 100644 .mvn/maven.config create mode 100644 application/src/test/resources/junit-platform.properties create mode 100644 core/src/test/resources/junit-platform.properties create mode 100644 infrastructure/src/test/kotlin/finance/idem/infrastructure/SharedPostgresTestBase.kt create mode 100644 sdk-kotlin/src/test/resources/junit-platform.properties diff --git a/.mvn/maven.config b/.mvn/maven.config new file mode 100644 index 0000000..ebbe288 --- /dev/null +++ b/.mvn/maven.config @@ -0,0 +1 @@ +-T 1C diff --git a/app/src/test/kotlin/finance/idem/TestcontainersConfiguration.kt b/app/src/test/kotlin/finance/idem/TestcontainersConfiguration.kt index 8147b13..0006259 100644 --- a/app/src/test/kotlin/finance/idem/TestcontainersConfiguration.kt +++ b/app/src/test/kotlin/finance/idem/TestcontainersConfiguration.kt @@ -9,11 +9,23 @@ import org.testcontainers.utility.DockerImageName @TestConfiguration(proxyBeanMethods = false) class TestcontainersConfiguration { + companion object { + // JVM-wide singletons: every @SpringBootTest context that imports this + // configuration shares one Postgres + Redis pair instead of booting its + // own. Held here (not as context-managed lifecycle) so a context close + // never stops the containers; Ryuk reaps them at JVM exit. + private val postgres: PostgreSQLContainer<*> = + PostgreSQLContainer(DockerImageName.parse("postgres:16")).also { it.start() } + + private val redis: GenericContainer<*> = + GenericContainer(DockerImageName.parse("redis:7")).withExposedPorts(6379).also { it.start() } + } + @Bean @ServiceConnection - fun postgresContainer(): PostgreSQLContainer<*> = PostgreSQLContainer(DockerImageName.parse("postgres:16")) + fun postgresContainer(): PostgreSQLContainer<*> = postgres @Bean @ServiceConnection(name = "redis") - fun redisContainer(): GenericContainer<*> = GenericContainer(DockerImageName.parse("redis:7")).withExposedPorts(6379) + fun redisContainer(): GenericContainer<*> = redis } diff --git a/application/src/test/resources/junit-platform.properties b/application/src/test/resources/junit-platform.properties new file mode 100644 index 0000000..d78fcbd --- /dev/null +++ b/application/src/test/resources/junit-platform.properties @@ -0,0 +1,4 @@ +# Class-level parallel test execution — safe here: no Spring context, no containers. +junit.jupiter.execution.parallel.enabled=true +junit.jupiter.execution.parallel.mode.default=same_thread +junit.jupiter.execution.parallel.mode.classes.default=concurrent diff --git a/core/src/test/resources/junit-platform.properties b/core/src/test/resources/junit-platform.properties new file mode 100644 index 0000000..d78fcbd --- /dev/null +++ b/core/src/test/resources/junit-platform.properties @@ -0,0 +1,4 @@ +# Class-level parallel test execution — safe here: no Spring context, no containers. +junit.jupiter.execution.parallel.enabled=true +junit.jupiter.execution.parallel.mode.default=same_thread +junit.jupiter.execution.parallel.mode.classes.default=concurrent diff --git a/infrastructure/src/test/kotlin/finance/idem/infrastructure/SharedPostgresTestBase.kt b/infrastructure/src/test/kotlin/finance/idem/infrastructure/SharedPostgresTestBase.kt new file mode 100644 index 0000000..b793ffc --- /dev/null +++ b/infrastructure/src/test/kotlin/finance/idem/infrastructure/SharedPostgresTestBase.kt @@ -0,0 +1,32 @@ +package finance.idem.infrastructure + +import finance.idem.infrastructure.service.PostgresTestContainers +import org.springframework.test.context.DynamicPropertyRegistry +import org.springframework.test.context.DynamicPropertySource + +/** + * Points the subclass's Spring context at the module-wide singleton Postgres + * container instead of a per-class one, so the whole module boots one container + * (and runs Flyway once) rather than one per test class. + * + * Safe only for tests whose writes roll back (e.g. `@DataJpaTest`) or are scoped + * to a per-class random `TenantId`. Tests that commit rows a scheduler or another + * class could observe (poller/orchestrator/telemetry-ping) and tests that assert + * fresh-database state (FlywayMigrationTest) must keep their own container. + */ +@Suppress("UtilityClassWithPublicConstructor") // subclassed so Spring inherits the @DynamicPropertySource +abstract class SharedPostgresTestBase { + companion object { + @DynamicPropertySource + @JvmStatic + fun sharedPostgresProps(registry: DynamicPropertyRegistry) { + registry.add("spring.datasource.url", PostgresTestContainers.postgres::getJdbcUrl) + registry.add("spring.datasource.username", PostgresTestContainers.postgres::getUsername) + registry.add("spring.datasource.password", PostgresTestContainers.postgres::getPassword) + // Cached contexts each hold a live Hikari pool against the ONE shared + // container — at the default pool size (10) ~25 cached contexts exceed + // Postgres max_connections. Two connections suffice for slice tests. + registry.add("spring.datasource.hikari.maximum-pool-size") { "2" } + } + } +} diff --git a/infrastructure/src/test/kotlin/finance/idem/infrastructure/compliance/ComplianceQueueRepositoryAdapterTest.kt b/infrastructure/src/test/kotlin/finance/idem/infrastructure/compliance/ComplianceQueueRepositoryAdapterTest.kt index a5092d0..a190276 100644 --- a/infrastructure/src/test/kotlin/finance/idem/infrastructure/compliance/ComplianceQueueRepositoryAdapterTest.kt +++ b/infrastructure/src/test/kotlin/finance/idem/infrastructure/compliance/ComplianceQueueRepositoryAdapterTest.kt @@ -9,6 +9,7 @@ import finance.idem.core.MonetaryAmount import finance.idem.core.StablecoinToken import finance.idem.core.TenantId import finance.idem.core.monetary.OnChainEntry +import finance.idem.infrastructure.SharedPostgresTestBase import finance.idem.infrastructure.persistence.PersistenceTestConfig import jakarta.persistence.EntityManager import org.junit.jupiter.api.Test @@ -16,36 +17,13 @@ import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest import org.springframework.context.annotation.Import -import org.springframework.test.context.DynamicPropertyRegistry -import org.springframework.test.context.DynamicPropertySource -import org.testcontainers.containers.PostgreSQLContainer -import org.testcontainers.junit.jupiter.Container -import org.testcontainers.junit.jupiter.Testcontainers import kotlin.test.assertEquals import kotlin.test.assertNotNull @DataJpaTest @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) -@Testcontainers @Import(ComplianceQueueRepositoryAdapter::class, PersistenceTestConfig::class) -class ComplianceQueueRepositoryAdapterTest { - companion object { - @Container - val postgres = - PostgreSQLContainer("postgres:16") - .withDatabaseName("idem_test") - .withUsername("idem") - .withPassword("idem") - - @DynamicPropertySource - @JvmStatic - fun props(registry: DynamicPropertyRegistry) { - registry.add("spring.datasource.url", postgres::getJdbcUrl) - registry.add("spring.datasource.username", postgres::getUsername) - registry.add("spring.datasource.password", postgres::getPassword) - } - } - +class ComplianceQueueRepositoryAdapterTest : SharedPostgresTestBase() { @Autowired lateinit var adapter: ComplianceQueueRepositoryAdapter diff --git a/infrastructure/src/test/kotlin/finance/idem/infrastructure/compliance/LgpdRetentionServiceTest.kt b/infrastructure/src/test/kotlin/finance/idem/infrastructure/compliance/LgpdRetentionServiceTest.kt index 778e005..0ea3a87 100644 --- a/infrastructure/src/test/kotlin/finance/idem/infrastructure/compliance/LgpdRetentionServiceTest.kt +++ b/infrastructure/src/test/kotlin/finance/idem/infrastructure/compliance/LgpdRetentionServiceTest.kt @@ -1,17 +1,13 @@ package finance.idem.infrastructure.compliance import finance.idem.core.TenantId +import finance.idem.infrastructure.SharedPostgresTestBase import finance.idem.infrastructure.persistence.PersistenceTestConfig import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest import org.springframework.context.annotation.Import -import org.springframework.test.context.DynamicPropertyRegistry -import org.springframework.test.context.DynamicPropertySource -import org.testcontainers.containers.PostgreSQLContainer -import org.testcontainers.junit.jupiter.Container -import org.testcontainers.junit.jupiter.Testcontainers import java.math.BigDecimal import java.time.Instant import java.time.ZoneOffset @@ -23,26 +19,8 @@ import kotlin.test.assertNull @DataJpaTest @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) -@Testcontainers @Import(LgpdRetentionRepositoryAdapter::class, LgpdRetentionService::class, PersistenceTestConfig::class) -class LgpdRetentionServiceTest { - companion object { - @Container - val postgres = - PostgreSQLContainer("postgres:16") - .withDatabaseName("idem_test") - .withUsername("idem") - .withPassword("idem") - - @DynamicPropertySource - @JvmStatic - fun props(registry: DynamicPropertyRegistry) { - registry.add("spring.datasource.url", postgres::getJdbcUrl) - registry.add("spring.datasource.username", postgres::getUsername) - registry.add("spring.datasource.password", postgres::getPassword) - } - } - +class LgpdRetentionServiceTest : SharedPostgresTestBase() { @Autowired lateinit var adapter: LgpdRetentionRepositoryAdapter @Autowired lateinit var service: LgpdRetentionService diff --git a/infrastructure/src/test/kotlin/finance/idem/infrastructure/compliance/TravelRuleRepositoryAdapterTest.kt b/infrastructure/src/test/kotlin/finance/idem/infrastructure/compliance/TravelRuleRepositoryAdapterTest.kt index 9120094..58266fb 100644 --- a/infrastructure/src/test/kotlin/finance/idem/infrastructure/compliance/TravelRuleRepositoryAdapterTest.kt +++ b/infrastructure/src/test/kotlin/finance/idem/infrastructure/compliance/TravelRuleRepositoryAdapterTest.kt @@ -7,6 +7,7 @@ import finance.idem.core.compliance.LegalPerson import finance.idem.core.compliance.NaturalPerson import finance.idem.core.compliance.TravelRuleData import finance.idem.core.compliance.VaspTransferParty +import finance.idem.infrastructure.SharedPostgresTestBase import finance.idem.infrastructure.persistence.PersistenceTestConfig import jakarta.persistence.EntityManager import org.junit.jupiter.api.Test @@ -14,11 +15,6 @@ import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest import org.springframework.context.annotation.Import -import org.springframework.test.context.DynamicPropertyRegistry -import org.springframework.test.context.DynamicPropertySource -import org.testcontainers.containers.PostgreSQLContainer -import org.testcontainers.junit.jupiter.Container -import org.testcontainers.junit.jupiter.Testcontainers import java.time.LocalDate import kotlin.test.assertEquals import kotlin.test.assertNotNull @@ -26,26 +22,8 @@ import kotlin.test.assertNull @DataJpaTest @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) -@Testcontainers @Import(TravelRuleRepositoryAdapter::class, PersistenceTestConfig::class) -class TravelRuleRepositoryAdapterTest { - companion object { - @Container - val postgres = - PostgreSQLContainer("postgres:16") - .withDatabaseName("idem_test") - .withUsername("idem") - .withPassword("idem") - - @DynamicPropertySource - @JvmStatic - fun props(registry: DynamicPropertyRegistry) { - registry.add("spring.datasource.url", postgres::getJdbcUrl) - registry.add("spring.datasource.username", postgres::getUsername) - registry.add("spring.datasource.password", postgres::getPassword) - } - } - +class TravelRuleRepositoryAdapterTest : SharedPostgresTestBase() { @Autowired lateinit var adapter: TravelRuleRepositoryAdapter diff --git a/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/AccountRepositoryAdapterTest.kt b/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/AccountRepositoryAdapterTest.kt index 7e264d1..7b6b96c 100644 --- a/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/AccountRepositoryAdapterTest.kt +++ b/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/AccountRepositoryAdapterTest.kt @@ -5,17 +5,13 @@ import finance.idem.core.FiatCurrency import finance.idem.core.TenantId import finance.idem.core.ledger.Account import finance.idem.core.ledger.AccountType +import finance.idem.infrastructure.SharedPostgresTestBase import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest import org.springframework.boot.testcontainers.service.connection.ServiceConnection import org.springframework.context.annotation.Import -import org.springframework.test.context.DynamicPropertyRegistry -import org.springframework.test.context.DynamicPropertySource -import org.testcontainers.containers.PostgreSQLContainer -import org.testcontainers.junit.jupiter.Container -import org.testcontainers.junit.jupiter.Testcontainers import java.time.Instant import kotlin.test.assertEquals import kotlin.test.assertFalse @@ -25,26 +21,8 @@ import kotlin.test.assertTrue @DataJpaTest @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) -@Testcontainers @Import(AccountRepositoryAdapter::class) -class AccountRepositoryAdapterTest { - companion object { - @Container - val postgres = - PostgreSQLContainer("postgres:16") - .withDatabaseName("idem_test") - .withUsername("idem") - .withPassword("idem") - - @DynamicPropertySource - @JvmStatic - fun props(registry: DynamicPropertyRegistry) { - registry.add("spring.datasource.url", postgres::getJdbcUrl) - registry.add("spring.datasource.username", postgres::getUsername) - registry.add("spring.datasource.password", postgres::getPassword) - } - } - +class AccountRepositoryAdapterTest : SharedPostgresTestBase() { @Autowired lateinit var adapter: AccountRepositoryAdapter private val tenantA = TenantId.generate() diff --git a/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/JournalLineRepositoryAdapterTest.kt b/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/JournalLineRepositoryAdapterTest.kt index ea3c504..1185619 100644 --- a/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/JournalLineRepositoryAdapterTest.kt +++ b/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/JournalLineRepositoryAdapterTest.kt @@ -12,17 +12,13 @@ import finance.idem.core.ledger.AccountType import finance.idem.core.ledger.JournalLine import finance.idem.core.ledger.Transaction import finance.idem.core.monetary.FiatEntry +import finance.idem.infrastructure.SharedPostgresTestBase import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest import org.springframework.context.annotation.Import -import org.springframework.test.context.DynamicPropertyRegistry -import org.springframework.test.context.DynamicPropertySource -import org.testcontainers.containers.PostgreSQLContainer -import org.testcontainers.junit.jupiter.Container -import org.testcontainers.junit.jupiter.Testcontainers import java.time.Instant import java.util.UUID import kotlin.test.assertEquals @@ -32,31 +28,13 @@ import kotlin.test.assertTrue @DataJpaTest @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) -@Testcontainers @Import( JournalLineRepositoryAdapter::class, TransactionRepositoryAdapter::class, AccountRepositoryAdapter::class, PersistenceTestConfig::class, ) -class JournalLineRepositoryAdapterTest { - companion object { - @Container - val postgres = - PostgreSQLContainer("postgres:16") - .withDatabaseName("idem_test") - .withUsername("idem") - .withPassword("idem") - - @DynamicPropertySource - @JvmStatic - fun props(registry: DynamicPropertyRegistry) { - registry.add("spring.datasource.url", postgres::getJdbcUrl) - registry.add("spring.datasource.username", postgres::getUsername) - registry.add("spring.datasource.password", postgres::getPassword) - } - } - +class JournalLineRepositoryAdapterTest : SharedPostgresTestBase() { @Autowired lateinit var journalLineAdapter: JournalLineRepositoryAdapter @Autowired lateinit var transactionAdapter: TransactionRepositoryAdapter diff --git a/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/TransactionRepositoryAdapterTest.kt b/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/TransactionRepositoryAdapterTest.kt index ba0b29c..1b34d9e 100644 --- a/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/TransactionRepositoryAdapterTest.kt +++ b/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/TransactionRepositoryAdapterTest.kt @@ -16,17 +16,13 @@ import finance.idem.core.ledger.Transaction import finance.idem.core.ledger.TransactionStatus import finance.idem.core.monetary.FiatEntry import finance.idem.core.monetary.OnChainEntry +import finance.idem.infrastructure.SharedPostgresTestBase import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest import org.springframework.context.annotation.Import -import org.springframework.test.context.DynamicPropertyRegistry -import org.springframework.test.context.DynamicPropertySource -import org.testcontainers.containers.PostgreSQLContainer -import org.testcontainers.junit.jupiter.Container -import org.testcontainers.junit.jupiter.Testcontainers import java.time.Instant import java.util.UUID import kotlin.test.assertEquals @@ -36,26 +32,8 @@ import kotlin.test.assertTrue @DataJpaTest @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) -@Testcontainers @Import(TransactionRepositoryAdapter::class, AccountRepositoryAdapter::class, PersistenceTestConfig::class) -class TransactionRepositoryAdapterTest { - companion object { - @Container - val postgres = - PostgreSQLContainer("postgres:16") - .withDatabaseName("idem_test") - .withUsername("idem") - .withPassword("idem") - - @DynamicPropertySource - @JvmStatic - fun props(registry: DynamicPropertyRegistry) { - registry.add("spring.datasource.url", postgres::getJdbcUrl) - registry.add("spring.datasource.username", postgres::getUsername) - registry.add("spring.datasource.password", postgres::getPassword) - } - } - +class TransactionRepositoryAdapterTest : SharedPostgresTestBase() { @Autowired lateinit var adapter: TransactionRepositoryAdapter @Autowired lateinit var accountAdapter: AccountRepositoryAdapter diff --git a/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/audit/AgentAuditRepositoryAdapterTest.kt b/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/audit/AgentAuditRepositoryAdapterTest.kt index ac3be7b..df464a6 100644 --- a/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/audit/AgentAuditRepositoryAdapterTest.kt +++ b/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/audit/AgentAuditRepositoryAdapterTest.kt @@ -5,7 +5,9 @@ import finance.idem.core.WorkflowPlanId import finance.idem.core.agentic.AgentAuditEvent import finance.idem.core.agentic.AgentAuditStatus import finance.idem.core.agentic.AgentContext +import finance.idem.infrastructure.SharedPostgresTestBase import finance.idem.infrastructure.persistence.PersistenceTestConfig +import finance.idem.infrastructure.service.PostgresTestContainers import jakarta.persistence.EntityManager import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows @@ -13,11 +15,6 @@ import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest import org.springframework.context.annotation.Import -import org.springframework.test.context.DynamicPropertyRegistry -import org.springframework.test.context.DynamicPropertySource -import org.testcontainers.containers.PostgreSQLContainer -import org.testcontainers.junit.jupiter.Container -import org.testcontainers.junit.jupiter.Testcontainers import java.sql.DriverManager import java.sql.SQLException import java.time.Instant @@ -28,27 +25,13 @@ import kotlin.test.assertTrue @DataJpaTest @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) -@Testcontainers @Import(AgentAuditRepositoryAdapter::class, AuditConfig::class, PersistenceTestConfig::class) -class AgentAuditRepositoryAdapterTest { +class AgentAuditRepositoryAdapterTest : SharedPostgresTestBase() { companion object { private const val APP_ROLE = "idem_app_role" private const val APP_ROLE_PASSWORD = "app_role_pass" - @Container - val postgres = - PostgreSQLContainer("postgres:16") - .withDatabaseName("idem_test") - .withUsername("idem") - .withPassword("idem") - - @DynamicPropertySource - @JvmStatic - fun props(registry: DynamicPropertyRegistry) { - registry.add("spring.datasource.url", postgres::getJdbcUrl) - registry.add("spring.datasource.username", postgres::getUsername) - registry.add("spring.datasource.password", postgres::getPassword) - } + private val postgres get() = PostgresTestContainers.postgres fun ensureRestrictedRole() { DriverManager.getConnection(postgres.jdbcUrl, "idem", "idem").use { conn -> diff --git a/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/audit/AuditExportRepositoryAdapterTest.kt b/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/audit/AuditExportRepositoryAdapterTest.kt index 2775b44..c0a9c45 100644 --- a/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/audit/AuditExportRepositoryAdapterTest.kt +++ b/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/audit/AuditExportRepositoryAdapterTest.kt @@ -9,6 +9,7 @@ import finance.idem.core.TransactionId import finance.idem.core.WorkflowPlanId import finance.idem.core.agentic.AgentAuditEvent import finance.idem.core.agentic.AgentContext +import finance.idem.infrastructure.SharedPostgresTestBase import finance.idem.infrastructure.persistence.PersistenceTestConfig import jakarta.persistence.EntityManager import org.junit.jupiter.api.Test @@ -16,11 +17,6 @@ import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest import org.springframework.context.annotation.Import -import org.springframework.test.context.DynamicPropertyRegistry -import org.springframework.test.context.DynamicPropertySource -import org.testcontainers.containers.PostgreSQLContainer -import org.testcontainers.junit.jupiter.Container -import org.testcontainers.junit.jupiter.Testcontainers import java.time.Instant import java.util.UUID import kotlin.test.assertEquals @@ -28,7 +24,6 @@ import kotlin.test.assertTrue @DataJpaTest @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) -@Testcontainers @Import( AuditExportRepositoryAdapter::class, AuditRepositoryAdapter::class, @@ -36,24 +31,7 @@ import kotlin.test.assertTrue AuditConfig::class, PersistenceTestConfig::class, ) -class AuditExportRepositoryAdapterTest { - companion object { - @Container - val postgres = - PostgreSQLContainer("postgres:16") - .withDatabaseName("idem_test") - .withUsername("idem") - .withPassword("idem") - - @DynamicPropertySource - @JvmStatic - fun props(registry: DynamicPropertyRegistry) { - registry.add("spring.datasource.url", postgres::getJdbcUrl) - registry.add("spring.datasource.username", postgres::getUsername) - registry.add("spring.datasource.password", postgres::getPassword) - } - } - +class AuditExportRepositoryAdapterTest : SharedPostgresTestBase() { @Autowired lateinit var adapter: AuditExportRepositoryAdapter diff --git a/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/audit/AuditRepositoryAdapterTest.kt b/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/audit/AuditRepositoryAdapterTest.kt index 5874314..ec24185 100644 --- a/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/audit/AuditRepositoryAdapterTest.kt +++ b/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/audit/AuditRepositoryAdapterTest.kt @@ -3,7 +3,9 @@ package finance.idem.infrastructure.persistence.audit import finance.idem.application.audit.AuditEntry import finance.idem.core.TenantId import finance.idem.core.TransactionId +import finance.idem.infrastructure.SharedPostgresTestBase import finance.idem.infrastructure.persistence.PersistenceTestConfig +import finance.idem.infrastructure.service.PostgresTestContainers import jakarta.persistence.EntityManager import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows @@ -11,11 +13,6 @@ import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest import org.springframework.context.annotation.Import -import org.springframework.test.context.DynamicPropertyRegistry -import org.springframework.test.context.DynamicPropertySource -import org.testcontainers.containers.PostgreSQLContainer -import org.testcontainers.junit.jupiter.Container -import org.testcontainers.junit.jupiter.Testcontainers import java.sql.DriverManager import java.sql.SQLException import java.time.Instant @@ -26,27 +23,13 @@ import kotlin.test.assertTrue @DataJpaTest @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) -@Testcontainers @Import(AuditRepositoryAdapter::class, AuditConfig::class, PersistenceTestConfig::class) -class AuditRepositoryAdapterTest { +class AuditRepositoryAdapterTest : SharedPostgresTestBase() { companion object { private const val APP_ROLE = "idem_app_role" private const val APP_ROLE_PASSWORD = "app_role_pass" - @Container - val postgres = - PostgreSQLContainer("postgres:16") - .withDatabaseName("idem_test") - .withUsername("idem") - .withPassword("idem") - - @DynamicPropertySource - @JvmStatic - fun props(registry: DynamicPropertyRegistry) { - registry.add("spring.datasource.url", postgres::getJdbcUrl) - registry.add("spring.datasource.username", postgres::getUsername) - registry.add("spring.datasource.password", postgres::getPassword) - } + private val postgres get() = PostgresTestContainers.postgres fun ensureRestrictedRole() { // Runs after Flyway — called lazily from tests that need the restricted role diff --git a/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/chain/ChainCheckpointRepositoryAdapterTest.kt b/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/chain/ChainCheckpointRepositoryAdapterTest.kt index bfb4ceb..ff87035 100644 --- a/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/chain/ChainCheckpointRepositoryAdapterTest.kt +++ b/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/chain/ChainCheckpointRepositoryAdapterTest.kt @@ -1,41 +1,19 @@ package finance.idem.infrastructure.persistence.chain +import finance.idem.infrastructure.SharedPostgresTestBase import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest import org.springframework.context.annotation.Import -import org.springframework.test.context.DynamicPropertyRegistry -import org.springframework.test.context.DynamicPropertySource -import org.testcontainers.containers.PostgreSQLContainer -import org.testcontainers.junit.jupiter.Container -import org.testcontainers.junit.jupiter.Testcontainers import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertNull @DataJpaTest @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) -@Testcontainers @Import(ChainCheckpointRepositoryAdapter::class) -class ChainCheckpointRepositoryAdapterTest { - companion object { - @Container - val postgres = - PostgreSQLContainer("postgres:16") - .withDatabaseName("idem_test") - .withUsername("idem") - .withPassword("idem") - - @DynamicPropertySource - @JvmStatic - fun props(registry: DynamicPropertyRegistry) { - registry.add("spring.datasource.url", postgres::getJdbcUrl) - registry.add("spring.datasource.username", postgres::getUsername) - registry.add("spring.datasource.password", postgres::getPassword) - } - } - +class ChainCheckpointRepositoryAdapterTest : SharedPostgresTestBase() { @Autowired lateinit var adapter: ChainCheckpointRepositoryAdapter @Test diff --git a/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/chain/FailedChainTransferRepositoryAdapterTest.kt b/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/chain/FailedChainTransferRepositoryAdapterTest.kt index 23fc9ab..829ca52 100644 --- a/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/chain/FailedChainTransferRepositoryAdapterTest.kt +++ b/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/chain/FailedChainTransferRepositoryAdapterTest.kt @@ -4,16 +4,12 @@ import finance.idem.core.MonetaryAmount import finance.idem.core.StablecoinToken import finance.idem.core.TenantId import finance.idem.core.chain.FailedChainTransfer +import finance.idem.infrastructure.SharedPostgresTestBase import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest import org.springframework.context.annotation.Import -import org.springframework.test.context.DynamicPropertyRegistry -import org.springframework.test.context.DynamicPropertySource -import org.testcontainers.containers.PostgreSQLContainer -import org.testcontainers.junit.jupiter.Container -import org.testcontainers.junit.jupiter.Testcontainers import java.time.Instant import java.util.UUID import kotlin.test.assertEquals @@ -22,26 +18,8 @@ import kotlin.test.assertTrue @DataJpaTest @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) -@Testcontainers @Import(FailedChainTransferRepositoryAdapter::class) -class FailedChainTransferRepositoryAdapterTest { - companion object { - @Container - val postgres = - PostgreSQLContainer("postgres:16") - .withDatabaseName("idem_test") - .withUsername("idem") - .withPassword("idem") - - @DynamicPropertySource - @JvmStatic - fun props(registry: DynamicPropertyRegistry) { - registry.add("spring.datasource.url", postgres::getJdbcUrl) - registry.add("spring.datasource.username", postgres::getUsername) - registry.add("spring.datasource.password", postgres::getPassword) - } - } - +class FailedChainTransferRepositoryAdapterTest : SharedPostgresTestBase() { @Autowired lateinit var adapter: FailedChainTransferRepositoryAdapter @Autowired lateinit var jpaRepository: FailedChainTransferJpaRepository diff --git a/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/chain/WatchedAddressRepositoryAdapterTest.kt b/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/chain/WatchedAddressRepositoryAdapterTest.kt index 3fbc048..815771c 100644 --- a/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/chain/WatchedAddressRepositoryAdapterTest.kt +++ b/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/chain/WatchedAddressRepositoryAdapterTest.kt @@ -1,6 +1,7 @@ package finance.idem.infrastructure.persistence.chain import finance.idem.core.StablecoinToken +import finance.idem.infrastructure.SharedPostgresTestBase import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.BeforeEach @@ -10,36 +11,13 @@ import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabas import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest import org.springframework.context.annotation.Import import org.springframework.jdbc.core.JdbcTemplate -import org.springframework.test.context.DynamicPropertyRegistry -import org.springframework.test.context.DynamicPropertySource -import org.testcontainers.containers.PostgreSQLContainer -import org.testcontainers.junit.jupiter.Container -import org.testcontainers.junit.jupiter.Testcontainers import java.time.Instant import java.util.UUID @DataJpaTest @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) -@Testcontainers @Import(WatchedAddressRepositoryAdapter::class) -class WatchedAddressRepositoryAdapterTest { - companion object { - @Container - val postgres = - PostgreSQLContainer("postgres:16") - .withDatabaseName("idem_test") - .withUsername("idem") - .withPassword("idem") - - @DynamicPropertySource - @JvmStatic - fun props(registry: DynamicPropertyRegistry) { - registry.add("spring.datasource.url", postgres::getJdbcUrl) - registry.add("spring.datasource.username", postgres::getUsername) - registry.add("spring.datasource.password", postgres::getPassword) - } - } - +class WatchedAddressRepositoryAdapterTest : SharedPostgresTestBase() { @Autowired lateinit var jpaRepository: WatchedAddressJpaRepository @Autowired lateinit var adapter: WatchedAddressRepositoryAdapter diff --git a/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/idempotency/PostgresIdempotencyStoreTest.kt b/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/idempotency/PostgresIdempotencyStoreTest.kt index b3fc85a..759bbf2 100644 --- a/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/idempotency/PostgresIdempotencyStoreTest.kt +++ b/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/idempotency/PostgresIdempotencyStoreTest.kt @@ -2,17 +2,13 @@ package finance.idem.infrastructure.persistence.idempotency import finance.idem.core.TenantId import finance.idem.core.TransactionId +import finance.idem.infrastructure.SharedPostgresTestBase import jakarta.persistence.EntityManager import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest import org.springframework.context.annotation.Import -import org.springframework.test.context.DynamicPropertyRegistry -import org.springframework.test.context.DynamicPropertySource -import org.testcontainers.containers.PostgreSQLContainer -import org.testcontainers.junit.jupiter.Container -import org.testcontainers.junit.jupiter.Testcontainers import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertNotNull @@ -21,26 +17,8 @@ import kotlin.test.assertTrue @DataJpaTest @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) -@Testcontainers @Import(PostgresIdempotencyStore::class) -class PostgresIdempotencyStoreTest { - companion object { - @Container - val postgres = - PostgreSQLContainer("postgres:16") - .withDatabaseName("idem_test") - .withUsername("idem") - .withPassword("idem") - - @DynamicPropertySource - @JvmStatic - fun props(registry: DynamicPropertyRegistry) { - registry.add("spring.datasource.url", postgres::getJdbcUrl) - registry.add("spring.datasource.username", postgres::getUsername) - registry.add("spring.datasource.password", postgres::getPassword) - } - } - +class PostgresIdempotencyStoreTest : SharedPostgresTestBase() { @Autowired lateinit var store: PostgresIdempotencyStore diff --git a/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/idempotency/PostgresSettlementIdempotencyStoreTest.kt b/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/idempotency/PostgresSettlementIdempotencyStoreTest.kt index 74dfea1..f71e837 100644 --- a/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/idempotency/PostgresSettlementIdempotencyStoreTest.kt +++ b/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/idempotency/PostgresSettlementIdempotencyStoreTest.kt @@ -1,17 +1,13 @@ package finance.idem.infrastructure.persistence.idempotency import finance.idem.core.TenantId +import finance.idem.infrastructure.SharedPostgresTestBase import jakarta.persistence.EntityManager import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest import org.springframework.context.annotation.Import -import org.springframework.test.context.DynamicPropertyRegistry -import org.springframework.test.context.DynamicPropertySource -import org.testcontainers.containers.PostgreSQLContainer -import org.testcontainers.junit.jupiter.Container -import org.testcontainers.junit.jupiter.Testcontainers import java.util.UUID import kotlin.test.assertEquals import kotlin.test.assertFalse @@ -21,26 +17,8 @@ import kotlin.test.assertTrue @DataJpaTest @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) -@Testcontainers @Import(PostgresSettlementIdempotencyStore::class) -class PostgresSettlementIdempotencyStoreTest { - companion object { - @Container - val postgres = - PostgreSQLContainer("postgres:16") - .withDatabaseName("idem_test") - .withUsername("idem") - .withPassword("idem") - - @DynamicPropertySource - @JvmStatic - fun props(registry: DynamicPropertyRegistry) { - registry.add("spring.datasource.url", postgres::getJdbcUrl) - registry.add("spring.datasource.username", postgres::getUsername) - registry.add("spring.datasource.password", postgres::getPassword) - } - } - +class PostgresSettlementIdempotencyStoreTest : SharedPostgresTestBase() { @Autowired lateinit var store: PostgresSettlementIdempotencyStore diff --git a/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/outbox/WebhookOutboxRepositoryAdapterTest.kt b/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/outbox/WebhookOutboxRepositoryAdapterTest.kt index ae9c74f..d1cafc1 100644 --- a/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/outbox/WebhookOutboxRepositoryAdapterTest.kt +++ b/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/outbox/WebhookOutboxRepositoryAdapterTest.kt @@ -4,6 +4,7 @@ import finance.idem.application.outbox.OutboxStatus import finance.idem.application.outbox.WebhookOutboxEntry import finance.idem.core.TenantId import finance.idem.core.TransactionId +import finance.idem.infrastructure.SharedPostgresTestBase import finance.idem.infrastructure.persistence.PersistenceTestConfig import jakarta.persistence.EntityManager import org.junit.jupiter.api.Test @@ -11,11 +12,6 @@ import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest import org.springframework.context.annotation.Import -import org.springframework.test.context.DynamicPropertyRegistry -import org.springframework.test.context.DynamicPropertySource -import org.testcontainers.containers.PostgreSQLContainer -import org.testcontainers.junit.jupiter.Container -import org.testcontainers.junit.jupiter.Testcontainers import java.time.Instant import java.util.UUID import kotlin.test.assertEquals @@ -25,26 +21,8 @@ import kotlin.test.assertTrue @DataJpaTest @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) -@Testcontainers @Import(WebhookOutboxRepositoryAdapter::class, PersistenceTestConfig::class) -class WebhookOutboxRepositoryAdapterTest { - companion object { - @Container - val postgres = - PostgreSQLContainer("postgres:16") - .withDatabaseName("idem_test") - .withUsername("idem") - .withPassword("idem") - - @DynamicPropertySource - @JvmStatic - fun props(registry: DynamicPropertyRegistry) { - registry.add("spring.datasource.url", postgres::getJdbcUrl) - registry.add("spring.datasource.username", postgres::getUsername) - registry.add("spring.datasource.password", postgres::getPassword) - } - } - +class WebhookOutboxRepositoryAdapterTest : SharedPostgresTestBase() { @Autowired lateinit var adapter: WebhookOutboxRepositoryAdapter diff --git a/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/policy/PolicyRepositoryAdapterTest.kt b/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/policy/PolicyRepositoryAdapterTest.kt index 090bd5e..e17dd3e 100644 --- a/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/policy/PolicyRepositoryAdapterTest.kt +++ b/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/policy/PolicyRepositoryAdapterTest.kt @@ -7,17 +7,13 @@ import finance.idem.core.StablecoinToken import finance.idem.core.TenantId import finance.idem.core.agentic.PolicyRule import finance.idem.core.agentic.PolicyRuleId +import finance.idem.infrastructure.SharedPostgresTestBase import finance.idem.infrastructure.persistence.PersistenceTestConfig import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest import org.springframework.context.annotation.Import -import org.springframework.test.context.DynamicPropertyRegistry -import org.springframework.test.context.DynamicPropertySource -import org.testcontainers.containers.PostgreSQLContainer -import org.testcontainers.junit.jupiter.Container -import org.testcontainers.junit.jupiter.Testcontainers import java.util.UUID import kotlin.test.assertEquals import kotlin.test.assertFalse @@ -26,26 +22,8 @@ import kotlin.test.assertTrue @DataJpaTest @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) -@Testcontainers @Import(PolicyRepositoryAdapter::class, PersistenceTestConfig::class) -class PolicyRepositoryAdapterTest { - companion object { - @Container - val postgres = - PostgreSQLContainer("postgres:16") - .withDatabaseName("idem_test") - .withUsername("idem") - .withPassword("idem") - - @DynamicPropertySource - @JvmStatic - fun props(registry: DynamicPropertyRegistry) { - registry.add("spring.datasource.url", postgres::getJdbcUrl) - registry.add("spring.datasource.username", postgres::getUsername) - registry.add("spring.datasource.password", postgres::getPassword) - } - } - +class PolicyRepositoryAdapterTest : SharedPostgresTestBase() { @Autowired lateinit var adapter: PolicyRepositoryAdapter diff --git a/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/reconciliation/SettlementRepositoryAdapterTest.kt b/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/reconciliation/SettlementRepositoryAdapterTest.kt index f7d398c..0a048af 100644 --- a/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/reconciliation/SettlementRepositoryAdapterTest.kt +++ b/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/reconciliation/SettlementRepositoryAdapterTest.kt @@ -15,9 +15,11 @@ import finance.idem.core.ledger.JournalLine import finance.idem.core.ledger.Settlement import finance.idem.core.ledger.Transaction import finance.idem.core.monetary.OnChainEntry +import finance.idem.infrastructure.SharedPostgresTestBase import finance.idem.infrastructure.persistence.AccountRepositoryAdapter import finance.idem.infrastructure.persistence.PersistenceTestConfig import finance.idem.infrastructure.persistence.TransactionRepositoryAdapter +import finance.idem.infrastructure.service.PostgresTestContainers import jakarta.persistence.EntityManager import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -25,12 +27,7 @@ import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest import org.springframework.context.annotation.Import -import org.springframework.test.context.DynamicPropertyRegistry -import org.springframework.test.context.DynamicPropertySource import org.springframework.test.context.transaction.TestTransaction -import org.testcontainers.containers.PostgreSQLContainer -import org.testcontainers.junit.jupiter.Container -import org.testcontainers.junit.jupiter.Testcontainers import java.math.BigDecimal import java.sql.SQLException import java.time.Instant @@ -42,31 +39,13 @@ import kotlin.test.assertNull @DataJpaTest @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) -@Testcontainers @Import( SettlementRepositoryAdapter::class, AccountRepositoryAdapter::class, TransactionRepositoryAdapter::class, PersistenceTestConfig::class, ) -class SettlementRepositoryAdapterTest { - companion object { - @Container - val postgres = - PostgreSQLContainer("postgres:16") - .withDatabaseName("idem_test") - .withUsername("idem") - .withPassword("idem") - - @DynamicPropertySource - @JvmStatic - fun props(registry: DynamicPropertyRegistry) { - registry.add("spring.datasource.url", postgres::getJdbcUrl) - registry.add("spring.datasource.username", postgres::getUsername) - registry.add("spring.datasource.password", postgres::getPassword) - } - } - +class SettlementRepositoryAdapterTest : SharedPostgresTestBase() { @Autowired lateinit var adapter: SettlementRepositoryAdapter @Autowired lateinit var accountAdapter: AccountRepositoryAdapter @@ -395,7 +374,7 @@ class SettlementRepositoryAdapterTest { // PESSIMISTIC_WRITE held by the still-open transaction above must block a // concurrent FOR UPDATE NOWAIT from a second connection. - postgres.createConnection("").use { conn -> + PostgresTestContainers.postgres.createConnection("").use { conn -> conn.autoCommit = false conn.createStatement().use { it.execute("SET LOCAL app.tenant_id = '${tenantA.value}'") } conn.prepareStatement("SELECT id FROM settlements WHERE id = ? FOR UPDATE NOWAIT").use { stmt -> diff --git a/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/telemetry/InstallationMetadataAdapterTest.kt b/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/telemetry/InstallationMetadataAdapterTest.kt index fe43425..068a5fb 100644 --- a/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/telemetry/InstallationMetadataAdapterTest.kt +++ b/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/telemetry/InstallationMetadataAdapterTest.kt @@ -1,40 +1,18 @@ package finance.idem.infrastructure.persistence.telemetry +import finance.idem.infrastructure.SharedPostgresTestBase import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest import org.springframework.context.annotation.Import -import org.springframework.test.context.DynamicPropertyRegistry -import org.springframework.test.context.DynamicPropertySource -import org.testcontainers.containers.PostgreSQLContainer -import org.testcontainers.junit.jupiter.Container -import org.testcontainers.junit.jupiter.Testcontainers import kotlin.test.assertEquals import kotlin.test.assertNotNull @DataJpaTest @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) -@Testcontainers @Import(InstallationMetadataAdapter::class) -class InstallationMetadataAdapterTest { - companion object { - @Container - val postgres = - PostgreSQLContainer("postgres:16") - .withDatabaseName("idem_test") - .withUsername("idem") - .withPassword("idem") - - @DynamicPropertySource - @JvmStatic - fun props(registry: DynamicPropertyRegistry) { - registry.add("spring.datasource.url", postgres::getJdbcUrl) - registry.add("spring.datasource.username", postgres::getUsername) - registry.add("spring.datasource.password", postgres::getPassword) - } - } - +class InstallationMetadataAdapterTest : SharedPostgresTestBase() { @Autowired lateinit var adapter: InstallationMetadataAdapter diff --git a/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/tenant/TenantRepositoryAdapterTest.kt b/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/tenant/TenantRepositoryAdapterTest.kt index 7c10a44..cc81960 100644 --- a/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/tenant/TenantRepositoryAdapterTest.kt +++ b/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/tenant/TenantRepositoryAdapterTest.kt @@ -2,6 +2,7 @@ package finance.idem.infrastructure.persistence.tenant import finance.idem.application.tenant.TenantWebhookConfig import finance.idem.core.TenantId +import finance.idem.infrastructure.SharedPostgresTestBase import jakarta.persistence.EntityManager import org.hibernate.Session import org.junit.jupiter.api.Test @@ -9,36 +10,13 @@ import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest import org.springframework.context.annotation.Import -import org.springframework.test.context.DynamicPropertyRegistry -import org.springframework.test.context.DynamicPropertySource -import org.testcontainers.containers.PostgreSQLContainer -import org.testcontainers.junit.jupiter.Container -import org.testcontainers.junit.jupiter.Testcontainers import kotlin.test.assertEquals import kotlin.test.assertNull @DataJpaTest @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) -@Testcontainers @Import(TenantRepositoryAdapter::class) -class TenantRepositoryAdapterTest { - companion object { - @Container - val postgres = - PostgreSQLContainer("postgres:16") - .withDatabaseName("idem_test") - .withUsername("idem") - .withPassword("idem") - - @DynamicPropertySource - @JvmStatic - fun props(registry: DynamicPropertyRegistry) { - registry.add("spring.datasource.url", postgres::getJdbcUrl) - registry.add("spring.datasource.username", postgres::getUsername) - registry.add("spring.datasource.password", postgres::getPassword) - } - } - +class TenantRepositoryAdapterTest : SharedPostgresTestBase() { @Autowired lateinit var adapter: TenantRepositoryAdapter diff --git a/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/workflow/WorkflowPlanRepositoryAdapterTest.kt b/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/workflow/WorkflowPlanRepositoryAdapterTest.kt index 5302ea5..97b1404 100644 --- a/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/workflow/WorkflowPlanRepositoryAdapterTest.kt +++ b/infrastructure/src/test/kotlin/finance/idem/infrastructure/persistence/workflow/WorkflowPlanRepositoryAdapterTest.kt @@ -7,6 +7,7 @@ import finance.idem.core.agentic.AgentContext import finance.idem.core.agentic.StepStatus import finance.idem.core.agentic.WorkflowPlan import finance.idem.core.agentic.WorkflowStatus +import finance.idem.infrastructure.SharedPostgresTestBase import finance.idem.infrastructure.persistence.PersistenceTestConfig import jakarta.persistence.EntityManager import org.junit.jupiter.api.Test @@ -14,11 +15,6 @@ import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest import org.springframework.context.annotation.Import -import org.springframework.test.context.DynamicPropertyRegistry -import org.springframework.test.context.DynamicPropertySource -import org.testcontainers.containers.PostgreSQLContainer -import org.testcontainers.junit.jupiter.Container -import org.testcontainers.junit.jupiter.Testcontainers import java.time.Instant import kotlin.test.assertEquals import kotlin.test.assertNotNull @@ -26,26 +22,8 @@ import kotlin.test.assertNull @DataJpaTest @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) -@Testcontainers @Import(WorkflowPlanRepositoryAdapter::class, PersistenceTestConfig::class) -class WorkflowPlanRepositoryAdapterTest { - companion object { - @Container - val postgres = - PostgreSQLContainer("postgres:16") - .withDatabaseName("idem_test") - .withUsername("idem") - .withPassword("idem") - - @DynamicPropertySource - @JvmStatic - fun props(registry: DynamicPropertyRegistry) { - registry.add("spring.datasource.url", postgres::getJdbcUrl) - registry.add("spring.datasource.username", postgres::getUsername) - registry.add("spring.datasource.password", postgres::getPassword) - } - } - +class WorkflowPlanRepositoryAdapterTest : SharedPostgresTestBase() { @Autowired lateinit var adapter: WorkflowPlanRepositoryAdapter diff --git a/infrastructure/src/test/kotlin/finance/idem/infrastructure/security/ApiKeyServiceIntegrationTest.kt b/infrastructure/src/test/kotlin/finance/idem/infrastructure/security/ApiKeyServiceIntegrationTest.kt index 4c7a9d9..8e6add7 100644 --- a/infrastructure/src/test/kotlin/finance/idem/infrastructure/security/ApiKeyServiceIntegrationTest.kt +++ b/infrastructure/src/test/kotlin/finance/idem/infrastructure/security/ApiKeyServiceIntegrationTest.kt @@ -2,13 +2,13 @@ package finance.idem.infrastructure.security import finance.idem.core.TenantId import finance.idem.core.security.ApiScope +import finance.idem.infrastructure.SharedPostgresTestBase import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest import org.springframework.test.context.DynamicPropertyRegistry import org.springframework.test.context.DynamicPropertySource import org.testcontainers.containers.GenericContainer -import org.testcontainers.containers.PostgreSQLContainer import org.testcontainers.junit.jupiter.Container import org.testcontainers.junit.jupiter.Testcontainers import kotlin.test.assertFalse @@ -18,15 +18,9 @@ import kotlin.test.assertTrue @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE) @Testcontainers -class ApiKeyServiceIntegrationTest { +class ApiKeyServiceIntegrationTest : SharedPostgresTestBase() { companion object { - @Container - val postgres: PostgreSQLContainer<*> = - PostgreSQLContainer("postgres:16") - .withDatabaseName("idem_test") - .withUsername("idem") - .withPassword("idem") - + // Redis has no module-wide singleton — this is the only infra test that needs it. @Container val redis: GenericContainer<*> = GenericContainer("redis:7") @@ -35,9 +29,6 @@ class ApiKeyServiceIntegrationTest { @DynamicPropertySource @JvmStatic fun props(registry: DynamicPropertyRegistry) { - registry.add("spring.datasource.url", postgres::getJdbcUrl) - registry.add("spring.datasource.username", postgres::getUsername) - registry.add("spring.datasource.password", postgres::getPassword) registry.add("spring.data.redis.host", redis::getHost) registry.add("spring.data.redis.port") { redis.getMappedPort(6379) } } diff --git a/infrastructure/src/test/kotlin/finance/idem/infrastructure/service/PostgresServiceIntegrationTestBase.kt b/infrastructure/src/test/kotlin/finance/idem/infrastructure/service/PostgresServiceIntegrationTestBase.kt index 7d92209..22a7150 100644 --- a/infrastructure/src/test/kotlin/finance/idem/infrastructure/service/PostgresServiceIntegrationTestBase.kt +++ b/infrastructure/src/test/kotlin/finance/idem/infrastructure/service/PostgresServiceIntegrationTestBase.kt @@ -1,24 +1,13 @@ package finance.idem.infrastructure.service +import finance.idem.infrastructure.SharedPostgresTestBase import jakarta.persistence.EntityManager import org.springframework.beans.factory.annotation.Autowired -import org.springframework.test.context.DynamicPropertyRegistry -import org.springframework.test.context.DynamicPropertySource -abstract class PostgresServiceIntegrationTestBase { +abstract class PostgresServiceIntegrationTestBase : SharedPostgresTestBase() { @Autowired protected lateinit var entityManager: EntityManager - companion object { - @DynamicPropertySource - @JvmStatic - fun props(registry: DynamicPropertyRegistry) { - registry.add("spring.datasource.url", PostgresTestContainers.postgres::getJdbcUrl) - registry.add("spring.datasource.username", PostgresTestContainers.postgres::getUsername) - registry.add("spring.datasource.password", PostgresTestContainers.postgres::getPassword) - } - } - protected fun outboxCount(eventType: String): Long = ( entityManager diff --git a/infrastructure/src/test/kotlin/finance/idem/infrastructure/service/PostgresTestContainers.kt b/infrastructure/src/test/kotlin/finance/idem/infrastructure/service/PostgresTestContainers.kt index 80b3621..3f8ef3e 100644 --- a/infrastructure/src/test/kotlin/finance/idem/infrastructure/service/PostgresTestContainers.kt +++ b/infrastructure/src/test/kotlin/finance/idem/infrastructure/service/PostgresTestContainers.kt @@ -8,5 +8,8 @@ internal object PostgresTestContainers { .withDatabaseName("idem_test") .withUsername("idem") .withPassword("idem") + // Headroom for the Hikari pools of every cached Spring context in the + // module (each holds its own pool against this one shared instance). + .withCommand("postgres", "-c", "max_connections=300") .also { it.start() } } diff --git a/infrastructure/src/test/kotlin/finance/idem/infrastructure/telemetry/TenantThresholdWarnServiceIntegrationTest.kt b/infrastructure/src/test/kotlin/finance/idem/infrastructure/telemetry/TenantThresholdWarnServiceIntegrationTest.kt index 7c3a15c..a857b42 100644 --- a/infrastructure/src/test/kotlin/finance/idem/infrastructure/telemetry/TenantThresholdWarnServiceIntegrationTest.kt +++ b/infrastructure/src/test/kotlin/finance/idem/infrastructure/telemetry/TenantThresholdWarnServiceIntegrationTest.kt @@ -1,36 +1,14 @@ package finance.idem.infrastructure.telemetry +import finance.idem.infrastructure.SharedPostgresTestBase import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest import org.springframework.context.ApplicationContext -import org.springframework.test.context.DynamicPropertyRegistry -import org.springframework.test.context.DynamicPropertySource -import org.testcontainers.containers.PostgreSQLContainer -import org.testcontainers.junit.jupiter.Container -import org.testcontainers.junit.jupiter.Testcontainers import kotlin.test.assertNotNull @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE) -@Testcontainers -class TenantThresholdWarnServiceIntegrationTest { - companion object { - @Container - val postgres: PostgreSQLContainer<*> = - PostgreSQLContainer("postgres:16") - .withDatabaseName("idem_test") - .withUsername("idem") - .withPassword("idem") - - @DynamicPropertySource - @JvmStatic - fun props(registry: DynamicPropertyRegistry) { - registry.add("spring.datasource.url", postgres::getJdbcUrl) - registry.add("spring.datasource.username", postgres::getUsername) - registry.add("spring.datasource.password", postgres::getPassword) - } - } - +class TenantThresholdWarnServiceIntegrationTest : SharedPostgresTestBase() { @Autowired lateinit var tenantThresholdWarnService: TenantThresholdWarnService diff --git a/sdk-kotlin/src/test/resources/junit-platform.properties b/sdk-kotlin/src/test/resources/junit-platform.properties new file mode 100644 index 0000000..116a69e --- /dev/null +++ b/sdk-kotlin/src/test/resources/junit-platform.properties @@ -0,0 +1,4 @@ +# Class-level parallel test execution — safe here: Ktor MockEngine only, no shared state. +junit.jupiter.execution.parallel.enabled=true +junit.jupiter.execution.parallel.mode.default=same_thread +junit.jupiter.execution.parallel.mode.classes.default=concurrent