From 5f35318ebdf38081deea03d1887de1106c4f6750 Mon Sep 17 00:00:00 2001 From: Janis Eccarius Date: Sat, 6 Jun 2026 19:15:08 +0200 Subject: [PATCH] fix(events): suppress stream poll loop in test profile CDI fires StartupEvent to all observer beans regardless of @InjectMock substitution, causing GameCreationStreamClient and GameCreationStreamListener to start their poll loops during @QuarkusTest even when mocked. Without Redis configured in account tests, this floods logs with NPE stack traces from the unconfigured RedisDataSource synthetic bean. - Switch @Startup/@PostConstruct to @Observes StartupEvent on both beans - Guard startup body with nowchess.game-creation-stream.enabled (default true) - Disable the flag in modules/account/src/test/resources/application.yml Mirrors the nowchess.coordinator.enabled pattern in InstanceHeartbeatService. Co-Authored-By: Claude Sonnet 4.6 --- .../client/GameCreationStreamClient.scala | 24 ++++++++++--------- .../src/test/resources/application.yml | 2 ++ .../redis/GameCreationStreamListener.scala | 24 ++++++++++--------- 3 files changed, 28 insertions(+), 22 deletions(-) diff --git a/modules/account/src/main/scala/de/nowchess/account/client/GameCreationStreamClient.scala b/modules/account/src/main/scala/de/nowchess/account/client/GameCreationStreamClient.scala index f67d548..137ee57 100644 --- a/modules/account/src/main/scala/de/nowchess/account/client/GameCreationStreamClient.scala +++ b/modules/account/src/main/scala/de/nowchess/account/client/GameCreationStreamClient.scala @@ -8,10 +8,11 @@ import de.nowchess.api.player.PlayerType import de.nowchess.api.event.{EventEnvelope, EventType} import io.quarkus.redis.datasource.RedisDataSource import io.quarkus.redis.datasource.stream.{StreamMessage, XAddArgs, XGroupCreateArgs, XReadGroupArgs} -import io.quarkus.runtime.Startup -import jakarta.annotation.PostConstruct +import io.quarkus.runtime.StartupEvent import jakarta.enterprise.context.ApplicationScoped +import jakarta.enterprise.event.Observes import jakarta.inject.Inject +import org.eclipse.microprofile.config.inject.ConfigProperty import org.eclipse.microprofile.context.ManagedExecutor import org.jboss.logging.Logger import scala.compiletime.uninitialized @@ -21,7 +22,6 @@ import java.time.Duration import java.util.UUID import java.util.concurrent.{CompletableFuture, ConcurrentHashMap, TimeUnit} -@Startup @ApplicationScoped class GameCreationStreamClient: @@ -30,6 +30,8 @@ class GameCreationStreamClient: @Inject var redisConfig: RedisConfig = uninitialized @Inject var objectMapper: ObjectMapper = uninitialized @Inject var executor: ManagedExecutor = uninitialized + @ConfigProperty(name = "nowchess.game-creation-stream.enabled", defaultValue = "true") + private var streamEnabled: Boolean = true // scalafix:on DisableSyntax.var private val log = Logger.getLogger(classOf[GameCreationStreamClient]) @@ -44,14 +46,14 @@ class GameCreationStreamClient: private def requestStream: String = s"${redisConfig.prefix}:game-creation" private def responseStream: String = s"${redisConfig.prefix}:game-creation-response" - @PostConstruct - def start(): Unit = - createGroupIfAbsent() - executor.submit( - new Runnable: - def run(): Unit = pollLoop(), - ) - log.infof("Game-creation response listener started (consumer=%s)", consumerId) + def start(@Observes _ev: StartupEvent): Unit = + if streamEnabled then + createGroupIfAbsent() + executor.submit( + new Runnable: + def run(): Unit = pollLoop(), + ) + log.infof("Game-creation response listener started (consumer=%s)", consumerId) def createGame(req: CoreCreateGameRequest): GameCreationResponseDto = val correlationId = UUID.randomUUID().toString diff --git a/modules/account/src/test/resources/application.yml b/modules/account/src/test/resources/application.yml index 82fe950..fc197a6 100644 --- a/modules/account/src/test/resources/application.yml +++ b/modules/account/src/test/resources/application.yml @@ -34,3 +34,5 @@ nowchess: secret: test-secret auth: enabled: false + game-creation-stream: + enabled: false diff --git a/modules/core/src/main/scala/de/nowchess/chess/redis/GameCreationStreamListener.scala b/modules/core/src/main/scala/de/nowchess/chess/redis/GameCreationStreamListener.scala index f3a6892..fea7b7b 100644 --- a/modules/core/src/main/scala/de/nowchess/chess/redis/GameCreationStreamListener.scala +++ b/modules/core/src/main/scala/de/nowchess/chess/redis/GameCreationStreamListener.scala @@ -7,10 +7,11 @@ import de.nowchess.chess.config.RedisConfig import de.nowchess.chess.service.GameCreationService import io.quarkus.redis.datasource.RedisDataSource import io.quarkus.redis.datasource.stream.{StreamMessage, XAddArgs, XGroupCreateArgs, XReadGroupArgs} -import io.quarkus.runtime.Startup -import jakarta.annotation.PostConstruct +import io.quarkus.runtime.StartupEvent import jakarta.enterprise.context.ApplicationScoped +import jakarta.enterprise.event.Observes import jakarta.inject.Inject +import org.eclipse.microprofile.config.inject.ConfigProperty import org.eclipse.microprofile.context.ManagedExecutor import org.jboss.logging.Logger import scala.compiletime.uninitialized @@ -19,7 +20,6 @@ import scala.util.{Failure, Success, Try} import java.time.Duration import java.util.UUID -@Startup @ApplicationScoped class GameCreationStreamListener: @@ -29,6 +29,8 @@ class GameCreationStreamListener: @Inject var creationService: GameCreationService = uninitialized @Inject var executor: ManagedExecutor = uninitialized @Inject var redisConfig: RedisConfig = uninitialized + @ConfigProperty(name = "nowchess.game-creation-stream.enabled", defaultValue = "true") + private var streamEnabled: Boolean = true // scalafix:on DisableSyntax.var private val log = Logger.getLogger(classOf[GameCreationStreamListener]) @@ -41,14 +43,14 @@ class GameCreationStreamListener: private def responseStream: String = s"${redisConfig.prefix}:game-creation-response" private def dlqStream: String = s"${redisConfig.prefix}:dlq" - @PostConstruct - def start(): Unit = - createGroupIfAbsent() - executor.submit( - new Runnable: - def run(): Unit = pollLoop(), - ) - log.infof("Game-creation request listener started (consumer=%s)", consumerId) + def start(@Observes _ev: StartupEvent): Unit = + if streamEnabled then + createGroupIfAbsent() + executor.submit( + new Runnable: + def run(): Unit = pollLoop(), + ) + log.infof("Game-creation request listener started (consumer=%s)", consumerId) private def createGroupIfAbsent(): Unit = Try(