summaryrefslogtreecommitdiff
path: root/backend
diff options
context:
space:
mode:
Diffstat (limited to 'backend')
-rw-r--r--backend/build.gradle41
-rw-r--r--backend/src/main/java/org/luxons/sevenwonders/SevenWonders.java14
-rw-r--r--backend/src/main/java/org/luxons/sevenwonders/actions/ChooseNameAction.java30
-rw-r--r--backend/src/main/java/org/luxons/sevenwonders/actions/CreateGameAction.java30
-rw-r--r--backend/src/main/java/org/luxons/sevenwonders/actions/JoinGameAction.java27
-rw-r--r--backend/src/main/java/org/luxons/sevenwonders/actions/PrepareMoveAction.java28
-rw-r--r--backend/src/main/java/org/luxons/sevenwonders/actions/ReorderPlayersAction.java29
-rw-r--r--backend/src/main/java/org/luxons/sevenwonders/actions/UpdateSettingsAction.java28
-rw-r--r--backend/src/main/java/org/luxons/sevenwonders/config/AnonymousUsersHandshakeHandler.java24
-rw-r--r--backend/src/main/java/org/luxons/sevenwonders/config/TopicSubscriptionInterceptor.java43
-rw-r--r--backend/src/main/java/org/luxons/sevenwonders/config/WebSecurityConfig.java13
-rw-r--r--backend/src/main/java/org/luxons/sevenwonders/config/WebSocketConfig.java51
-rw-r--r--backend/src/main/java/org/luxons/sevenwonders/controllers/GameBrowserController.java129
-rw-r--r--backend/src/main/java/org/luxons/sevenwonders/controllers/GameController.java111
-rw-r--r--backend/src/main/java/org/luxons/sevenwonders/controllers/HomeController.java52
-rw-r--r--backend/src/main/java/org/luxons/sevenwonders/controllers/LobbyController.java141
-rw-r--r--backend/src/main/java/org/luxons/sevenwonders/doc/Documentation.java6
-rw-r--r--backend/src/main/java/org/luxons/sevenwonders/errors/ApiMisuseException.java8
-rw-r--r--backend/src/main/java/org/luxons/sevenwonders/errors/ErrorType.java8
-rw-r--r--backend/src/main/java/org/luxons/sevenwonders/errors/ExceptionHandler.java81
-rw-r--r--backend/src/main/java/org/luxons/sevenwonders/errors/UIError.java54
-rw-r--r--backend/src/main/java/org/luxons/sevenwonders/errors/UIErrorDetail.java37
-rw-r--r--backend/src/main/java/org/luxons/sevenwonders/errors/UserInputException.java21
-rw-r--r--backend/src/main/java/org/luxons/sevenwonders/lobby/Lobby.java168
-rw-r--r--backend/src/main/java/org/luxons/sevenwonders/lobby/Player.java76
-rw-r--r--backend/src/main/java/org/luxons/sevenwonders/lobby/State.java6
-rw-r--r--backend/src/main/java/org/luxons/sevenwonders/output/PreparedCard.java24
-rw-r--r--backend/src/main/java/org/luxons/sevenwonders/repositories/LobbyRepository.java59
-rw-r--r--backend/src/main/java/org/luxons/sevenwonders/repositories/PlayerRepository.java60
-rw-r--r--backend/src/main/java/org/luxons/sevenwonders/validation/DestinationAccessValidator.java66
-rw-r--r--backend/src/main/kotlin/org/luxons/sevenwonders/SevenWonders.kt13
-rw-r--r--backend/src/main/kotlin/org/luxons/sevenwonders/actions/ChooseNameAction.kt19
-rw-r--r--backend/src/main/kotlin/org/luxons/sevenwonders/actions/CreateGameAction.kt21
-rw-r--r--backend/src/main/kotlin/org/luxons/sevenwonders/actions/JoinGameAction.kt17
-rw-r--r--backend/src/main/kotlin/org/luxons/sevenwonders/actions/PrepareMoveAction.kt18
-rw-r--r--backend/src/main/kotlin/org/luxons/sevenwonders/actions/ReorderPlayersAction.kt18
-rw-r--r--backend/src/main/kotlin/org/luxons/sevenwonders/actions/UpdateSettingsAction.kt18
-rw-r--r--backend/src/main/kotlin/org/luxons/sevenwonders/config/AnonymousUsersHandshakeHandler.kt27
-rw-r--r--backend/src/main/kotlin/org/luxons/sevenwonders/config/TopicSubscriptionInterceptor.kt38
-rw-r--r--backend/src/main/kotlin/org/luxons/sevenwonders/config/WebSecurityConfig.kt12
-rw-r--r--backend/src/main/kotlin/org/luxons/sevenwonders/config/WebSocketConfig.kt42
-rw-r--r--backend/src/main/kotlin/org/luxons/sevenwonders/controllers/GameBrowserController.kt109
-rw-r--r--backend/src/main/kotlin/org/luxons/sevenwonders/controllers/GameController.kt99
-rw-r--r--backend/src/main/kotlin/org/luxons/sevenwonders/controllers/HomeController.kt47
-rw-r--r--backend/src/main/kotlin/org/luxons/sevenwonders/controllers/LobbyController.kt106
-rw-r--r--backend/src/main/kotlin/org/luxons/sevenwonders/doc/Documentation.kt6
-rw-r--r--backend/src/main/kotlin/org/luxons/sevenwonders/errors/ErrorDTO.kt29
-rw-r--r--backend/src/main/kotlin/org/luxons/sevenwonders/errors/ExceptionHandler.kt44
-rw-r--r--backend/src/main/kotlin/org/luxons/sevenwonders/lobby/Lobby.kt107
-rw-r--r--backend/src/main/kotlin/org/luxons/sevenwonders/lobby/Player.kt70
-rw-r--r--backend/src/main/kotlin/org/luxons/sevenwonders/output/PreparedCard.kt6
-rw-r--r--backend/src/main/kotlin/org/luxons/sevenwonders/repositories/LobbyRepository.kt33
-rw-r--r--backend/src/main/kotlin/org/luxons/sevenwonders/repositories/PlayerRepository.kt41
-rw-r--r--backend/src/main/kotlin/org/luxons/sevenwonders/validation/DestinationAccessValidator.kt55
-rw-r--r--backend/src/test/java/org/luxons/sevenwonders/SevenWondersTest.java158
-rw-r--r--backend/src/test/java/org/luxons/sevenwonders/controllers/GameBrowserControllerTest.java133
-rw-r--r--backend/src/test/java/org/luxons/sevenwonders/controllers/HomeControllerTest.java33
-rw-r--r--backend/src/test/java/org/luxons/sevenwonders/controllers/LobbyControllerTest.java203
-rw-r--r--backend/src/test/java/org/luxons/sevenwonders/controllers/TestPrincipal.java17
-rw-r--r--backend/src/test/java/org/luxons/sevenwonders/lobby/LobbyTest.java225
-rw-r--r--backend/src/test/java/org/luxons/sevenwonders/repositories/LobbyRepositoryTest.java76
-rw-r--r--backend/src/test/java/org/luxons/sevenwonders/repositories/PlayerRepositoryTest.java73
-rw-r--r--backend/src/test/java/org/luxons/sevenwonders/test/TestUtils.java23
-rw-r--r--backend/src/test/java/org/luxons/sevenwonders/test/api/ApiBoard.java166
-rw-r--r--backend/src/test/java/org/luxons/sevenwonders/test/api/ApiCard.java100
-rw-r--r--backend/src/test/java/org/luxons/sevenwonders/test/api/ApiCardBack.java14
-rw-r--r--backend/src/test/java/org/luxons/sevenwonders/test/api/ApiEffect.java4
-rw-r--r--backend/src/test/java/org/luxons/sevenwonders/test/api/ApiHandCard.java48
-rw-r--r--backend/src/test/java/org/luxons/sevenwonders/test/api/ApiLobby.java89
-rw-r--r--backend/src/test/java/org/luxons/sevenwonders/test/api/ApiMilitary.java34
-rw-r--r--backend/src/test/java/org/luxons/sevenwonders/test/api/ApiPlayer.java50
-rw-r--r--backend/src/test/java/org/luxons/sevenwonders/test/api/ApiPlayerTurnInfo.java98
-rw-r--r--backend/src/test/java/org/luxons/sevenwonders/test/api/ApiProduction.java29
-rw-r--r--backend/src/test/java/org/luxons/sevenwonders/test/api/ApiScience.java28
-rw-r--r--backend/src/test/java/org/luxons/sevenwonders/test/api/ApiTable.java73
-rw-r--r--backend/src/test/java/org/luxons/sevenwonders/test/api/ApiTradingRules.java19
-rw-r--r--backend/src/test/java/org/luxons/sevenwonders/test/api/ApiWonder.java18
-rw-r--r--backend/src/test/java/org/luxons/sevenwonders/test/api/ApiWonderStage.java37
-rw-r--r--backend/src/test/java/org/luxons/sevenwonders/test/api/SevenWondersClient.java32
-rw-r--r--backend/src/test/java/org/luxons/sevenwonders/test/api/SevenWondersSession.java83
-rw-r--r--backend/src/test/java/org/luxons/sevenwonders/validation/DestinationAccessValidatorTest.java140
-rw-r--r--backend/src/test/kotlin/org/luxons/sevenwonders/SevenWondersTest.kt151
-rw-r--r--backend/src/test/kotlin/org/luxons/sevenwonders/controllers/GameBrowserControllerTest.kt114
-rw-r--r--backend/src/test/kotlin/org/luxons/sevenwonders/controllers/HomeControllerTest.kt26
-rw-r--r--backend/src/test/kotlin/org/luxons/sevenwonders/controllers/LobbyControllerTest.kt199
-rw-r--r--backend/src/test/kotlin/org/luxons/sevenwonders/controllers/TestPrincipal.kt10
-rw-r--r--backend/src/test/kotlin/org/luxons/sevenwonders/lobby/LobbyTest.kt229
-rw-r--r--backend/src/test/kotlin/org/luxons/sevenwonders/repositories/LobbyRepositoryTest.kt70
-rw-r--r--backend/src/test/kotlin/org/luxons/sevenwonders/repositories/PlayerRepositoryTest.kt67
-rw-r--r--backend/src/test/kotlin/org/luxons/sevenwonders/test/TestUtils.kt21
-rw-r--r--backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiBoard.kt64
-rw-r--r--backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiCard.kt38
-rw-r--r--backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiCardBack.kt6
-rw-r--r--backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiEffect.kt3
-rw-r--r--backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiHandCard.kt16
-rw-r--r--backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiLobby.kt36
-rw-r--r--backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiMilitary.kt10
-rw-r--r--backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiPlayer.kt27
-rw-r--r--backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiPlayerTurnInfo.kt37
-rw-r--r--backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiProduction.kt11
-rw-r--r--backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiScience.kt10
-rw-r--r--backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiTable.kt23
-rw-r--r--backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiTradingRules.kt9
-rw-r--r--backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiWonder.kt16
-rw-r--r--backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiWonderStage.kt13
-rw-r--r--backend/src/test/kotlin/org/luxons/sevenwonders/test/api/SevenWondersClient.kt25
-rw-r--r--backend/src/test/kotlin/org/luxons/sevenwonders/test/api/SevenWondersSession.kt70
-rw-r--r--backend/src/test/kotlin/org/luxons/sevenwonders/validation/DestinationAccessValidatorTest.kt138
108 files changed, 2469 insertions, 3433 deletions
diff --git a/backend/build.gradle b/backend/build.gradle
index aec1bc9e..6c733559 100644
--- a/backend/build.gradle
+++ b/backend/build.gradle
@@ -1,10 +1,21 @@
-plugins {
- id 'org.springframework.boot' version '2.0.1.RELEASE'
+buildscript {
+ ext {
+ kotlinVersion = '1.2.51'
+ springBootVersion = '2.0.3.RELEASE'
+ }
+ repositories {
+ mavenCentral()
+ }
+ dependencies {
+ classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
+ classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}")
+ classpath("org.jetbrains.kotlin:kotlin-allopen:${kotlinVersion}")
+ }
}
-apply plugin: 'java'
-apply plugin: 'idea'
apply plugin: 'checkstyle'
+apply plugin: 'kotlin'
+apply plugin: 'kotlin-spring'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
@@ -13,6 +24,20 @@ version '1.0-SNAPSHOT'
sourceCompatibility = 1.8
+compileKotlin {
+ kotlinOptions {
+ freeCompilerArgs = ["-Xjsr305=strict"]
+ jvmTarget = "1.8"
+ }
+}
+
+compileTestKotlin {
+ kotlinOptions {
+ freeCompilerArgs = ["-Xjsr305=strict"]
+ jvmTarget = "1.8"
+ }
+}
+
repositories {
mavenCentral()
}
@@ -23,12 +48,16 @@ configurations {
dependencies {
compile project(':game-engine')
+ compile 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
+ compile 'org.jetbrains.kotlin:kotlin-reflect' // required by Spring 5
compile 'org.springframework.boot:spring-boot-starter-websocket'
compile 'org.springframework.boot:spring-boot-starter-security'
- // required by spring security with websockets
+ // required by spring security when using websockets
compile 'org.springframework.security:spring-security-messaging'
+ compile "com.fasterxml.jackson.module:jackson-module-kotlin"
+
compile 'ch.qos.logback:logback-classic:1.1.8'
compile 'org.hildan.livedoc:livedoc-springboot:4.3.2'
compile 'org.hildan.livedoc:livedoc-ui-webjar:4.3.2'
@@ -38,7 +67,7 @@ dependencies {
testCompile 'org.springframework.boot:spring-boot-starter-test'
testCompile 'org.hildan.jackstomp:jackstomp:1.1.0'
- checkstyleConfig "org.hildan.checkstyle:checkstyle-config:2.1.0"
+ checkstyleConfig 'org.hildan.checkstyle:checkstyle-config:2.1.0'
}
checkstyle {
diff --git a/backend/src/main/java/org/luxons/sevenwonders/SevenWonders.java b/backend/src/main/java/org/luxons/sevenwonders/SevenWonders.java
deleted file mode 100644
index c656b2c3..00000000
--- a/backend/src/main/java/org/luxons/sevenwonders/SevenWonders.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package org.luxons.sevenwonders;
-
-import org.hildan.livedoc.spring.boot.starter.EnableJSONDoc;
-import org.springframework.boot.SpringApplication;
-import org.springframework.boot.autoconfigure.SpringBootApplication;
-
-@SpringBootApplication
-@EnableJSONDoc
-public class SevenWonders {
-
- public static void main(String[] args) {
- SpringApplication.run(SevenWonders.class, args);
- }
-}
diff --git a/backend/src/main/java/org/luxons/sevenwonders/actions/ChooseNameAction.java b/backend/src/main/java/org/luxons/sevenwonders/actions/ChooseNameAction.java
deleted file mode 100644
index c404cd74..00000000
--- a/backend/src/main/java/org/luxons/sevenwonders/actions/ChooseNameAction.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package org.luxons.sevenwonders.actions;
-
-import javax.validation.constraints.NotNull;
-import javax.validation.constraints.Size;
-
-import org.hildan.livedoc.core.annotations.types.ApiType;
-import org.hildan.livedoc.core.annotations.types.ApiTypeProperty;
-
-/**
- * The action to choose the player's name. This is the first action that should be called.
- */
-@ApiType(group = "Actions")
-public class ChooseNameAction {
-
- @NotNull
- @Size(min = 2, max = 20)
- private String playerName;
-
- /**
- * @return The display name of the player. May contain spaces and special characters.
- */
- @ApiTypeProperty(required = true)
- public String getPlayerName() {
- return playerName;
- }
-
- public void setPlayerName(String playerName) {
- this.playerName = playerName;
- }
-}
diff --git a/backend/src/main/java/org/luxons/sevenwonders/actions/CreateGameAction.java b/backend/src/main/java/org/luxons/sevenwonders/actions/CreateGameAction.java
deleted file mode 100644
index 771a5a9c..00000000
--- a/backend/src/main/java/org/luxons/sevenwonders/actions/CreateGameAction.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package org.luxons.sevenwonders.actions;
-
-import javax.validation.constraints.NotNull;
-import javax.validation.constraints.Size;
-
-import org.hildan.livedoc.core.annotations.types.ApiType;
-
-/**
- * The action to create a game.
- */
-@ApiType(group = "Actions")
-public class CreateGameAction {
-
- @NotNull
- @Size(min = 2, max = 30)
- private String gameName;
-
- /**
- * @return The name of the game to create
- */
- public String getGameName() {
- return gameName;
- }
-
- public void setGameName(String gameName) {
- this.gameName = gameName;
- }
-}
-
-
diff --git a/backend/src/main/java/org/luxons/sevenwonders/actions/JoinGameAction.java b/backend/src/main/java/org/luxons/sevenwonders/actions/JoinGameAction.java
deleted file mode 100644
index 2675dc71..00000000
--- a/backend/src/main/java/org/luxons/sevenwonders/actions/JoinGameAction.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package org.luxons.sevenwonders.actions;
-
-import javax.validation.constraints.NotNull;
-
-import org.hildan.livedoc.core.annotations.types.ApiType;
-import org.luxons.sevenwonders.doc.Documentation;
-
-/**
- * The action to join a game.
- */
-@ApiType(group = Documentation.GROUP_ACTIONS)
-public class JoinGameAction {
-
- @NotNull
- private Long gameId;
-
- /**
- * @return The ID of the game to join
- */
- public Long getGameId() {
- return gameId;
- }
-
- public void setGameId(Long gameId) {
- this.gameId = gameId;
- }
-}
diff --git a/backend/src/main/java/org/luxons/sevenwonders/actions/PrepareMoveAction.java b/backend/src/main/java/org/luxons/sevenwonders/actions/PrepareMoveAction.java
deleted file mode 100644
index e3622b1b..00000000
--- a/backend/src/main/java/org/luxons/sevenwonders/actions/PrepareMoveAction.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package org.luxons.sevenwonders.actions;
-
-import javax.validation.constraints.NotNull;
-
-import org.hildan.livedoc.core.annotations.types.ApiType;
-import org.luxons.sevenwonders.doc.Documentation;
-import org.luxons.sevenwonders.game.api.PlayerMove;
-
-/**
- * The action to prepare the next move during a game.
- */
-@ApiType(group = Documentation.GROUP_ACTIONS)
-public class PrepareMoveAction {
-
- @NotNull
- private PlayerMove move;
-
- /**
- * @return the move to prepare
- */
- public PlayerMove getMove() {
- return move;
- }
-
- public void setMove(PlayerMove move) {
- this.move = move;
- }
-}
diff --git a/backend/src/main/java/org/luxons/sevenwonders/actions/ReorderPlayersAction.java b/backend/src/main/java/org/luxons/sevenwonders/actions/ReorderPlayersAction.java
deleted file mode 100644
index ce272340..00000000
--- a/backend/src/main/java/org/luxons/sevenwonders/actions/ReorderPlayersAction.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package org.luxons.sevenwonders.actions;
-
-import java.util.List;
-import javax.validation.constraints.NotNull;
-
-import org.hildan.livedoc.core.annotations.types.ApiType;
-import org.luxons.sevenwonders.doc.Documentation;
-
-/**
- * The action to update the order of the players around the table. Can only be called in the lobby by the owner of the
- * game.
- */
-@ApiType(group = Documentation.GROUP_ACTIONS)
-public class ReorderPlayersAction {
-
- @NotNull
- private List<String> orderedPlayers;
-
- /**
- * @return the list of usernames of the players, in the new order
- */
- public List<String> getOrderedPlayers() {
- return orderedPlayers;
- }
-
- public void setOrderedPlayers(List<String> orderedPlayers) {
- this.orderedPlayers = orderedPlayers;
- }
-}
diff --git a/backend/src/main/java/org/luxons/sevenwonders/actions/UpdateSettingsAction.java b/backend/src/main/java/org/luxons/sevenwonders/actions/UpdateSettingsAction.java
deleted file mode 100644
index 7c48dee1..00000000
--- a/backend/src/main/java/org/luxons/sevenwonders/actions/UpdateSettingsAction.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package org.luxons.sevenwonders.actions;
-
-import javax.validation.constraints.NotNull;
-
-import org.hildan.livedoc.core.annotations.types.ApiType;
-import org.luxons.sevenwonders.doc.Documentation;
-import org.luxons.sevenwonders.game.api.CustomizableSettings;
-
-/**
- * The action to update the settings of the game. Can only be called in the lobby by the owner of the game.
- */
-@ApiType(group = Documentation.GROUP_ACTIONS)
-public class UpdateSettingsAction {
-
- @NotNull
- private CustomizableSettings settings;
-
- /**
- * @return the new values for the settings
- */
- public CustomizableSettings getSettings() {
- return settings;
- }
-
- public void setSettings(CustomizableSettings settings) {
- this.settings = settings;
- }
-}
diff --git a/backend/src/main/java/org/luxons/sevenwonders/config/AnonymousUsersHandshakeHandler.java b/backend/src/main/java/org/luxons/sevenwonders/config/AnonymousUsersHandshakeHandler.java
deleted file mode 100644
index bebbd477..00000000
--- a/backend/src/main/java/org/luxons/sevenwonders/config/AnonymousUsersHandshakeHandler.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package org.luxons.sevenwonders.config;
-
-import java.security.Principal;
-import java.util.Map;
-
-import org.springframework.http.server.ServerHttpRequest;
-import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
-import org.springframework.web.socket.WebSocketHandler;
-import org.springframework.web.socket.server.support.DefaultHandshakeHandler;
-
-class AnonymousUsersHandshakeHandler extends DefaultHandshakeHandler {
-
- private int playerId = 0;
-
- @Override
- public Principal determineUser(ServerHttpRequest request, WebSocketHandler wsHandler,
- Map<String, Object> attributes) {
- Principal p = super.determineUser(request, wsHandler, attributes);
- if (p == null) {
- p = new UsernamePasswordAuthenticationToken("player" + playerId++, null);
- }
- return p;
- }
-}
diff --git a/backend/src/main/java/org/luxons/sevenwonders/config/TopicSubscriptionInterceptor.java b/backend/src/main/java/org/luxons/sevenwonders/config/TopicSubscriptionInterceptor.java
deleted file mode 100644
index 855b7800..00000000
--- a/backend/src/main/java/org/luxons/sevenwonders/config/TopicSubscriptionInterceptor.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package org.luxons.sevenwonders.config;
-
-import org.luxons.sevenwonders.validation.DestinationAccessValidator;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.messaging.Message;
-import org.springframework.messaging.MessageChannel;
-import org.springframework.messaging.simp.stomp.StompCommand;
-import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
-import org.springframework.messaging.support.ChannelInterceptorAdapter;
-import org.springframework.stereotype.Component;
-
-@Component
-public class TopicSubscriptionInterceptor extends ChannelInterceptorAdapter {
-
- private static final Logger logger = LoggerFactory.getLogger(TopicSubscriptionInterceptor.class);
-
- private final DestinationAccessValidator destinationAccessValidator;
-
- @Autowired
- public TopicSubscriptionInterceptor(DestinationAccessValidator destinationAccessValidator) {
- this.destinationAccessValidator = destinationAccessValidator;
- }
-
- @Override
- public Message<?> preSend(Message<?> message, MessageChannel channel) {
- StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap(message);
- if (StompCommand.SUBSCRIBE.equals(headerAccessor.getCommand())) {
- String username = headerAccessor.getUser().getName();
- String destination = headerAccessor.getDestination();
- if (!destinationAccessValidator.hasAccess(username, destination)) {
- sendForbiddenSubscriptionError(username, destination);
- return null;
- }
- }
- return message;
- }
-
- private void sendForbiddenSubscriptionError(String username, String destination) {
- logger.error(String.format("Player '%s' is not allowed to access %s", username, destination));
- }
-}
diff --git a/backend/src/main/java/org/luxons/sevenwonders/config/WebSecurityConfig.java b/backend/src/main/java/org/luxons/sevenwonders/config/WebSecurityConfig.java
deleted file mode 100644
index 2cfb966d..00000000
--- a/backend/src/main/java/org/luxons/sevenwonders/config/WebSecurityConfig.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package org.luxons.sevenwonders.config;
-
-import org.springframework.context.annotation.Configuration;
-import org.springframework.security.config.annotation.web.builders.HttpSecurity;
-import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
-
-@Configuration
-public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
- @Override
- protected void configure(HttpSecurity httpSecurity) {
- // this disables default authentication settings
- }
-}
diff --git a/backend/src/main/java/org/luxons/sevenwonders/config/WebSocketConfig.java b/backend/src/main/java/org/luxons/sevenwonders/config/WebSocketConfig.java
deleted file mode 100644
index b8018a86..00000000
--- a/backend/src/main/java/org/luxons/sevenwonders/config/WebSocketConfig.java
+++ /dev/null
@@ -1,51 +0,0 @@
-package org.luxons.sevenwonders.config;
-
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.messaging.simp.config.ChannelRegistration;
-import org.springframework.messaging.simp.config.MessageBrokerRegistry;
-import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
-import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
-import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
-import org.springframework.web.socket.server.support.DefaultHandshakeHandler;
-
-@Configuration
-@EnableWebSocketMessageBroker
-public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
-
- private final TopicSubscriptionInterceptor topicSubscriptionInterceptor;
-
- @Autowired
- public WebSocketConfig(TopicSubscriptionInterceptor topicSubscriptionInterceptor) {
- this.topicSubscriptionInterceptor = topicSubscriptionInterceptor;
- }
-
- @Override
- public void configureMessageBroker(MessageBrokerRegistry config) {
- // prefixes for all subscriptions
- config.enableSimpleBroker("/queue", "/topic");
- config.setUserDestinationPrefix("/user");
-
- // /app for normal calls, /topic for subscription events
- config.setApplicationDestinationPrefixes("/app", "/topic");
- }
-
- @Override
- public void registerStompEndpoints(StompEndpointRegistry registry) {
- registry.addEndpoint("/seven-wonders-websocket")
- .setHandshakeHandler(handshakeHandler())
- .setAllowedOrigins("http://localhost:3000") // to allow frontend server proxy requests in dev mode
- .withSockJS();
- }
-
- @Bean
- public DefaultHandshakeHandler handshakeHandler() {
- return new AnonymousUsersHandshakeHandler();
- }
-
- @Override
- public void configureClientInboundChannel(ChannelRegistration registration) {
- registration.interceptors(topicSubscriptionInterceptor);
- }
-}
diff --git a/backend/src/main/java/org/luxons/sevenwonders/controllers/GameBrowserController.java b/backend/src/main/java/org/luxons/sevenwonders/controllers/GameBrowserController.java
deleted file mode 100644
index 9bb96559..00000000
--- a/backend/src/main/java/org/luxons/sevenwonders/controllers/GameBrowserController.java
+++ /dev/null
@@ -1,129 +0,0 @@
-package org.luxons.sevenwonders.controllers;
-
-import java.security.Principal;
-import java.util.Collection;
-import java.util.Collections;
-
-import org.hildan.livedoc.core.annotations.Api;
-import org.luxons.sevenwonders.actions.CreateGameAction;
-import org.luxons.sevenwonders.actions.JoinGameAction;
-import org.luxons.sevenwonders.errors.ApiMisuseException;
-import org.luxons.sevenwonders.lobby.Lobby;
-import org.luxons.sevenwonders.lobby.Player;
-import org.luxons.sevenwonders.repositories.LobbyRepository;
-import org.luxons.sevenwonders.repositories.PlayerRepository;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.messaging.handler.annotation.MessageMapping;
-import org.springframework.messaging.simp.SimpMessagingTemplate;
-import org.springframework.messaging.simp.annotation.SendToUser;
-import org.springframework.messaging.simp.annotation.SubscribeMapping;
-import org.springframework.stereotype.Controller;
-import org.springframework.validation.annotation.Validated;
-
-/**
- * This is the place where the player looks for a game.
- */
-@Api(name = "GameBrowser")
-@Controller
-public class GameBrowserController {
-
- private static final Logger logger = LoggerFactory.getLogger(GameBrowserController.class);
-
- private final LobbyController lobbyController;
-
- private final LobbyRepository lobbyRepository;
-
- private final PlayerRepository playerRepository;
-
- private final SimpMessagingTemplate template;
-
- @Autowired
- public GameBrowserController(LobbyController lobbyController, LobbyRepository lobbyRepository,
- PlayerRepository playerRepository, SimpMessagingTemplate template) {
- this.lobbyController = lobbyController;
- this.lobbyRepository = lobbyRepository;
- this.playerRepository = playerRepository;
- this.template = template;
- }
-
- /**
- * Gets the created or updated games. The list of existing games is received on this topic at once upon
- * subscription, and then each time the list changes.
- *
- * @param principal
- * the connected user's information
- *
- * @return the current list of {@link Lobby}s
- */
- @SubscribeMapping("/games") // prefix /topic not shown
- public Collection<Lobby> listGames(Principal principal) {
- logger.info("Player '{}' subscribed to /topic/games", principal.getName());
- return lobbyRepository.list();
- }
-
- /**
- * Creates a new {@link Lobby}.
- *
- * @param action
- * the action to create the game
- * @param principal
- * the connected user's information
- *
- * @return the newly created {@link Lobby}
- */
- @MessageMapping("/lobby/create")
- @SendToUser("/queue/lobby/joined")
- public Lobby createGame(@Validated CreateGameAction action, Principal principal) {
- checkThatUserIsNotInAGame(principal, "cannot create another game");
-
- Player gameOwner = playerRepository.find(principal.getName());
- Lobby lobby = lobbyRepository.create(action.getGameName(), gameOwner);
-
- logger.info("Game '{}' ({}) created by {} ({})", lobby.getName(), lobby.getId(), gameOwner.getDisplayName(),
- gameOwner.getUsername());
-
- // notify everyone that a new game exists
- template.convertAndSend("/topic/games", Collections.singletonList(lobby));
- return lobby;
- }
-
- /**
- * Joins an existing {@link Lobby}.
- *
- * @param action
- * the action to join the game
- * @param principal
- * the connected user's information
- *
- * @return the {@link Lobby} that has just been joined
- */
- @MessageMapping("/lobby/join")
- @SendToUser("/queue/lobby/joined")
- public Lobby joinGame(@Validated JoinGameAction action, Principal principal) {
- checkThatUserIsNotInAGame(principal, "cannot join another game");
-
- Lobby lobby = lobbyRepository.find(action.getGameId());
- Player newPlayer = playerRepository.find(principal.getName());
- lobby.addPlayer(newPlayer);
-
- logger.info("Player '{}' ({}) joined game {}", newPlayer.getDisplayName(), newPlayer.getUsername(),
- lobby.getName());
- lobbyController.sendLobbyUpdateToPlayers(lobby);
- return lobby;
- }
-
- private void checkThatUserIsNotInAGame(Principal principal, String impossibleActionDescription) {
- Lobby lobby = playerRepository.find(principal.getName()).getLobby();
- if (lobby != null) {
- throw new UserAlreadyInGameException(lobby.getName(), impossibleActionDescription);
- }
- }
-
- static class UserAlreadyInGameException extends ApiMisuseException {
- UserAlreadyInGameException(String gameName, String impossibleActionDescription) {
- super("Client already in game '" + gameName + "', " + impossibleActionDescription);
- }
- }
-}
diff --git a/backend/src/main/java/org/luxons/sevenwonders/controllers/GameController.java b/backend/src/main/java/org/luxons/sevenwonders/controllers/GameController.java
deleted file mode 100644
index f8d60ece..00000000
--- a/backend/src/main/java/org/luxons/sevenwonders/controllers/GameController.java
+++ /dev/null
@@ -1,111 +0,0 @@
-package org.luxons.sevenwonders.controllers;
-
-import java.security.Principal;
-import java.util.List;
-
-import org.hildan.livedoc.core.annotations.Api;
-import org.luxons.sevenwonders.actions.PrepareMoveAction;
-import org.luxons.sevenwonders.game.Game;
-import org.luxons.sevenwonders.game.api.PlayerTurnInfo;
-import org.luxons.sevenwonders.game.api.Table;
-import org.luxons.sevenwonders.game.cards.CardBack;
-import org.luxons.sevenwonders.lobby.Lobby;
-import org.luxons.sevenwonders.lobby.Player;
-import org.luxons.sevenwonders.output.PreparedCard;
-import org.luxons.sevenwonders.repositories.PlayerRepository;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.messaging.handler.annotation.MessageMapping;
-import org.springframework.messaging.simp.SimpMessagingTemplate;
-import org.springframework.stereotype.Controller;
-
-/**
- * This API is for in-game events management.
- */
-@Api(name = "Game")
-@Controller
-public class GameController {
-
- private static final Logger logger = LoggerFactory.getLogger(GameController.class);
-
- private final SimpMessagingTemplate template;
-
- private final PlayerRepository playerRepository;
-
- @Autowired
- public GameController(SimpMessagingTemplate template, PlayerRepository playerRepository) {
- this.template = template;
- this.playerRepository = playerRepository;
- }
-
- /**
- * Notifies the game that the player is ready to receive his hand.
- *
- * @param principal
- * the connected user's information
- */
- @MessageMapping("/game/sayReady")
- public void ready(Principal principal) {
- Player player = playerRepository.find(principal.getName());
- player.setReady(true);
- Game game = player.getGame();
- logger.info("Game {}: player {} is ready for the next turn", game.getId(), player);
-
- Lobby lobby = player.getLobby();
- List<Player> players = lobby.getPlayers();
-
- boolean allReady = players.stream().allMatch(Player::isReady);
- if (allReady) {
- logger.info("Game {}: all players ready, sending turn info", game.getId());
- players.forEach(p -> p.setReady(false));
- sendTurnInfo(players, game);
- } else {
- sendPlayerReady(game.getId(), player);
- }
- }
-
- private void sendTurnInfo(List<Player> players, Game game) {
- for (PlayerTurnInfo turnInfo : game.getCurrentTurnInfo()) {
- Player player = players.get(turnInfo.getPlayerIndex());
- template.convertAndSendToUser(player.getUsername(), "/queue/game/turn", turnInfo);
- }
- }
-
- private void sendPlayerReady(long gameId, Player player) {
- template.convertAndSend("/topic/game/" + gameId + "/playerReady", player.getUsername());
- }
-
- /**
- * Prepares the player's next move. When all players have prepared their moves, all moves are executed.
- *
- * @param action
- * the action to prepare the move
- * @param principal
- * the connected user's information
- */
- @MessageMapping("/game/prepareMove")
- public void prepareMove(PrepareMoveAction action, Principal principal) {
- Player player = playerRepository.find(principal.getName());
- Game game = player.getGame();
- CardBack preparedCardBack = game.prepareMove(player.getIndex(), action.getMove());
- PreparedCard preparedCard = new PreparedCard(player, preparedCardBack);
- logger.info("Game {}: player {} prepared move {}", game.getId(), principal.getName(), action.getMove());
-
- if (game.allPlayersPreparedTheirMove()) {
- logger.info("Game {}: all players have prepared their move, executing turn...", game.getId());
- Table table = game.playTurn();
- sendPlayedMoves(game.getId(), table);
- } else {
- sendPreparedCard(game.getId(), preparedCard);
- }
- }
-
- private void sendPlayedMoves(long gameId, Table table) {
- template.convertAndSend("/topic/game/" + gameId + "/tableUpdates", table);
- }
-
- private void sendPreparedCard(long gameId, PreparedCard preparedCard) {
- template.convertAndSend("/topic/game/" + gameId + "/prepared", preparedCard);
- }
-}
diff --git a/backend/src/main/java/org/luxons/sevenwonders/controllers/HomeController.java b/backend/src/main/java/org/luxons/sevenwonders/controllers/HomeController.java
deleted file mode 100644
index 97bc8b67..00000000
--- a/backend/src/main/java/org/luxons/sevenwonders/controllers/HomeController.java
+++ /dev/null
@@ -1,52 +0,0 @@
-package org.luxons.sevenwonders.controllers;
-
-import java.security.Principal;
-
-import org.hildan.livedoc.core.annotations.Api;
-import org.luxons.sevenwonders.actions.ChooseNameAction;
-import org.luxons.sevenwonders.lobby.Player;
-import org.luxons.sevenwonders.repositories.PlayerRepository;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.messaging.handler.annotation.MessageMapping;
-import org.springframework.messaging.simp.annotation.SendToUser;
-import org.springframework.stereotype.Controller;
-import org.springframework.validation.annotation.Validated;
-
-/**
- * Handles actions in the homepage of the game.
- */
-@Api(name = "Home")
-@Controller
-public class HomeController {
-
- private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
-
- private final PlayerRepository playerRepository;
-
- @Autowired
- public HomeController(PlayerRepository playerRepository) {
- this.playerRepository = playerRepository;
- }
-
- /**
- * Creates/updates the player's name (for the user's session).
- *
- * @param action
- * the action to choose the name of the player
- * @param principal
- * the connected user's information
- *
- * @return the created {@link Player} object
- */
- @MessageMapping("/chooseName")
- @SendToUser("/queue/nameChoice")
- public Player chooseName(@Validated ChooseNameAction action, Principal principal) {
- String username = principal.getName();
- Player player = playerRepository.createOrUpdate(username, action.getPlayerName());
-
- logger.info("Player '{}' chose the name '{}'", username, player.getDisplayName());
- return player;
- }
-}
diff --git a/backend/src/main/java/org/luxons/sevenwonders/controllers/LobbyController.java b/backend/src/main/java/org/luxons/sevenwonders/controllers/LobbyController.java
deleted file mode 100644
index 330a0f7f..00000000
--- a/backend/src/main/java/org/luxons/sevenwonders/controllers/LobbyController.java
+++ /dev/null
@@ -1,141 +0,0 @@
-package org.luxons.sevenwonders.controllers;
-
-import java.security.Principal;
-import java.util.Collections;
-
-import org.hildan.livedoc.core.annotations.Api;
-import org.luxons.sevenwonders.actions.ReorderPlayersAction;
-import org.luxons.sevenwonders.actions.UpdateSettingsAction;
-import org.luxons.sevenwonders.errors.ApiMisuseException;
-import org.luxons.sevenwonders.game.Game;
-import org.luxons.sevenwonders.lobby.Lobby;
-import org.luxons.sevenwonders.lobby.Player;
-import org.luxons.sevenwonders.repositories.PlayerRepository;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.messaging.handler.annotation.MessageMapping;
-import org.springframework.messaging.simp.SimpMessagingTemplate;
-import org.springframework.stereotype.Controller;
-import org.springframework.validation.annotation.Validated;
-
-/**
- * Handles actions in the game's lobby. The lobby is the place where players gather before a game.
- */
-@Api(name = "Lobby")
-@Controller
-public class LobbyController {
-
- private static final Logger logger = LoggerFactory.getLogger(LobbyController.class);
-
- private final PlayerRepository playerRepository;
-
- private final SimpMessagingTemplate template;
-
- @Autowired
- public LobbyController(PlayerRepository playerRepository, SimpMessagingTemplate template) {
- this.playerRepository = playerRepository;
- this.template = template;
- }
-
- /**
- * Leaves the current lobby.
- *
- * @param principal
- * the connected user's information
- */
- @MessageMapping("/lobby/leave")
- public void leave(Principal principal) {
- Lobby lobby = getLobby(principal);
- Player player = lobby.removePlayer(principal.getName());
-
- logger.info("Player {} left game '{}'", player, lobby.getName());
- sendLobbyUpdateToPlayers(lobby);
- }
-
- /**
- * Reorders the players in the current lobby. This can only be done by the lobby's owner.
- *
- * @param action
- * the action to reorder the players
- * @param principal
- * the connected user's information
- */
- @MessageMapping("/lobby/reorderPlayers")
- public void reorderPlayers(@Validated ReorderPlayersAction action, Principal principal) {
- Lobby lobby = getOwnedLobby(principal);
- lobby.reorderPlayers(action.getOrderedPlayers());
-
- logger.info("Players in game '{}' reordered to {}", lobby.getName(), action.getOrderedPlayers());
- sendLobbyUpdateToPlayers(lobby);
- }
-
- /**
- * Updates the game settings. This can only be done by the lobby's owner.
- *
- * @param action
- * the action to update the settings
- * @param principal
- * the connected user's information
- */
- @MessageMapping("/lobby/updateSettings")
- public void updateSettings(@Validated UpdateSettingsAction action, Principal principal) {
- Lobby lobby = getOwnedLobby(principal);
- lobby.setSettings(action.getSettings());
-
- logger.info("Updated settings of game '{}'", lobby.getName());
- sendLobbyUpdateToPlayers(lobby);
- }
-
- void sendLobbyUpdateToPlayers(Lobby lobby) {
- template.convertAndSend("/topic/lobby/" + lobby.getId() + "/updated", lobby);
- template.convertAndSend("/topic/games", Collections.singletonList(lobby));
- }
-
- /**
- * Starts the game.
- *
- * @param principal
- * the connected user's information
- */
- @MessageMapping("/lobby/startGame")
- public void startGame(Principal principal) {
- Lobby lobby = getOwnedLobby(principal);
- Game game = lobby.startGame();
-
- logger.info("Game {} successfully started", game.getId());
- template.convertAndSend("/topic/lobby/" + lobby.getId() + "/started", "");
- }
-
- private Lobby getOwnedLobby(Principal principal) {
- Lobby lobby = getLobby(principal);
- if (!lobby.isOwner(principal.getName())) {
- throw new PlayerIsNotOwnerException(principal.getName());
- }
- return lobby;
- }
-
- private Lobby getLobby(Principal principal) {
- Lobby lobby = getPlayer(principal).getLobby();
- if (lobby == null) {
- throw new PlayerNotInLobbyException(principal.getName());
- }
- return lobby;
- }
-
- private Player getPlayer(Principal principal) {
- return playerRepository.find(principal.getName());
- }
-
- static class PlayerNotInLobbyException extends ApiMisuseException {
- PlayerNotInLobbyException(String username) {
- super("User " + username + " is not in a lobby, create or join a game first");
- }
- }
-
- static class PlayerIsNotOwnerException extends ApiMisuseException {
- PlayerIsNotOwnerException(String username) {
- super("User " + username + " does not own the lobby he's in");
- }
- }
-}
diff --git a/backend/src/main/java/org/luxons/sevenwonders/doc/Documentation.java b/backend/src/main/java/org/luxons/sevenwonders/doc/Documentation.java
deleted file mode 100644
index e27bc2ff..00000000
--- a/backend/src/main/java/org/luxons/sevenwonders/doc/Documentation.java
+++ /dev/null
@@ -1,6 +0,0 @@
-package org.luxons.sevenwonders.doc;
-
-public class Documentation {
-
- public static final String GROUP_ACTIONS = "Actions";
-}
diff --git a/backend/src/main/java/org/luxons/sevenwonders/errors/ApiMisuseException.java b/backend/src/main/java/org/luxons/sevenwonders/errors/ApiMisuseException.java
deleted file mode 100644
index 0d7d1a82..00000000
--- a/backend/src/main/java/org/luxons/sevenwonders/errors/ApiMisuseException.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package org.luxons.sevenwonders.errors;
-
-public class ApiMisuseException extends RuntimeException {
-
- public ApiMisuseException(String message) {
- super(message);
- }
-}
diff --git a/backend/src/main/java/org/luxons/sevenwonders/errors/ErrorType.java b/backend/src/main/java/org/luxons/sevenwonders/errors/ErrorType.java
deleted file mode 100644
index 16a40046..00000000
--- a/backend/src/main/java/org/luxons/sevenwonders/errors/ErrorType.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package org.luxons.sevenwonders.errors;
-
-enum ErrorType {
- USER_INPUT,
- VALIDATION,
- CLIENT,
- SERVER
-}
diff --git a/backend/src/main/java/org/luxons/sevenwonders/errors/ExceptionHandler.java b/backend/src/main/java/org/luxons/sevenwonders/errors/ExceptionHandler.java
deleted file mode 100644
index 628da4f8..00000000
--- a/backend/src/main/java/org/luxons/sevenwonders/errors/ExceptionHandler.java
+++ /dev/null
@@ -1,81 +0,0 @@
-package org.luxons.sevenwonders.errors;
-
-import java.util.List;
-import java.util.Locale;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.MessageSource;
-import org.springframework.messaging.converter.MessageConversionException;
-import org.springframework.messaging.handler.annotation.MessageExceptionHandler;
-import org.springframework.messaging.handler.annotation.support.MethodArgumentNotValidException;
-import org.springframework.messaging.simp.annotation.SendToUser;
-import org.springframework.validation.BindingResult;
-import org.springframework.validation.ObjectError;
-import org.springframework.web.bind.annotation.ControllerAdvice;
-
-@ControllerAdvice
-public class ExceptionHandler {
-
- private static final Logger logger = LoggerFactory.getLogger(ExceptionHandler.class);
-
- private static final String ERROR_CODE_VALIDATION = "INVALID_DATA";
-
- private static final String ERROR_CODE_CONVERSION = "INVALID_MESSAGE_FORMAT";
-
- private static final String ERROR_MSG_VALIDATION = "Invalid input data";
-
- private static final String ERROR_MSG_CONVERSION = "Invalid input format";
-
- private final MessageSource messageSource;
-
- @Autowired
- public ExceptionHandler(MessageSource messageSource) {
- this.messageSource = messageSource;
- }
-
- @MessageExceptionHandler
- @SendToUser("/queue/errors")
- public UIError handleUserInputError(UserInputException exception) {
- logger.error("Incorrect user input: " + exception.getMessage());
- String messageKey = exception.getMessageResourceKey();
- String message = messageSource.getMessage(messageKey, exception.getParams(), messageKey, Locale.US);
- return new UIError(messageKey, message, ErrorType.USER_INPUT);
- }
-
- @MessageExceptionHandler
- @SendToUser("/queue/errors")
- public UIError handleValidationError(MethodArgumentNotValidException exception) {
- logger.error("Invalid input", exception);
- UIError uiError = new UIError(ERROR_CODE_VALIDATION, ERROR_MSG_VALIDATION, ErrorType.VALIDATION);
-
- BindingResult result = exception.getBindingResult();
- if (result != null) {
- List<ObjectError> errors = result.getAllErrors();
- uiError.addDetails(errors);
- }
- return uiError;
- }
-
- @MessageExceptionHandler
- @SendToUser("/queue/errors")
- public UIError handleConversionError(MessageConversionException exception) {
- logger.error("Error interpreting the message", exception);
- return new UIError(ERROR_CODE_CONVERSION, ERROR_MSG_CONVERSION, ErrorType.VALIDATION);
- }
-
- @MessageExceptionHandler
- @SendToUser("/queue/errors")
- public UIError handleApiError(ApiMisuseException exception) {
- logger.error("Invalid API input", exception);
- return new UIError(exception.getClass().getSimpleName(), exception.getMessage(), ErrorType.CLIENT);
- }
-
- @MessageExceptionHandler
- @SendToUser("/queue/errors")
- public UIError handleUnexpectedInternalError(Throwable exception) {
- logger.error("Uncaught exception thrown during message handling", exception);
- return new UIError(exception.getClass().getSimpleName(), exception.getMessage(), ErrorType.SERVER);
- }
-}
diff --git a/backend/src/main/java/org/luxons/sevenwonders/errors/UIError.java b/backend/src/main/java/org/luxons/sevenwonders/errors/UIError.java
deleted file mode 100644
index fbc1ee1d..00000000
--- a/backend/src/main/java/org/luxons/sevenwonders/errors/UIError.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package org.luxons.sevenwonders.errors;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import org.springframework.validation.FieldError;
-import org.springframework.validation.ObjectError;
-
-public class UIError {
-
- private final String code;
-
- private final String message;
-
- private final ErrorType type;
-
- private List<UIErrorDetail> details = new ArrayList<>();
-
- UIError(String code, String message, ErrorType type) {
- this.code = code;
- this.message = message;
- this.type = type;
- }
-
- public String getCode() {
- return code;
- }
-
- public String getMessage() {
- return message;
- }
-
- public ErrorType getType() {
- return type;
- }
-
- public List<UIErrorDetail> getDetails() {
- return details;
- }
-
- void addDetails(List<ObjectError> objectErrors) {
- for (ObjectError objectError : objectErrors) {
- this.details.add(convertError(objectError));
- }
- }
-
- private UIErrorDetail convertError(ObjectError objectError) {
- if (objectError instanceof FieldError) {
- return new UIErrorDetail((FieldError) objectError);
- } else {
- return new UIErrorDetail(objectError);
- }
- }
-}
diff --git a/backend/src/main/java/org/luxons/sevenwonders/errors/UIErrorDetail.java b/backend/src/main/java/org/luxons/sevenwonders/errors/UIErrorDetail.java
deleted file mode 100644
index dc4250bb..00000000
--- a/backend/src/main/java/org/luxons/sevenwonders/errors/UIErrorDetail.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package org.luxons.sevenwonders.errors;
-
-import org.springframework.validation.FieldError;
-import org.springframework.validation.ObjectError;
-
-class UIErrorDetail {
-
- private final Object rejectedValue;
-
- private final String path;
-
- private final String message;
-
- UIErrorDetail(FieldError error) {
- rejectedValue = error.getRejectedValue();
- path = error.getObjectName() + '.' + error.getField();
- message = "Invalid value for field '" + error.getField() + "': " + error.getDefaultMessage();
- }
-
- UIErrorDetail(ObjectError error) {
- rejectedValue = null;
- path = error.getObjectName();
- message = "Invalid value for object '" + error.getObjectName() + "': " + error.getDefaultMessage();
- }
-
- public Object getRejectedValue() {
- return rejectedValue;
- }
-
- public String getPath() {
- return path;
- }
-
- public String getMessage() {
- return message;
- }
-}
diff --git a/backend/src/main/java/org/luxons/sevenwonders/errors/UserInputException.java b/backend/src/main/java/org/luxons/sevenwonders/errors/UserInputException.java
deleted file mode 100644
index 4033a696..00000000
--- a/backend/src/main/java/org/luxons/sevenwonders/errors/UserInputException.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package org.luxons.sevenwonders.errors;
-
-public class UserInputException extends RuntimeException {
-
- private final String messageResourceKey;
-
- private final Object[] params;
-
- public UserInputException(String messageResourceKey, Object... params) {
- this.messageResourceKey = messageResourceKey;
- this.params = params;
- }
-
- String getMessageResourceKey() {
- return messageResourceKey;
- }
-
- Object[] getParams() {
- return params;
- }
-}
diff --git a/backend/src/main/java/org/luxons/sevenwonders/lobby/Lobby.java b/backend/src/main/java/org/luxons/sevenwonders/lobby/Lobby.java
deleted file mode 100644
index 1ddde0d7..00000000
--- a/backend/src/main/java/org/luxons/sevenwonders/lobby/Lobby.java
+++ /dev/null
@@ -1,168 +0,0 @@
-package org.luxons.sevenwonders.lobby;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import org.luxons.sevenwonders.game.Game;
-import org.luxons.sevenwonders.game.api.CustomizableSettings;
-import org.luxons.sevenwonders.game.data.GameDefinition;
-
-public class Lobby {
-
- private final long id;
-
- private final String name;
-
- private final Player owner;
-
- private final transient GameDefinition gameDefinition;
-
- private final List<Player> players;
-
- private CustomizableSettings settings;
-
- private State state = State.LOBBY;
-
- public Lobby(long id, String name, Player owner, GameDefinition gameDefinition) {
- this.id = id;
- this.name = name;
- this.owner = owner;
- this.gameDefinition = gameDefinition;
- this.players = new ArrayList<>(gameDefinition.getMinPlayers());
- this.settings = new CustomizableSettings();
- addPlayer(owner);
- }
-
- public long getId() {
- return id;
- }
-
- public String getName() {
- return name;
- }
-
- public String getOwner() {
- return owner.getUsername();
- }
-
- public List<Player> getPlayers() {
- return players;
- }
-
- public CustomizableSettings getSettings() {
- return settings;
- }
-
- public void setSettings(CustomizableSettings settings) {
- this.settings = settings;
- }
-
- public State getState() {
- return state;
- }
-
- public synchronized void addPlayer(Player player) throws GameAlreadyStartedException, PlayerOverflowException {
- if (hasStarted()) {
- throw new GameAlreadyStartedException(name);
- }
- if (maxPlayersReached()) {
- throw new PlayerOverflowException(gameDefinition.getMaxPlayers());
- }
- if (playerNameAlreadyUsed(player.getDisplayName())) {
- throw new PlayerNameAlreadyUsedException(player.getDisplayName(), name);
- }
- player.setIndex(players.size());
- player.setLobby(this);
- players.add(player);
- }
-
- private boolean hasStarted() {
- return state != State.LOBBY;
- }
-
- private boolean maxPlayersReached() {
- return players.size() >= gameDefinition.getMaxPlayers();
- }
-
- private boolean playerNameAlreadyUsed(String name) {
- return players.stream().anyMatch(p -> p.getDisplayName().equals(name));
- }
-
- public synchronized Game startGame() throws PlayerUnderflowException {
- if (!hasEnoughPlayers()) {
- throw new PlayerUnderflowException(gameDefinition.getMinPlayers());
- }
- state = State.PLAYING;
- Game game = gameDefinition.initGame(id, settings, players.size());
- players.forEach(p -> p.setGame(game));
- return game;
- }
-
- private boolean hasEnoughPlayers() {
- return players.size() >= gameDefinition.getMinPlayers();
- }
-
- public void reorderPlayers(List<String> orderedUsernames) throws UnknownPlayerException {
- List<Player> formerList = new ArrayList<>(players);
- players.clear();
- for (int i = 0; i < orderedUsernames.size(); i++) {
- Player player = getPlayer(formerList, orderedUsernames.get(i));
- players.add(player);
- player.setIndex(i);
- }
- }
-
- private static Player getPlayer(List<Player> players, String username) throws UnknownPlayerException {
- return players.stream()
- .filter(p -> p.getUsername().equals(username))
- .findAny()
- .orElseThrow(() -> new UnknownPlayerException(username));
- }
-
- public boolean isOwner(String username) {
- return owner.getUsername().equals(username);
- }
-
- public boolean containsUser(String username) {
- return players.stream().anyMatch(p -> p.getUsername().equals(username));
- }
-
- public Player removePlayer(String username) throws UnknownPlayerException {
- Player player = getPlayer(players, username);
- players.remove(player);
- player.setIndex(-1);
- player.setLobby(null);
- player.setGame(null);
- return player;
- }
-
- static class GameAlreadyStartedException extends IllegalStateException {
- GameAlreadyStartedException(String name) {
- super(String.format("Game '%s' has already started", name));
- }
- }
-
- static class PlayerOverflowException extends IllegalStateException {
- PlayerOverflowException(int max) {
- super(String.format("Maximum %d players allowed", max));
- }
- }
-
- static class PlayerUnderflowException extends IllegalStateException {
- PlayerUnderflowException(int min) {
- super(String.format("Minimum %d players required to start a game", min));
- }
- }
-
- static class PlayerNameAlreadyUsedException extends IllegalStateException {
- PlayerNameAlreadyUsedException(String displayName, String gameName) {
- super(String.format("Name '%s' is already used by a player in game '%s'", displayName, gameName));
- }
- }
-
- static class UnknownPlayerException extends IllegalStateException {
- UnknownPlayerException(String username) {
- super(String.format("Unknown player '%s'", username));
- }
- }
-}
diff --git a/backend/src/main/java/org/luxons/sevenwonders/lobby/Player.java b/backend/src/main/java/org/luxons/sevenwonders/lobby/Player.java
deleted file mode 100644
index 496e1e67..00000000
--- a/backend/src/main/java/org/luxons/sevenwonders/lobby/Player.java
+++ /dev/null
@@ -1,76 +0,0 @@
-package org.luxons.sevenwonders.lobby;
-
-import org.luxons.sevenwonders.game.Game;
-
-import com.fasterxml.jackson.annotation.JsonIgnore;
-
-public class Player {
-
- private final String username;
-
- private String displayName;
-
- private int index;
-
- private boolean ready;
-
- private transient Lobby lobby;
-
- private transient Game game;
-
- public Player(String username, String displayName) {
- this.username = username;
- this.displayName = displayName;
- }
-
- public String getUsername() {
- return username;
- }
-
- public String getDisplayName() {
- return displayName;
- }
-
- public void setDisplayName(String displayName) {
- this.displayName = displayName;
- }
-
- public int getIndex() {
- return index;
- }
-
- public void setIndex(int index) {
- this.index = index;
- }
-
- public boolean isReady() {
- return ready;
- }
-
- public void setReady(boolean ready) {
- this.ready = ready;
- }
-
- @JsonIgnore
- public Lobby getLobby() {
- return lobby;
- }
-
- public void setLobby(Lobby lobby) {
- this.lobby = lobby;
- }
-
- @JsonIgnore
- public Game getGame() {
- return game;
- }
-
- public void setGame(Game game) {
- this.game = game;
- }
-
- @Override
- public String toString() {
- return "'" + displayName + "' (" + username + ")";
- }
-}
diff --git a/backend/src/main/java/org/luxons/sevenwonders/lobby/State.java b/backend/src/main/java/org/luxons/sevenwonders/lobby/State.java
deleted file mode 100644
index 17f3b101..00000000
--- a/backend/src/main/java/org/luxons/sevenwonders/lobby/State.java
+++ /dev/null
@@ -1,6 +0,0 @@
-package org.luxons.sevenwonders.lobby;
-
-public enum State {
- LOBBY,
- PLAYING
-}
diff --git a/backend/src/main/java/org/luxons/sevenwonders/output/PreparedCard.java b/backend/src/main/java/org/luxons/sevenwonders/output/PreparedCard.java
deleted file mode 100644
index 5a122274..00000000
--- a/backend/src/main/java/org/luxons/sevenwonders/output/PreparedCard.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package org.luxons.sevenwonders.output;
-
-import org.luxons.sevenwonders.game.cards.CardBack;
-import org.luxons.sevenwonders.lobby.Player;
-
-public class PreparedCard {
-
- private final Player player;
-
- private final CardBack cardBack;
-
- public PreparedCard(Player player, CardBack cardBack) {
- this.player = player;
- this.cardBack = cardBack;
- }
-
- public Player getPlayer() {
- return player;
- }
-
- public CardBack getCardBack() {
- return cardBack;
- }
-}
diff --git a/backend/src/main/java/org/luxons/sevenwonders/repositories/LobbyRepository.java b/backend/src/main/java/org/luxons/sevenwonders/repositories/LobbyRepository.java
deleted file mode 100644
index b1b11c82..00000000
--- a/backend/src/main/java/org/luxons/sevenwonders/repositories/LobbyRepository.java
+++ /dev/null
@@ -1,59 +0,0 @@
-package org.luxons.sevenwonders.repositories;
-
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-
-import org.luxons.sevenwonders.game.data.GameDefinitionLoader;
-import org.luxons.sevenwonders.lobby.Lobby;
-import org.luxons.sevenwonders.lobby.Player;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Repository;
-
-@Repository
-public class LobbyRepository {
-
- private final GameDefinitionLoader gameDefinitionLoader;
-
- private Map<Long, Lobby> lobbies = new HashMap<>();
-
- private long lastGameId = 0;
-
- @Autowired
- public LobbyRepository() {
- this.gameDefinitionLoader = new GameDefinitionLoader();
- }
-
- public Collection<Lobby> list() {
- return lobbies.values();
- }
-
- public Lobby create(String gameName, Player owner) {
- long id = lastGameId++;
- Lobby lobby = new Lobby(id, gameName, owner, gameDefinitionLoader.getGameDefinition());
- lobbies.put(id, lobby);
- return lobby;
- }
-
- public Lobby find(long lobbyId) throws LobbyNotFoundException {
- Lobby lobby = lobbies.get(lobbyId);
- if (lobby == null) {
- throw new LobbyNotFoundException(lobbyId);
- }
- return lobby;
- }
-
- public Lobby remove(long lobbyId) throws LobbyNotFoundException {
- Lobby lobby = lobbies.remove(lobbyId);
- if (lobby == null) {
- throw new LobbyNotFoundException(lobbyId);
- }
- return lobby;
- }
-
- public static class LobbyNotFoundException extends RuntimeException {
- LobbyNotFoundException(long id) {
- super("Lobby not found for id '" + id + "'");
- }
- }
-}
diff --git a/backend/src/main/java/org/luxons/sevenwonders/repositories/PlayerRepository.java b/backend/src/main/java/org/luxons/sevenwonders/repositories/PlayerRepository.java
deleted file mode 100644
index 62c827db..00000000
--- a/backend/src/main/java/org/luxons/sevenwonders/repositories/PlayerRepository.java
+++ /dev/null
@@ -1,60 +0,0 @@
-package org.luxons.sevenwonders.repositories;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import org.luxons.sevenwonders.errors.ApiMisuseException;
-import org.luxons.sevenwonders.lobby.Player;
-import org.springframework.stereotype.Repository;
-
-@Repository
-public class PlayerRepository {
-
- private Map<String, Player> players = new HashMap<>();
-
- public boolean contains(String username) {
- return players.containsKey(username);
- }
-
- public Player createOrUpdate(String username, String displayName) {
- if (players.containsKey(username)) {
- return update(username, displayName);
- } else {
- return create(username, displayName);
- }
- }
-
- private Player create(String username, String displayName) {
- Player player = new Player(username, displayName);
- players.put(username, player);
- return player;
- }
-
- private Player update(String username, String displayName) throws PlayerNotFoundException {
- Player player = find(username);
- player.setDisplayName(displayName);
- return player;
- }
-
- public Player find(String username) throws PlayerNotFoundException {
- Player player = players.get(username);
- if (player == null) {
- throw new PlayerNotFoundException(username);
- }
- return player;
- }
-
- public Player remove(String username) {
- Player player = players.remove(username);
- if (player == null) {
- throw new PlayerNotFoundException(username);
- }
- return player;
- }
-
- public static class PlayerNotFoundException extends ApiMisuseException {
- PlayerNotFoundException(String username) {
- super("Player '" + username + "' doesn't exist");
- }
- }
-}
diff --git a/backend/src/main/java/org/luxons/sevenwonders/validation/DestinationAccessValidator.java b/backend/src/main/java/org/luxons/sevenwonders/validation/DestinationAccessValidator.java
deleted file mode 100644
index cd17b0a5..00000000
--- a/backend/src/main/java/org/luxons/sevenwonders/validation/DestinationAccessValidator.java
+++ /dev/null
@@ -1,66 +0,0 @@
-package org.luxons.sevenwonders.validation;
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import org.luxons.sevenwonders.lobby.Lobby;
-import org.luxons.sevenwonders.repositories.LobbyRepository;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Component;
-
-@Component
-public class DestinationAccessValidator {
-
- private static final Pattern lobbyDestination = Pattern.compile(".*?/lobby/(?<id>\\d+?)(/.*)?");
-
- private static final Pattern gameDestination = Pattern.compile(".*?/game/(?<id>\\d+?)(/.*)?");
-
- private final LobbyRepository lobbyRepository;
-
- @Autowired
- public DestinationAccessValidator(LobbyRepository lobbyRepository) {
- this.lobbyRepository = lobbyRepository;
- }
-
- public boolean hasAccess(String username, String destination) {
- if (username == null) {
- // unnamed user cannot belong to anything
- return false;
- }
- if (hasForbiddenGameReference(username, destination)) {
- return false;
- }
- if (hasForbiddenLobbyReference(username, destination)) {
- return false;
- }
- return true;
- }
-
- private boolean hasForbiddenGameReference(String username, String destination) {
- Matcher gameMatcher = gameDestination.matcher(destination);
- if (!gameMatcher.matches()) {
- return false; // no game reference is always OK
- }
- int gameId = extractId(gameMatcher);
- return !isUserInLobby(username, gameId);
- }
-
- private boolean hasForbiddenLobbyReference(String username, String destination) {
- Matcher lobbyMatcher = lobbyDestination.matcher(destination);
- if (!lobbyMatcher.matches()) {
- return false; // no lobby reference is always OK
- }
- int lobbyId = extractId(lobbyMatcher);
- return !isUserInLobby(username, lobbyId);
- }
-
- private boolean isUserInLobby(String username, int lobbyId) {
- Lobby lobby = lobbyRepository.find(lobbyId);
- return lobby.containsUser(username);
- }
-
- private static int extractId(Matcher matcher) {
- String id = matcher.group("id");
- return Integer.parseInt(id);
- }
-}
diff --git a/backend/src/main/kotlin/org/luxons/sevenwonders/SevenWonders.kt b/backend/src/main/kotlin/org/luxons/sevenwonders/SevenWonders.kt
new file mode 100644
index 00000000..04f03956
--- /dev/null
+++ b/backend/src/main/kotlin/org/luxons/sevenwonders/SevenWonders.kt
@@ -0,0 +1,13 @@
+package org.luxons.sevenwonders
+
+import org.hildan.livedoc.spring.boot.starter.EnableJSONDoc
+import org.springframework.boot.autoconfigure.SpringBootApplication
+import org.springframework.boot.runApplication
+
+@SpringBootApplication
+@EnableJSONDoc
+class SevenWonders
+
+fun main(args: Array<String>) {
+ runApplication<SevenWonders>(*args)
+}
diff --git a/backend/src/main/kotlin/org/luxons/sevenwonders/actions/ChooseNameAction.kt b/backend/src/main/kotlin/org/luxons/sevenwonders/actions/ChooseNameAction.kt
new file mode 100644
index 00000000..ab444780
--- /dev/null
+++ b/backend/src/main/kotlin/org/luxons/sevenwonders/actions/ChooseNameAction.kt
@@ -0,0 +1,19 @@
+package org.luxons.sevenwonders.actions
+
+import org.hildan.livedoc.core.annotations.types.ApiType
+import org.hildan.livedoc.core.annotations.types.ApiTypeProperty
+import org.luxons.sevenwonders.doc.Documentation
+import javax.validation.constraints.Size
+
+/**
+ * The action to choose the player's name. This is the first action that should be called.
+ */
+@ApiType(group = Documentation.GROUP_ACTIONS)
+class ChooseNameAction(
+ /**
+ * The display name of the player. May contain spaces and special characters.
+ */
+ @Size(min = 2, max = 20)
+ @ApiTypeProperty(required = true)
+ val playerName: String
+)
diff --git a/backend/src/main/kotlin/org/luxons/sevenwonders/actions/CreateGameAction.kt b/backend/src/main/kotlin/org/luxons/sevenwonders/actions/CreateGameAction.kt
new file mode 100644
index 00000000..fbe598f2
--- /dev/null
+++ b/backend/src/main/kotlin/org/luxons/sevenwonders/actions/CreateGameAction.kt
@@ -0,0 +1,21 @@
+package org.luxons.sevenwonders.actions
+
+import org.hildan.livedoc.core.annotations.types.ApiType
+import org.hildan.livedoc.core.annotations.types.ApiTypeProperty
+import org.luxons.sevenwonders.doc.Documentation
+import javax.validation.constraints.Size
+
+/**
+ * The action to create a game.
+ */
+@ApiType(group = Documentation.GROUP_ACTIONS)
+class CreateGameAction(
+ /**
+ * The name of the game to create.
+ */
+ @Size(min = 2, max = 30)
+ @ApiTypeProperty(required = true)
+ val gameName: String
+)
+
+
diff --git a/backend/src/main/kotlin/org/luxons/sevenwonders/actions/JoinGameAction.kt b/backend/src/main/kotlin/org/luxons/sevenwonders/actions/JoinGameAction.kt
new file mode 100644
index 00000000..002309b3
--- /dev/null
+++ b/backend/src/main/kotlin/org/luxons/sevenwonders/actions/JoinGameAction.kt
@@ -0,0 +1,17 @@
+package org.luxons.sevenwonders.actions
+
+import org.hildan.livedoc.core.annotations.types.ApiType
+import org.hildan.livedoc.core.annotations.types.ApiTypeProperty
+import org.luxons.sevenwonders.doc.Documentation
+
+/**
+ * The action to join a game.
+ */
+@ApiType(group = Documentation.GROUP_ACTIONS)
+class JoinGameAction(
+ /**
+ * The ID of the game to join.
+ */
+ @ApiTypeProperty(required = true)
+ val gameId: Long
+)
diff --git a/backend/src/main/kotlin/org/luxons/sevenwonders/actions/PrepareMoveAction.kt b/backend/src/main/kotlin/org/luxons/sevenwonders/actions/PrepareMoveAction.kt
new file mode 100644
index 00000000..6b39c486
--- /dev/null
+++ b/backend/src/main/kotlin/org/luxons/sevenwonders/actions/PrepareMoveAction.kt
@@ -0,0 +1,18 @@
+package org.luxons.sevenwonders.actions
+
+import org.hildan.livedoc.core.annotations.types.ApiType
+import org.hildan.livedoc.core.annotations.types.ApiTypeProperty
+import org.luxons.sevenwonders.doc.Documentation
+import org.luxons.sevenwonders.game.api.PlayerMove
+
+/**
+ * The action to prepare the next move during a game.
+ */
+@ApiType(group = Documentation.GROUP_ACTIONS)
+class PrepareMoveAction(
+ /**
+ * The move to prepare.
+ */
+ @ApiTypeProperty(required = true)
+ val move: PlayerMove
+)
diff --git a/backend/src/main/kotlin/org/luxons/sevenwonders/actions/ReorderPlayersAction.kt b/backend/src/main/kotlin/org/luxons/sevenwonders/actions/ReorderPlayersAction.kt
new file mode 100644
index 00000000..79a32137
--- /dev/null
+++ b/backend/src/main/kotlin/org/luxons/sevenwonders/actions/ReorderPlayersAction.kt
@@ -0,0 +1,18 @@
+package org.luxons.sevenwonders.actions
+
+import org.hildan.livedoc.core.annotations.types.ApiType
+import org.hildan.livedoc.core.annotations.types.ApiTypeProperty
+import org.luxons.sevenwonders.doc.Documentation
+
+/**
+ * The action to update the order of the players around the table. Can only be called in the lobby by the owner of the
+ * game.
+ */
+@ApiType(group = Documentation.GROUP_ACTIONS)
+class ReorderPlayersAction(
+ /**
+ * The list of usernames of the players, in the new order.
+ */
+ @ApiTypeProperty(required = true)
+ val orderedPlayers: List<String>
+)
diff --git a/backend/src/main/kotlin/org/luxons/sevenwonders/actions/UpdateSettingsAction.kt b/backend/src/main/kotlin/org/luxons/sevenwonders/actions/UpdateSettingsAction.kt
new file mode 100644
index 00000000..d13e5b45
--- /dev/null
+++ b/backend/src/main/kotlin/org/luxons/sevenwonders/actions/UpdateSettingsAction.kt
@@ -0,0 +1,18 @@
+package org.luxons.sevenwonders.actions
+
+import org.hildan.livedoc.core.annotations.types.ApiType
+import org.hildan.livedoc.core.annotations.types.ApiTypeProperty
+import org.luxons.sevenwonders.doc.Documentation
+import org.luxons.sevenwonders.game.api.CustomizableSettings
+
+/**
+ * The action to update the settings of the game. Can only be called in the lobby by the owner of the game.
+ */
+@ApiType(group = Documentation.GROUP_ACTIONS)
+class UpdateSettingsAction(
+ /**
+ * The new values for the settings.
+ */
+ @ApiTypeProperty(required = true)
+ val settings: CustomizableSettings
+)
diff --git a/backend/src/main/kotlin/org/luxons/sevenwonders/config/AnonymousUsersHandshakeHandler.kt b/backend/src/main/kotlin/org/luxons/sevenwonders/config/AnonymousUsersHandshakeHandler.kt
new file mode 100644
index 00000000..db707d1b
--- /dev/null
+++ b/backend/src/main/kotlin/org/luxons/sevenwonders/config/AnonymousUsersHandshakeHandler.kt
@@ -0,0 +1,27 @@
+package org.luxons.sevenwonders.config
+
+import org.springframework.http.server.ServerHttpRequest
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
+import org.springframework.web.socket.WebSocketHandler
+import org.springframework.web.socket.server.support.DefaultHandshakeHandler
+import java.security.Principal
+
+/**
+ * Generates [Principal] objects for anonymous users in the form "playerX", where X is an auto-incremented number.
+ */
+internal class AnonymousUsersHandshakeHandler : DefaultHandshakeHandler() {
+
+ private var playerId = 0
+
+ override fun determineUser(
+ request: ServerHttpRequest,
+ wsHandler: WebSocketHandler,
+ attributes: Map<String, Any>
+ ): Principal? {
+ var p = super.determineUser(request, wsHandler, attributes)
+ if (p == null) {
+ p = UsernamePasswordAuthenticationToken("player" + playerId++, null)
+ }
+ return p
+ }
+}
diff --git a/backend/src/main/kotlin/org/luxons/sevenwonders/config/TopicSubscriptionInterceptor.kt b/backend/src/main/kotlin/org/luxons/sevenwonders/config/TopicSubscriptionInterceptor.kt
new file mode 100644
index 00000000..f4c55c2c
--- /dev/null
+++ b/backend/src/main/kotlin/org/luxons/sevenwonders/config/TopicSubscriptionInterceptor.kt
@@ -0,0 +1,38 @@
+package org.luxons.sevenwonders.config
+
+import org.luxons.sevenwonders.validation.DestinationAccessValidator
+import org.slf4j.LoggerFactory
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.messaging.Message
+import org.springframework.messaging.MessageChannel
+import org.springframework.messaging.simp.stomp.StompCommand
+import org.springframework.messaging.simp.stomp.StompHeaderAccessor
+import org.springframework.messaging.support.ChannelInterceptor
+import org.springframework.stereotype.Component
+
+@Component
+class TopicSubscriptionInterceptor @Autowired constructor(
+ private val destinationAccessValidator: DestinationAccessValidator
+) : ChannelInterceptor {
+
+ override fun preSend(message: Message<*>, channel: MessageChannel): Message<*>? {
+ val headerAccessor = StompHeaderAccessor.wrap(message)
+ if (StompCommand.SUBSCRIBE == headerAccessor.command) {
+ val username = headerAccessor.user!!.name
+ val destination = headerAccessor.destination!!
+ if (!destinationAccessValidator.hasAccess(username, destination)) {
+ sendForbiddenSubscriptionError(username, destination)
+ return null
+ }
+ }
+ return message
+ }
+
+ private fun sendForbiddenSubscriptionError(username: String, destination: String?) {
+ logger.error(String.format("Player '%s' is not allowed to access %s", username, destination))
+ }
+
+ companion object {
+ private val logger = LoggerFactory.getLogger(TopicSubscriptionInterceptor::class.java)
+ }
+}
diff --git a/backend/src/main/kotlin/org/luxons/sevenwonders/config/WebSecurityConfig.kt b/backend/src/main/kotlin/org/luxons/sevenwonders/config/WebSecurityConfig.kt
new file mode 100644
index 00000000..06b2bc90
--- /dev/null
+++ b/backend/src/main/kotlin/org/luxons/sevenwonders/config/WebSecurityConfig.kt
@@ -0,0 +1,12 @@
+package org.luxons.sevenwonders.config
+
+import org.springframework.context.annotation.Configuration
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
+
+@Configuration
+class WebSecurityConfig : WebSecurityConfigurerAdapter() {
+
+ // this disables default authentication settings
+ override fun configure(httpSecurity: HttpSecurity) = Unit
+}
diff --git a/backend/src/main/kotlin/org/luxons/sevenwonders/config/WebSocketConfig.kt b/backend/src/main/kotlin/org/luxons/sevenwonders/config/WebSocketConfig.kt
new file mode 100644
index 00000000..bebb3233
--- /dev/null
+++ b/backend/src/main/kotlin/org/luxons/sevenwonders/config/WebSocketConfig.kt
@@ -0,0 +1,42 @@
+package org.luxons.sevenwonders.config
+
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.messaging.simp.config.ChannelRegistration
+import org.springframework.messaging.simp.config.MessageBrokerRegistry
+import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker
+import org.springframework.web.socket.config.annotation.StompEndpointRegistry
+import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer
+import org.springframework.web.socket.server.support.DefaultHandshakeHandler
+
+@Configuration
+@EnableWebSocketMessageBroker
+class WebSocketConfig @Autowired constructor(private val topicSubscriptionInterceptor: TopicSubscriptionInterceptor) :
+ WebSocketMessageBrokerConfigurer {
+
+ override fun configureMessageBroker(config: MessageBrokerRegistry) {
+ // prefixes for all subscriptions
+ config.enableSimpleBroker("/queue", "/topic")
+ config.setUserDestinationPrefix("/user")
+
+ // /app for normal calls, /topic for subscription events
+ config.setApplicationDestinationPrefixes("/app", "/topic")
+ }
+
+ override fun registerStompEndpoints(registry: StompEndpointRegistry) {
+ registry.addEndpoint("/seven-wonders-websocket")
+ .setHandshakeHandler(handshakeHandler())
+ .setAllowedOrigins("http://localhost:3000") // to allow frontend server proxy requests in dev mode
+ .withSockJS()
+ }
+
+ @Bean
+ fun handshakeHandler(): DefaultHandshakeHandler {
+ return AnonymousUsersHandshakeHandler()
+ }
+
+ override fun configureClientInboundChannel(registration: ChannelRegistration) {
+ registration.interceptors(topicSubscriptionInterceptor)
+ }
+}
diff --git a/backend/src/main/kotlin/org/luxons/sevenwonders/controllers/GameBrowserController.kt b/backend/src/main/kotlin/org/luxons/sevenwonders/controllers/GameBrowserController.kt
new file mode 100644
index 00000000..b8e4e732
--- /dev/null
+++ b/backend/src/main/kotlin/org/luxons/sevenwonders/controllers/GameBrowserController.kt
@@ -0,0 +1,109 @@
+package org.luxons.sevenwonders.controllers
+
+import org.hildan.livedoc.core.annotations.Api
+import org.luxons.sevenwonders.actions.CreateGameAction
+import org.luxons.sevenwonders.actions.JoinGameAction
+import org.luxons.sevenwonders.errors.ApiMisuseException
+import org.luxons.sevenwonders.lobby.Lobby
+import org.luxons.sevenwonders.repositories.LobbyRepository
+import org.luxons.sevenwonders.repositories.PlayerRepository
+import org.slf4j.LoggerFactory
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.messaging.handler.annotation.MessageMapping
+import org.springframework.messaging.simp.SimpMessagingTemplate
+import org.springframework.messaging.simp.annotation.SendToUser
+import org.springframework.messaging.simp.annotation.SubscribeMapping
+import org.springframework.stereotype.Controller
+import org.springframework.validation.annotation.Validated
+import java.security.Principal
+
+/**
+ * This is the place where the player looks for a game.
+ */
+@Api(name = "GameBrowser")
+@Controller
+class GameBrowserController @Autowired constructor(
+ private val lobbyController: LobbyController,
+ private val lobbyRepository: LobbyRepository,
+ private val playerRepository: PlayerRepository,
+ private val template: SimpMessagingTemplate
+) {
+
+ /**
+ * Gets the created or updated games. The list of existing games is received on this topic at once upon
+ * subscription, and then each time the list changes.
+ *
+ * @param principal the connected user's information
+ *
+ * @return the current list of [Lobby]s
+ */
+ @SubscribeMapping("/games") // prefix /topic not shown
+ fun listGames(principal: Principal): Collection<Lobby> {
+ logger.info("Player '{}' subscribed to /topic/games", principal.name)
+ return lobbyRepository.list()
+ }
+
+ /**
+ * Creates a new [Lobby].
+ *
+ * @param action the action to create the game
+ * @param principal the connected user's information
+ *
+ * @return the newly created [Lobby]
+ */
+ @MessageMapping("/lobby/create")
+ @SendToUser("/queue/lobby/joined")
+ fun createGame(@Validated action: CreateGameAction, principal: Principal): Lobby {
+ checkThatUserIsNotInAGame(principal, "cannot create another game")
+
+ val gameOwner = playerRepository.find(principal.name)
+ val lobby = lobbyRepository.create(action.gameName, gameOwner)
+
+ logger.info(
+ "Game '{}' ({}) created by {} ({})", lobby.name, lobby.id, gameOwner.displayName, gameOwner.username
+ )
+
+ // notify everyone that a new game exists
+ template.convertAndSend("/topic/games", listOf(lobby))
+ return lobby
+ }
+
+ /**
+ * Joins an existing [Lobby].
+ *
+ * @param action the action to join the game
+ * @param principal the connected user's information
+ *
+ * @return the [Lobby] that has just been joined
+ */
+ @MessageMapping("/lobby/join")
+ @SendToUser("/queue/lobby/joined")
+ fun joinGame(@Validated action: JoinGameAction, principal: Principal): Lobby {
+ checkThatUserIsNotInAGame(principal, "cannot join another game")
+
+ val lobby = lobbyRepository.find(action.gameId!!)
+ val newPlayer = playerRepository.find(principal.name)
+ lobby.addPlayer(newPlayer)
+
+ logger.info(
+ "Player '{}' ({}) joined game {}", newPlayer.displayName, newPlayer.username, lobby.name
+ )
+ lobbyController.sendLobbyUpdateToPlayers(lobby)
+ return lobby
+ }
+
+ private fun checkThatUserIsNotInAGame(principal: Principal, impossibleActionDescription: String) {
+ val player = playerRepository.find(principal.name)
+ if (player.isInLobby || player.isInGame) {
+ throw UserAlreadyInGameException(player.lobby.name, impossibleActionDescription)
+ }
+ }
+
+ internal class UserAlreadyInGameException(gameName: String, impossibleActionDescription: String) :
+ ApiMisuseException("Client already in game '$gameName', $impossibleActionDescription")
+
+ companion object {
+
+ private val logger = LoggerFactory.getLogger(GameBrowserController::class.java)
+ }
+}
diff --git a/backend/src/main/kotlin/org/luxons/sevenwonders/controllers/GameController.kt b/backend/src/main/kotlin/org/luxons/sevenwonders/controllers/GameController.kt
new file mode 100644
index 00000000..e05bf319
--- /dev/null
+++ b/backend/src/main/kotlin/org/luxons/sevenwonders/controllers/GameController.kt
@@ -0,0 +1,99 @@
+package org.luxons.sevenwonders.controllers
+
+import org.hildan.livedoc.core.annotations.Api
+import org.luxons.sevenwonders.actions.PrepareMoveAction
+import org.luxons.sevenwonders.game.Game
+import org.luxons.sevenwonders.game.api.Table
+import org.luxons.sevenwonders.lobby.Player
+import org.luxons.sevenwonders.output.PreparedCard
+import org.luxons.sevenwonders.repositories.PlayerRepository
+import org.slf4j.LoggerFactory
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.messaging.handler.annotation.MessageMapping
+import org.springframework.messaging.simp.SimpMessagingTemplate
+import org.springframework.stereotype.Controller
+import java.security.Principal
+
+/**
+ * This API is for in-game events management.
+ */
+@Api(name = "Game")
+@Controller
+class GameController @Autowired constructor(
+ private val template: SimpMessagingTemplate,
+ private val playerRepository: PlayerRepository
+) {
+ private val Principal.player
+ get() = playerRepository.find(name)
+
+ /**
+ * Notifies the game that the player is ready to receive his hand.
+ *
+ * @param principal
+ * the connected user's information
+ */
+ @MessageMapping("/game/sayReady")
+ fun ready(principal: Principal) {
+ val player = principal.player
+ player.isReady = true
+ val game = player.game
+ logger.info("Game {}: player {} is ready for the next turn", game.id, player)
+
+ val lobby = player.lobby
+ val players = lobby.getPlayers()
+
+ val allReady = players.stream().allMatch { it.isReady }
+ if (allReady) {
+ logger.info("Game {}: all players ready, sending turn info", game.id)
+ players.forEach { it.isReady = false }
+ sendTurnInfo(players, game)
+ } else {
+ sendPlayerReady(game.id, player)
+ }
+ }
+
+ private fun sendTurnInfo(players: List<Player>, game: Game) {
+ for (turnInfo in game.getCurrentTurnInfo()) {
+ val player = players[turnInfo.playerIndex]
+ template.convertAndSendToUser(player.username, "/queue/game/turn", turnInfo)
+ }
+ }
+
+ private fun sendPlayerReady(gameId: Long, player: Player) =
+ template.convertAndSend("/topic/game/$gameId/playerReady", player.username)
+
+ /**
+ * Prepares the player's next move. When all players have prepared their moves, all moves are executed.
+ *
+ * @param action
+ * the action to prepare the move
+ * @param principal
+ * the connected user's information
+ */
+ @MessageMapping("/game/prepareMove")
+ fun prepareMove(action: PrepareMoveAction, principal: Principal) {
+ val player = principal.player
+ val game = player.game
+ val preparedCardBack = game.prepareMove(player.index, action.move)
+ val preparedCard = PreparedCard(player, preparedCardBack)
+ logger.info("Game {}: player {} prepared move {}", game.id, principal.name, action.move)
+
+ if (game.allPlayersPreparedTheirMove()) {
+ logger.info("Game {}: all players have prepared their move, executing turn...", game.id)
+ val table = game.playTurn()
+ sendPlayedMoves(game.id, table)
+ } else {
+ sendPreparedCard(game.id, preparedCard)
+ }
+ }
+
+ private fun sendPlayedMoves(gameId: Long, table: Table) =
+ template.convertAndSend("/topic/game/$gameId/tableUpdates", table)
+
+ private fun sendPreparedCard(gameId: Long, preparedCard: PreparedCard) =
+ template.convertAndSend("/topic/game/$gameId/prepared", preparedCard)
+
+ companion object {
+ private val logger = LoggerFactory.getLogger(GameController::class.java)
+ }
+}
diff --git a/backend/src/main/kotlin/org/luxons/sevenwonders/controllers/HomeController.kt b/backend/src/main/kotlin/org/luxons/sevenwonders/controllers/HomeController.kt
new file mode 100644
index 00000000..e658a26b
--- /dev/null
+++ b/backend/src/main/kotlin/org/luxons/sevenwonders/controllers/HomeController.kt
@@ -0,0 +1,47 @@
+package org.luxons.sevenwonders.controllers
+
+import org.hildan.livedoc.core.annotations.Api
+import org.luxons.sevenwonders.actions.ChooseNameAction
+import org.luxons.sevenwonders.lobby.Player
+import org.luxons.sevenwonders.repositories.PlayerRepository
+import org.slf4j.LoggerFactory
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.messaging.handler.annotation.MessageMapping
+import org.springframework.messaging.simp.annotation.SendToUser
+import org.springframework.stereotype.Controller
+import org.springframework.validation.annotation.Validated
+import java.security.Principal
+
+/**
+ * Handles actions in the homepage of the game.
+ */
+@Api(name = "Home")
+@Controller
+class HomeController @Autowired constructor(
+ private val playerRepository: PlayerRepository
+) {
+
+ /**
+ * Creates/updates the player's name (for the user's session).
+ *
+ * @param action
+ * the action to choose the name of the player
+ * @param principal
+ * the connected user's information
+ *
+ * @return the created [Player] object
+ */
+ @MessageMapping("/chooseName")
+ @SendToUser("/queue/nameChoice")
+ fun chooseName(@Validated action: ChooseNameAction, principal: Principal): Player {
+ val username = principal.name
+ val player = playerRepository.createOrUpdate(username, action.playerName)
+
+ logger.info("Player '{}' chose the name '{}'", username, player.displayName)
+ return player
+ }
+
+ companion object {
+ private val logger = LoggerFactory.getLogger(HomeController::class.java)
+ }
+}
diff --git a/backend/src/main/kotlin/org/luxons/sevenwonders/controllers/LobbyController.kt b/backend/src/main/kotlin/org/luxons/sevenwonders/controllers/LobbyController.kt
new file mode 100644
index 00000000..3b15d68a
--- /dev/null
+++ b/backend/src/main/kotlin/org/luxons/sevenwonders/controllers/LobbyController.kt
@@ -0,0 +1,106 @@
+package org.luxons.sevenwonders.controllers
+
+import org.hildan.livedoc.core.annotations.Api
+import org.luxons.sevenwonders.actions.ReorderPlayersAction
+import org.luxons.sevenwonders.actions.UpdateSettingsAction
+import org.luxons.sevenwonders.lobby.Lobby
+import org.luxons.sevenwonders.lobby.Player
+import org.luxons.sevenwonders.repositories.LobbyRepository
+import org.luxons.sevenwonders.repositories.PlayerRepository
+import org.slf4j.LoggerFactory
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.messaging.handler.annotation.MessageMapping
+import org.springframework.messaging.simp.SimpMessagingTemplate
+import org.springframework.stereotype.Controller
+import org.springframework.validation.annotation.Validated
+import java.security.Principal
+
+/**
+ * Handles actions in the game's lobby. The lobby is the place where players gather before a game.
+ */
+@Api(name = "Lobby")
+@Controller
+class LobbyController @Autowired constructor(
+ private val lobbyRepository: LobbyRepository,
+ private val playerRepository: PlayerRepository,
+ private val template: SimpMessagingTemplate
+) {
+ private val Principal.player: Player
+ get() = playerRepository.find(name)
+
+ /**
+ * Leaves the current lobby.
+ *
+ * @param principal
+ * the connected user's information
+ */
+ @MessageMapping("/lobby/leave")
+ fun leave(principal: Principal) {
+ val lobby = principal.player.lobby
+ val player = lobby.removePlayer(principal.name)
+ if (lobby.getPlayers().isEmpty()) {
+ lobbyRepository.remove(lobby.id)
+ }
+
+ logger.info("Player {} left game '{}'", player, lobby.name)
+ sendLobbyUpdateToPlayers(lobby)
+ }
+
+ /**
+ * Reorders the players in the current lobby. This can only be done by the lobby's owner.
+ *
+ * @param action
+ * the action to reorder the players
+ * @param principal
+ * the connected user's information
+ */
+ @MessageMapping("/lobby/reorderPlayers")
+ fun reorderPlayers(@Validated action: ReorderPlayersAction, principal: Principal) {
+ val lobby = principal.player.ownedLobby
+ lobby.reorderPlayers(action.orderedPlayers)
+
+ logger.info("Players in game '{}' reordered to {}", lobby.name, action.orderedPlayers)
+ sendLobbyUpdateToPlayers(lobby)
+ }
+
+ /**
+ * Updates the game settings. This can only be done by the lobby's owner.
+ *
+ * @param action
+ * the action to update the settings
+ * @param principal
+ * the connected user's information
+ */
+ @MessageMapping("/lobby/updateSettings")
+ fun updateSettings(@Validated action: UpdateSettingsAction, principal: Principal) {
+ val lobby = principal.player.ownedLobby
+ lobby.settings = action.settings
+
+ logger.info("Updated settings of game '{}'", lobby.name)
+ sendLobbyUpdateToPlayers(lobby)
+ }
+
+ internal fun sendLobbyUpdateToPlayers(lobby: Lobby) {
+ template.convertAndSend("/topic/lobby/" + lobby.id + "/updated", lobby)
+ template.convertAndSend("/topic/games", listOf(lobby))
+ }
+
+ /**
+ * Starts the game.
+ *
+ * @param principal
+ * the connected user's information
+ */
+ @MessageMapping("/lobby/startGame")
+ fun startGame(principal: Principal) {
+ val lobby = principal.player.ownedLobby
+ val game = lobby.startGame()
+
+ logger.info("Game {} successfully started", game.id)
+ template.convertAndSend("/topic/lobby/" + lobby.id + "/started", "")
+ }
+
+ companion object {
+ private val logger = LoggerFactory.getLogger(LobbyController::class.java)
+ }
+}
diff --git a/backend/src/main/kotlin/org/luxons/sevenwonders/doc/Documentation.kt b/backend/src/main/kotlin/org/luxons/sevenwonders/doc/Documentation.kt
new file mode 100644
index 00000000..3b04356a
--- /dev/null
+++ b/backend/src/main/kotlin/org/luxons/sevenwonders/doc/Documentation.kt
@@ -0,0 +1,6 @@
+package org.luxons.sevenwonders.doc
+
+object Documentation {
+
+ const val GROUP_ACTIONS = "Actions"
+}
diff --git a/backend/src/main/kotlin/org/luxons/sevenwonders/errors/ErrorDTO.kt b/backend/src/main/kotlin/org/luxons/sevenwonders/errors/ErrorDTO.kt
new file mode 100644
index 00000000..c3eae0b5
--- /dev/null
+++ b/backend/src/main/kotlin/org/luxons/sevenwonders/errors/ErrorDTO.kt
@@ -0,0 +1,29 @@
+package org.luxons.sevenwonders.errors
+
+import org.springframework.validation.FieldError
+import org.springframework.validation.ObjectError
+
+enum class ErrorType {
+ VALIDATION, CLIENT, SERVER
+}
+
+data class ErrorDTO(
+ val code: String,
+ val message: String,
+ val type: ErrorType,
+ val details: List<ValidationErrorDTO> = emptyList()
+)
+
+data class ValidationErrorDTO(
+ val path: String,
+ val message: String,
+ val rejectedValue: Any? = null
+)
+
+fun ObjectError.toDTO() = (this as? FieldError)?.fieldError() ?: objectError()
+
+fun FieldError.fieldError(): ValidationErrorDTO =
+ ValidationErrorDTO("$objectName.$field", "Invalid value for field '$field': $defaultMessage", rejectedValue)
+
+fun ObjectError.objectError(): ValidationErrorDTO =
+ ValidationErrorDTO(objectName, "Invalid value for object '$objectName': $defaultMessage")
diff --git a/backend/src/main/kotlin/org/luxons/sevenwonders/errors/ExceptionHandler.kt b/backend/src/main/kotlin/org/luxons/sevenwonders/errors/ExceptionHandler.kt
new file mode 100644
index 00000000..76d01f5f
--- /dev/null
+++ b/backend/src/main/kotlin/org/luxons/sevenwonders/errors/ExceptionHandler.kt
@@ -0,0 +1,44 @@
+package org.luxons.sevenwonders.errors
+
+import org.slf4j.LoggerFactory
+import org.springframework.messaging.converter.MessageConversionException
+import org.springframework.messaging.handler.annotation.MessageExceptionHandler
+import org.springframework.messaging.handler.annotation.support.MethodArgumentNotValidException
+import org.springframework.messaging.simp.annotation.SendToUser
+import org.springframework.web.bind.annotation.ControllerAdvice
+
+open class ApiMisuseException(message: String) : RuntimeException(message)
+
+@ControllerAdvice
+@SendToUser("/queue/errors")
+class ExceptionHandler {
+
+ @MessageExceptionHandler
+ fun handleValidationError(exception: MethodArgumentNotValidException): ErrorDTO {
+ logger.error("Invalid input", exception)
+ val validationErrors = exception.bindingResult?.allErrors?.map { it.toDTO() } ?: emptyList()
+ return ErrorDTO("INVALID_DATA", "Invalid input data", ErrorType.VALIDATION, validationErrors)
+ }
+
+ @MessageExceptionHandler
+ fun handleConversionError(exception: MessageConversionException): ErrorDTO {
+ logger.error("Error interpreting the message", exception)
+ return ErrorDTO("INVALID_MESSAGE_FORMAT", "Invalid input format", ErrorType.VALIDATION)
+ }
+
+ @MessageExceptionHandler
+ fun handleApiError(exception: ApiMisuseException): ErrorDTO {
+ logger.error("Invalid API input", exception)
+ return ErrorDTO(exception.javaClass.simpleName, exception.message!!, ErrorType.CLIENT)
+ }
+
+ @MessageExceptionHandler
+ fun handleUnexpectedInternalError(exception: Throwable): ErrorDTO {
+ logger.error("Uncaught exception thrown during message handling", exception)
+ return ErrorDTO(exception.javaClass.simpleName, exception.localizedMessage ?: "", ErrorType.SERVER)
+ }
+
+ companion object {
+ private val logger = LoggerFactory.getLogger(ExceptionHandler::class.java)
+ }
+}
diff --git a/backend/src/main/kotlin/org/luxons/sevenwonders/lobby/Lobby.kt b/backend/src/main/kotlin/org/luxons/sevenwonders/lobby/Lobby.kt
new file mode 100644
index 00000000..aaafd517
--- /dev/null
+++ b/backend/src/main/kotlin/org/luxons/sevenwonders/lobby/Lobby.kt
@@ -0,0 +1,107 @@
+package org.luxons.sevenwonders.lobby
+
+import org.luxons.sevenwonders.game.Game
+import org.luxons.sevenwonders.game.api.CustomizableSettings
+import org.luxons.sevenwonders.game.data.GameDefinition
+import java.util.ArrayList
+
+enum class State {
+ LOBBY, PLAYING
+}
+
+class Lobby(
+ val id: Long,
+ val name: String,
+ private var _owner: Player,
+ @field:Transient private val gameDefinition: GameDefinition
+) {
+ private var players: MutableList<Player> = ArrayList(gameDefinition.maxPlayers)
+
+ var settings: CustomizableSettings = CustomizableSettings()
+
+ var owner = _owner.username
+
+ var state = State.LOBBY
+ private set
+
+ init {
+ addPlayer(_owner)
+ }
+
+ fun getPlayers(): List<Player> = players
+
+ @Synchronized
+ fun addPlayer(player: Player) {
+ if (hasStarted()) {
+ throw GameAlreadyStartedException(name)
+ }
+ if (maxPlayersReached()) {
+ throw PlayerOverflowException(gameDefinition.maxPlayers)
+ }
+ if (playerNameAlreadyUsed(player.displayName)) {
+ throw PlayerNameAlreadyUsedException(player.displayName, name)
+ }
+ player.join(this)
+ players.add(player)
+ }
+
+ private fun hasStarted(): Boolean = state != State.LOBBY
+
+ private fun maxPlayersReached(): Boolean = players.size >= gameDefinition.maxPlayers
+
+ private fun playerNameAlreadyUsed(name: String?): Boolean = players.any { it.displayName == name }
+
+ @Synchronized
+ fun startGame(): Game {
+ if (!hasEnoughPlayers()) {
+ throw PlayerUnderflowException(gameDefinition.minPlayers)
+ }
+ state = State.PLAYING
+ val game = gameDefinition.initGame(id, settings, players.size)
+ players.forEachIndexed { index, player -> player.join(game, index) }
+ return game
+ }
+
+ private fun hasEnoughPlayers(): Boolean = players.size >= gameDefinition.minPlayers
+
+ @Synchronized
+ fun reorderPlayers(orderedUsernames: List<String>) {
+ players = orderedUsernames.map { find(it) }.toMutableList()
+ }
+
+ private fun find(username: String): Player =
+ players.firstOrNull { it.username == username } ?: throw UnknownPlayerException(username)
+
+ @Synchronized
+ fun isOwner(username: String?): Boolean = _owner.username == username
+
+ @Synchronized
+ fun containsUser(username: String): Boolean = players.any { it.username == username }
+
+ @Synchronized
+ fun removePlayer(username: String): Player {
+ val player = find(username)
+ players.remove(player)
+ player.leave()
+
+ if (player == _owner && !players.isEmpty()) {
+ _owner = players[0]
+ }
+ return player
+ }
+
+ internal class GameAlreadyStartedException(name: String) :
+ IllegalStateException("Game '$name' has already started")
+
+ internal class PlayerOverflowException(max: Int) :
+ IllegalStateException("Maximum $max players allowed")
+
+ internal class PlayerUnderflowException(min: Int) :
+ IllegalStateException("Minimum $min players required to start a game")
+
+ internal class PlayerNameAlreadyUsedException(displayName: String, gameName: String) :
+ IllegalStateException("Name '$displayName' is already used by a player in game '$gameName'")
+
+ internal class UnknownPlayerException(username: String) :
+ IllegalStateException("Unknown player '$username'")
+}
diff --git a/backend/src/main/kotlin/org/luxons/sevenwonders/lobby/Player.kt b/backend/src/main/kotlin/org/luxons/sevenwonders/lobby/Player.kt
new file mode 100644
index 00000000..48a31047
--- /dev/null
+++ b/backend/src/main/kotlin/org/luxons/sevenwonders/lobby/Player.kt
@@ -0,0 +1,70 @@
+package org.luxons.sevenwonders.lobby
+
+import com.fasterxml.jackson.annotation.JsonIgnore
+import org.luxons.sevenwonders.errors.ApiMisuseException
+import org.luxons.sevenwonders.game.Game
+
+class Player(
+ val username: String,
+ var displayName: String
+) {
+ var index: Int = -1
+
+ var isReady: Boolean = false
+
+ val isGameOwner: Boolean
+ get() = _lobby?.isOwner(username) ?: false
+
+ val isInLobby: Boolean
+ get() = _lobby != null
+
+ val isInGame: Boolean
+ get() = _game != null
+
+ @Transient
+ private var _lobby: Lobby? = null
+
+ @get:JsonIgnore
+ val lobby: Lobby
+ get() = _lobby ?: throw PlayerNotInLobbyException(username)
+
+ @get:JsonIgnore
+ val ownedLobby: Lobby
+ get() = if (isGameOwner) lobby else throw PlayerIsNotOwnerException(username)
+
+ @Transient
+ private var _game: Game? = null
+
+ @get:JsonIgnore
+ val game: Game
+ get() = _game ?: throw PlayerNotInGameException(username)
+
+ fun join(lobby: Lobby) {
+ _lobby = lobby
+ }
+
+ fun join(game: Game, index: Int) {
+ _game = game
+ this.index = index
+ }
+
+ fun leave() {
+ _lobby = null
+ _game = null
+ index = -1
+ }
+
+ override fun toString(): String {
+ return "'$displayName' ($username)"
+ }
+}
+
+internal class PlayerNotInLobbyException(username: String) :
+ ApiMisuseException("User $username is not in a lobby, create or join a game first")
+
+internal class PlayerIsNotOwnerException(username: String) :
+ ApiMisuseException("User $username does not own the lobby he's in")
+
+internal class PlayerNotInGameException(username: String) :
+ ApiMisuseException("User $username is not in a game, start a game first")
+
diff --git a/backend/src/main/kotlin/org/luxons/sevenwonders/output/PreparedCard.kt b/backend/src/main/kotlin/org/luxons/sevenwonders/output/PreparedCard.kt
new file mode 100644
index 00000000..956b1a2c
--- /dev/null
+++ b/backend/src/main/kotlin/org/luxons/sevenwonders/output/PreparedCard.kt
@@ -0,0 +1,6 @@
+package org.luxons.sevenwonders.output
+
+import org.luxons.sevenwonders.game.cards.CardBack
+import org.luxons.sevenwonders.lobby.Player
+
+class PreparedCard(val player: Player, val cardBack: CardBack)
diff --git a/backend/src/main/kotlin/org/luxons/sevenwonders/repositories/LobbyRepository.kt b/backend/src/main/kotlin/org/luxons/sevenwonders/repositories/LobbyRepository.kt
new file mode 100644
index 00000000..261f723c
--- /dev/null
+++ b/backend/src/main/kotlin/org/luxons/sevenwonders/repositories/LobbyRepository.kt
@@ -0,0 +1,33 @@
+package org.luxons.sevenwonders.repositories
+
+import org.luxons.sevenwonders.game.data.GameDefinitionLoader
+import org.luxons.sevenwonders.lobby.Lobby
+import org.luxons.sevenwonders.lobby.Player
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.stereotype.Repository
+import java.util.HashMap
+
+@Repository
+class LobbyRepository @Autowired constructor() {
+
+ private val gameDefinitionLoader: GameDefinitionLoader = GameDefinitionLoader()
+
+ private val lobbies = HashMap<Long, Lobby>()
+
+ private var lastGameId: Long = 0
+
+ fun list(): Collection<Lobby> = lobbies.values
+
+ fun create(gameName: String, owner: Player): Lobby {
+ val id = lastGameId++
+ val lobby = Lobby(id, gameName, owner, gameDefinitionLoader.gameDefinition)
+ lobbies[id] = lobby
+ return lobby
+ }
+
+ fun find(lobbyId: Long): Lobby = lobbies[lobbyId] ?: throw LobbyNotFoundException(lobbyId)
+
+ fun remove(lobbyId: Long): Lobby = lobbies.remove(lobbyId) ?: throw LobbyNotFoundException(lobbyId)
+}
+
+internal class LobbyNotFoundException(id: Long) : RuntimeException("Lobby not found for id '$id'")
diff --git a/backend/src/main/kotlin/org/luxons/sevenwonders/repositories/PlayerRepository.kt b/backend/src/main/kotlin/org/luxons/sevenwonders/repositories/PlayerRepository.kt
new file mode 100644
index 00000000..4d552eaa
--- /dev/null
+++ b/backend/src/main/kotlin/org/luxons/sevenwonders/repositories/PlayerRepository.kt
@@ -0,0 +1,41 @@
+package org.luxons.sevenwonders.repositories
+
+import org.luxons.sevenwonders.errors.ApiMisuseException
+import org.luxons.sevenwonders.lobby.Player
+import org.springframework.stereotype.Repository
+import java.util.HashMap
+
+@Repository
+class PlayerRepository {
+
+ private val players = HashMap<String, Player>()
+
+ operator fun contains(username: String): Boolean = players.containsKey(username)
+
+ fun createOrUpdate(username: String, displayName: String): Player {
+ return if (players.containsKey(username)) {
+ update(username, displayName)
+ } else {
+ create(username, displayName)
+ }
+ }
+
+ private fun create(username: String, displayName: String): Player {
+ val player = Player(username, displayName)
+ players[username] = player
+ return player
+ }
+
+ private fun update(username: String, displayName: String): Player {
+ val player = find(username)
+ player.displayName = displayName
+ return player
+ }
+
+ fun find(username: String): Player = players[username] ?: throw PlayerNotFoundException(username)
+
+ fun remove(username: String): Player = players.remove(username) ?: throw PlayerNotFoundException(username)
+}
+
+internal class PlayerNotFoundException(username: String) :
+ ApiMisuseException("Player '$username' doesn't exist")
diff --git a/backend/src/main/kotlin/org/luxons/sevenwonders/validation/DestinationAccessValidator.kt b/backend/src/main/kotlin/org/luxons/sevenwonders/validation/DestinationAccessValidator.kt
new file mode 100644
index 00000000..5800edbb
--- /dev/null
+++ b/backend/src/main/kotlin/org/luxons/sevenwonders/validation/DestinationAccessValidator.kt
@@ -0,0 +1,55 @@
+package org.luxons.sevenwonders.validation
+
+import org.luxons.sevenwonders.repositories.LobbyRepository
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.stereotype.Component
+import java.util.regex.Matcher
+import java.util.regex.Pattern
+
+@Component
+class DestinationAccessValidator @Autowired constructor(private val lobbyRepository: LobbyRepository) {
+
+ fun hasAccess(username: String?, destination: String): Boolean {
+ return when {
+ username == null -> false // unnamed user cannot belong to anything
+ hasForbiddenGameReference(username, destination) -> false
+ hasForbiddenLobbyReference(username, destination) -> false
+ else -> true
+ }
+ }
+
+ private fun hasForbiddenGameReference(username: String, destination: String): Boolean {
+ val gameMatcher = gameDestination.matcher(destination)
+ if (!gameMatcher.matches()) {
+ return false // no game reference is always OK
+ }
+ val gameId = extractId(gameMatcher)
+ return !isUserInLobby(username, gameId)
+ }
+
+ private fun hasForbiddenLobbyReference(username: String, destination: String): Boolean {
+ val lobbyMatcher = lobbyDestination.matcher(destination)
+ if (!lobbyMatcher.matches()) {
+ return false // no lobby reference is always OK
+ }
+ val lobbyId = extractId(lobbyMatcher)
+ return !isUserInLobby(username, lobbyId)
+ }
+
+ private fun isUserInLobby(username: String, lobbyId: Int): Boolean {
+ val lobby = lobbyRepository.find(lobbyId.toLong())
+ return lobby.containsUser(username)
+ }
+
+ companion object {
+
+ private val lobbyDestination = Pattern.compile(".*?/lobby/(?<id>\\d+?)(/.*)?")
+
+ private val gameDestination = Pattern.compile(".*?/game/(?<id>\\d+?)(/.*)?")
+
+ private fun extractId(matcher: Matcher): Int {
+ val id = matcher.group("id")
+ return Integer.parseInt(id)
+ }
+ }
+}
diff --git a/backend/src/test/java/org/luxons/sevenwonders/SevenWondersTest.java b/backend/src/test/java/org/luxons/sevenwonders/SevenWondersTest.java
deleted file mode 100644
index 8cd959e3..00000000
--- a/backend/src/test/java/org/luxons/sevenwonders/SevenWondersTest.java
+++ /dev/null
@@ -1,158 +0,0 @@
-package org.luxons.sevenwonders;
-
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-import org.hildan.jackstomp.Channel;
-import org.hildan.jackstomp.JackstompSession;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.luxons.sevenwonders.test.api.ApiLobby;
-import org.luxons.sevenwonders.test.api.ApiPlayer;
-import org.luxons.sevenwonders.test.api.ApiPlayerTurnInfo;
-import org.luxons.sevenwonders.test.api.SevenWondersClient;
-import org.luxons.sevenwonders.test.api.SevenWondersSession;
-import org.springframework.boot.web.server.LocalServerPort;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
-import org.springframework.test.context.junit4.SpringRunner;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-
-@RunWith(SpringRunner.class)
-@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
-public class SevenWondersTest {
-
- private static final String WEBSOCKET_URI = "ws://localhost:%d";
-
- private SevenWondersClient client;
-
- @LocalServerPort
- private int randomServerPort;
-
- private String serverUrl;
-
- @Before
- public void setUpClientAndUrl() {
- client = new SevenWondersClient();
- serverUrl = String.format(WEBSOCKET_URI, randomServerPort);
- }
-
- private void disconnect(SevenWondersSession... sessions) {
- for (SevenWondersSession session : sessions) {
- session.disconnect();
- }
- }
-
- @Test
- public void chooseName() throws InterruptedException, ExecutionException, TimeoutException {
- SevenWondersSession session = client.connect(serverUrl);
- String playerName = "Test User";
- ApiPlayer player = session.chooseName(playerName);
- assertNotNull(player);
- assertEquals(playerName, player.getDisplayName());
- session.disconnect();
- }
-
- private SevenWondersSession newPlayer(String name) throws InterruptedException, TimeoutException,
- ExecutionException {
- SevenWondersSession otherSession = client.connect(serverUrl);
- otherSession.chooseName(name);
- return otherSession;
- }
-
- @Test
- public void lobbySubscription_ignoredForOutsiders() throws InterruptedException, ExecutionException,
- TimeoutException {
- SevenWondersSession ownerSession = newPlayer("GameOwner");
- SevenWondersSession session1 = newPlayer("Player1");
- SevenWondersSession session2 = newPlayer("Player2");
- String gameName = "Test Game";
- ApiLobby lobby = ownerSession.createGame(gameName);
- session1.joinGame(lobby.getId());
- session2.joinGame(lobby.getId());
-
- SevenWondersSession outsiderSession = newPlayer("Outsider");
- JackstompSession session = outsiderSession.getJackstompSession();
- Channel<Object> started = session.subscribeEmptyMsgs("/topic/lobby/" + lobby.getId() + "/started");
-
- ownerSession.startGame(lobby.getId());
- Object nothing = started.next(1, TimeUnit.SECONDS);
- assertNull(nothing);
- disconnect(ownerSession, session1, session2, outsiderSession);
- }
-
- @Test
- public void createGame_success() throws InterruptedException, ExecutionException, TimeoutException {
- SevenWondersSession ownerSession = newPlayer("GameOwner");
-
- String gameName = "Test Game";
- ApiLobby lobby = ownerSession.createGame(gameName);
- assertNotNull(lobby);
- assertEquals(gameName, lobby.getName());
-
- disconnect(ownerSession);
- }
-
- @Test
- public void createGame_seenByConnectedPlayers() throws InterruptedException, ExecutionException, TimeoutException {
- SevenWondersSession otherSession = newPlayer("OtherPlayer");
- Channel<ApiLobby[]> games = otherSession.watchGames();
-
- ApiLobby[] receivedLobbies = games.next();
- assertNotNull(receivedLobbies);
- assertEquals(0, receivedLobbies.length);
-
- SevenWondersSession ownerSession = newPlayer("GameOwner");
- String gameName = "Test Game";
- ApiLobby createdLobby = ownerSession.createGame(gameName);
-
- receivedLobbies = games.next();
- assertNotNull(receivedLobbies);
- assertEquals(1, receivedLobbies.length);
- ApiLobby receivedLobby = receivedLobbies[0];
- assertEquals(createdLobby.getId(), receivedLobby.getId());
- assertEquals(createdLobby.getName(), receivedLobby.getName());
-
- disconnect(ownerSession, otherSession);
- }
-
- @Test
- public void startGame_3players() throws Exception {
- SevenWondersSession session1 = newPlayer("Player1");
- SevenWondersSession session2 = newPlayer("Player2");
-
- ApiLobby lobby = session1.createGame("Test Game");
- session2.joinGame(lobby.getId());
-
- SevenWondersSession session3 = newPlayer("Player3");
- session3.joinGame(lobby.getId());
-
- session1.startGame(lobby.getId());
-
- Channel<ApiPlayerTurnInfo> turns1 = session1.watchTurns();
- Channel<ApiPlayerTurnInfo> turns2 = session2.watchTurns();
- Channel<ApiPlayerTurnInfo> turns3 = session3.watchTurns();
- session1.sayReady();
- session2.sayReady();
- session3.sayReady();
- ApiPlayerTurnInfo turn1 = turns1.next();
- ApiPlayerTurnInfo turn2 = turns2.next();
- ApiPlayerTurnInfo turn3 = turns3.next();
- assertNotNull(turn1);
- assertNotNull(turn2);
- assertNotNull(turn3);
-
- disconnect(session1, session2, session3);
- }
-
- @After
- public void tearDown() {
- client.stop();
- }
-}
diff --git a/backend/src/test/java/org/luxons/sevenwonders/controllers/GameBrowserControllerTest.java b/backend/src/test/java/org/luxons/sevenwonders/controllers/GameBrowserControllerTest.java
deleted file mode 100644
index 65d2aa04..00000000
--- a/backend/src/test/java/org/luxons/sevenwonders/controllers/GameBrowserControllerTest.java
+++ /dev/null
@@ -1,133 +0,0 @@
-package org.luxons.sevenwonders.controllers;
-
-import java.security.Principal;
-import java.util.Collection;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.luxons.sevenwonders.actions.CreateGameAction;
-import org.luxons.sevenwonders.actions.JoinGameAction;
-import org.luxons.sevenwonders.controllers.GameBrowserController.UserAlreadyInGameException;
-import org.luxons.sevenwonders.lobby.Lobby;
-import org.luxons.sevenwonders.lobby.Player;
-import org.luxons.sevenwonders.repositories.LobbyRepository;
-import org.luxons.sevenwonders.repositories.PlayerRepository;
-import org.luxons.sevenwonders.repositories.PlayerRepository.PlayerNotFoundException;
-import org.luxons.sevenwonders.test.TestUtils;
-import org.springframework.messaging.simp.SimpMessagingTemplate;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-
-public class GameBrowserControllerTest {
-
- private PlayerRepository playerRepository;
-
- private GameBrowserController gameBrowserController;
-
- @Before
- public void setUp() {
- playerRepository = new PlayerRepository();
- LobbyRepository lobbyRepository = new LobbyRepository();
- SimpMessagingTemplate template = TestUtils.createSimpMessagingTemplate();
- LobbyController lobbyController = new LobbyController(playerRepository, template);
- gameBrowserController = new GameBrowserController(lobbyController, lobbyRepository, playerRepository, template);
- }
-
- @Test
- public void listGames_initiallyEmpty() {
- Principal principal = new TestPrincipal("testuser");
- Collection<Lobby> games = gameBrowserController.listGames(principal);
- assertTrue(games.isEmpty());
- }
-
- @Test
- public void createGame_success() {
- Player player = playerRepository.createOrUpdate("testuser", "Test User");
- Principal principal = new TestPrincipal("testuser");
-
- CreateGameAction action = new CreateGameAction();
- action.setGameName("Test Game");
-
- Lobby createdLobby = gameBrowserController.createGame(action, principal);
-
- assertEquals("Test Game", createdLobby.getName());
-
- Collection<Lobby> games = gameBrowserController.listGames(principal);
- assertFalse(games.isEmpty());
- Lobby lobby = games.iterator().next();
- assertSame(lobby, createdLobby);
- assertSame(player, lobby.getPlayers().get(0));
- }
-
- @Test(expected = PlayerNotFoundException.class)
- public void createGame_failsForUnknownPlayer() {
- Principal principal = new TestPrincipal("unknown");
-
- CreateGameAction action = new CreateGameAction();
- action.setGameName("Test Game");
-
- gameBrowserController.createGame(action, principal);
- }
-
- @Test(expected = UserAlreadyInGameException.class)
- public void createGame_failsWhenAlreadyInGame() {
- playerRepository.createOrUpdate("testuser", "Test User");
- Principal principal = new TestPrincipal("testuser");
-
- CreateGameAction createGameAction1 = new CreateGameAction();
- createGameAction1.setGameName("Test Game 1");
-
- // auto-enters the game
- gameBrowserController.createGame(createGameAction1, principal);
-
- CreateGameAction createGameAction2 = new CreateGameAction();
- createGameAction2.setGameName("Test Game 2");
-
- // already in a game
- gameBrowserController.createGame(createGameAction2, principal);
- }
-
- @Test
- public void joinGame_success() {
- Player owner = playerRepository.createOrUpdate("testowner", "Test User Owner");
- Principal ownerPrincipal = new TestPrincipal("testowner");
- CreateGameAction createGameAction = new CreateGameAction();
- createGameAction.setGameName("Test Game");
-
- Lobby createdLobby = gameBrowserController.createGame(createGameAction, ownerPrincipal);
-
- Player joiner = playerRepository.createOrUpdate("testjoiner", "Test User Joiner");
- Principal joinerPrincipal = new TestPrincipal("testjoiner");
- JoinGameAction joinGameAction = new JoinGameAction();
- joinGameAction.setGameId(createdLobby.getId());
-
- Lobby joinedLobby = gameBrowserController.joinGame(joinGameAction, joinerPrincipal);
-
- assertSame(createdLobby, joinedLobby);
- assertSame(owner, joinedLobby.getPlayers().get(0));
- assertSame(joiner, joinedLobby.getPlayers().get(1));
- }
-
- @Test(expected = UserAlreadyInGameException.class)
- public void joinGame_failsWhenAlreadyInGame() {
- playerRepository.createOrUpdate("testowner", "Test User Owner");
- Principal ownerPrincipal = new TestPrincipal("testowner");
- CreateGameAction createGameAction = new CreateGameAction();
- createGameAction.setGameName("Test Game");
-
- Lobby createdLobby = gameBrowserController.createGame(createGameAction, ownerPrincipal);
-
- playerRepository.createOrUpdate("testjoiner", "Test User Joiner");
- Principal joinerPrincipal = new TestPrincipal("testjoiner");
- JoinGameAction joinGameAction = new JoinGameAction();
- joinGameAction.setGameId(createdLobby.getId());
-
- // joins the game
- gameBrowserController.joinGame(joinGameAction, joinerPrincipal);
- // already in a game
- gameBrowserController.joinGame(joinGameAction, joinerPrincipal);
- }
-}
diff --git a/backend/src/test/java/org/luxons/sevenwonders/controllers/HomeControllerTest.java b/backend/src/test/java/org/luxons/sevenwonders/controllers/HomeControllerTest.java
deleted file mode 100644
index 3104bf4a..00000000
--- a/backend/src/test/java/org/luxons/sevenwonders/controllers/HomeControllerTest.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package org.luxons.sevenwonders.controllers;
-
-import java.security.Principal;
-
-import org.junit.Test;
-import org.luxons.sevenwonders.actions.ChooseNameAction;
-import org.luxons.sevenwonders.lobby.Player;
-import org.luxons.sevenwonders.repositories.PlayerRepository;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertSame;
-
-public class HomeControllerTest {
-
- @Test
- public void chooseName() {
- PlayerRepository playerRepository = new PlayerRepository();
- HomeController homeController = new HomeController(playerRepository);
-
- ChooseNameAction action = new ChooseNameAction();
- action.setPlayerName("Test User");
-
- Principal principal = new TestPrincipal("testuser");
-
- Player player = homeController.chooseName(action, principal);
-
- assertSame(player, playerRepository.find("testuser"));
- assertEquals("testuser", player.getUsername());
- assertEquals("Test User", player.getDisplayName());
- assertEquals(null, player.getLobby());
- assertEquals(null, player.getGame());
- }
-}
diff --git a/backend/src/test/java/org/luxons/sevenwonders/controllers/LobbyControllerTest.java b/backend/src/test/java/org/luxons/sevenwonders/controllers/LobbyControllerTest.java
deleted file mode 100644
index 2da7e3c1..00000000
--- a/backend/src/test/java/org/luxons/sevenwonders/controllers/LobbyControllerTest.java
+++ /dev/null
@@ -1,203 +0,0 @@
-package org.luxons.sevenwonders.controllers;
-
-import java.security.Principal;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.stream.Collectors;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.luxons.sevenwonders.actions.ReorderPlayersAction;
-import org.luxons.sevenwonders.actions.UpdateSettingsAction;
-import org.luxons.sevenwonders.controllers.LobbyController.PlayerIsNotOwnerException;
-import org.luxons.sevenwonders.controllers.LobbyController.PlayerNotInLobbyException;
-import org.luxons.sevenwonders.game.api.CustomizableSettings;
-import org.luxons.sevenwonders.lobby.Lobby;
-import org.luxons.sevenwonders.lobby.Player;
-import org.luxons.sevenwonders.lobby.State;
-import org.luxons.sevenwonders.repositories.LobbyRepository;
-import org.luxons.sevenwonders.repositories.PlayerRepository;
-import org.luxons.sevenwonders.repositories.PlayerRepository.PlayerNotFoundException;
-import org.luxons.sevenwonders.test.TestUtils;
-import org.springframework.messaging.simp.SimpMessagingTemplate;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-import static org.luxons.sevenwonders.game.data.WonderSidePickMethod.ALL_A;
-
-public class LobbyControllerTest {
-
- private PlayerRepository playerRepository;
-
- private LobbyRepository lobbyRepository;
-
- private LobbyController lobbyController;
-
- @Before
- public void setUp() {
- playerRepository = new PlayerRepository();
- lobbyRepository = new LobbyRepository();
- SimpMessagingTemplate template = TestUtils.createSimpMessagingTemplate();
- lobbyController = new LobbyController(playerRepository, template);
- }
-
- @Test(expected = PlayerNotFoundException.class)
- public void leave_failsWhenPlayerDoesNotExist() {
- Principal principal = new TestPrincipal("testuser");
- lobbyController.leave(principal);
- }
-
- @Test(expected = PlayerNotInLobbyException.class)
- public void leave_failsWhenNotInLobby() {
- playerRepository.createOrUpdate("testuser", "Test User");
- Principal principal = new TestPrincipal("testuser");
- lobbyController.leave(principal);
- }
-
- @Test
- public void leave_succeedsWhenInALobby_asOwner() {
- Player player = playerRepository.createOrUpdate("testuser", "Test User");
- Lobby lobby = lobbyRepository.create("Test Game", player);
-
- assertTrue(lobby.getPlayers().contains(player));
- assertSame(lobby, player.getLobby());
-
- Principal principal = new TestPrincipal("testuser");
- lobbyController.leave(principal);
-
- assertFalse(lobby.getPlayers().contains(player));
- assertNull(player.getLobby());
- }
-
- @Test
- public void leave_succeedsWhenInALobby_asPeasant() {
- Player player = playerRepository.createOrUpdate("testuser", "Test User");
- Lobby lobby = lobbyRepository.create("Test Game", player);
- Player player2 = addPlayer(lobby, "testuser2");
-
- assertTrue(lobby.getPlayers().contains(player2));
- assertSame(lobby, player2.getLobby());
-
- Principal principal = new TestPrincipal("testuser2");
- lobbyController.leave(principal);
-
- assertFalse(lobby.getPlayers().contains(player2));
- assertNull(player2.getLobby());
- }
-
- @Test
- public void reorderPlayers_succeedsForOwner() {
- Player player = playerRepository.createOrUpdate("testuser", "Test User");
- Lobby lobby = lobbyRepository.create("Test Game", player);
-
- Player player2 = addPlayer(lobby, "testuser2");
- Player player3 = addPlayer(lobby, "testuser3");
- Player player4 = addPlayer(lobby, "testuser4");
-
- List<Player> players = Arrays.asList(player, player2, player3, player4);
- assertEquals(players, lobby.getPlayers());
-
- ReorderPlayersAction reorderPlayersAction = new ReorderPlayersAction();
- List<Player> reorderedPlayers = Arrays.asList(player3, player, player2, player4);
- List<String> playerNames = reorderedPlayers.stream().map(Player::getUsername).collect(Collectors.toList());
- reorderPlayersAction.setOrderedPlayers(playerNames);
-
- Principal principal = new TestPrincipal("testuser");
- lobbyController.reorderPlayers(reorderPlayersAction, principal);
-
- assertEquals(reorderedPlayers, lobby.getPlayers());
- }
-
- @Test(expected = PlayerIsNotOwnerException.class)
- public void reorderPlayers_failsForPeasant() {
- Player player = playerRepository.createOrUpdate("testuser", "Test User");
- Lobby lobby = lobbyRepository.create("Test Game", player);
-
- Player player2 = addPlayer(lobby, "testuser2");
- Player player3 = addPlayer(lobby, "testuser3");
-
- ReorderPlayersAction reorderPlayersAction = new ReorderPlayersAction();
- List<Player> reorderedPlayers = Arrays.asList(player3, player, player2);
- List<String> playerNames = reorderedPlayers.stream().map(Player::getUsername).collect(Collectors.toList());
- reorderPlayersAction.setOrderedPlayers(playerNames);
-
- Principal principal = new TestPrincipal("testuser2");
- lobbyController.reorderPlayers(reorderPlayersAction, principal);
- }
-
- @Test
- public void updateSettings_succeedsForOwner() {
- Player player = playerRepository.createOrUpdate("testuser", "Test User");
- Lobby lobby = lobbyRepository.create("Test Game", player);
-
- addPlayer(lobby, "testuser2");
- addPlayer(lobby, "testuser3");
- addPlayer(lobby, "testuser4");
-
- assertEquals(new CustomizableSettings(), lobby.getSettings());
-
- UpdateSettingsAction updateSettingsAction = new UpdateSettingsAction();
- CustomizableSettings newSettings = new CustomizableSettings(12L, 5, ALL_A, 5, 5, 4, 10, 2, new HashMap<>());
- updateSettingsAction.setSettings(newSettings);
-
- Principal principal = new TestPrincipal("testuser");
- lobbyController.updateSettings(updateSettingsAction, principal);
-
- assertEquals(newSettings, lobby.getSettings());
- }
-
- @Test(expected = PlayerIsNotOwnerException.class)
- public void updateSettings_failsForPeasant() {
- Player player = playerRepository.createOrUpdate("testuser", "Test User");
- Lobby lobby = lobbyRepository.create("Test Game", player);
-
- addPlayer(lobby, "testuser2");
- addPlayer(lobby, "testuser3");
-
- UpdateSettingsAction updateSettingsAction = new UpdateSettingsAction();
- updateSettingsAction.setSettings(new CustomizableSettings());
-
- Principal principal = new TestPrincipal("testuser2");
- lobbyController.updateSettings(updateSettingsAction, principal);
- }
-
- @Test
- public void startGame_succeedsForOwner() {
- Player player = playerRepository.createOrUpdate("testuser", "Test User");
- Lobby lobby = lobbyRepository.create("Test Game", player);
-
- addPlayer(lobby, "testuser2");
- addPlayer(lobby, "testuser3");
- addPlayer(lobby, "testuser4");
-
- Principal principal = new TestPrincipal("testuser");
- lobbyController.startGame(principal);
-
- assertSame(State.PLAYING, lobby.getState());
- }
-
- @Test(expected = PlayerIsNotOwnerException.class)
- public void startGame_failsForPeasant() {
- Player player = playerRepository.createOrUpdate("testuser", "Test User");
- Lobby lobby = lobbyRepository.create("Test Game", player);
-
- addPlayer(lobby, "testuser2");
- addPlayer(lobby, "testuser3");
-
- Principal principal = new TestPrincipal("testuser2");
- lobbyController.startGame(principal);
- }
-
- private Player addPlayer(Lobby lobby, String username) {
- Player player = playerRepository.createOrUpdate(username, username);
- lobby.addPlayer(player);
-
- assertTrue(lobby.getPlayers().contains(player));
- assertSame(lobby, player.getLobby());
- return player;
- }
-}
diff --git a/backend/src/test/java/org/luxons/sevenwonders/controllers/TestPrincipal.java b/backend/src/test/java/org/luxons/sevenwonders/controllers/TestPrincipal.java
deleted file mode 100644
index db601cd2..00000000
--- a/backend/src/test/java/org/luxons/sevenwonders/controllers/TestPrincipal.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package org.luxons.sevenwonders.controllers;
-
-import java.security.Principal;
-
-public class TestPrincipal implements Principal {
-
- private final String name;
-
- TestPrincipal(String name) {
- this.name = name;
- }
-
- @Override
- public String getName() {
- return name;
- }
-}
diff --git a/backend/src/test/java/org/luxons/sevenwonders/lobby/LobbyTest.java b/backend/src/test/java/org/luxons/sevenwonders/lobby/LobbyTest.java
deleted file mode 100644
index 5747eb5a..00000000
--- a/backend/src/test/java/org/luxons/sevenwonders/lobby/LobbyTest.java
+++ /dev/null
@@ -1,225 +0,0 @@
-package org.luxons.sevenwonders.lobby;
-
-import java.util.Arrays;
-
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.experimental.theories.DataPoints;
-import org.junit.experimental.theories.Theories;
-import org.junit.experimental.theories.Theory;
-import org.junit.rules.ExpectedException;
-import org.junit.runner.RunWith;
-import org.luxons.sevenwonders.game.Game;
-import org.luxons.sevenwonders.game.api.CustomizableSettings;
-import org.luxons.sevenwonders.game.data.GameDefinition;
-import org.luxons.sevenwonders.game.data.GameDefinitionLoader;
-import org.luxons.sevenwonders.lobby.Lobby.GameAlreadyStartedException;
-import org.luxons.sevenwonders.lobby.Lobby.PlayerNameAlreadyUsedException;
-import org.luxons.sevenwonders.lobby.Lobby.PlayerOverflowException;
-import org.luxons.sevenwonders.lobby.Lobby.PlayerUnderflowException;
-import org.luxons.sevenwonders.lobby.Lobby.UnknownPlayerException;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeTrue;
-
-@RunWith(Theories.class)
-public class LobbyTest {
-
- private static GameDefinition gameDefinition;
-
- @Rule
- public ExpectedException thrown = ExpectedException.none();
-
- private Player gameOwner;
-
- private Lobby lobby;
-
- @DataPoints
- public static int[] nbPlayers() {
- return new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
- }
-
- @BeforeClass
- public static void loadDefinition() {
- gameDefinition = new GameDefinitionLoader().getGameDefinition();
- }
-
- @Before
- public void setUp() {
- gameOwner = new Player("gameowner", "Game owner");
- lobby = new Lobby(0, "Test Game", gameOwner, gameDefinition);
- }
-
- @Test
- public void testId() {
- Lobby l = new Lobby(5, "Test Game", gameOwner, gameDefinition);
- assertEquals(5, l.getId());
- }
-
- @Test
- public void testName() {
- Lobby l = new Lobby(5, "Test Game", gameOwner, gameDefinition);
- assertEquals("Test Game", l.getName());
- }
-
- @Test
- public void testOwner() {
- gameOwner.setIndex(42);
- Lobby l = new Lobby(5, "Test Game", gameOwner, gameDefinition);
- assertSame(gameOwner, l.getPlayers().get(0));
- assertSame(l, gameOwner.getLobby());
- assertEquals(0, gameOwner.getIndex());
- }
-
- @Test
- public void isOwner_falseWhenNull() {
- assertFalse(lobby.isOwner(null));
- }
-
- @Test
- public void isOwner_falseWhenEmptyString() {
- assertFalse(lobby.isOwner(""));
- }
-
- @Test
- public void isOwner_falseWhenGarbageString() {
- assertFalse(lobby.isOwner("this is garbage"));
- }
-
- @Test
- public void isOwner_trueWhenOwnerUsername() {
- assertTrue(lobby.isOwner(gameOwner.getUsername()));
- }
-
- @Test
- public void isOwner_falseWhenOtherPlayerName() {
- Player player = new Player("testuser", "Test User");
- lobby.addPlayer(player);
- assertFalse(lobby.isOwner(player.getUsername()));
- }
-
- @Test
- public void addPlayer_success() {
- Player player = new Player("testuser", "Test User");
- lobby.addPlayer(player);
- assertTrue(lobby.containsUser("testuser"));
- assertSame(lobby, player.getLobby());
- assertSame(1, player.getIndex()); // the owner is 0
- }
-
- @Test(expected = PlayerNameAlreadyUsedException.class)
- public void addPlayer_failsOnSameName() {
- Player player = new Player("testuser", "Test User");
- Player player2 = new Player("testuser2", "Test User");
- lobby.addPlayer(player);
- lobby.addPlayer(player2);
- }
-
- @Test(expected = PlayerOverflowException.class)
- public void addPlayer_playerOverflowWhenTooMany() {
- // the owner + the max number gives an overflow
- addPlayers(gameDefinition.getMaxPlayers());
- }
-
- @Test(expected = GameAlreadyStartedException.class)
- public void addPlayer_failWhenGameStarted() {
- // total with owner is the minimum
- addPlayers(gameDefinition.getMinPlayers() - 1);
- lobby.startGame();
- lobby.addPlayer(new Player("soonerNextTime", "The Late Guy"));
- }
-
- private void addPlayers(int nbPlayers) {
- for (int i = 0; i < nbPlayers; i++) {
- Player player = new Player("testuser" + i, "Test User " + i);
- lobby.addPlayer(player);
- }
- }
-
- @Test(expected = UnknownPlayerException.class)
- public void removePlayer_failsWhenNotPresent() {
- lobby.removePlayer("anyname");
- }
-
- @Test
- public void removePlayer_success() {
- Player player = new Player("testuser", "Test User");
- lobby.addPlayer(player);
- lobby.removePlayer("testuser");
- assertFalse(lobby.containsUser("testuser"));
- assertNull(player.getLobby());
- assertNull(player.getGame());
- assertEquals(-1, player.getIndex());
- }
-
- @Test
- public void reorderPlayers_success() {
- Player player1 = new Player("testuser1", "Test User 1");
- Player player2 = new Player("testuser2", "Test User 2");
- Player player3 = new Player("testuser3", "Test User 3");
- lobby.addPlayer(player1);
- lobby.addPlayer(player2);
- lobby.addPlayer(player3);
- lobby.reorderPlayers(Arrays.asList("testuser3", "testuser1", "testuser2"));
- assertEquals("testuser3", lobby.getPlayers().get(0).getUsername());
- assertEquals("testuser1", lobby.getPlayers().get(1).getUsername());
- assertEquals("testuser2", lobby.getPlayers().get(2).getUsername());
- assertEquals(0, lobby.getPlayers().get(0).getIndex());
- assertEquals(1, lobby.getPlayers().get(1).getIndex());
- assertEquals(2, lobby.getPlayers().get(2).getIndex());
- }
-
- @Test(expected = UnknownPlayerException.class)
- public void reorderPlayers_failsOnUnknownPlayer() {
- Player player1 = new Player("testuser1", "Test User 1");
- Player player2 = new Player("testuser2", "Test User 2");
- Player player3 = new Player("testuser3", "Test User 3");
- lobby.addPlayer(player1);
- lobby.addPlayer(player2);
- lobby.addPlayer(player3);
- lobby.reorderPlayers(Arrays.asList("unknown", "testuser1", "testuser2"));
- }
-
- @Theory
- public void startGame_failsBelowMinPlayers(int nbPlayers) {
- assumeTrue(nbPlayers < gameDefinition.getMinPlayers());
- thrown.expect(PlayerUnderflowException.class);
- // there is already the owner
- addPlayers(nbPlayers - 1);
- lobby.startGame();
- }
-
- @Theory
- public void startGame_succeedsAboveMinPlayers(int nbPlayers) {
- assumeTrue(nbPlayers >= gameDefinition.getMinPlayers());
- assumeTrue(nbPlayers <= gameDefinition.getMaxPlayers());
- // there is already the owner
- addPlayers(nbPlayers - 1);
- Game game = lobby.startGame();
- assertNotNull(game);
- lobby.getPlayers().forEach(p -> assertSame(game, p.getGame()));
- }
-
- @Test
- public void startGame_switchesState() {
- assertTrue(lobby.getState() == State.LOBBY);
- // there is already the owner
- addPlayers(gameDefinition.getMinPlayers() - 1);
- lobby.startGame();
- assertTrue(lobby.getState() == State.PLAYING);
- }
-
- @Test
- public void setSettings() {
- CustomizableSettings settings = new CustomizableSettings();
- lobby.setSettings(settings);
- assertSame(settings, lobby.getSettings());
- }
-}
diff --git a/backend/src/test/java/org/luxons/sevenwonders/repositories/LobbyRepositoryTest.java b/backend/src/test/java/org/luxons/sevenwonders/repositories/LobbyRepositoryTest.java
deleted file mode 100644
index 444e7aaa..00000000
--- a/backend/src/test/java/org/luxons/sevenwonders/repositories/LobbyRepositoryTest.java
+++ /dev/null
@@ -1,76 +0,0 @@
-package org.luxons.sevenwonders.repositories;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.luxons.sevenwonders.lobby.Lobby;
-import org.luxons.sevenwonders.lobby.Player;
-import org.luxons.sevenwonders.repositories.LobbyRepository.LobbyNotFoundException;
-
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-public class LobbyRepositoryTest {
-
- private LobbyRepository repository;
-
- @Before
- public void setUp() {
- repository = new LobbyRepository();
- }
-
- @Test
- public void list_initiallyEmpty() {
- assertTrue(repository.list().isEmpty());
- }
-
- @Test
- public void list_returnsAllLobbies() {
- Player owner = new Player("owner", "The Owner");
- Lobby lobby1 = repository.create("Test Name 1", owner);
- Lobby lobby2 = repository.create("Test Name 2", owner);
- assertTrue(repository.list().contains(lobby1));
- assertTrue(repository.list().contains(lobby2));
- }
-
- @Test
- public void create_withCorrectOwner() {
- Player owner = new Player("owner", "The Owner");
- Lobby lobby = repository.create("Test Name", owner);
- assertTrue(lobby.isOwner(owner.getUsername()));
- }
-
- @Test(expected = LobbyNotFoundException.class)
- public void find_failsOnUnknownId() {
- repository.find(123);
- }
-
- @Test
- public void find_returnsTheSameObject() {
- Player owner = new Player("owner", "The Owner");
- Lobby lobby1 = repository.create("Test Name 1", owner);
- Lobby lobby2 = repository.create("Test Name 2", owner);
- assertSame(lobby1, repository.find(lobby1.getId()));
- assertSame(lobby2, repository.find(lobby2.getId()));
- }
-
- @Test(expected = LobbyNotFoundException.class)
- public void remove_failsOnUnknownId() {
- repository.remove(123);
- }
-
- @Test
- public void remove_succeeds() {
- Player owner = new Player("owner", "The Owner");
- Lobby lobby1 = repository.create("Test Name 1", owner);
- assertNotNull(repository.find(lobby1.getId()));
- repository.remove(lobby1.getId());
- try {
- repository.find(lobby1.getId());
- fail(); // the call to find() should have failed
- } catch (LobbyNotFoundException e) {
- // the lobby has been properly removed
- }
- }
-}
diff --git a/backend/src/test/java/org/luxons/sevenwonders/repositories/PlayerRepositoryTest.java b/backend/src/test/java/org/luxons/sevenwonders/repositories/PlayerRepositoryTest.java
deleted file mode 100644
index 95fd80fd..00000000
--- a/backend/src/test/java/org/luxons/sevenwonders/repositories/PlayerRepositoryTest.java
+++ /dev/null
@@ -1,73 +0,0 @@
-package org.luxons.sevenwonders.repositories;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.luxons.sevenwonders.lobby.Player;
-import org.luxons.sevenwonders.repositories.PlayerRepository.PlayerNotFoundException;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-
-public class PlayerRepositoryTest {
-
- private PlayerRepository repository;
-
- @Before
- public void setUp() {
- repository = new PlayerRepository();
- }
-
- @Test
- public void contains_falseIfNoUserAdded() {
- assertFalse(repository.contains("anyUsername"));
- }
-
- @Test
- public void contains_trueForCreatedPlayer() {
- repository.createOrUpdate("player1", "Player 1");
- assertTrue(repository.contains("player1"));
- }
-
- @Test
- public void createOrUpdate_createsProperly() {
- Player player1 = repository.createOrUpdate("player1", "Player 1");
- assertEquals("player1", player1.getUsername());
- assertEquals("Player 1", player1.getDisplayName());
- }
-
- @Test
- public void createOrUpdate_updatesDisplayName() {
- Player player1 = repository.createOrUpdate("player1", "Player 1");
- Player player1Updated = repository.createOrUpdate("player1", "Much Better Name");
- assertSame(player1, player1Updated);
- assertEquals("Much Better Name", player1Updated.getDisplayName());
- }
-
- @Test(expected = PlayerNotFoundException.class)
- public void find_failsOnUnknownUsername() {
- repository.find("anyUsername");
- }
-
- @Test
- public void find_returnsTheSameObject() {
- Player player1 = repository.createOrUpdate("player1", "Player 1");
- Player player2 = repository.createOrUpdate("player2", "Player 2");
- assertSame(player1, repository.find("player1"));
- assertSame(player2, repository.find("player2"));
- }
-
- @Test(expected = PlayerNotFoundException.class)
- public void remove_failsOnUnknownUsername() {
- repository.remove("anyUsername");
- }
-
- @Test
- public void remove_succeeds() {
- repository.createOrUpdate("player1", "Player 1");
- assertTrue(repository.contains("player1"));
- repository.remove("player1");
- assertFalse(repository.contains("player1"));
- }
-}
diff --git a/backend/src/test/java/org/luxons/sevenwonders/test/TestUtils.java b/backend/src/test/java/org/luxons/sevenwonders/test/TestUtils.java
deleted file mode 100644
index b29f970e..00000000
--- a/backend/src/test/java/org/luxons/sevenwonders/test/TestUtils.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package org.luxons.sevenwonders.test;
-
-import org.springframework.messaging.Message;
-import org.springframework.messaging.MessageChannel;
-import org.springframework.messaging.simp.SimpMessagingTemplate;
-
-public class TestUtils {
-
- public static SimpMessagingTemplate createSimpMessagingTemplate() {
- MessageChannel messageChannel = new MessageChannel() {
- @Override
- public boolean send(Message<?> message) {
- return true;
- }
-
- @Override
- public boolean send(Message<?> message, long timeout) {
- return true;
- }
- };
- return new SimpMessagingTemplate(messageChannel);
- }
-}
diff --git a/backend/src/test/java/org/luxons/sevenwonders/test/api/ApiBoard.java b/backend/src/test/java/org/luxons/sevenwonders/test/api/ApiBoard.java
deleted file mode 100644
index 56a67482..00000000
--- a/backend/src/test/java/org/luxons/sevenwonders/test/api/ApiBoard.java
+++ /dev/null
@@ -1,166 +0,0 @@
-package org.luxons.sevenwonders.test.api;
-
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-
-import org.luxons.sevenwonders.game.cards.Card;
-import org.luxons.sevenwonders.game.effects.SpecialAbility;
-
-public class ApiBoard {
-
- private ApiWonder wonder;
-
- private int playerIndex;
-
- private List<ApiCard> playedCards;
-
- private ApiProduction production;
-
- private ApiProduction publicProduction;
-
- private ApiScience science;
-
- private ApiTradingRules tradingRules;
-
- private ApiMilitary military;
-
- private Set<SpecialAbility> specialAbilities;
-
- private Map<Integer, Boolean> consumedFreeCards;
-
- private Card copiedGuild;
-
- private int gold;
-
- private int pointsPer3Gold;
-
- public ApiWonder getWonder() {
- return wonder;
- }
-
- public void setWonder(ApiWonder wonder) {
- this.wonder = wonder;
- }
-
- public int getPlayerIndex() {
- return playerIndex;
- }
-
- public void setPlayerIndex(int playerIndex) {
- this.playerIndex = playerIndex;
- }
-
- public List<ApiCard> getPlayedCards() {
- return playedCards;
- }
-
- public void setPlayedCards(List<ApiCard> playedCards) {
- this.playedCards = playedCards;
- }
-
- public ApiProduction getProduction() {
- return production;
- }
-
- public void setProduction(ApiProduction production) {
- this.production = production;
- }
-
- public ApiProduction getPublicProduction() {
- return publicProduction;
- }
-
- public void setPublicProduction(ApiProduction publicProduction) {
- this.publicProduction = publicProduction;
- }
-
- public ApiScience getScience() {
- return science;
- }
-
- public void setScience(ApiScience science) {
- this.science = science;
- }
-
- public ApiTradingRules getTradingRules() {
- return tradingRules;
- }
-
- public void setTradingRules(ApiTradingRules tradingRules) {
- this.tradingRules = tradingRules;
- }
-
- public ApiMilitary getMilitary() {
- return military;
- }
-
- public void setMilitary(ApiMilitary military) {
- this.military = military;
- }
-
- public Set<SpecialAbility> getSpecialAbilities() {
- return specialAbilities;
- }
-
- public void setSpecialAbilities(Set<SpecialAbility> specialAbilities) {
- this.specialAbilities = specialAbilities;
- }
-
- public Map<Integer, Boolean> getConsumedFreeCards() {
- return consumedFreeCards;
- }
-
- public void setConsumedFreeCards(Map<Integer, Boolean> consumedFreeCards) {
- this.consumedFreeCards = consumedFreeCards;
- }
-
- public Card getCopiedGuild() {
- return copiedGuild;
- }
-
- public void setCopiedGuild(Card copiedGuild) {
- this.copiedGuild = copiedGuild;
- }
-
- public int getGold() {
- return gold;
- }
-
- public void setGold(int gold) {
- this.gold = gold;
- }
-
- public int getPointsPer3Gold() {
- return pointsPer3Gold;
- }
-
- public void setPointsPer3Gold(int pointsPer3Gold) {
- this.pointsPer3Gold = pointsPer3Gold;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- ApiBoard apiBoard = (ApiBoard) o;
- return playerIndex == apiBoard.playerIndex && gold == apiBoard.gold && pointsPer3Gold == apiBoard.pointsPer3Gold
- && Objects.equals(wonder, apiBoard.wonder) && Objects.equals(playedCards, apiBoard.playedCards)
- && Objects.equals(production, apiBoard.production) && Objects.equals(publicProduction,
- apiBoard.publicProduction) && Objects.equals(science, apiBoard.science) && Objects.equals(tradingRules,
- apiBoard.tradingRules) && Objects.equals(military, apiBoard.military) && Objects.equals(
- specialAbilities, apiBoard.specialAbilities) && Objects.equals(consumedFreeCards,
- apiBoard.consumedFreeCards) && Objects.equals(copiedGuild, apiBoard.copiedGuild);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(wonder, playerIndex, playedCards, production, publicProduction, science, tradingRules,
- military, specialAbilities, consumedFreeCards, copiedGuild, gold, pointsPer3Gold);
- }
-}
diff --git a/backend/src/test/java/org/luxons/sevenwonders/test/api/ApiCard.java b/backend/src/test/java/org/luxons/sevenwonders/test/api/ApiCard.java
deleted file mode 100644
index f927dd4c..00000000
--- a/backend/src/test/java/org/luxons/sevenwonders/test/api/ApiCard.java
+++ /dev/null
@@ -1,100 +0,0 @@
-package org.luxons.sevenwonders.test.api;
-
-import java.util.List;
-import java.util.Objects;
-
-import org.luxons.sevenwonders.game.cards.Color;
-import org.luxons.sevenwonders.game.cards.Requirements;
-
-public class ApiCard {
-
- private String name;
-
- private Color color;
-
- private Requirements requirements;
-
- private String chainParent;
-
- private List<String> chainChildren;
-
- private String image;
-
- private ApiCardBack back;
-
- public String getName() {
- return name;
- }
-
- public void setName(String name) {
- this.name = name;
- }
-
- public Color getColor() {
- return color;
- }
-
- public void setColor(Color color) {
- this.color = color;
- }
-
- public Requirements getRequirements() {
- return requirements;
- }
-
- public void setRequirements(Requirements requirements) {
- this.requirements = requirements;
- }
-
- public String getChainParent() {
- return chainParent;
- }
-
- public void setChainParent(String chainParent) {
- this.chainParent = chainParent;
- }
-
- public List<String> getChainChildren() {
- return chainChildren;
- }
-
- public void setChainChildren(List<String> chainChildren) {
- this.chainChildren = chainChildren;
- }
-
- public String getImage() {
- return image;
- }
-
- public void setImage(String image) {
- this.image = image;
- }
-
- public ApiCardBack getBack() {
- return back;
- }
-
- public void setBack(ApiCardBack back) {
- this.back = back;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- ApiCard apiCard = (ApiCard) o;
- return Objects.equals(name, apiCard.name) && color == apiCard.color && Objects.equals(requirements,
- apiCard.requirements) && Objects.equals(chainParent, apiCard.chainParent) && Objects.equals(
- chainChildren, apiCard.chainChildren) && Objects.equals(image, apiCard.image) && Objects.equals(back,
- apiCard.back);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(name, color, requirements, chainParent, chainChildren, image, back);
- }
-}
diff --git a/backend/src/test/java/org/luxons/sevenwonders/test/api/ApiCardBack.java b/backend/src/test/java/org/luxons/sevenwonders/test/api/ApiCardBack.java
deleted file mode 100644
index e0f5a50e..00000000
--- a/backend/src/test/java/org/luxons/sevenwonders/test/api/ApiCardBack.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package org.luxons.sevenwonders.test.api;
-
-public class ApiCardBack {
-
- private String image;
-
- public String getImage() {
- return image;
- }
-
- public void setImage(String image) {
- this.image = image;
- }
-}
diff --git a/backend/src/test/java/org/luxons/sevenwonders/test/api/ApiEffect.java b/backend/src/test/java/org/luxons/sevenwonders/test/api/ApiEffect.java
deleted file mode 100644
index df71365d..00000000
--- a/backend/src/test/java/org/luxons/sevenwonders/test/api/ApiEffect.java
+++ /dev/null
@@ -1,4 +0,0 @@
-package org.luxons.sevenwonders.test.api;
-
-public class ApiEffect {
-}
diff --git a/backend/src/test/java/org/luxons/sevenwonders/test/api/ApiHandCard.java b/backend/src/test/java/org/luxons/sevenwonders/test/api/ApiHandCard.java
deleted file mode 100644
index 0cb143c1..00000000
--- a/backend/src/test/java/org/luxons/sevenwonders/test/api/ApiHandCard.java
+++ /dev/null
@@ -1,48 +0,0 @@
-package org.luxons.sevenwonders.test.api;
-
-/**
- * A card with contextual information relative to the hand it is sitting in. The extra information is especially
- * useful because it frees the client from a painful business logic implementation.
- */
-public class ApiHandCard {
-
- private ApiCard card;
-
- private boolean chainable;
-
- private boolean free;
-
- private boolean playable;
-
- public ApiCard getCard() {
- return card;
- }
-
- public void setCard(ApiCard card) {
- this.card = card;
- }
-
- public boolean isChainable() {
- return chainable;
- }
-
- public void setChainable(boolean chainable) {
- this.chainable = chainable;
- }
-
- public boolean isFree() {
- return free;
- }
-
- public void setFree(boolean free) {
- this.free = free;
- }
-
- public boolean isPlayable() {
- return playable;
- }
-
- public void setPlayable(boolean playable) {
- this.playable = playable;
- }
-}
diff --git a/backend/src/test/java/org/luxons/sevenwonders/test/api/ApiLobby.java b/backend/src/test/java/org/luxons/sevenwonders/test/api/ApiLobby.java
deleted file mode 100644
index e17369b1..00000000
--- a/backend/src/test/java/org/luxons/sevenwonders/test/api/ApiLobby.java
+++ /dev/null
@@ -1,89 +0,0 @@
-package org.luxons.sevenwonders.test.api;
-
-import java.util.List;
-import java.util.Objects;
-
-import org.luxons.sevenwonders.game.api.CustomizableSettings;
-import org.luxons.sevenwonders.lobby.State;
-
-public class ApiLobby {
-
- private long id;
-
- private String name;
-
- private String owner;
-
- private List<ApiPlayer> players;
-
- private CustomizableSettings settings;
-
- private State state;
-
- public long getId() {
- return id;
- }
-
- public void setId(long id) {
- this.id = id;
- }
-
- public String getName() {
- return name;
- }
-
- public void setName(String name) {
- this.name = name;
- }
-
- public String getOwner() {
- return owner;
- }
-
- public void setOwner(String owner) {
- this.owner = owner;
- }
-
- public List<ApiPlayer> getPlayers() {
- return players;
- }
-
- public void setPlayers(List<ApiPlayer> players) {
- this.players = players;
- }
-
- public CustomizableSettings getSettings() {
- return settings;
- }
-
- public void setSettings(CustomizableSettings settings) {
- this.settings = settings;
- }
-
- public State getState() {
- return state;
- }
-
- public void setState(State state) {
- this.state = state;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- ApiLobby apiLobby = (ApiLobby) o;
- return id == apiLobby.id && Objects.equals(name, apiLobby.name) && Objects.equals(owner, apiLobby.owner)
- && Objects.equals(players, apiLobby.players) && Objects.equals(settings, apiLobby.settings)
- && state == apiLobby.state;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(id, name, owner, players, settings, state);
- }
-}
diff --git a/backend/src/test/java/org/luxons/sevenwonders/test/api/ApiMilitary.java b/backend/src/test/java/org/luxons/sevenwonders/test/api/ApiMilitary.java
deleted file mode 100644
index 11ba2b1c..00000000
--- a/backend/src/test/java/org/luxons/sevenwonders/test/api/ApiMilitary.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package org.luxons.sevenwonders.test.api;
-
-public class ApiMilitary {
-
- private int nbShields = 0;
-
- private int totalPoints = 0;
-
- private int nbDefeatTokens = 0;
-
- public int getNbShields() {
- return nbShields;
- }
-
- public void setNbShields(int nbShields) {
- this.nbShields = nbShields;
- }
-
- public int getTotalPoints() {
- return totalPoints;
- }
-
- public void setTotalPoints(int totalPoints) {
- this.totalPoints = totalPoints;
- }
-
- public int getNbDefeatTokens() {
- return nbDefeatTokens;
- }
-
- public void setNbDefeatTokens(int nbDefeatTokens) {
- this.nbDefeatTokens = nbDefeatTokens;
- }
-}
diff --git a/backend/src/test/java/org/luxons/sevenwonders/test/api/ApiPlayer.java b/backend/src/test/java/org/luxons/sevenwonders/test/api/ApiPlayer.java
deleted file mode 100644
index 940e410b..00000000
--- a/backend/src/test/java/org/luxons/sevenwonders/test/api/ApiPlayer.java
+++ /dev/null
@@ -1,50 +0,0 @@
-package org.luxons.sevenwonders.test.api;
-
-import java.util.Objects;
-
-public class ApiPlayer {
-
- private String username;
-
- private String displayName;
-
- private int index;
-
- public String getUsername() {
- return username;
- }
-
- public String getDisplayName() {
- return displayName;
- }
-
- public void setDisplayName(String displayName) {
- this.displayName = displayName;
- }
-
- public int getIndex() {
- return index;
- }
-
- public void setIndex(int index) {
- this.index = index;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- ApiPlayer apiPlayer = (ApiPlayer) o;
- return index == apiPlayer.index && Objects.equals(username, apiPlayer.username) && Objects.equals(displayName,
- apiPlayer.displayName);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(username, displayName, index);
- }
-}
diff --git a/backend/src/test/java/org/luxons/sevenwonders/test/api/ApiPlayerTurnInfo.java b/backend/src/test/java/org/luxons/sevenwonders/test/api/ApiPlayerTurnInfo.java
deleted file mode 100644
index ae4d06ad..00000000
--- a/backend/src/test/java/org/luxons/sevenwonders/test/api/ApiPlayerTurnInfo.java
+++ /dev/null
@@ -1,98 +0,0 @@
-package org.luxons.sevenwonders.test.api;
-
-import java.util.List;
-import java.util.Objects;
-
-import org.luxons.sevenwonders.game.api.Action;
-
-public class ApiPlayerTurnInfo {
-
- private int playerIndex;
-
- private ApiTable table;
-
- private int currentAge;
-
- private Action action;
-
- private List<ApiHandCard> hand;
-
- private List<ApiCard> neighbourGuildCards;
-
- private String message;
-
- public int getPlayerIndex() {
- return playerIndex;
- }
-
- public void setPlayerIndex(int playerIndex) {
- this.playerIndex = playerIndex;
- }
-
- public ApiTable getTable() {
- return table;
- }
-
- public void setTable(ApiTable table) {
- this.table = table;
- }
-
- public int getCurrentAge() {
- return currentAge;
- }
-
- public void setCurrentAge(int currentAge) {
- this.currentAge = currentAge;
- }
-
- public List<ApiHandCard> getHand() {
- return hand;
- }
-
- public void setHand(List<ApiHandCard> hand) {
- this.hand = hand;
- }
-
- public List<ApiCard> getNeighbourGuildCards() {
- return neighbourGuildCards;
- }
-
- public void setNeighbourGuildCards(List<ApiCard> neighbourGuildCards) {
- this.neighbourGuildCards = neighbourGuildCards;
- }
-
- public Action getAction() {
- return action;
- }
-
- public void setAction(Action action) {
- this.action = action;
- }
-
- public String getMessage() {
- return message;
- }
-
- public void setMessage(String message) {
- this.message = message;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- ApiPlayerTurnInfo that = (ApiPlayerTurnInfo) o;
- return playerIndex == that.playerIndex && currentAge == that.currentAge && Objects.equals(table, that.table)
- && action == that.action && Objects.equals(hand, that.hand) && Objects.equals(neighbourGuildCards,
- that.neighbourGuildCards) && Objects.equals(message, that.message);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(playerIndex, table, currentAge, action, hand, neighbourGuildCards, message);
- }
-}
diff --git a/backend/src/test/java/org/luxons/sevenwonders/test/api/ApiProduction.java b/backend/src/test/java/org/luxons/sevenwonders/test/api/ApiProduction.java
deleted file mode 100644
index 0c3bf8b3..00000000
--- a/backend/src/test/java/org/luxons/sevenwonders/test/api/ApiProduction.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package org.luxons.sevenwonders.test.api;
-
-import java.util.Set;
-
-import org.luxons.sevenwonders.game.resources.ResourceType;
-import org.luxons.sevenwonders.game.resources.Resources;
-
-public class ApiProduction {
-
- private Resources fixedResources;
-
- private Set<Set<ResourceType>> alternativeResources;
-
- public Resources getFixedResources() {
- return fixedResources;
- }
-
- public void setFixedResources(Resources fixedResources) {
- this.fixedResources = fixedResources;
- }
-
- public Set<Set<ResourceType>> getAlternativeResources() {
- return alternativeResources;
- }
-
- public void setAlternativeResources(Set<Set<ResourceType>> alternativeResources) {
- this.alternativeResources = alternativeResources;
- }
-}
diff --git a/backend/src/test/java/org/luxons/sevenwonders/test/api/ApiScience.java b/backend/src/test/java/org/luxons/sevenwonders/test/api/ApiScience.java
deleted file mode 100644
index b2808c83..00000000
--- a/backend/src/test/java/org/luxons/sevenwonders/test/api/ApiScience.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package org.luxons.sevenwonders.test.api;
-
-import java.util.Map;
-
-import org.luxons.sevenwonders.game.boards.ScienceType;
-
-public class ApiScience {
-
- private Map<ScienceType, Integer> quantities;
-
- private int jokers;
-
- public Map<ScienceType, Integer> getQuantities() {
- return quantities;
- }
-
- public void setQuantities(Map<ScienceType, Integer> quantities) {
- this.quantities = quantities;
- }
-
- public int getJokers() {
- return jokers;
- }
-
- public void setJokers(int jokers) {
- this.jokers = jokers;
- }
-}
diff --git a/backend/src/test/java/org/luxons/sevenwonders/test/api/ApiTable.java b/backend/src/test/java/org/luxons/sevenwonders/test/api/ApiTable.java
deleted file mode 100644
index d0d991aa..00000000
--- a/backend/src/test/java/org/luxons/sevenwonders/test/api/ApiTable.java
+++ /dev/null
@@ -1,73 +0,0 @@
-package org.luxons.sevenwonders.test.api;
-
-import java.util.List;
-
-import org.luxons.sevenwonders.game.cards.HandRotationDirection;
-import org.luxons.sevenwonders.game.moves.Move;
-
-public class ApiTable {
-
- private int nbPlayers;
-
- private List<ApiBoard> boards;
-
- private int currentAge = 0;
-
- private HandRotationDirection handRotationDirection;
-
- private List<Move> lastPlayedMoves;
-
- private List<ApiCard> neighbourGuildCards;
-
- public int getNbPlayers() {
- return nbPlayers;
- }
-
- public void setNbPlayers(int nbPlayers) {
- this.nbPlayers = nbPlayers;
- }
-
- public List<ApiBoard> getBoards() {
- return boards;
- }
-
- public void setBoards(List<ApiBoard> boards) {
- this.boards = boards;
- }
-
- public int getCurrentAge() {
- return currentAge;
- }
-
- public void setCurrentAge(int currentAge) {
- this.currentAge = currentAge;
- }
-
- public HandRotationDirection getHandRotationDirection() {
- return handRotationDirection;
- }
-
- public void setHandRotationDirection(HandRotationDirection handRotationDirection) {
- this.handRotationDirection = handRotationDirection;
- }
-
- public List<Move> getLastPlayedMoves() {
- return lastPlayedMoves;
- }
-
- public void setLastPlayedMoves(List<Move> lastPlayedMoves) {
- this.lastPlayedMoves = lastPlayedMoves;
- }
-
- public void increaseCurrentAge() {
- this.currentAge++;
- }
-
- public List<ApiCard> getNeighbourGuildCards() {
- return neighbourGuildCards;
- }
-
- public void setNeighbourGuildCards(List<ApiCard> neighbourGuildCards) {
- this.neighbourGuildCards = neighbourGuildCards;
- }
-}
diff --git a/backend/src/test/java/org/luxons/sevenwonders/test/api/ApiTradingRules.java b/backend/src/test/java/org/luxons/sevenwonders/test/api/ApiTradingRules.java
deleted file mode 100644
index 324b7d0e..00000000
--- a/backend/src/test/java/org/luxons/sevenwonders/test/api/ApiTradingRules.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package org.luxons.sevenwonders.test.api;
-
-import java.util.Map;
-
-import org.luxons.sevenwonders.game.resources.Provider;
-import org.luxons.sevenwonders.game.resources.ResourceType;
-
-public class ApiTradingRules {
-
- private Map<ResourceType, Map<Provider, Integer>> costs;
-
- public Map<ResourceType, Map<Provider, Integer>> getCosts() {
- return costs;
- }
-
- public void setCosts(Map<ResourceType, Map<Provider, Integer>> costs) {
- this.costs = costs;
- }
-}
diff --git a/backend/src/test/java/org/luxons/sevenwonders/test/api/ApiWonder.java b/backend/src/test/java/org/luxons/sevenwonders/test/api/ApiWonder.java
deleted file mode 100644
index d671b2f7..00000000
--- a/backend/src/test/java/org/luxons/sevenwonders/test/api/ApiWonder.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package org.luxons.sevenwonders.test.api;
-
-import java.util.List;
-
-import org.luxons.sevenwonders.game.resources.ResourceType;
-
-public class ApiWonder {
-
- private String name;
-
- private ResourceType initialResource;
-
- private List<ApiWonderStage> stages;
-
- private String image;
-
- private int nbBbuiltStages;
-}
diff --git a/backend/src/test/java/org/luxons/sevenwonders/test/api/ApiWonderStage.java b/backend/src/test/java/org/luxons/sevenwonders/test/api/ApiWonderStage.java
deleted file mode 100644
index 6a6e0884..00000000
--- a/backend/src/test/java/org/luxons/sevenwonders/test/api/ApiWonderStage.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package org.luxons.sevenwonders.test.api;
-
-import org.luxons.sevenwonders.game.cards.CardBack;
-import org.luxons.sevenwonders.game.cards.Requirements;
-
-public class ApiWonderStage {
-
- private Requirements requirements;
-
- private CardBack cardBack;
-
- private boolean built;
-
- public Requirements getRequirements() {
- return requirements;
- }
-
- public void setRequirements(Requirements requirements) {
- this.requirements = requirements;
- }
-
- public CardBack getCardBack() {
- return cardBack;
- }
-
- public void setCardBack(CardBack cardBack) {
- this.cardBack = cardBack;
- }
-
- public boolean isBuilt() {
- return built;
- }
-
- public void setBuilt(boolean built) {
- this.built = built;
- }
-}
diff --git a/backend/src/test/java/org/luxons/sevenwonders/test/api/SevenWondersClient.java b/backend/src/test/java/org/luxons/sevenwonders/test/api/SevenWondersClient.java
deleted file mode 100644
index ade43677..00000000
--- a/backend/src/test/java/org/luxons/sevenwonders/test/api/SevenWondersClient.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package org.luxons.sevenwonders.test.api;
-
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeoutException;
-
-import org.hildan.jackstomp.JackstompClient;
-import org.hildan.jackstomp.JackstompSession;
-
-public class SevenWondersClient {
-
- private static final String WEBSOCKET_ENDPOINT = "/seven-wonders-websocket";
-
- private final JackstompClient client;
-
- public SevenWondersClient() {
- this(new JackstompClient());
- }
-
- public SevenWondersClient(JackstompClient client) {
- this.client = client;
- }
-
- public SevenWondersSession connect(String serverUrl)
- throws InterruptedException, ExecutionException, TimeoutException {
- JackstompSession session = client.connect(serverUrl + WEBSOCKET_ENDPOINT);
- return new SevenWondersSession(session);
- }
-
- public void stop() {
- client.stop();
- }
-}
diff --git a/backend/src/test/java/org/luxons/sevenwonders/test/api/SevenWondersSession.java b/backend/src/test/java/org/luxons/sevenwonders/test/api/SevenWondersSession.java
deleted file mode 100644
index b6c36f8a..00000000
--- a/backend/src/test/java/org/luxons/sevenwonders/test/api/SevenWondersSession.java
+++ /dev/null
@@ -1,83 +0,0 @@
-package org.luxons.sevenwonders.test.api;
-
-import org.hildan.jackstomp.Channel;
-import org.hildan.jackstomp.JackstompSession;
-import org.luxons.sevenwonders.actions.ChooseNameAction;
-import org.luxons.sevenwonders.actions.CreateGameAction;
-import org.luxons.sevenwonders.actions.JoinGameAction;
-import org.luxons.sevenwonders.errors.UIError;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-public class SevenWondersSession {
-
- private final JackstompSession session;
-
- public SevenWondersSession(JackstompSession session) {
- this.session = session;
- }
-
- public JackstompSession getJackstompSession() {
- return session;
- }
-
- public void disconnect() {
- session.disconnect();
- }
-
- public Channel<UIError> watchErrors() {
- return session.subscribe("/user/queue/errors", UIError.class);
- }
-
- public ApiPlayer chooseName(String displayName) throws InterruptedException {
- ChooseNameAction action = new ChooseNameAction();
- action.setPlayerName(displayName);
- return session.request(action, ApiPlayer.class, "/app/chooseName", "/user/queue/nameChoice");
- }
-
- public Channel<ApiLobby[]> watchGames() {
- return session.subscribe("/topic/games", ApiLobby[].class);
- }
-
- public ApiLobby createGame(String gameName) throws InterruptedException {
- CreateGameAction action = new CreateGameAction();
- action.setGameName(gameName);
-
- return session.request(action, ApiLobby.class, "/app/lobby/create", "/user/queue/lobby/joined");
- }
-
- public ApiLobby joinGame(long gameId) throws InterruptedException {
- JoinGameAction action = new JoinGameAction();
- action.setGameId(gameId);
-
- ApiLobby lobby = session.request(action, ApiLobby.class, "/app/lobby/join", "/user/queue/lobby/joined");
- assertNotNull(lobby);
- assertEquals(gameId, lobby.getId());
- return lobby;
- }
-
- public Channel<ApiLobby> watchLobbyUpdates(long gameId) {
- return session.subscribe("/topic/lobby/" + gameId + "/updated", ApiLobby.class);
- }
-
- public Channel<ApiLobby> watchLobbyStart(long gameId) {
- return session.subscribe("/topic/lobby/" + gameId + "/started", ApiLobby.class);
- }
-
- public void startGame(long gameId) throws InterruptedException {
- String sendDestination = "/app/lobby/startGame";
- String receiveDestination = "/topic/lobby/" + gameId + "/started";
- boolean received = session.request(null, sendDestination, receiveDestination);
- assertTrue(received);
- }
-
- public void sayReady() {
- session.send("/app/game/sayReady", null);
- }
-
- public Channel<ApiPlayerTurnInfo> watchTurns() {
- return session.subscribe("/user/queue/game/turn", ApiPlayerTurnInfo.class);
- }
-}
diff --git a/backend/src/test/java/org/luxons/sevenwonders/validation/DestinationAccessValidatorTest.java b/backend/src/test/java/org/luxons/sevenwonders/validation/DestinationAccessValidatorTest.java
deleted file mode 100644
index 604b927d..00000000
--- a/backend/src/test/java/org/luxons/sevenwonders/validation/DestinationAccessValidatorTest.java
+++ /dev/null
@@ -1,140 +0,0 @@
-package org.luxons.sevenwonders.validation;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.luxons.sevenwonders.lobby.Lobby;
-import org.luxons.sevenwonders.lobby.Player;
-import org.luxons.sevenwonders.repositories.LobbyRepository;
-import org.luxons.sevenwonders.repositories.LobbyRepository.LobbyNotFoundException;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-public class DestinationAccessValidatorTest {
-
- private LobbyRepository lobbyRepository;
-
- private DestinationAccessValidator destinationAccessValidator;
-
- @Before
- public void setup() {
- lobbyRepository = new LobbyRepository();
- destinationAccessValidator = new DestinationAccessValidator(lobbyRepository);
- }
-
- private Lobby createLobby(String gameName, String ownerUsername, String... otherPlayers) {
- Player owner = new Player(ownerUsername, ownerUsername);
- Lobby lobby = lobbyRepository.create(gameName, owner);
- for (String playerName : otherPlayers) {
- Player player = new Player(playerName, playerName);
- lobby.addPlayer(player);
- }
- return lobby;
- }
-
- private void createGame(String gameName, String ownerUsername, String... otherPlayers) {
- Lobby lobby = createLobby(gameName, ownerUsername, otherPlayers);
- lobby.startGame();
- }
-
- @Test
- public void validate_failsOnNullUser() {
- assertFalse(destinationAccessValidator.hasAccess(null, "doesNotMatter"));
- }
-
- @Test
- public void validate_successWhenNoReference() {
- assertTrue(destinationAccessValidator.hasAccess("", ""));
- assertTrue(destinationAccessValidator.hasAccess("", "test"));
- assertTrue(destinationAccessValidator.hasAccess("testUser", "test"));
- }
-
- @Test
- public void validate_successWhenNoRefFollows() {
- assertTrue(destinationAccessValidator.hasAccess("testUser", "/game/"));
- assertTrue(destinationAccessValidator.hasAccess("testUser", "/lobby/"));
- assertTrue(destinationAccessValidator.hasAccess("testUser", "prefix/game/"));
- assertTrue(destinationAccessValidator.hasAccess("testUser", "prefix/lobby/"));
- assertTrue(destinationAccessValidator.hasAccess("testUser", "/game//suffix"));
- assertTrue(destinationAccessValidator.hasAccess("testUser", "/lobby//suffix"));
- }
-
- @Test
- public void validate_successWhenRefIsNotANumber() {
- assertTrue(destinationAccessValidator.hasAccess("testUser", "/game/notANumber"));
- assertTrue(destinationAccessValidator.hasAccess("testUser", "/lobby/notANumber"));
- assertTrue(destinationAccessValidator.hasAccess("testUser", "prefix/game/notANumber"));
- assertTrue(destinationAccessValidator.hasAccess("testUser", "prefix/lobby/notANumber"));
- assertTrue(destinationAccessValidator.hasAccess("testUser", "/game/notANumber/suffix"));
- assertTrue(destinationAccessValidator.hasAccess("testUser", "/lobby/notANumber/suffix"));
- }
-
- @Test(expected = LobbyNotFoundException.class)
- public void validate_failWhenNoLobbyExist() {
- destinationAccessValidator.hasAccess("", "/lobby/0");
- }
-
- @Test(expected = LobbyNotFoundException.class)
- public void validate_failWhenNoGameExist() {
- destinationAccessValidator.hasAccess("", "/game/0");
- }
-
- @Test(expected = LobbyNotFoundException.class)
- public void validate_failWhenReferencedLobbyDoesNotExist() {
- createLobby("Test Game", "ownerUser1");
- createLobby("Test Game 2", "ownerUser2");
- destinationAccessValidator.hasAccess("doesNotMatter", "/lobby/3");
- }
-
- @Test(expected = LobbyNotFoundException.class)
- public void validate_failWhenReferencedGameDoesNotExist() {
- createGame("Test Game 1", "user1", "user2", "user3");
- createGame("Test Game 2", "user4", "user5", "user6");
- destinationAccessValidator.hasAccess("doesNotMatter", "/game/3");
- }
-
- @Test
- public void validate_failWhenUserIsNotPartOfReferencedLobby() {
- createLobby("Test Game", "ownerUser");
- destinationAccessValidator.hasAccess("userNotInLobby", "/lobby/0");
- }
-
- @Test
- public void validate_failWhenUserIsNotPartOfReferencedGame() {
- createGame("Test Game", "ownerUser", "otherUser1", "otherUser2");
- destinationAccessValidator.hasAccess("userNotInGame", "/game/0");
- }
-
- @Test
- public void validate_successWhenUserIsOwnerOfReferencedLobby() {
- createLobby("Test Game 1", "user1");
- assertTrue(destinationAccessValidator.hasAccess("user1", "/lobby/0"));
- createLobby("Test Game 2", "user2");
- assertTrue(destinationAccessValidator.hasAccess("user2", "/lobby/1"));
- }
-
- @Test
- public void validate_successWhenUserIsMemberOfReferencedLobby() {
- createLobby("Test Game 1", "user1", "user2");
- assertTrue(destinationAccessValidator.hasAccess("user2", "/lobby/0"));
- createLobby("Test Game 2", "user3", "user4");
- assertTrue(destinationAccessValidator.hasAccess("user4", "/lobby/1"));
- }
-
- @Test
- public void validate_successWhenUserIsOwnerOfReferencedGame() {
- createGame("Test Game 1", "owner1", "user2", "user3");
- assertTrue(destinationAccessValidator.hasAccess("owner1", "/game/0"));
- createGame("Test Game 2", "owner4", "user5", "user6");
- assertTrue(destinationAccessValidator.hasAccess("owner4", "/game/1"));
- }
-
- @Test
- public void validate_successWhenUserIsMemberOfReferencedGame() {
- createGame("Test Game 1", "owner1", "user2", "user3");
- assertTrue(destinationAccessValidator.hasAccess("user2", "/game/0"));
- createGame("Test Game 2", "owner4", "user5", "user6");
- assertTrue(destinationAccessValidator.hasAccess("user6", "/game/1"));
- }
-
-}
diff --git a/backend/src/test/kotlin/org/luxons/sevenwonders/SevenWondersTest.kt b/backend/src/test/kotlin/org/luxons/sevenwonders/SevenWondersTest.kt
new file mode 100644
index 00000000..cf6c7ce8
--- /dev/null
+++ b/backend/src/test/kotlin/org/luxons/sevenwonders/SevenWondersTest.kt
@@ -0,0 +1,151 @@
+package org.luxons.sevenwonders
+
+import org.junit.After
+import org.junit.Assert.*
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.luxons.sevenwonders.test.api.SevenWondersClient
+import org.luxons.sevenwonders.test.api.SevenWondersSession
+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 java.util.concurrent.ExecutionException
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.TimeoutException
+
+@RunWith(SpringRunner::class)
+@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
+class SevenWondersTest {
+
+ @LocalServerPort
+ private val randomServerPort: Int = 0
+
+ private lateinit var client: SevenWondersClient
+
+ private lateinit var serverUrl: String
+
+ @Before
+ fun setUpClientAndUrl() {
+ client = SevenWondersClient()
+ serverUrl = "ws://localhost:$randomServerPort"
+ }
+
+ private fun disconnect(vararg sessions: SevenWondersSession) {
+ for (session in sessions) {
+ session.disconnect()
+ }
+ }
+
+ @Test
+ @Throws(InterruptedException::class, ExecutionException::class, TimeoutException::class)
+ fun chooseName() {
+ val session = client.connect(serverUrl)
+ val playerName = "Test User"
+ val player = session.chooseName(playerName)
+ assertNotNull(player)
+ assertEquals(playerName, player.displayName)
+ session.disconnect()
+ }
+
+ @Throws(InterruptedException::class, TimeoutException::class, ExecutionException::class)
+ private fun newPlayer(name: String): SevenWondersSession {
+ val otherSession = client.connect(serverUrl)
+ otherSession.chooseName(name)
+ return otherSession
+ }
+
+ @Test
+ @Throws(InterruptedException::class, ExecutionException::class, TimeoutException::class)
+ fun lobbySubscription_ignoredForOutsiders() {
+ val ownerSession = newPlayer("GameOwner")
+ val session1 = newPlayer("Player1")
+ val session2 = newPlayer("Player2")
+ val gameName = "Test Game"
+ val lobby = ownerSession.createGame(gameName)
+ session1.joinGame(lobby.id)
+ session2.joinGame(lobby.id)
+
+ val outsiderSession = newPlayer("Outsider")
+ val session = outsiderSession.jackstompSession
+ val started = session.subscribeEmptyMsgs("/topic/lobby/" + lobby.id + "/started")
+
+ ownerSession.startGame(lobby.id)
+ val nothing = started.next(1, TimeUnit.SECONDS)
+ assertNull(nothing)
+ disconnect(ownerSession, session1, session2, outsiderSession)
+ }
+
+ @Test
+ @Throws(InterruptedException::class, ExecutionException::class, TimeoutException::class)
+ fun createGame_success() {
+ val ownerSession = newPlayer("GameOwner")
+
+ val gameName = "Test Game"
+ val lobby = ownerSession.createGame(gameName)
+ assertNotNull(lobby)
+ assertEquals(gameName, lobby.name)
+
+ disconnect(ownerSession)
+ }
+
+ @Test
+ @Throws(InterruptedException::class, ExecutionException::class, TimeoutException::class)
+ fun createGame_seenByConnectedPlayers() {
+ val otherSession = newPlayer("OtherPlayer")
+ val games = otherSession.watchGames()
+
+ var receivedLobbies = games.next()
+ assertNotNull(receivedLobbies)
+ assertEquals(0, receivedLobbies.size.toLong())
+
+ val ownerSession = newPlayer("GameOwner")
+ val gameName = "Test Game"
+ val createdLobby = ownerSession.createGame(gameName)
+
+ receivedLobbies = games.next()
+ assertNotNull(receivedLobbies)
+ assertEquals(1, receivedLobbies.size.toLong())
+ val receivedLobby = receivedLobbies[0]
+ assertEquals(createdLobby.id, receivedLobby.id)
+ assertEquals(createdLobby.name, receivedLobby.name)
+
+ disconnect(ownerSession, otherSession)
+ }
+
+ @Test
+ @Throws(Exception::class)
+ fun startGame_3players() {
+ val session1 = newPlayer("Player1")
+ val session2 = newPlayer("Player2")
+
+ val lobby = session1.createGame("Test Game")
+ session2.joinGame(lobby.id)
+
+ val session3 = newPlayer("Player3")
+ session3.joinGame(lobby.id)
+
+ session1.startGame(lobby.id)
+
+ val turns1 = session1.watchTurns()
+ val turns2 = session2.watchTurns()
+ val turns3 = session3.watchTurns()
+ session1.sayReady()
+ session2.sayReady()
+ session3.sayReady()
+ val turn1 = turns1.next()
+ val turn2 = turns2.next()
+ val turn3 = turns3.next()
+ assertNotNull(turn1)
+ assertNotNull(turn2)
+ assertNotNull(turn3)
+
+ disconnect(session1, session2, session3)
+ }
+
+ @After
+ fun tearDown() {
+ client.stop()
+ }
+}
diff --git a/backend/src/test/kotlin/org/luxons/sevenwonders/controllers/GameBrowserControllerTest.kt b/backend/src/test/kotlin/org/luxons/sevenwonders/controllers/GameBrowserControllerTest.kt
new file mode 100644
index 00000000..96d4bc85
--- /dev/null
+++ b/backend/src/test/kotlin/org/luxons/sevenwonders/controllers/GameBrowserControllerTest.kt
@@ -0,0 +1,114 @@
+package org.luxons.sevenwonders.controllers
+
+import org.junit.Assert.*
+import org.junit.Before
+import org.junit.Test
+import org.luxons.sevenwonders.actions.CreateGameAction
+import org.luxons.sevenwonders.actions.JoinGameAction
+import org.luxons.sevenwonders.controllers.GameBrowserController.UserAlreadyInGameException
+import org.luxons.sevenwonders.repositories.LobbyRepository
+import org.luxons.sevenwonders.repositories.PlayerNotFoundException
+import org.luxons.sevenwonders.repositories.PlayerRepository
+import org.luxons.sevenwonders.test.TestUtils
+
+class GameBrowserControllerTest {
+
+ private lateinit var playerRepository: PlayerRepository
+
+ private lateinit var gameBrowserController: GameBrowserController
+
+ @Before
+ fun setUp() {
+ playerRepository = PlayerRepository()
+ val lobbyRepository = LobbyRepository()
+ val template = TestUtils.createSimpMessagingTemplate()
+ val lobbyController = LobbyController(lobbyRepository, playerRepository, template)
+ gameBrowserController = GameBrowserController(lobbyController, lobbyRepository, playerRepository, template)
+ }
+
+ @Test
+ fun listGames_initiallyEmpty() {
+ val principal = TestPrincipal("testuser")
+ val games = gameBrowserController.listGames(principal)
+ assertTrue(games.isEmpty())
+ }
+
+ @Test
+ fun createGame_success() {
+ val player = playerRepository.createOrUpdate("testuser", "Test User")
+ val principal = TestPrincipal("testuser")
+
+ val action = CreateGameAction("Test Game")
+
+ val createdLobby = gameBrowserController.createGame(action, principal)
+
+ assertEquals("Test Game", createdLobby.name)
+
+ val games = gameBrowserController.listGames(principal)
+ assertFalse(games.isEmpty())
+ val lobby = games.iterator().next()
+ assertSame(lobby, createdLobby)
+ assertSame(player, lobby.getPlayers()[0])
+ }
+
+ @Test(expected = PlayerNotFoundException::class)
+ fun createGame_failsForUnknownPlayer() {
+ val principal = TestPrincipal("unknown")
+
+ val action = CreateGameAction("Test Game")
+ gameBrowserController.createGame(action, principal)
+ }
+
+ @Test(expected = UserAlreadyInGameException::class)
+ fun createGame_failsWhenAlreadyInGame() {
+ playerRepository.createOrUpdate("testuser", "Test User")
+ val principal = TestPrincipal("testuser")
+
+ val createGameAction1 = CreateGameAction("Test Game 1")
+
+ // auto-enters the game
+ gameBrowserController.createGame(createGameAction1, principal)
+
+ val createGameAction2 = CreateGameAction("Test Game 2")
+
+ // already in a game
+ gameBrowserController.createGame(createGameAction2, principal)
+ }
+
+ @Test
+ fun joinGame_success() {
+ val owner = playerRepository.createOrUpdate("testowner", "Test User Owner")
+ val ownerPrincipal = TestPrincipal("testowner")
+ val createGameAction = CreateGameAction("Test Game")
+
+ val createdLobby = gameBrowserController.createGame(createGameAction, ownerPrincipal)
+
+ val joiner = playerRepository.createOrUpdate("testjoiner", "Test User Joiner")
+ val joinerPrincipal = TestPrincipal("testjoiner")
+ val joinGameAction = JoinGameAction(createdLobby.id)
+
+ val joinedLobby = gameBrowserController.joinGame(joinGameAction, joinerPrincipal)
+
+ assertSame(createdLobby, joinedLobby)
+ assertSame(owner, joinedLobby.getPlayers()[0])
+ assertSame(joiner, joinedLobby.getPlayers()[1])
+ }
+
+ @Test(expected = UserAlreadyInGameException::class)
+ fun joinGame_failsWhenAlreadyInGame() {
+ playerRepository.createOrUpdate("testowner", "Test User Owner")
+ val ownerPrincipal = TestPrincipal("testowner")
+ val createGameAction = CreateGameAction("Test Game")
+
+ val createdLobby = gameBrowserController.createGame(createGameAction, ownerPrincipal)
+
+ playerRepository.createOrUpdate("testjoiner", "Test User Joiner")
+ val joinerPrincipal = TestPrincipal("testjoiner")
+ val joinGameAction = JoinGameAction(createdLobby.id)
+
+ // joins the game
+ gameBrowserController.joinGame(joinGameAction, joinerPrincipal)
+ // should fail because already in a game
+ gameBrowserController.joinGame(joinGameAction, joinerPrincipal)
+ }
+}
diff --git a/backend/src/test/kotlin/org/luxons/sevenwonders/controllers/HomeControllerTest.kt b/backend/src/test/kotlin/org/luxons/sevenwonders/controllers/HomeControllerTest.kt
new file mode 100644
index 00000000..b11ef878
--- /dev/null
+++ b/backend/src/test/kotlin/org/luxons/sevenwonders/controllers/HomeControllerTest.kt
@@ -0,0 +1,26 @@
+package org.luxons.sevenwonders.controllers
+
+import org.junit.Assert.*
+import org.junit.Test
+import org.luxons.sevenwonders.actions.ChooseNameAction
+import org.luxons.sevenwonders.repositories.PlayerRepository
+
+class HomeControllerTest {
+
+ @Test
+ fun chooseName() {
+ val playerRepository = PlayerRepository()
+ val homeController = HomeController(playerRepository)
+
+ val action = ChooseNameAction("Test User")
+ val principal = TestPrincipal("testuser")
+
+ val player = homeController.chooseName(action, principal)
+
+ assertSame(player, playerRepository.find("testuser"))
+ assertEquals("testuser", player.username)
+ assertEquals("Test User", player.displayName)
+ assertFalse(player.isInLobby)
+ assertFalse(player.isInGame)
+ }
+}
diff --git a/backend/src/test/kotlin/org/luxons/sevenwonders/controllers/LobbyControllerTest.kt b/backend/src/test/kotlin/org/luxons/sevenwonders/controllers/LobbyControllerTest.kt
new file mode 100644
index 00000000..6269ae40
--- /dev/null
+++ b/backend/src/test/kotlin/org/luxons/sevenwonders/controllers/LobbyControllerTest.kt
@@ -0,0 +1,199 @@
+package org.luxons.sevenwonders.controllers
+
+import org.junit.Assert.*
+import org.junit.Before
+import org.junit.Test
+import org.luxons.sevenwonders.actions.ReorderPlayersAction
+import org.luxons.sevenwonders.actions.UpdateSettingsAction
+import org.luxons.sevenwonders.game.api.CustomizableSettings
+import org.luxons.sevenwonders.game.data.WonderSidePickMethod.ALL_A
+import org.luxons.sevenwonders.lobby.Lobby
+import org.luxons.sevenwonders.lobby.Player
+import org.luxons.sevenwonders.lobby.PlayerIsNotOwnerException
+import org.luxons.sevenwonders.lobby.PlayerNotInLobbyException
+import org.luxons.sevenwonders.lobby.State
+import org.luxons.sevenwonders.repositories.LobbyRepository
+import org.luxons.sevenwonders.repositories.PlayerNotFoundException
+import org.luxons.sevenwonders.repositories.PlayerRepository
+import org.luxons.sevenwonders.test.TestUtils
+import java.util.Arrays
+import java.util.HashMap
+
+class LobbyControllerTest {
+
+ private lateinit var playerRepository: PlayerRepository
+
+ private lateinit var lobbyRepository: LobbyRepository
+
+ private lateinit var lobbyController: LobbyController
+
+ @Before
+ fun setUp() {
+ val template = TestUtils.createSimpMessagingTemplate()
+ playerRepository = PlayerRepository()
+ lobbyRepository = LobbyRepository()
+ lobbyController = LobbyController(lobbyRepository, playerRepository, template)
+ }
+
+ @Test
+ fun init_succeeds() {
+ val owner = playerRepository.createOrUpdate("testuser", "Test User")
+ val lobby = lobbyRepository.create("Test Game", owner)
+
+ assertTrue(lobby.getPlayers().contains(owner))
+ assertSame(lobby, owner.lobby)
+ assertEquals(owner.username, lobby.owner)
+ assertTrue(owner.isInLobby)
+ assertFalse(owner.isInGame)
+ }
+
+ @Test(expected = PlayerNotFoundException::class)
+ fun leave_failsWhenPlayerDoesNotExist() {
+ val principal = TestPrincipal("I don't exist")
+ lobbyController.leave(principal)
+ }
+
+ @Test(expected = PlayerNotInLobbyException::class)
+ fun leave_failsWhenNotInLobby() {
+ playerRepository.createOrUpdate("testuser", "Test User")
+ val principal = TestPrincipal("testuser")
+ lobbyController.leave(principal)
+ }
+
+ @Test
+ fun leave_succeedsWhenInALobby_asOwner() {
+ val player = playerRepository.createOrUpdate("testuser", "Test User")
+ val lobby = lobbyRepository.create("Test Game", player)
+
+ val principal = TestPrincipal("testuser")
+ lobbyController.leave(principal)
+
+ assertFalse(lobbyRepository.list().contains(lobby))
+ assertFalse(player.isInLobby)
+ assertFalse(player.isInGame)
+ }
+
+ @Test
+ fun leave_succeedsWhenInALobby_asPeasant() {
+ val player = playerRepository.createOrUpdate("testuser", "Test User")
+ val lobby = lobbyRepository.create("Test Game", player)
+ val player2 = addPlayer(lobby, "testuser2")
+
+ val principal = TestPrincipal("testuser2")
+ lobbyController.leave(principal)
+
+ assertFalse(lobby.getPlayers().contains(player2))
+ assertFalse(player2.isInLobby)
+ assertFalse(player2.isInGame)
+ }
+
+ @Test
+ fun reorderPlayers_succeedsForOwner() {
+ val player = playerRepository.createOrUpdate("testuser", "Test User")
+ val lobby = lobbyRepository.create("Test Game", player)
+
+ val player2 = addPlayer(lobby, "testuser2")
+ val player3 = addPlayer(lobby, "testuser3")
+ val player4 = addPlayer(lobby, "testuser4")
+
+ val players = Arrays.asList(player, player2, player3, player4)
+ assertEquals(players, lobby.getPlayers())
+
+ val reorderedPlayers = Arrays.asList(player3, player, player2, player4)
+ val playerNames = reorderedPlayers.map { it.username }
+ val reorderPlayersAction = ReorderPlayersAction(playerNames)
+
+ val principal = TestPrincipal("testuser")
+ lobbyController.reorderPlayers(reorderPlayersAction, principal)
+
+ assertEquals(reorderedPlayers, lobby.getPlayers())
+ }
+
+ @Test(expected = PlayerIsNotOwnerException::class)
+ fun reorderPlayers_failsForPeasant() {
+ val player = playerRepository.createOrUpdate("testuser", "Test User")
+ val lobby = lobbyRepository.create("Test Game", player)
+
+ val player2 = addPlayer(lobby, "testuser2")
+ val player3 = addPlayer(lobby, "testuser3")
+
+ val reorderedPlayers = Arrays.asList(player3, player, player2)
+ val playerNames = reorderedPlayers.map { it.username }
+ val reorderPlayersAction = ReorderPlayersAction(playerNames)
+
+ val principal = TestPrincipal("testuser2")
+ lobbyController.reorderPlayers(reorderPlayersAction, principal)
+ }
+
+ @Test
+ fun updateSettings_succeedsForOwner() {
+ val player = playerRepository.createOrUpdate("testuser", "Test User")
+ val lobby = lobbyRepository.create("Test Game", player)
+
+ addPlayer(lobby, "testuser2")
+ addPlayer(lobby, "testuser3")
+ addPlayer(lobby, "testuser4")
+
+ assertEquals(CustomizableSettings(), lobby.settings)
+
+ val newSettings = CustomizableSettings(12L, 5, ALL_A, 5, 5, 4, 10, 2, HashMap())
+ val updateSettingsAction = UpdateSettingsAction(newSettings)
+
+ val principal = TestPrincipal("testuser")
+ lobbyController.updateSettings(updateSettingsAction, principal)
+
+ assertEquals(newSettings, lobby.settings)
+ }
+
+ @Test(expected = PlayerIsNotOwnerException::class)
+ fun updateSettings_failsForPeasant() {
+ val player = playerRepository.createOrUpdate("testuser", "Test User")
+ val lobby = lobbyRepository.create("Test Game", player)
+
+ addPlayer(lobby, "testuser2")
+ addPlayer(lobby, "testuser3")
+
+ val updateSettingsAction = UpdateSettingsAction(CustomizableSettings())
+
+ val principal = TestPrincipal("testuser2")
+ lobbyController.updateSettings(updateSettingsAction, principal)
+ }
+
+ @Test
+ fun startGame_succeedsForOwner() {
+ val player = playerRepository.createOrUpdate("testuser", "Test User")
+ val lobby = lobbyRepository.create("Test Game", player)
+
+ addPlayer(lobby, "testuser2")
+ addPlayer(lobby, "testuser3")
+ addPlayer(lobby, "testuser4")
+
+ val principal = TestPrincipal("testuser")
+ lobbyController.startGame(principal)
+
+ assertSame(State.PLAYING, lobby.state)
+ }
+
+ @Test(expected = PlayerIsNotOwnerException::class)
+ fun startGame_failsForPeasant() {
+ val player = playerRepository.createOrUpdate("testuser", "Test User")
+ val lobby = lobbyRepository.create("Test Game", player)
+
+ addPlayer(lobby, "testuser2")
+ addPlayer(lobby, "testuser3")
+
+ val principal = TestPrincipal("testuser2")
+ lobbyController.startGame(principal)
+ }
+
+ private fun addPlayer(lobby: Lobby, username: String): Player {
+ val player = playerRepository.createOrUpdate(username, username)
+ lobby.addPlayer(player)
+
+ assertTrue(lobby.getPlayers().contains(player))
+ assertSame(lobby, player.lobby)
+ assertTrue(player.isInLobby)
+ assertFalse(player.isInGame)
+ return player
+ }
+}
diff --git a/backend/src/test/kotlin/org/luxons/sevenwonders/controllers/TestPrincipal.kt b/backend/src/test/kotlin/org/luxons/sevenwonders/controllers/TestPrincipal.kt
new file mode 100644
index 00000000..ce3cf317
--- /dev/null
+++ b/backend/src/test/kotlin/org/luxons/sevenwonders/controllers/TestPrincipal.kt
@@ -0,0 +1,10 @@
+package org.luxons.sevenwonders.controllers
+
+import java.security.Principal
+
+internal class TestPrincipal(private val name: String) : Principal {
+
+ override fun getName(): String {
+ return name
+ }
+}
diff --git a/backend/src/test/kotlin/org/luxons/sevenwonders/lobby/LobbyTest.kt b/backend/src/test/kotlin/org/luxons/sevenwonders/lobby/LobbyTest.kt
new file mode 100644
index 00000000..3abdde3b
--- /dev/null
+++ b/backend/src/test/kotlin/org/luxons/sevenwonders/lobby/LobbyTest.kt
@@ -0,0 +1,229 @@
+package org.luxons.sevenwonders.lobby
+
+import org.junit.Assert.*
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.BeforeClass
+import org.junit.Rule
+import org.junit.Test
+import org.junit.experimental.theories.DataPoints
+import org.junit.experimental.theories.Theories
+import org.junit.experimental.theories.Theory
+import org.junit.rules.ExpectedException
+import org.junit.runner.RunWith
+import org.luxons.sevenwonders.game.api.CustomizableSettings
+import org.luxons.sevenwonders.game.data.GameDefinition
+import org.luxons.sevenwonders.game.data.GameDefinitionLoader
+import org.luxons.sevenwonders.lobby.Lobby.*
+import java.util.Arrays
+
+@RunWith(Theories::class)
+class LobbyTest {
+
+ @JvmField
+ @Rule
+ var thrown = ExpectedException.none()
+
+ private lateinit var gameOwner: Player
+
+ private lateinit var lobby: Lobby
+
+ @Before
+ fun setUp() {
+ gameOwner = Player("gameowner", "Game owner")
+ lobby = Lobby(0, "Test Game", gameOwner, gameDefinition)
+ }
+
+ @Test
+ fun testId() {
+ val lobby = Lobby(5, "Test Game", gameOwner, gameDefinition)
+ assertEquals(5, lobby.id)
+ }
+
+ @Test
+ fun testName() {
+ val lobby = Lobby(5, "Test Game", gameOwner, gameDefinition)
+ assertEquals("Test Game", lobby.name)
+ }
+
+ @Test
+ fun testOwner() {
+ val lobby = Lobby(5, "Test Game", gameOwner, gameDefinition)
+ assertSame(gameOwner, lobby.getPlayers()[0])
+ assertSame(lobby, gameOwner.lobby)
+ }
+
+ @Test
+ fun isOwner_falseWhenNull() {
+ assertFalse(lobby.isOwner(null))
+ }
+
+ @Test
+ fun isOwner_falseWhenEmptyString() {
+ assertFalse(lobby.isOwner(""))
+ }
+
+ @Test
+ fun isOwner_falseWhenGarbageString() {
+ assertFalse(lobby.isOwner("this is garbage"))
+ }
+
+ @Test
+ fun isOwner_trueWhenOwnerUsername() {
+ assertTrue(lobby.isOwner(gameOwner.username))
+ }
+
+ @Test
+ fun isOwner_falseWhenOtherPlayerName() {
+ val player = Player("testuser", "Test User")
+ lobby.addPlayer(player)
+ assertFalse(lobby.isOwner(player.username))
+ }
+
+ @Test
+ fun addPlayer_success() {
+ val player = Player("testuser", "Test User")
+ lobby.addPlayer(player)
+ assertTrue(lobby.containsUser("testuser"))
+ assertSame(lobby, player.lobby)
+ }
+
+ @Test(expected = PlayerNameAlreadyUsedException::class)
+ fun addPlayer_failsOnSameName() {
+ val player = Player("testuser", "Test User")
+ val player2 = Player("testuser2", "Test User")
+ lobby.addPlayer(player)
+ lobby.addPlayer(player2)
+ }
+
+ @Test(expected = PlayerOverflowException::class)
+ fun addPlayer_playerOverflowWhenTooMany() {
+ // the owner + the max number gives an overflow
+ addPlayers(gameDefinition.maxPlayers)
+ }
+
+ @Test(expected = GameAlreadyStartedException::class)
+ fun addPlayer_failWhenGameStarted() {
+ // total with owner is the minimum
+ addPlayers(gameDefinition.minPlayers - 1)
+ lobby.startGame()
+ lobby.addPlayer(Player("soonerNextTime", "The Late Guy"))
+ }
+
+ private fun addPlayers(nbPlayers: Int) {
+ for (i in 0 until nbPlayers) {
+ val player = Player("testuser$i", "Test User $i")
+ lobby.addPlayer(player)
+ }
+ }
+
+ @Test(expected = UnknownPlayerException::class)
+ fun removePlayer_failsWhenNotPresent() {
+ lobby.removePlayer("anyname")
+ }
+
+ @Test
+ fun removePlayer_success() {
+ val player = Player("testuser", "Test User")
+ lobby.addPlayer(player)
+ assertTrue(player.isInLobby)
+ assertFalse(player.isInGame)
+
+ lobby.removePlayer("testuser")
+ assertFalse(lobby.containsUser("testuser"))
+ assertFalse(player.isInLobby)
+ assertFalse(player.isInGame)
+ }
+
+ @Test
+ fun reorderPlayers_success() {
+ val player1 = Player("testuser1", "Test User 1")
+ val player2 = Player("testuser2", "Test User 2")
+ val player3 = Player("testuser3", "Test User 3")
+ lobby.addPlayer(player1)
+ lobby.addPlayer(player2)
+ lobby.addPlayer(player3)
+ lobby.reorderPlayers(Arrays.asList("testuser3", "testuser1", "testuser2"))
+ assertEquals("testuser3", lobby.getPlayers()[0].username)
+ assertEquals("testuser1", lobby.getPlayers()[1].username)
+ assertEquals("testuser2", lobby.getPlayers()[2].username)
+ }
+
+ @Test(expected = UnknownPlayerException::class)
+ fun reorderPlayers_failsOnUnknownPlayer() {
+ val player1 = Player("testuser1", "Test User 1")
+ val player2 = Player("testuser2", "Test User 2")
+ val player3 = Player("testuser3", "Test User 3")
+ lobby.addPlayer(player1)
+ lobby.addPlayer(player2)
+ lobby.addPlayer(player3)
+ lobby.reorderPlayers(Arrays.asList("unknown", "testuser1", "testuser2"))
+ }
+
+ @Theory
+ fun startGame_failsBelowMinPlayers(nbPlayers: Int) {
+ assumeTrue(nbPlayers < gameDefinition.minPlayers)
+ thrown.expect(PlayerUnderflowException::class.java)
+ // there is already the owner
+ addPlayers(nbPlayers - 1)
+ lobby.startGame()
+ }
+
+ @Theory
+ fun startGame_succeedsAboveMinPlayers(nbPlayers: Int) {
+ assumeTrue(nbPlayers >= gameDefinition.minPlayers)
+ assumeTrue(nbPlayers <= gameDefinition.maxPlayers)
+ // there is already the owner
+ addPlayers(nbPlayers - 1)
+
+ assertEquals(nbPlayers, lobby.getPlayers().size)
+ lobby.getPlayers().forEach {
+ assertSame(lobby, it.lobby)
+ assertTrue(it.isInLobby)
+ assertFalse(it.isInGame)
+ }
+
+ val game = lobby.startGame()
+ assertNotNull(game)
+ lobby.getPlayers().forEachIndexed { index, it ->
+ assertSame(index, it.index)
+ assertSame(lobby, it.lobby)
+ assertSame(game, it.game)
+ assertTrue(it.isInLobby)
+ assertTrue(it.isInGame)
+ }
+ }
+
+ @Test
+ fun startGame_switchesState() {
+ assertTrue(lobby.state === State.LOBBY)
+ // there is already the owner
+ addPlayers(gameDefinition.minPlayers - 1)
+ lobby.startGame()
+ assertTrue(lobby.state === State.PLAYING)
+ }
+
+ @Test
+ fun setSettings() {
+ val settings = CustomizableSettings()
+ lobby.settings = settings
+ assertSame(settings, lobby.settings)
+ }
+
+ companion object {
+
+ private lateinit var gameDefinition: GameDefinition
+
+ @JvmStatic
+ @DataPoints
+ fun nbPlayers(): IntArray {
+ return intArrayOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
+ }
+
+ @JvmStatic
+ @BeforeClass
+ fun loadDefinition() {
+ gameDefinition = GameDefinitionLoader().gameDefinition
+ }
+ }
+}
diff --git a/backend/src/test/kotlin/org/luxons/sevenwonders/repositories/LobbyRepositoryTest.kt b/backend/src/test/kotlin/org/luxons/sevenwonders/repositories/LobbyRepositoryTest.kt
new file mode 100644
index 00000000..a5009d2a
--- /dev/null
+++ b/backend/src/test/kotlin/org/luxons/sevenwonders/repositories/LobbyRepositoryTest.kt
@@ -0,0 +1,70 @@
+package org.luxons.sevenwonders.repositories
+
+import org.junit.Assert.*
+import org.junit.Before
+import org.junit.Test
+import org.luxons.sevenwonders.lobby.Player
+
+class LobbyRepositoryTest {
+
+ private lateinit var repository: LobbyRepository
+
+ @Before
+ fun setUp() {
+ repository = LobbyRepository()
+ }
+
+ @Test
+ fun list_initiallyEmpty() {
+ assertTrue(repository.list().isEmpty())
+ }
+
+ @Test
+ fun list_returnsAllLobbies() {
+ val owner = Player("owner", "The Owner")
+ val lobby1 = repository.create("Test Name 1", owner)
+ val lobby2 = repository.create("Test Name 2", owner)
+ assertTrue(repository.list().contains(lobby1))
+ assertTrue(repository.list().contains(lobby2))
+ }
+
+ @Test
+ fun create_withCorrectOwner() {
+ val owner = Player("owner", "The Owner")
+ val lobby = repository.create("Test Name", owner)
+ assertTrue(lobby.isOwner(owner.username))
+ }
+
+ @Test(expected = LobbyNotFoundException::class)
+ fun find_failsOnUnknownId() {
+ repository.find(123)
+ }
+
+ @Test
+ fun find_returnsTheSameObject() {
+ val owner = Player("owner", "The Owner")
+ val lobby1 = repository.create("Test Name 1", owner)
+ val lobby2 = repository.create("Test Name 2", owner)
+ assertSame(lobby1, repository.find(lobby1.id))
+ assertSame(lobby2, repository.find(lobby2.id))
+ }
+
+ @Test(expected = LobbyNotFoundException::class)
+ fun remove_failsOnUnknownId() {
+ repository.remove(123)
+ }
+
+ @Test
+ fun remove_succeeds() {
+ val owner = Player("owner", "The Owner")
+ val lobby1 = repository.create("Test Name 1", owner)
+ assertNotNull(repository.find(lobby1.id))
+ repository.remove(lobby1.id)
+ try {
+ repository.find(lobby1.id)
+ fail() // the call to find() should have failed
+ } catch (e: LobbyNotFoundException) {
+ // the lobby has been properly removed
+ }
+ }
+}
diff --git a/backend/src/test/kotlin/org/luxons/sevenwonders/repositories/PlayerRepositoryTest.kt b/backend/src/test/kotlin/org/luxons/sevenwonders/repositories/PlayerRepositoryTest.kt
new file mode 100644
index 00000000..64e3d8fd
--- /dev/null
+++ b/backend/src/test/kotlin/org/luxons/sevenwonders/repositories/PlayerRepositoryTest.kt
@@ -0,0 +1,67 @@
+package org.luxons.sevenwonders.repositories
+
+import org.junit.Assert.*
+import org.junit.Before
+import org.junit.Test
+
+class PlayerRepositoryTest {
+
+ private lateinit var repository: PlayerRepository
+
+ @Before
+ fun setUp() {
+ repository = PlayerRepository()
+ }
+
+ @Test
+ fun contains_falseIfNoUserAdded() {
+ assertFalse(repository.contains("anyUsername"))
+ }
+
+ @Test
+ fun contains_trueForCreatedPlayer() {
+ repository.createOrUpdate("player1", "Player 1")
+ assertTrue(repository.contains("player1"))
+ }
+
+ @Test
+ fun createOrUpdate_createsProperly() {
+ val player1 = repository.createOrUpdate("player1", "Player 1")
+ assertEquals("player1", player1.username)
+ assertEquals("Player 1", player1.displayName)
+ }
+
+ @Test
+ fun createOrUpdate_updatesDisplayName() {
+ val player1 = repository.createOrUpdate("player1", "Player 1")
+ val player1Updated = repository.createOrUpdate("player1", "Much Better Name")
+ assertSame(player1, player1Updated)
+ assertEquals("Much Better Name", player1Updated.displayName)
+ }
+
+ @Test(expected = PlayerNotFoundException::class)
+ fun find_failsOnUnknownUsername() {
+ repository.find("anyUsername")
+ }
+
+ @Test
+ fun find_returnsTheSameObject() {
+ val player1 = repository.createOrUpdate("player1", "Player 1")
+ val player2 = repository.createOrUpdate("player2", "Player 2")
+ assertSame(player1, repository.find("player1"))
+ assertSame(player2, repository.find("player2"))
+ }
+
+ @Test(expected = PlayerNotFoundException::class)
+ fun remove_failsOnUnknownUsername() {
+ repository.remove("anyUsername")
+ }
+
+ @Test
+ fun remove_succeeds() {
+ repository.createOrUpdate("player1", "Player 1")
+ assertTrue(repository.contains("player1"))
+ repository.remove("player1")
+ assertFalse(repository.contains("player1"))
+ }
+}
diff --git a/backend/src/test/kotlin/org/luxons/sevenwonders/test/TestUtils.kt b/backend/src/test/kotlin/org/luxons/sevenwonders/test/TestUtils.kt
new file mode 100644
index 00000000..51db8d91
--- /dev/null
+++ b/backend/src/test/kotlin/org/luxons/sevenwonders/test/TestUtils.kt
@@ -0,0 +1,21 @@
+package org.luxons.sevenwonders.test
+
+import org.springframework.messaging.Message
+import org.springframework.messaging.MessageChannel
+import org.springframework.messaging.simp.SimpMessagingTemplate
+
+object TestUtils {
+
+ fun createSimpMessagingTemplate(): SimpMessagingTemplate {
+ val messageChannel = object : MessageChannel {
+ override fun send(message: Message<*>): Boolean {
+ return true
+ }
+
+ override fun send(message: Message<*>, timeout: Long): Boolean {
+ return true
+ }
+ }
+ return SimpMessagingTemplate(messageChannel)
+ }
+}
diff --git a/backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiBoard.kt b/backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiBoard.kt
new file mode 100644
index 00000000..a3d54773
--- /dev/null
+++ b/backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiBoard.kt
@@ -0,0 +1,64 @@
+package org.luxons.sevenwonders.test.api
+
+import java.util.Objects
+
+import org.luxons.sevenwonders.game.cards.Card
+import org.luxons.sevenwonders.game.effects.SpecialAbility
+
+class ApiBoard {
+
+ var wonder: ApiWonder? = null
+
+ var playerIndex: Int = 0
+
+ var playedCards: List<ApiCard>? = null
+
+ var production: ApiProduction? = null
+
+ var publicProduction: ApiProduction? = null
+
+ var science: ApiScience? = null
+
+ var tradingRules: ApiTradingRules? = null
+
+ var military: ApiMilitary? = null
+
+ var specialAbilities: Set<SpecialAbility>? = null
+
+ var consumedFreeCards: Map<Int, Boolean>? = null
+
+ var copiedGuild: Card? = null
+
+ var gold: Int = 0
+
+ var pointsPer3Gold: Int = 0
+
+ override fun equals(o: Any?): Boolean {
+ if (this === o) {
+ return true
+ }
+ if (o == null || javaClass != o.javaClass) {
+ return false
+ }
+ val apiBoard = o as ApiBoard?
+ return (playerIndex == apiBoard!!.playerIndex && gold == apiBoard.gold && pointsPer3Gold == apiBoard.pointsPer3Gold && wonder == apiBoard.wonder && playedCards == apiBoard.playedCards && production == apiBoard.production && publicProduction == apiBoard.publicProduction && science == apiBoard.science && tradingRules == apiBoard.tradingRules && military == apiBoard.military && specialAbilities == apiBoard.specialAbilities && consumedFreeCards == apiBoard.consumedFreeCards && copiedGuild == apiBoard.copiedGuild)
+ }
+
+ override fun hashCode(): Int {
+ return Objects.hash(
+ wonder,
+ playerIndex,
+ playedCards,
+ production,
+ publicProduction,
+ science,
+ tradingRules,
+ military,
+ specialAbilities,
+ consumedFreeCards,
+ copiedGuild,
+ gold,
+ pointsPer3Gold
+ )
+ }
+}
diff --git a/backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiCard.kt b/backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiCard.kt
new file mode 100644
index 00000000..2f473367
--- /dev/null
+++ b/backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiCard.kt
@@ -0,0 +1,38 @@
+package org.luxons.sevenwonders.test.api
+
+import java.util.Objects
+
+import org.luxons.sevenwonders.game.cards.Color
+import org.luxons.sevenwonders.game.cards.Requirements
+
+class ApiCard {
+
+ var name: String? = null
+
+ var color: Color? = null
+
+ var requirements: Requirements? = null
+
+ var chainParent: String? = null
+
+ var chainChildren: List<String>? = null
+
+ var image: String? = null
+
+ var back: ApiCardBack? = null
+
+ override fun equals(o: Any?): Boolean {
+ if (this === o) {
+ return true
+ }
+ if (o == null || javaClass != o.javaClass) {
+ return false
+ }
+ val apiCard = o as ApiCard?
+ return name == apiCard!!.name && color === apiCard.color && requirements == apiCard.requirements && chainParent == apiCard.chainParent && chainChildren == apiCard.chainChildren && image == apiCard.image && back == apiCard.back
+ }
+
+ override fun hashCode(): Int {
+ return Objects.hash(name, color, requirements, chainParent, chainChildren, image, back)
+ }
+}
diff --git a/backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiCardBack.kt b/backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiCardBack.kt
new file mode 100644
index 00000000..15bfd009
--- /dev/null
+++ b/backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiCardBack.kt
@@ -0,0 +1,6 @@
+package org.luxons.sevenwonders.test.api
+
+class ApiCardBack {
+
+ var image: String? = null
+}
diff --git a/backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiEffect.kt b/backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiEffect.kt
new file mode 100644
index 00000000..afe8809f
--- /dev/null
+++ b/backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiEffect.kt
@@ -0,0 +1,3 @@
+package org.luxons.sevenwonders.test.api
+
+class ApiEffect
diff --git a/backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiHandCard.kt b/backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiHandCard.kt
new file mode 100644
index 00000000..003c3783
--- /dev/null
+++ b/backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiHandCard.kt
@@ -0,0 +1,16 @@
+package org.luxons.sevenwonders.test.api
+
+/**
+ * A card with contextual information relative to the hand it is sitting in. The extra information is especially
+ * useful because it frees the client from a painful business logic implementation.
+ */
+class ApiHandCard {
+
+ var card: ApiCard? = null
+
+ var isChainable: Boolean = false
+
+ var isFree: Boolean = false
+
+ var isPlayable: Boolean = false
+}
diff --git a/backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiLobby.kt b/backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiLobby.kt
new file mode 100644
index 00000000..5bc4dad6
--- /dev/null
+++ b/backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiLobby.kt
@@ -0,0 +1,36 @@
+package org.luxons.sevenwonders.test.api
+
+import java.util.Objects
+
+import org.luxons.sevenwonders.game.api.CustomizableSettings
+import org.luxons.sevenwonders.lobby.State
+
+class ApiLobby {
+
+ var id: Long = 0
+
+ var name: String? = null
+
+ var owner: String? = null
+
+ var players: List<ApiPlayer>? = null
+
+ var settings: CustomizableSettings? = null
+
+ var state: State? = null
+
+ override fun equals(o: Any?): Boolean {
+ if (this === o) {
+ return true
+ }
+ if (o == null || javaClass != o.javaClass) {
+ return false
+ }
+ val apiLobby = o as ApiLobby?
+ return (id == apiLobby!!.id && name == apiLobby.name && owner == apiLobby.owner && players == apiLobby.players && settings == apiLobby.settings && state === apiLobby.state)
+ }
+
+ override fun hashCode(): Int {
+ return Objects.hash(id, name, owner, players, settings, state)
+ }
+}
diff --git a/backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiMilitary.kt b/backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiMilitary.kt
new file mode 100644
index 00000000..23e4f34c
--- /dev/null
+++ b/backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiMilitary.kt
@@ -0,0 +1,10 @@
+package org.luxons.sevenwonders.test.api
+
+class ApiMilitary {
+
+ var nbShields = 0
+
+ var totalPoints = 0
+
+ var nbDefeatTokens = 0
+}
diff --git a/backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiPlayer.kt b/backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiPlayer.kt
new file mode 100644
index 00000000..fc4c01fb
--- /dev/null
+++ b/backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiPlayer.kt
@@ -0,0 +1,27 @@
+package org.luxons.sevenwonders.test.api
+
+import java.util.Objects
+
+class ApiPlayer {
+
+ val username: String? = null
+
+ var displayName: String? = null
+
+ var index: Int = 0
+
+ override fun equals(o: Any?): Boolean {
+ if (this === o) {
+ return true
+ }
+ if (o == null || javaClass != o.javaClass) {
+ return false
+ }
+ val apiPlayer = o as ApiPlayer?
+ return index == apiPlayer!!.index && username == apiPlayer.username && displayName == apiPlayer.displayName
+ }
+
+ override fun hashCode(): Int {
+ return Objects.hash(username, displayName, index)
+ }
+}
diff --git a/backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiPlayerTurnInfo.kt b/backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiPlayerTurnInfo.kt
new file mode 100644
index 00000000..ae22cdb6
--- /dev/null
+++ b/backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiPlayerTurnInfo.kt
@@ -0,0 +1,37 @@
+package org.luxons.sevenwonders.test.api
+
+import java.util.Objects
+
+import org.luxons.sevenwonders.game.api.Action
+
+class ApiPlayerTurnInfo {
+
+ var playerIndex: Int = 0
+
+ var table: ApiTable? = null
+
+ var currentAge: Int = 0
+
+ var action: Action? = null
+
+ var hand: List<ApiHandCard>? = null
+
+ var neighbourGuildCards: List<ApiCard>? = null
+
+ var message: String? = null
+
+ override fun equals(o: Any?): Boolean {
+ if (this === o) {
+ return true
+ }
+ if (o == null || javaClass != o.javaClass) {
+ return false
+ }
+ val that = o as ApiPlayerTurnInfo?
+ return (playerIndex == that!!.playerIndex && currentAge == that.currentAge && table == that.table && action === that.action && hand == that.hand && neighbourGuildCards == that.neighbourGuildCards && message == that.message)
+ }
+
+ override fun hashCode(): Int {
+ return Objects.hash(playerIndex, table, currentAge, action, hand, neighbourGuildCards, message)
+ }
+}
diff --git a/backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiProduction.kt b/backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiProduction.kt
new file mode 100644
index 00000000..2ab7c343
--- /dev/null
+++ b/backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiProduction.kt
@@ -0,0 +1,11 @@
+package org.luxons.sevenwonders.test.api
+
+import org.luxons.sevenwonders.game.resources.ResourceType
+import org.luxons.sevenwonders.game.resources.Resources
+
+class ApiProduction {
+
+ var fixedResources: Resources? = null
+
+ var alternativeResources: Set<Set<ResourceType>>? = null
+}
diff --git a/backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiScience.kt b/backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiScience.kt
new file mode 100644
index 00000000..5e619904
--- /dev/null
+++ b/backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiScience.kt
@@ -0,0 +1,10 @@
+package org.luxons.sevenwonders.test.api
+
+import org.luxons.sevenwonders.game.boards.ScienceType
+
+class ApiScience {
+
+ var quantities: Map<ScienceType, Int>? = null
+
+ var jokers: Int = 0
+}
diff --git a/backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiTable.kt b/backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiTable.kt
new file mode 100644
index 00000000..cffbdf2f
--- /dev/null
+++ b/backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiTable.kt
@@ -0,0 +1,23 @@
+package org.luxons.sevenwonders.test.api
+
+import org.luxons.sevenwonders.game.cards.HandRotationDirection
+import org.luxons.sevenwonders.game.moves.Move
+
+class ApiTable {
+
+ var nbPlayers: Int = 0
+
+ var boards: List<ApiBoard>? = null
+
+ var currentAge = 0
+
+ var handRotationDirection: HandRotationDirection? = null
+
+ var lastPlayedMoves: List<Move>? = null
+
+ var neighbourGuildCards: List<ApiCard>? = null
+
+ fun increaseCurrentAge() {
+ this.currentAge++
+ }
+}
diff --git a/backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiTradingRules.kt b/backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiTradingRules.kt
new file mode 100644
index 00000000..fcebe59e
--- /dev/null
+++ b/backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiTradingRules.kt
@@ -0,0 +1,9 @@
+package org.luxons.sevenwonders.test.api
+
+import org.luxons.sevenwonders.game.resources.Provider
+import org.luxons.sevenwonders.game.resources.ResourceType
+
+class ApiTradingRules {
+
+ var costs: Map<ResourceType, Map<Provider, Int>>? = null
+}
diff --git a/backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiWonder.kt b/backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiWonder.kt
new file mode 100644
index 00000000..1efe7917
--- /dev/null
+++ b/backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiWonder.kt
@@ -0,0 +1,16 @@
+package org.luxons.sevenwonders.test.api
+
+import org.luxons.sevenwonders.game.resources.ResourceType
+
+class ApiWonder {
+
+ private val name: String? = null
+
+ private val initialResource: ResourceType? = null
+
+ private val stages: List<ApiWonderStage>? = null
+
+ private val image: String? = null
+
+ private val nbBbuiltStages: Int = 0
+}
diff --git a/backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiWonderStage.kt b/backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiWonderStage.kt
new file mode 100644
index 00000000..41925a55
--- /dev/null
+++ b/backend/src/test/kotlin/org/luxons/sevenwonders/test/api/ApiWonderStage.kt
@@ -0,0 +1,13 @@
+package org.luxons.sevenwonders.test.api
+
+import org.luxons.sevenwonders.game.cards.CardBack
+import org.luxons.sevenwonders.game.cards.Requirements
+
+class ApiWonderStage {
+
+ var requirements: Requirements? = null
+
+ var cardBack: CardBack? = null
+
+ var isBuilt: Boolean = false
+}
diff --git a/backend/src/test/kotlin/org/luxons/sevenwonders/test/api/SevenWondersClient.kt b/backend/src/test/kotlin/org/luxons/sevenwonders/test/api/SevenWondersClient.kt
new file mode 100644
index 00000000..d98e932e
--- /dev/null
+++ b/backend/src/test/kotlin/org/luxons/sevenwonders/test/api/SevenWondersClient.kt
@@ -0,0 +1,25 @@
+package org.luxons.sevenwonders.test.api
+
+import java.util.concurrent.ExecutionException
+import java.util.concurrent.TimeoutException
+
+import org.hildan.jackstomp.JackstompClient
+import org.hildan.jackstomp.JackstompSession
+
+class SevenWondersClient @JvmOverloads constructor(private val client: JackstompClient = JackstompClient()) {
+
+ @Throws(InterruptedException::class, ExecutionException::class, TimeoutException::class)
+ fun connect(serverUrl: String): SevenWondersSession {
+ val session = client.connect(serverUrl + WEBSOCKET_ENDPOINT)
+ return SevenWondersSession(session)
+ }
+
+ fun stop() {
+ client.stop()
+ }
+
+ companion object {
+
+ private val WEBSOCKET_ENDPOINT = "/seven-wonders-websocket"
+ }
+}
diff --git a/backend/src/test/kotlin/org/luxons/sevenwonders/test/api/SevenWondersSession.kt b/backend/src/test/kotlin/org/luxons/sevenwonders/test/api/SevenWondersSession.kt
new file mode 100644
index 00000000..90b40cb0
--- /dev/null
+++ b/backend/src/test/kotlin/org/luxons/sevenwonders/test/api/SevenWondersSession.kt
@@ -0,0 +1,70 @@
+package org.luxons.sevenwonders.test.api
+
+import org.hildan.jackstomp.Channel
+import org.hildan.jackstomp.JackstompSession
+import org.junit.Assert.*
+import org.luxons.sevenwonders.actions.ChooseNameAction
+import org.luxons.sevenwonders.actions.CreateGameAction
+import org.luxons.sevenwonders.actions.JoinGameAction
+import org.luxons.sevenwonders.errors.ErrorDTO
+
+class SevenWondersSession(val jackstompSession: JackstompSession) {
+
+ fun disconnect() {
+ jackstompSession.disconnect()
+ }
+
+ fun watchErrors(): Channel<ErrorDTO> {
+ return jackstompSession.subscribe("/user/queue/errors", ErrorDTO::class.java)
+ }
+
+ @Throws(InterruptedException::class)
+ fun chooseName(displayName: String): ApiPlayer {
+ val action = ChooseNameAction(displayName)
+ return jackstompSession.request(action, ApiPlayer::class.java, "/app/chooseName", "/user/queue/nameChoice")
+ }
+
+ fun watchGames(): Channel<Array<ApiLobby>> {
+ return jackstompSession.subscribe("/topic/games", Array<ApiLobby>::class.java)
+ }
+
+ @Throws(InterruptedException::class)
+ fun createGame(gameName: String): ApiLobby {
+ val action = CreateGameAction(gameName)
+ return jackstompSession.request(action, ApiLobby::class.java, "/app/lobby/create", "/user/queue/lobby/joined")
+ }
+
+ @Throws(InterruptedException::class)
+ fun joinGame(gameId: Long): ApiLobby {
+ val action = JoinGameAction(gameId)
+ val lobby =
+ jackstompSession.request(action, ApiLobby::class.java, "/app/lobby/join", "/user/queue/lobby/joined")
+ assertNotNull(lobby)
+ assertEquals(gameId, lobby.id)
+ return lobby
+ }
+
+ fun watchLobbyUpdates(gameId: Long): Channel<ApiLobby> {
+ return jackstompSession.subscribe("/topic/lobby/$gameId/updated", ApiLobby::class.java)
+ }
+
+ fun watchLobbyStart(gameId: Long): Channel<ApiLobby> {
+ return jackstompSession.subscribe("/topic/lobby/$gameId/started", ApiLobby::class.java)
+ }
+
+ @Throws(InterruptedException::class)
+ fun startGame(gameId: Long) {
+ val sendDestination = "/app/lobby/startGame"
+ val receiveDestination = "/topic/lobby/$gameId/started"
+ val received = jackstompSession.request(null, sendDestination, receiveDestination)
+ assertTrue(received)
+ }
+
+ fun sayReady() {
+ jackstompSession.send("/app/game/sayReady", "")
+ }
+
+ fun watchTurns(): Channel<ApiPlayerTurnInfo> {
+ return jackstompSession.subscribe("/user/queue/game/turn", ApiPlayerTurnInfo::class.java)
+ }
+}
diff --git a/backend/src/test/kotlin/org/luxons/sevenwonders/validation/DestinationAccessValidatorTest.kt b/backend/src/test/kotlin/org/luxons/sevenwonders/validation/DestinationAccessValidatorTest.kt
new file mode 100644
index 00000000..45e58b7f
--- /dev/null
+++ b/backend/src/test/kotlin/org/luxons/sevenwonders/validation/DestinationAccessValidatorTest.kt
@@ -0,0 +1,138 @@
+package org.luxons.sevenwonders.validation
+
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.luxons.sevenwonders.lobby.Lobby
+import org.luxons.sevenwonders.lobby.Player
+import org.luxons.sevenwonders.repositories.LobbyNotFoundException
+import org.luxons.sevenwonders.repositories.LobbyRepository
+
+class DestinationAccessValidatorTest {
+
+ private lateinit var lobbyRepository: LobbyRepository
+
+ private lateinit var destinationAccessValidator: DestinationAccessValidator
+
+ @Before
+ fun setup() {
+ lobbyRepository = LobbyRepository()
+ destinationAccessValidator = DestinationAccessValidator(lobbyRepository)
+ }
+
+ private fun createLobby(gameName: String, ownerUsername: String, vararg otherPlayers: String): Lobby {
+ val owner = Player(ownerUsername, ownerUsername)
+ val lobby = lobbyRepository.create(gameName, owner)
+ for (playerName in otherPlayers) {
+ val player = Player(playerName, playerName)
+ lobby.addPlayer(player)
+ }
+ return lobby
+ }
+
+ private fun createGame(gameName: String, ownerUsername: String, vararg otherPlayers: String) {
+ val lobby = createLobby(gameName, ownerUsername, *otherPlayers)
+ lobby.startGame()
+ }
+
+ @Test
+ fun validate_failsOnNullUser() {
+ assertFalse(destinationAccessValidator.hasAccess(null, "doesNotMatter"))
+ }
+
+ @Test
+ fun validate_successWhenNoReference() {
+ assertTrue(destinationAccessValidator.hasAccess("", ""))
+ assertTrue(destinationAccessValidator.hasAccess("", "test"))
+ assertTrue(destinationAccessValidator.hasAccess("testUser", "test"))
+ }
+
+ @Test
+ fun validate_successWhenNoRefFollows() {
+ assertTrue(destinationAccessValidator.hasAccess("testUser", "/game/"))
+ assertTrue(destinationAccessValidator.hasAccess("testUser", "/lobby/"))
+ assertTrue(destinationAccessValidator.hasAccess("testUser", "prefix/game/"))
+ assertTrue(destinationAccessValidator.hasAccess("testUser", "prefix/lobby/"))
+ assertTrue(destinationAccessValidator.hasAccess("testUser", "/game//suffix"))
+ assertTrue(destinationAccessValidator.hasAccess("testUser", "/lobby//suffix"))
+ }
+
+ @Test
+ fun validate_successWhenRefIsNotANumber() {
+ assertTrue(destinationAccessValidator.hasAccess("testUser", "/game/notANumber"))
+ assertTrue(destinationAccessValidator.hasAccess("testUser", "/lobby/notANumber"))
+ assertTrue(destinationAccessValidator.hasAccess("testUser", "prefix/game/notANumber"))
+ assertTrue(destinationAccessValidator.hasAccess("testUser", "prefix/lobby/notANumber"))
+ assertTrue(destinationAccessValidator.hasAccess("testUser", "/game/notANumber/suffix"))
+ assertTrue(destinationAccessValidator.hasAccess("testUser", "/lobby/notANumber/suffix"))
+ }
+
+ @Test(expected = LobbyNotFoundException::class)
+ fun validate_failWhenNoLobbyExist() {
+ destinationAccessValidator.hasAccess("", "/lobby/0")
+ }
+
+ @Test(expected = LobbyNotFoundException::class)
+ fun validate_failWhenNoGameExist() {
+ destinationAccessValidator.hasAccess("", "/game/0")
+ }
+
+ @Test(expected = LobbyNotFoundException::class)
+ fun validate_failWhenReferencedLobbyDoesNotExist() {
+ createLobby("Test Game", "ownerUser1")
+ createLobby("Test Game 2", "ownerUser2")
+ destinationAccessValidator.hasAccess("doesNotMatter", "/lobby/3")
+ }
+
+ @Test(expected = LobbyNotFoundException::class)
+ fun validate_failWhenReferencedGameDoesNotExist() {
+ createGame("Test Game 1", "user1", "user2", "user3")
+ createGame("Test Game 2", "user4", "user5", "user6")
+ destinationAccessValidator.hasAccess("doesNotMatter", "/game/3")
+ }
+
+ @Test
+ fun validate_failWhenUserIsNotPartOfReferencedLobby() {
+ createLobby("Test Game", "ownerUser")
+ destinationAccessValidator.hasAccess("userNotInLobby", "/lobby/0")
+ }
+
+ @Test
+ fun validate_failWhenUserIsNotPartOfReferencedGame() {
+ createGame("Test Game", "ownerUser", "otherUser1", "otherUser2")
+ destinationAccessValidator.hasAccess("userNotInGame", "/game/0")
+ }
+
+ @Test
+ fun validate_successWhenUserIsOwnerOfReferencedLobby() {
+ createLobby("Test Game 1", "user1")
+ assertTrue(destinationAccessValidator.hasAccess("user1", "/lobby/0"))
+ createLobby("Test Game 2", "user2")
+ assertTrue(destinationAccessValidator.hasAccess("user2", "/lobby/1"))
+ }
+
+ @Test
+ fun validate_successWhenUserIsMemberOfReferencedLobby() {
+ createLobby("Test Game 1", "user1", "user2")
+ assertTrue(destinationAccessValidator.hasAccess("user2", "/lobby/0"))
+ createLobby("Test Game 2", "user3", "user4")
+ assertTrue(destinationAccessValidator.hasAccess("user4", "/lobby/1"))
+ }
+
+ @Test
+ fun validate_successWhenUserIsOwnerOfReferencedGame() {
+ createGame("Test Game 1", "owner1", "user2", "user3")
+ assertTrue(destinationAccessValidator.hasAccess("owner1", "/game/0"))
+ createGame("Test Game 2", "owner4", "user5", "user6")
+ assertTrue(destinationAccessValidator.hasAccess("owner4", "/game/1"))
+ }
+
+ @Test
+ fun validate_successWhenUserIsMemberOfReferencedGame() {
+ createGame("Test Game 1", "owner1", "user2", "user3")
+ assertTrue(destinationAccessValidator.hasAccess("user2", "/game/0"))
+ createGame("Test Game 2", "owner4", "user5", "user6")
+ assertTrue(destinationAccessValidator.hasAccess("user6", "/game/1"))
+ }
+}
bgstack15