feat(events): migrate game-creation and bot flows to Redis Streams NCS-89 (#62)
Build & Test (NowChessSystems) TeamCity build finished
Build & Test (NowChessSystems) TeamCity build finished
Replace synchronous account→core game-creation HTTP call and plain pub/sub bot game-start events with Redis Streams using consumer groups, XACK, retry, and a Dead Letter Queue for at-least-once delivery and observability. - account: GameCreationStreamClient publishes game-creation requests and correlates responses via a per-instance consumer group (NCS-91) - core: GameCreationStreamListener consumes requests, calls GameCreationService, publishes response events, retries, and routes exhausted/unparseable events to the DLQ (NCS-91, NCS-93, NCS-94) - official-bots: bot game-start events migrated from pub/sub to Streams with consumer group, XACK, retry, and DLQ (NCS-92) - account EventPublisher dual-writes to the stream and legacy pub/sub channel for backward compatibility - all flows use the typed EventEnvelope (eventId/type/payload/timestamp/ correlationId) with DLQ error context (eventType, error, attempt) - register new DTOs and EventEnvelope/EventType for native reflection Closes NCS-91, NCS-92, NCS-93, NCS-94 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Janis Eccarius <eccariusjanis@gmail.com> Reviewed-on: #62
This commit was merged in pull request #62.
This commit is contained in:
@@ -18,6 +18,8 @@ nowchess:
|
||||
enabled: false
|
||||
coordinator:
|
||||
enabled: false
|
||||
game-creation-stream:
|
||||
enabled: false
|
||||
redis:
|
||||
host: localhost
|
||||
port: 6379
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
package de.nowchess.chess.service
|
||||
|
||||
import de.nowchess.api.dto.{GameCreationRequestDto, PlayerInfoDto, TimeControlDto}
|
||||
import de.nowchess.api.game.{GameMode, TimeControl}
|
||||
import de.nowchess.api.player.PlayerType
|
||||
import de.nowchess.chess.client.CombinedExportResponse
|
||||
import de.nowchess.chess.grpc.IoGrpcClientWrapper
|
||||
import de.nowchess.chess.redis.GameRedisSubscriberManager
|
||||
import io.quarkus.test.InjectMock
|
||||
import io.quarkus.test.junit.QuarkusTest
|
||||
import jakarta.inject.Inject
|
||||
import org.junit.jupiter.api.Assertions.*
|
||||
import org.junit.jupiter.api.{BeforeEach, DisplayName, Test}
|
||||
import org.mockito.ArgumentMatchers.any
|
||||
import org.mockito.Mockito.{verify, when}
|
||||
import scala.compiletime.uninitialized
|
||||
|
||||
// scalafix:off
|
||||
@QuarkusTest
|
||||
@DisplayName("GameCreationService")
|
||||
class GameCreationServiceTest:
|
||||
|
||||
@Inject
|
||||
var service: GameCreationService = uninitialized
|
||||
|
||||
@InjectMock
|
||||
var subscriberManager: GameRedisSubscriberManager = uninitialized
|
||||
|
||||
@InjectMock
|
||||
var ioWrapper: IoGrpcClientWrapper = uninitialized
|
||||
|
||||
@BeforeEach
|
||||
def setup(): Unit =
|
||||
when(ioWrapper.exportCombined(any()))
|
||||
.thenReturn(CombinedExportResponse("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", ""))
|
||||
|
||||
private def player(id: String, name: String): PlayerInfoDto =
|
||||
PlayerInfoDto(id, name, PlayerType.Human)
|
||||
|
||||
@Test
|
||||
def createsGameAndSubscribes(): Unit =
|
||||
val req =
|
||||
GameCreationRequestDto(Some(player("w", "White")), Some(player("b", "Black")), None, Some(GameMode.Authenticated))
|
||||
val entry = service.createGame(req)
|
||||
assertNotNull(entry.gameId)
|
||||
assertEquals("White", entry.white.displayName)
|
||||
assertEquals("Black", entry.black.displayName)
|
||||
assertEquals(GameMode.Authenticated, entry.mode)
|
||||
verify(subscriberManager).subscribeGame(entry.gameId)
|
||||
|
||||
@Test
|
||||
def defaultsToOpenModeAndDefaultPlayers(): Unit =
|
||||
val entry = service.createGame(GameCreationRequestDto(None, None, None, None))
|
||||
assertEquals(GameMode.Open, entry.mode)
|
||||
assertEquals("Player 1", entry.white.displayName)
|
||||
assertEquals("Player 2", entry.black.displayName)
|
||||
|
||||
@Test
|
||||
def mapsClockTimeControl(): Unit =
|
||||
val tc = TimeControlDto(Some(300), Some(5), None)
|
||||
val entry = service.createGame(GameCreationRequestDto(None, None, Some(tc), None))
|
||||
assertEquals(TimeControl.Clock(300, 5), entry.engine.timeControl)
|
||||
|
||||
@Test
|
||||
def mapsCorrespondenceTimeControl(): Unit =
|
||||
val tc = TimeControlDto(None, None, Some(3))
|
||||
val entry = service.createGame(GameCreationRequestDto(None, None, Some(tc), None))
|
||||
assertEquals(TimeControl.Correspondence(3), entry.engine.timeControl)
|
||||
Reference in New Issue
Block a user