From 4213266b2814907e8e1f11ecd14f9aab9d405746 Mon Sep 17 00:00:00 2001 From: "Kartikaya Gupta (kats)" Date: Mon, 16 Mar 2026 08:58:12 -0400 Subject: [PATCH] fix: use localhost instead of host.docker.internal for DynamoDB Local test client DefaultTestDynamoDbClient used hostName() to auto-detect whether to connect via host.docker.internal or localhost. In environments with Docker installed (including CI), host.docker.internal resolves to the host machine. Since DynamoDB Local binds to 0.0.0.0, the TCP connection check in hostName() always succeeds for host.docker.internal, but DynamoDB Local's Jetty 12 returns HTTP 404 for requests with that Host header, failing the test. The fix adds a hostName(port) method to TestDynamoDbServer.Factory, so the Factory implementations can more deterministically select the hostname. DockerDynamoDbServer.Factory overrides it with the host.docker.internal auto-detection (needed when the test process itself runs inside Docker). TestDynamoDbService.create() now passes the factory-determined hostname to DefaultTestDynamoDbClient. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../app/cash/tempest/testing/DockerDynamoDbServer.kt | 1 + .../testing/internal/DefaultTestDynamoDbClient.kt | 8 +++----- .../tempest/testing/internal/TestDynamoDbService.kt | 2 +- .../app/cash/tempest/testing/JvmDynamoDbServer.kt | 1 + .../app/cash/tempest/testing/TestDynamoDbServer.kt | 2 ++ .../cash/tempest2/testing/DockerDynamoDbServer.kt | 2 ++ .../testing/internal/DefaultTestDynamoDbClient.kt | 12 +++++------- .../tempest2/testing/internal/TestDynamoDbService.kt | 2 +- .../app/cash/tempest2/testing/JvmDynamoDbServer.kt | 1 + .../app/cash/tempest2/testing/TestDynamoDbServer.kt | 2 ++ 10 files changed, 19 insertions(+), 14 deletions(-) diff --git a/tempest-testing-docker/src/main/kotlin/app/cash/tempest/testing/DockerDynamoDbServer.kt b/tempest-testing-docker/src/main/kotlin/app/cash/tempest/testing/DockerDynamoDbServer.kt index 86291e674..4b95d8eab 100644 --- a/tempest-testing-docker/src/main/kotlin/app/cash/tempest/testing/DockerDynamoDbServer.kt +++ b/tempest-testing-docker/src/main/kotlin/app/cash/tempest/testing/DockerDynamoDbServer.kt @@ -70,6 +70,7 @@ class DockerDynamoDbServer private constructor( ) object Factory : TestDynamoDbServer.Factory { + override fun hostName(port: Int): String = app.cash.tempest.testing.internal.hostName(port) override fun create(port: Int, onBeforeStartup: () -> Unit) = DockerDynamoDbServer(port, onBeforeStartup) } } diff --git a/tempest-testing-internal/src/main/kotlin/app/cash/tempest/testing/internal/DefaultTestDynamoDbClient.kt b/tempest-testing-internal/src/main/kotlin/app/cash/tempest/testing/internal/DefaultTestDynamoDbClient.kt index 3fdc09390..f3c6ac742 100644 --- a/tempest-testing-internal/src/main/kotlin/app/cash/tempest/testing/internal/DefaultTestDynamoDbClient.kt +++ b/tempest-testing-internal/src/main/kotlin/app/cash/tempest/testing/internal/DefaultTestDynamoDbClient.kt @@ -22,14 +22,12 @@ import com.google.common.util.concurrent.AbstractIdleService class DefaultTestDynamoDbClient( override val tables: List, + private val hostName: String, private val port: Int, ) : AbstractIdleService(), TestDynamoDbClient { - // Lazy so that hostName is resolved after the DynamoDB Local server is started, - // not at construction time when the placeholder ServerSocket is still holding the port. - private val hostName by lazy { hostName(port) } - override val dynamoDb by lazy { buildDynamoDb(hostName, port) } - override val dynamoDbStreams by lazy { buildDynamoDbStreams(hostName, port) } + override val dynamoDb = buildDynamoDb(hostName, port) + override val dynamoDbStreams = buildDynamoDbStreams(hostName, port) override fun startUp() { reset() diff --git a/tempest-testing-internal/src/main/kotlin/app/cash/tempest/testing/internal/TestDynamoDbService.kt b/tempest-testing-internal/src/main/kotlin/app/cash/tempest/testing/internal/TestDynamoDbService.kt index bad67045a..a07c0de64 100644 --- a/tempest-testing-internal/src/main/kotlin/app/cash/tempest/testing/internal/TestDynamoDbService.kt +++ b/tempest-testing-internal/src/main/kotlin/app/cash/tempest/testing/internal/TestDynamoDbService.kt @@ -75,7 +75,7 @@ class TestDynamoDbService private constructor( ): TestDynamoDbService { val portHolder = port?.let { PortHolder(it) } ?: defaultPortHolder(serverFactory.toString()) return TestDynamoDbService( - DefaultTestDynamoDbClient(tables, portHolder.value), + DefaultTestDynamoDbClient(tables, serverFactory.hostName(portHolder.value), portHolder.value), serverFactory.create(portHolder.value, portHolder.releasePort) ) } diff --git a/tempest-testing-jvm/src/main/kotlin/app/cash/tempest/testing/JvmDynamoDbServer.kt b/tempest-testing-jvm/src/main/kotlin/app/cash/tempest/testing/JvmDynamoDbServer.kt index e396dd77b..6f5064836 100644 --- a/tempest-testing-jvm/src/main/kotlin/app/cash/tempest/testing/JvmDynamoDbServer.kt +++ b/tempest-testing-jvm/src/main/kotlin/app/cash/tempest/testing/JvmDynamoDbServer.kt @@ -73,6 +73,7 @@ class JvmDynamoDbServer private constructor( } object Factory : TestDynamoDbServer.Factory { + override fun hostName(port: Int) = "localhost" override fun create(port: Int, onBeforeStartup: () -> Unit) = JvmDynamoDbServer(port, onBeforeStartup) } } diff --git a/tempest-testing/src/main/kotlin/app/cash/tempest/testing/TestDynamoDbServer.kt b/tempest-testing/src/main/kotlin/app/cash/tempest/testing/TestDynamoDbServer.kt index b9bd828c4..443b0efcf 100644 --- a/tempest-testing/src/main/kotlin/app/cash/tempest/testing/TestDynamoDbServer.kt +++ b/tempest-testing/src/main/kotlin/app/cash/tempest/testing/TestDynamoDbServer.kt @@ -26,6 +26,8 @@ interface TestDynamoDbServer : Service { val port: Int interface Factory { + /** The hostname the client should use to connect to the server. */ + fun hostName(port: Int): String fun create(port: Int): T = create(port) {} fun create(port: Int, onBeforeStartup: () -> Unit): T } diff --git a/tempest2-testing-docker/src/main/kotlin/app/cash/tempest2/testing/DockerDynamoDbServer.kt b/tempest2-testing-docker/src/main/kotlin/app/cash/tempest2/testing/DockerDynamoDbServer.kt index 2ba8d17f7..c5f342fb9 100644 --- a/tempest2-testing-docker/src/main/kotlin/app/cash/tempest2/testing/DockerDynamoDbServer.kt +++ b/tempest2-testing-docker/src/main/kotlin/app/cash/tempest2/testing/DockerDynamoDbServer.kt @@ -17,6 +17,7 @@ package app.cash.tempest2.testing import app.cash.tempest2.testing.internal.buildDynamoDb +import app.cash.tempest2.testing.internal.hostName import com.github.dockerjava.api.model.ExposedPort import com.github.dockerjava.api.model.Ports import com.google.common.util.concurrent.AbstractIdleService @@ -69,6 +70,7 @@ class DockerDynamoDbServer private constructor( ) object Factory : TestDynamoDbServer.Factory { + override fun hostName(port: Int): String = app.cash.tempest2.testing.internal.hostName(port) override fun create(port: Int, onBeforeStartup: () -> Unit) = DockerDynamoDbServer(port, onBeforeStartup) } } diff --git a/tempest2-testing-internal/src/main/kotlin/app/cash/tempest2/testing/internal/DefaultTestDynamoDbClient.kt b/tempest2-testing-internal/src/main/kotlin/app/cash/tempest2/testing/internal/DefaultTestDynamoDbClient.kt index cc187aaab..b2fbe457f 100644 --- a/tempest2-testing-internal/src/main/kotlin/app/cash/tempest2/testing/internal/DefaultTestDynamoDbClient.kt +++ b/tempest2-testing-internal/src/main/kotlin/app/cash/tempest2/testing/internal/DefaultTestDynamoDbClient.kt @@ -23,16 +23,14 @@ import software.amazon.awssdk.services.dynamodb.model.DeleteTableRequest class DefaultTestDynamoDbClient( override val tables: List, + private val hostName: String, private val port: Int, ) : AbstractIdleService(), TestDynamoDbClient { - // Lazy so that hostName is resolved after the DynamoDB Local server is started, - // not at construction time when the placeholder ServerSocket is still holding the port. - private val hostName by lazy { hostName(port) } - override val dynamoDb by lazy { buildDynamoDb(hostName, port) } - override val asyncDynamoDb by lazy { buildAsyncDynamoDb(hostName, port) } - override val dynamoDbStreams by lazy { buildDynamoDbStreams(hostName, port) } - override val asyncDynamoDbStreams by lazy { buildAsyncDynamoDbStreams(hostName, port) } + override val dynamoDb = buildDynamoDb(hostName, port) + override val asyncDynamoDb = buildAsyncDynamoDb(hostName, port) + override val dynamoDbStreams = buildDynamoDbStreams(hostName, port) + override val asyncDynamoDbStreams = buildAsyncDynamoDbStreams(hostName, port) override fun startUp() { reset() diff --git a/tempest2-testing-internal/src/main/kotlin/app/cash/tempest2/testing/internal/TestDynamoDbService.kt b/tempest2-testing-internal/src/main/kotlin/app/cash/tempest2/testing/internal/TestDynamoDbService.kt index 55223c464..db11902ea 100644 --- a/tempest2-testing-internal/src/main/kotlin/app/cash/tempest2/testing/internal/TestDynamoDbService.kt +++ b/tempest2-testing-internal/src/main/kotlin/app/cash/tempest2/testing/internal/TestDynamoDbService.kt @@ -75,7 +75,7 @@ class TestDynamoDbService( ): TestDynamoDbService { val portHolder = port?.let { PortHolder(it) } ?: defaultPortHolder(serverFactory.toString()) return TestDynamoDbService( - DefaultTestDynamoDbClient(tables, portHolder.value), + DefaultTestDynamoDbClient(tables, serverFactory.hostName(portHolder.value), portHolder.value), serverFactory.create(portHolder.value, portHolder.releasePort) ) } diff --git a/tempest2-testing-jvm/src/main/kotlin/app/cash/tempest2/testing/JvmDynamoDbServer.kt b/tempest2-testing-jvm/src/main/kotlin/app/cash/tempest2/testing/JvmDynamoDbServer.kt index 7b649f1c9..a22564271 100644 --- a/tempest2-testing-jvm/src/main/kotlin/app/cash/tempest2/testing/JvmDynamoDbServer.kt +++ b/tempest2-testing-jvm/src/main/kotlin/app/cash/tempest2/testing/JvmDynamoDbServer.kt @@ -73,6 +73,7 @@ class JvmDynamoDbServer private constructor( } object Factory : TestDynamoDbServer.Factory { + override fun hostName(port: Int) = "localhost" override fun create(port: Int, onBeforeStartup: () -> Unit) = JvmDynamoDbServer(port, onBeforeStartup) } } diff --git a/tempest2-testing/src/main/kotlin/app/cash/tempest2/testing/TestDynamoDbServer.kt b/tempest2-testing/src/main/kotlin/app/cash/tempest2/testing/TestDynamoDbServer.kt index bcf261c3f..8d4d4d23b 100644 --- a/tempest2-testing/src/main/kotlin/app/cash/tempest2/testing/TestDynamoDbServer.kt +++ b/tempest2-testing/src/main/kotlin/app/cash/tempest2/testing/TestDynamoDbServer.kt @@ -26,6 +26,8 @@ interface TestDynamoDbServer : Service { val port: Int interface Factory { + /** The hostname the client should use to connect to the server. */ + fun hostName(port: Int): String fun create(port: Int): T = create(port) {} fun create(port: Int, onBeforeStartup: () -> Unit): T }