From 10366ca0240bc6bb5d66cd06a0c18373c2c8e875 Mon Sep 17 00:00:00 2001 From: Joffrey Bion Date: Tue, 9 Mar 2021 00:06:23 +0100 Subject: Unify game events --- .../luxons/sevenwonders/server/SevenWondersTest.kt | 107 +++++++++++---------- .../controllers/GameBrowserControllerTest.kt | 55 +++++++++-- .../server/controllers/HomeControllerTest.kt | 14 ++- .../server/controllers/LobbyControllerTest.kt | 7 +- .../server/test/ClientEventsAsserts.kt | 48 +++++++++ .../sevenwonders/server/test/MockMessageChannel.kt | 53 ++++++++++ .../luxons/sevenwonders/server/test/TestUtils.kt | 10 -- 7 files changed, 218 insertions(+), 76 deletions(-) create mode 100644 sw-server/src/test/kotlin/org/luxons/sevenwonders/server/test/ClientEventsAsserts.kt create mode 100644 sw-server/src/test/kotlin/org/luxons/sevenwonders/server/test/MockMessageChannel.kt (limited to 'sw-server/src/test') diff --git a/sw-server/src/test/kotlin/org/luxons/sevenwonders/server/SevenWondersTest.kt b/sw-server/src/test/kotlin/org/luxons/sevenwonders/server/SevenWondersTest.kt index 7f05688b..549ef267 100644 --- a/sw-server/src/test/kotlin/org/luxons/sevenwonders/server/SevenWondersTest.kt +++ b/sw-server/src/test/kotlin/org/luxons/sevenwonders/server/SevenWondersTest.kt @@ -1,23 +1,18 @@ package org.luxons.sevenwonders.server -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.produceIn -import kotlinx.coroutines.launch -import kotlinx.coroutines.withTimeout -import kotlinx.coroutines.withTimeoutOrNull +import kotlinx.coroutines.* import org.junit.runner.RunWith import org.luxons.sevenwonders.client.* import org.luxons.sevenwonders.model.TurnAction -import org.luxons.sevenwonders.model.api.GameListEvent -import org.luxons.sevenwonders.model.api.LobbyDTO -import org.luxons.sevenwonders.server.test.runAsyncTest +import org.luxons.sevenwonders.model.api.events.GameEvent +import org.luxons.sevenwonders.model.api.events.GameListEvent +import org.luxons.sevenwonders.server.test.* import org.springframework.boot.test.context.SpringBootTest import org.springframework.boot.test.context.SpringBootTest.WebEnvironment import org.springframework.boot.web.server.LocalServerPort import org.springframework.test.context.junit4.SpringRunner import kotlin.test.Test import kotlin.test.assertEquals -import kotlin.test.assertNull import kotlin.test.assertTrue @RunWith(SpringRunner::class) @@ -43,13 +38,13 @@ class SevenWondersTest { fun chooseName_succeedsWithCorrectDisplayName() = runAsyncTest { val session = connectNewClient() val playerName = "Test User" - val player = session.chooseName(playerName) + val player = session.chooseNameAndAwait(playerName) assertEquals(playerName, player.displayName) session.disconnect() } private suspend fun newPlayer(name: String): SevenWondersSession = connectNewClient().apply { - chooseName(name) + chooseNameAndAwait(name) } @Test @@ -59,17 +54,18 @@ class SevenWondersTest { val session2 = newPlayer("Player2") val gameName = "Test Game" - val lobby = ownerSession.createGameWithLegacySettingsAndWaitLobby(gameName) + val lobby = ownerSession.createGameAndAwaitLobby(gameName) session1.joinGameAndAwaitLobby(lobby.id) session2.joinGameAndAwaitLobby(lobby.id) val outsiderSession = newPlayer("Outsider") - val gameStartedEvents = outsiderSession.watchGameStarted() - ownerSession.startGameAndAwaitFirstTurn() + val outsiderAsserter = outsiderSession.eventAsserter(scope = this) - val nullForTimeout = withTimeoutOrNull(50) { gameStartedEvents.first() } - assertNull(nullForTimeout, "outsider should not receive the game start event of this game") + val ownerAsserter = ownerSession.eventAsserter(scope = this) + ownerSession.startGame() + ownerAsserter.expectGameEvent() + outsiderAsserter.expectNoGameEvent("outsider should not receive the game start event of this game") disconnect(ownerSession, session1, session2, outsiderSession) } @@ -79,7 +75,7 @@ class SevenWondersTest { val ownerSession = newPlayer("GameOwner") val gameName = "Test Game" - val lobby = ownerSession.createGameWithLegacySettingsAndWaitLobby(gameName) + val lobby = ownerSession.createGameAndAwaitLobby(gameName) assertEquals(gameName, lobby.name) disconnect(ownerSession) @@ -88,18 +84,16 @@ class SevenWondersTest { @Test fun createGame_seenByConnectedPlayers() = runAsyncTest { val otherSession = newPlayer("OtherPlayer") - val games = otherSession.watchGames().produceIn(this) + val asserter = otherSession.eventAsserter(scope = this) - val initialListEvent = withTimeout(500) { games.receive() } - assertTrue(initialListEvent is GameListEvent.ReplaceList) + val initialListEvent = asserter.expectGameListEvent() assertEquals(0, initialListEvent.lobbies.size) val ownerSession = newPlayer("GameOwner") val gameName = "Test Game" - val createdLobby = ownerSession.createGameWithLegacySettingsAndWaitLobby(gameName) + val createdLobby = ownerSession.createGameAndAwaitLobby(gameName) - val afterGameListEvent = withTimeout(500) { games.receive() } - assertTrue(afterGameListEvent is GameListEvent.CreateOrUpdate) + val afterGameListEvent = asserter.expectGameListEvent() val receivedLobby = afterGameListEvent.lobby assertEquals(createdLobby.id, receivedLobby.id) assertEquals(createdLobby.name, receivedLobby.name) @@ -107,46 +101,57 @@ class SevenWondersTest { disconnect(ownerSession, otherSession) } + @OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class) @Test fun startGame_3players() = runAsyncTest { val session1 = newPlayer("Player1") val session2 = newPlayer("Player2") - val startEvents1 = session1.watchGameStarted() - val lobby = session1.createGameWithLegacySettingsAndWaitLobby("Test Game") + val asserter1 = session1.eventAsserter(scope = this) + val lobby = session1.createGameAndAwaitLobby("Test Game") + asserter1.expectGameEvent() - val startEvents2 = session2.watchGameStarted() - session2.joinGameAndAwaitLobby(lobby.id) + val asserter2 = session2.eventAsserter(scope = this) + session2.joinGame(lobby.id) + asserter1.expectGameEvent() + asserter2.expectGameEvent() + asserter2.expectGameEvent() // player 3 connects after game creation (on purpose) val session3 = newPlayer("Player3") - val startEvents3 = session3.watchGameStarted() - session3.joinGameAndAwaitLobby(lobby.id) + val asserter3 = session3.eventAsserter(scope = this) + session3.joinGame(lobby.id) + asserter1.expectGameEvent() + asserter2.expectGameEvent() + asserter3.expectGameEvent() + asserter3.expectGameEvent() session1.startGame() - - listOf( - session1 to startEvents1, - session2 to startEvents2, - session3 to startEvents3, - ).forEach { (session, startEvents) -> - launch { - val initialReadyTurn = startEvents.first() - assertTrue(initialReadyTurn.action is TurnAction.SayReady) - val turns = session.watchTurns() - session.sayReady() - - val firstActualTurn = turns.first() - val action = firstActualTurn.action - assertTrue(action is TurnAction.PlayFromHand) - session.disconnect() - } - } + asserter1.expectGameEvent() + asserter2.expectGameEvent() + asserter3.expectGameEvent() + + session2.sayReady() + asserter2.expectNoGameEvent("nothing should happen while other players are not ready for game start") + asserter1.expectNoGameEvent("nothing should happen while other players are not ready for game start") + asserter3.expectNoGameEvent("nothing should happen while other players are not ready for game start") + + session1.sayReady() + asserter1.expectNoGameEvent("nothing should happen while other players are not ready for game start") + session3.sayReady() + + asserter1.expectPlayFromHandTurn() + asserter2.expectPlayFromHandTurn() + asserter3.expectPlayFromHandTurn() + + session1.disconnect() + session2.disconnect() + session3.disconnect() } } -private suspend fun SevenWondersSession.createGameWithLegacySettingsAndWaitLobby(gameName: String): LobbyDTO { - val lobby = createGameAndAwaitLobby(gameName) - updateSettings(lobby.settings.copy(askForReadiness = true)) - return lobby +private suspend fun EventAsserter.expectPlayFromHandTurn() { + val firstTurn = expectGameEvent() + val action = firstTurn.turnInfo.action + assertTrue(action is TurnAction.PlayFromHand) } diff --git a/sw-server/src/test/kotlin/org/luxons/sevenwonders/server/controllers/GameBrowserControllerTest.kt b/sw-server/src/test/kotlin/org/luxons/sevenwonders/server/controllers/GameBrowserControllerTest.kt index 0f6e031b..70369a59 100644 --- a/sw-server/src/test/kotlin/org/luxons/sevenwonders/server/controllers/GameBrowserControllerTest.kt +++ b/sw-server/src/test/kotlin/org/luxons/sevenwonders/server/controllers/GameBrowserControllerTest.kt @@ -3,14 +3,18 @@ package org.luxons.sevenwonders.server.controllers import io.micrometer.core.instrument.simple.SimpleMeterRegistry import org.junit.Before import org.junit.Test -import org.luxons.sevenwonders.model.api.GameListEvent import org.luxons.sevenwonders.model.api.actions.CreateGameAction import org.luxons.sevenwonders.model.api.actions.JoinGameAction +import org.luxons.sevenwonders.model.api.events.GameEvent +import org.luxons.sevenwonders.model.api.events.GameListEvent import org.luxons.sevenwonders.server.controllers.GameBrowserController.UserAlreadyInGameException import org.luxons.sevenwonders.server.repositories.LobbyRepository import org.luxons.sevenwonders.server.repositories.PlayerNotFoundException import org.luxons.sevenwonders.server.repositories.PlayerRepository -import org.luxons.sevenwonders.server.test.mockSimpMessagingTemplate +import org.luxons.sevenwonders.server.test.MockMessageChannel +import org.luxons.sevenwonders.server.test.expectSentGameEventTo +import org.luxons.sevenwonders.server.test.expectSentGameListEvent +import org.springframework.messaging.simp.SimpMessagingTemplate import kotlin.test.assertEquals import kotlin.test.assertFailsWith import kotlin.test.assertFalse @@ -18,18 +22,21 @@ import kotlin.test.assertTrue class GameBrowserControllerTest { + private lateinit var messageChannel: MockMessageChannel + private lateinit var playerRepository: PlayerRepository private lateinit var gameBrowserController: GameBrowserController @Before fun setUp() { + messageChannel = MockMessageChannel() + val messenger = SimpMessagingTemplate(messageChannel) val meterRegistry = SimpleMeterRegistry() - playerRepository = PlayerRepository(meterRegistry) val lobbyRepository = LobbyRepository(meterRegistry) - val template = mockSimpMessagingTemplate() - val lobbyController = LobbyController(lobbyRepository, playerRepository, template, "UNUSED", meterRegistry) - gameBrowserController = GameBrowserController(lobbyController, lobbyRepository, playerRepository, template) + playerRepository = PlayerRepository(meterRegistry) + val lobbyController = LobbyController(messenger, lobbyRepository, playerRepository, "UNUSED", meterRegistry) + gameBrowserController = GameBrowserController(messenger, lobbyController, lobbyRepository, playerRepository) } @Test @@ -46,9 +53,16 @@ class GameBrowserControllerTest { val action = CreateGameAction("Test Game") - val createdLobby = gameBrowserController.createGame(action, principal) + gameBrowserController.createGame(action, principal) + + val createdEvent = messageChannel.expectSentGameListEvent() + val lobbyJoinedEvent = messageChannel.expectSentGameEventTo("testuser") + val createdLobby = createdEvent.lobby assertEquals("Test Game", createdLobby.name) + assertEquals(createdLobby, lobbyJoinedEvent.lobby) + + messageChannel.expectNoMoreMessages() val gameListEvent = gameBrowserController.listGames(principal).event as GameListEvent.ReplaceList assertFalse(gameListEvent.lobbies.isEmpty()) @@ -91,17 +105,34 @@ class GameBrowserControllerTest { val ownerPrincipal = TestPrincipal("testowner") val createGameAction = CreateGameAction("Test Game") - val createdLobby = gameBrowserController.createGame(createGameAction, ownerPrincipal) + gameBrowserController.createGame(createGameAction, ownerPrincipal) + + val createdEvent = messageChannel.expectSentGameListEvent() + messageChannel.expectSentGameEventTo("testowner") + val createdLobby = createdEvent.lobby assertEquals(owner.username, createdLobby.players[0].username) + messageChannel.expectNoMoreMessages() + val joiner = playerRepository.createOrUpdate("testjoiner", "Test User Joiner") val joinerPrincipal = TestPrincipal("testjoiner") val joinGameAction = JoinGameAction(createdLobby.id) - val joinedLobby = gameBrowserController.joinGame(joinGameAction, joinerPrincipal) + gameBrowserController.joinGame(joinGameAction, joinerPrincipal) + + // lobby update for existing players + messageChannel.expectSentGameEventTo("testowner") + messageChannel.expectSentGameEventTo("testjoiner") + // lobby update for people on game browser page + messageChannel.expectSentGameListEvent() + // lobby joined for the player who joined + val joinedLobbyEvent = messageChannel.expectSentGameEventTo("testjoiner") + val joinedLobby = joinedLobbyEvent.lobby assertEquals(owner.username, joinedLobby.players[0].username) assertEquals(joiner.username, joinedLobby.players[1].username) + + messageChannel.expectNoMoreMessages() } @Test @@ -110,7 +141,11 @@ class GameBrowserControllerTest { val ownerPrincipal = TestPrincipal("testowner") val createGameAction = CreateGameAction("Test Game") - val createdLobby = gameBrowserController.createGame(createGameAction, ownerPrincipal) + gameBrowserController.createGame(createGameAction, ownerPrincipal) + + val createdEvent = messageChannel.expectSentGameListEvent() + messageChannel.expectSentGameEventTo("testowner") + val createdLobby = createdEvent.lobby playerRepository.createOrUpdate("testjoiner", "Test User Joiner") val joinerPrincipal = TestPrincipal("testjoiner") diff --git a/sw-server/src/test/kotlin/org/luxons/sevenwonders/server/controllers/HomeControllerTest.kt b/sw-server/src/test/kotlin/org/luxons/sevenwonders/server/controllers/HomeControllerTest.kt index c73fba10..4976e2de 100644 --- a/sw-server/src/test/kotlin/org/luxons/sevenwonders/server/controllers/HomeControllerTest.kt +++ b/sw-server/src/test/kotlin/org/luxons/sevenwonders/server/controllers/HomeControllerTest.kt @@ -4,7 +4,11 @@ import io.micrometer.core.instrument.simple.SimpleMeterRegistry import org.junit.Test import org.luxons.sevenwonders.model.api.actions.ChooseNameAction import org.luxons.sevenwonders.model.api.actions.Icon +import org.luxons.sevenwonders.model.api.events.GameEvent import org.luxons.sevenwonders.server.repositories.PlayerRepository +import org.luxons.sevenwonders.server.test.MockMessageChannel +import org.luxons.sevenwonders.server.test.expectSentGameEventTo +import org.springframework.messaging.simp.SimpMessagingTemplate import kotlin.test.assertEquals class HomeControllerTest { @@ -13,15 +17,21 @@ class HomeControllerTest { fun chooseName() { val meterRegistry = SimpleMeterRegistry() val playerRepository = PlayerRepository(meterRegistry) - val homeController = HomeController(playerRepository, meterRegistry) + val messageChannel = MockMessageChannel() + val messenger = SimpMessagingTemplate(messageChannel) + val homeController = HomeController(messenger, playerRepository, meterRegistry) val action = ChooseNameAction("Test User", Icon("person"), isHuman = true) val principal = TestPrincipal("testuser") - val player = homeController.chooseName(action, principal) + homeController.chooseName(action, principal) + val payload = messageChannel.expectSentGameEventTo("testuser") + val player = payload.player assertEquals("testuser", player.username) assertEquals("Test User", player.displayName) assertEquals("person", player.icon?.name) + + messageChannel.expectNoMoreMessages() } } diff --git a/sw-server/src/test/kotlin/org/luxons/sevenwonders/server/controllers/LobbyControllerTest.kt b/sw-server/src/test/kotlin/org/luxons/sevenwonders/server/controllers/LobbyControllerTest.kt index 7d9db01d..38f6ffb3 100644 --- a/sw-server/src/test/kotlin/org/luxons/sevenwonders/server/controllers/LobbyControllerTest.kt +++ b/sw-server/src/test/kotlin/org/luxons/sevenwonders/server/controllers/LobbyControllerTest.kt @@ -14,7 +14,8 @@ import org.luxons.sevenwonders.server.lobby.PlayerNotInLobbyException import org.luxons.sevenwonders.server.repositories.LobbyRepository import org.luxons.sevenwonders.server.repositories.PlayerNotFoundException import org.luxons.sevenwonders.server.repositories.PlayerRepository -import org.luxons.sevenwonders.server.test.mockSimpMessagingTemplate +import org.luxons.sevenwonders.server.test.MockMessageChannel +import org.springframework.messaging.simp.SimpMessagingTemplate import java.util.* import kotlin.test.* @@ -29,10 +30,10 @@ class LobbyControllerTest { @Before fun setUp() { val meterRegistry = SimpleMeterRegistry() - val template = mockSimpMessagingTemplate() + val template = SimpMessagingTemplate(MockMessageChannel()) playerRepository = PlayerRepository(meterRegistry) lobbyRepository = LobbyRepository(meterRegistry) - lobbyController = LobbyController(lobbyRepository, playerRepository, template, "UNUSED", meterRegistry) + lobbyController = LobbyController(template, lobbyRepository, playerRepository, "UNUSED", meterRegistry) } @Test diff --git a/sw-server/src/test/kotlin/org/luxons/sevenwonders/server/test/ClientEventsAsserts.kt b/sw-server/src/test/kotlin/org/luxons/sevenwonders/server/test/ClientEventsAsserts.kt new file mode 100644 index 00000000..d87c2122 --- /dev/null +++ b/sw-server/src/test/kotlin/org/luxons/sevenwonders/server/test/ClientEventsAsserts.kt @@ -0,0 +1,48 @@ +package org.luxons.sevenwonders.server.test + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.channels.ReceiveChannel +import kotlinx.coroutines.flow.produceIn +import kotlinx.coroutines.withTimeoutOrNull +import org.luxons.sevenwonders.client.SevenWondersSession +import org.luxons.sevenwonders.model.api.events.GameEvent +import org.luxons.sevenwonders.model.api.events.GameListEvent +import kotlin.test.assertNotNull +import kotlin.test.assertNull +import kotlin.test.assertTrue +import kotlin.time.Duration +import kotlin.time.milliseconds +import kotlin.time.seconds + +class EventAsserter( + val gameListEvents: ReceiveChannel, + val gameEvents: ReceiveChannel, +) + +@OptIn(FlowPreview::class) +suspend fun SevenWondersSession.eventAsserter(scope: CoroutineScope): EventAsserter { + val gameListEvents = watchGames().produceIn(scope) + val gameEvents = watchGameEvents().produceIn(scope) + return EventAsserter(gameListEvents, gameEvents) +} + +suspend inline fun EventAsserter.expectNoGameEvent(message: String? = null, timeout: Duration = 50.milliseconds) { + val event = withTimeoutOrNull(timeout) { gameEvents.receive() } + val extraMessage = message?.let { " ($it)" } ?: "" + assertNull(event, "Expected no game event$extraMessage, but received $event") +} + +suspend inline fun EventAsserter.expectGameEvent(timeout: Duration = 1.seconds): T { + val event = withTimeoutOrNull(timeout) { gameEvents.receive() } + assertNotNull(event, "Expected event of type ${T::class.simpleName}, received nothing in $timeout") + assertTrue(event is T, "Expected event of type ${T::class.simpleName}, received $event") + return event +} + +suspend inline fun EventAsserter.expectGameListEvent(timeout: Duration = 1.seconds): T { + val event = withTimeoutOrNull(timeout) { gameListEvents.receive() } + assertNotNull(event, "Expected event of type ${T::class.simpleName}, received nothing in $timeout") + assertTrue(event is T, "Expected event of type ${T::class.simpleName}, received $event") + return event +} diff --git a/sw-server/src/test/kotlin/org/luxons/sevenwonders/server/test/MockMessageChannel.kt b/sw-server/src/test/kotlin/org/luxons/sevenwonders/server/test/MockMessageChannel.kt new file mode 100644 index 00000000..e27a2550 --- /dev/null +++ b/sw-server/src/test/kotlin/org/luxons/sevenwonders/server/test/MockMessageChannel.kt @@ -0,0 +1,53 @@ +package org.luxons.sevenwonders.server.test + +import org.luxons.sevenwonders.model.api.events.GameEvent +import org.luxons.sevenwonders.model.api.events.GameEventWrapper +import org.luxons.sevenwonders.model.api.events.GameListEvent +import org.luxons.sevenwonders.model.api.events.GameListEventWrapper +import org.springframework.messaging.Message +import org.springframework.messaging.MessageChannel +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertTrue + +class MockMessageChannel : MessageChannel { + private val _messages: MutableList> = mutableListOf() + val messages: List> get() = _messages + + override fun send(message: Message<*>, timeout: Long): Boolean = _messages.add(message) + + fun expectSentMessageTo(expectedDestination: String): Message<*> { + val m = _messages.removeFirstOrNull() + assertNotNull(m, "Expected sent message, but no messages found") + assertEquals(expectedDestination, m.headers["simpDestination"], "Incorrect message destination") + return m + } + + fun expectNoMoreMessages() { + assertTrue( + actual = messages.isEmpty(), + message = "No more messages should have been sent, but ${messages.size} were found: $messages", + ) + } +} + +inline fun MockMessageChannel.expectSentMessageWithPayload(expectedDestination: String): T { + val m = expectSentMessageTo(expectedDestination) + val payload = m.payload + assertTrue(payload is T, "Message payload should be of type ${T::class.simpleName}") + return payload +} + +inline fun MockMessageChannel.expectSentGameListEvent(): T { + val wrappedEvent = expectSentMessageWithPayload("/topic/games") + val event = wrappedEvent.event + assertTrue(event is T, "Expected game list event of type ${T::class.simpleName}, got $event") + return event +} + +inline fun MockMessageChannel.expectSentGameEventTo(username: String): T { + val wrappedEvent = expectSentMessageWithPayload("/user/$username/queue/game/events") + val event = wrappedEvent.event + assertTrue(event is T, "Expected game event of type ${T::class.simpleName}, got $event") + return event +} diff --git a/sw-server/src/test/kotlin/org/luxons/sevenwonders/server/test/TestUtils.kt b/sw-server/src/test/kotlin/org/luxons/sevenwonders/server/test/TestUtils.kt index efd40a6d..6e64e9da 100644 --- a/sw-server/src/test/kotlin/org/luxons/sevenwonders/server/test/TestUtils.kt +++ b/sw-server/src/test/kotlin/org/luxons/sevenwonders/server/test/TestUtils.kt @@ -3,18 +3,8 @@ package org.luxons.sevenwonders.server.test import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withTimeoutOrNull -import org.springframework.messaging.Message -import org.springframework.messaging.MessageChannel -import org.springframework.messaging.simp.SimpMessagingTemplate import kotlin.test.assertNotNull -fun mockSimpMessagingTemplate(): SimpMessagingTemplate = SimpMessagingTemplate( - object : MessageChannel { - override fun send(message: Message<*>): Boolean = true - override fun send(message: Message<*>, timeout: Long): Boolean = true - }, -) - fun runAsyncTest(timeoutMillis: Long = 10000, block: suspend CoroutineScope.() -> Unit) = runBlocking { val result = withTimeoutOrNull(timeoutMillis, block) assertNotNull(result, "Test timed out, exceeded ${timeoutMillis}ms") -- cgit