Skip to content

Commit d2a9556

Browse files
committed
test: prove app boots when postgres is unreachable
1 parent 0aeeffd commit d2a9556

1 file changed

Lines changed: 81 additions & 0 deletions

File tree

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
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

Comments
 (0)