|
| 1 | +package io.sdkman.broker.adapter.secondary.persistence |
| 2 | + |
| 3 | +import arrow.core.None |
| 4 | +import arrow.core.Option |
| 5 | +import arrow.core.some |
| 6 | +import io.kotest.assertions.withClue |
| 7 | +import io.kotest.core.spec.style.ShouldSpec |
| 8 | +import io.kotest.matchers.shouldBe |
| 9 | +import io.kotest.matchers.shouldNotBe |
| 10 | +import io.sdkman.broker.config.AppConfig |
| 11 | +import io.sdkman.broker.config.PersistenceBackend |
| 12 | +import org.junit.jupiter.api.Tag |
| 13 | + |
| 14 | +/** |
| 15 | + * Proves the deliberate behaviour change of spec Business Rule 4: with |
| 16 | + * `initializationFailTimeout = -1`, `PostgresConnectivity.dataSource()` constructs a |
| 17 | + * usable pool even when Postgres is unreachable, so the application boots and surfaces |
| 18 | + * the outage through `/meta/health` rather than aborting at startup. This automates |
| 19 | + * the spec acceptance criterion "the application starts successfully against an |
| 20 | + * unreachable Postgres" instead of relying on a manual `./gradlew run` smoke. |
| 21 | + * |
| 22 | + * No Testcontainers Postgres is needed — the whole point is that there is no database |
| 23 | + * to reach. The endpoint is an unused localhost port (connection refused) and a small |
| 24 | + * `connectionTimeoutMs` keeps the health check's failure fast and deterministic. |
| 25 | + */ |
| 26 | +@Tag("integration") |
| 27 | +class PostgresConnectivityBootIntegrationSpec : |
| 28 | + ShouldSpec({ |
| 29 | + val connectivity = PostgresConnectivity(unreachablePostgresConfig()) |
| 30 | + val dataSource = connectivity.dataSource() |
| 31 | + |
| 32 | + afterSpec { dataSource.close() } |
| 33 | + |
| 34 | + should("construct a usable pool without throwing when Postgres is unreachable") { |
| 35 | + // given: a PostgresConnectivity pointed at an unused port (built in the spec body, |
| 36 | + // which proves construction itself does not throw) |
| 37 | + // then: the pool is handed back rather than the process aborting at boot |
| 38 | + withClue("initializationFailTimeout = -1 must let the pool build despite a down database") { |
| 39 | + dataSource shouldNotBe null |
| 40 | + } |
| 41 | + } |
| 42 | + |
| 43 | + should("surface the outage as a health-check failure rather than at startup") { |
| 44 | + // given: the booted pool wrapped in the production health repository |
| 45 | + val health = PostgresHealthRepository(dataSource) |
| 46 | + |
| 47 | + // when: connectivity is probed |
| 48 | + val result = health.checkConnectivity() |
| 49 | + |
| 50 | + // then: the unreachable database is reported as a Left, not a boot crash |
| 51 | + withClue("an unreachable Postgres must report unhealthy via /meta/health, not kill the process") { |
| 52 | + result.isLeft() shouldBe true |
| 53 | + } |
| 54 | + } |
| 55 | + }) |
| 56 | + |
| 57 | +private const val UNREACHABLE_PORT = "59999" |
| 58 | + |
| 59 | +private fun unreachablePostgresConfig(): AppConfig = |
| 60 | + object : AppConfig { |
| 61 | + override val mongodbHost: String = "localhost" |
| 62 | + override val mongodbPort: String = "27017" |
| 63 | + override val mongodbDatabase: String = "sdkman" |
| 64 | + override val mongodbUsername: Option<String> = None |
| 65 | + override val mongodbPassword: Option<String> = None |
| 66 | + override val mongodbAuthMechanism: Option<String> = None |
| 67 | + override val postgresHost: String = "127.0.0.1" |
| 68 | + override val postgresPort: String = UNREACHABLE_PORT |
| 69 | + override val postgresDatabase: String = "sdkman" |
| 70 | + override val postgresUsername: Option<String> = "postgres".some() |
| 71 | + override val postgresPassword: Option<String> = "postgres".some() |
| 72 | + override val postgresSslMode: String = "disable" |
| 73 | + override val postgresPoolMaxSize: Int = 2 |
| 74 | + override val postgresPoolMinIdle: Int = 1 |
| 75 | + override val postgresPoolConnectionTimeoutMs: Long = 1_000L |
| 76 | + override val postgresPoolMaxLifetimeMs: Long = 1_800_000L |
| 77 | + override val postgresPoolIdleTimeoutMs: Long = 600_000L |
| 78 | + override val serverPort: Int = 8080 |
| 79 | + override val serverHost: String = "127.0.0.1" |
| 80 | + override val persistenceBackend: PersistenceBackend = PersistenceBackend.Mongo |
| 81 | + } |
0 commit comments