fix(events): suppress stream poll loop in test profile
Build & Test (NowChessSystems) TeamCity build failed

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 <noreply@anthropic.com>
This commit is contained in:
Janis Eccarius
2026-06-06 19:15:08 +02:00
parent e7a49a2f3e
commit 5f35318ebd
3 changed files with 28 additions and 22 deletions
@@ -8,10 +8,11 @@ import de.nowchess.api.player.PlayerType
import de.nowchess.api.event.{EventEnvelope, EventType} import de.nowchess.api.event.{EventEnvelope, EventType}
import io.quarkus.redis.datasource.RedisDataSource import io.quarkus.redis.datasource.RedisDataSource
import io.quarkus.redis.datasource.stream.{StreamMessage, XAddArgs, XGroupCreateArgs, XReadGroupArgs} import io.quarkus.redis.datasource.stream.{StreamMessage, XAddArgs, XGroupCreateArgs, XReadGroupArgs}
import io.quarkus.runtime.Startup import io.quarkus.runtime.StartupEvent
import jakarta.annotation.PostConstruct
import jakarta.enterprise.context.ApplicationScoped import jakarta.enterprise.context.ApplicationScoped
import jakarta.enterprise.event.Observes
import jakarta.inject.Inject import jakarta.inject.Inject
import org.eclipse.microprofile.config.inject.ConfigProperty
import org.eclipse.microprofile.context.ManagedExecutor import org.eclipse.microprofile.context.ManagedExecutor
import org.jboss.logging.Logger import org.jboss.logging.Logger
import scala.compiletime.uninitialized import scala.compiletime.uninitialized
@@ -21,7 +22,6 @@ import java.time.Duration
import java.util.UUID import java.util.UUID
import java.util.concurrent.{CompletableFuture, ConcurrentHashMap, TimeUnit} import java.util.concurrent.{CompletableFuture, ConcurrentHashMap, TimeUnit}
@Startup
@ApplicationScoped @ApplicationScoped
class GameCreationStreamClient: class GameCreationStreamClient:
@@ -30,6 +30,8 @@ class GameCreationStreamClient:
@Inject var redisConfig: RedisConfig = uninitialized @Inject var redisConfig: RedisConfig = uninitialized
@Inject var objectMapper: ObjectMapper = uninitialized @Inject var objectMapper: ObjectMapper = uninitialized
@Inject var executor: ManagedExecutor = uninitialized @Inject var executor: ManagedExecutor = uninitialized
@ConfigProperty(name = "nowchess.game-creation-stream.enabled", defaultValue = "true")
private var streamEnabled: Boolean = true
// scalafix:on DisableSyntax.var // scalafix:on DisableSyntax.var
private val log = Logger.getLogger(classOf[GameCreationStreamClient]) private val log = Logger.getLogger(classOf[GameCreationStreamClient])
@@ -44,14 +46,14 @@ class GameCreationStreamClient:
private def requestStream: String = s"${redisConfig.prefix}:game-creation" private def requestStream: String = s"${redisConfig.prefix}:game-creation"
private def responseStream: String = s"${redisConfig.prefix}:game-creation-response" private def responseStream: String = s"${redisConfig.prefix}:game-creation-response"
@PostConstruct def start(@Observes _ev: StartupEvent): Unit =
def start(): Unit = if streamEnabled then
createGroupIfAbsent() createGroupIfAbsent()
executor.submit( executor.submit(
new Runnable: new Runnable:
def run(): Unit = pollLoop(), def run(): Unit = pollLoop(),
) )
log.infof("Game-creation response listener started (consumer=%s)", consumerId) log.infof("Game-creation response listener started (consumer=%s)", consumerId)
def createGame(req: CoreCreateGameRequest): GameCreationResponseDto = def createGame(req: CoreCreateGameRequest): GameCreationResponseDto =
val correlationId = UUID.randomUUID().toString val correlationId = UUID.randomUUID().toString
@@ -34,3 +34,5 @@ nowchess:
secret: test-secret secret: test-secret
auth: auth:
enabled: false enabled: false
game-creation-stream:
enabled: false
@@ -7,10 +7,11 @@ import de.nowchess.chess.config.RedisConfig
import de.nowchess.chess.service.GameCreationService import de.nowchess.chess.service.GameCreationService
import io.quarkus.redis.datasource.RedisDataSource import io.quarkus.redis.datasource.RedisDataSource
import io.quarkus.redis.datasource.stream.{StreamMessage, XAddArgs, XGroupCreateArgs, XReadGroupArgs} import io.quarkus.redis.datasource.stream.{StreamMessage, XAddArgs, XGroupCreateArgs, XReadGroupArgs}
import io.quarkus.runtime.Startup import io.quarkus.runtime.StartupEvent
import jakarta.annotation.PostConstruct
import jakarta.enterprise.context.ApplicationScoped import jakarta.enterprise.context.ApplicationScoped
import jakarta.enterprise.event.Observes
import jakarta.inject.Inject import jakarta.inject.Inject
import org.eclipse.microprofile.config.inject.ConfigProperty
import org.eclipse.microprofile.context.ManagedExecutor import org.eclipse.microprofile.context.ManagedExecutor
import org.jboss.logging.Logger import org.jboss.logging.Logger
import scala.compiletime.uninitialized import scala.compiletime.uninitialized
@@ -19,7 +20,6 @@ import scala.util.{Failure, Success, Try}
import java.time.Duration import java.time.Duration
import java.util.UUID import java.util.UUID
@Startup
@ApplicationScoped @ApplicationScoped
class GameCreationStreamListener: class GameCreationStreamListener:
@@ -29,6 +29,8 @@ class GameCreationStreamListener:
@Inject var creationService: GameCreationService = uninitialized @Inject var creationService: GameCreationService = uninitialized
@Inject var executor: ManagedExecutor = uninitialized @Inject var executor: ManagedExecutor = uninitialized
@Inject var redisConfig: RedisConfig = uninitialized @Inject var redisConfig: RedisConfig = uninitialized
@ConfigProperty(name = "nowchess.game-creation-stream.enabled", defaultValue = "true")
private var streamEnabled: Boolean = true
// scalafix:on DisableSyntax.var // scalafix:on DisableSyntax.var
private val log = Logger.getLogger(classOf[GameCreationStreamListener]) 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 responseStream: String = s"${redisConfig.prefix}:game-creation-response"
private def dlqStream: String = s"${redisConfig.prefix}:dlq" private def dlqStream: String = s"${redisConfig.prefix}:dlq"
@PostConstruct def start(@Observes _ev: StartupEvent): Unit =
def start(): Unit = if streamEnabled then
createGroupIfAbsent() createGroupIfAbsent()
executor.submit( executor.submit(
new Runnable: new Runnable:
def run(): Unit = pollLoop(), def run(): Unit = pollLoop(),
) )
log.infof("Game-creation request listener started (consumer=%s)", consumerId) log.infof("Game-creation request listener started (consumer=%s)", consumerId)
private def createGroupIfAbsent(): Unit = private def createGroupIfAbsent(): Unit =
Try( Try(