Merge branch 'feat/change-username-at-start-or-in-game' into 'main'
Feat: accept optional client username and forward to ClientApp See merge request cs108-fs26/Gruppe-13!115
This commit was merged in pull request #271.
This commit is contained in:
@@ -7,6 +7,12 @@ import org.apache.logging.log4j.Logger;
|
||||
|
||||
/** Main entry point for Casono application. Handles client and server startup. */
|
||||
public final class Main {
|
||||
|
||||
private static final int MIN_ARGS_FOR_USERNAME = 3;
|
||||
private static final int ARGS_COUNT_SERVER = 2;
|
||||
private static final int ARGS_COUNT_CLIENT_MIN = 2;
|
||||
private static final int ARGS_COUNT_CLIENT_WITH_USER = 3;
|
||||
|
||||
/**
|
||||
* Main entry point for Casono.
|
||||
*
|
||||
@@ -17,10 +23,13 @@ public final class Main {
|
||||
printUsage();
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
switch (args[0]) {
|
||||
case "server" -> ServerApp.start(args[1]);
|
||||
case "client" -> ClientApp.start(args[1]);
|
||||
case "client" -> {
|
||||
String address = args[1];
|
||||
String username = args.length >= MIN_ARGS_FOR_USERNAME ? args[2] : null;
|
||||
ClientApp.start(address, username);
|
||||
}
|
||||
default -> {
|
||||
printUsage();
|
||||
System.exit(1);
|
||||
@@ -29,12 +38,15 @@ public final class Main {
|
||||
}
|
||||
|
||||
private static boolean isValid(String[] args) {
|
||||
if (args.length != 2) {
|
||||
if (args.length < ARGS_COUNT_SERVER) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return switch (args[0]) {
|
||||
case "server", "client" -> true;
|
||||
case "server" -> args.length == ARGS_COUNT_SERVER;
|
||||
case "client" ->
|
||||
args.length == ARGS_COUNT_CLIENT_MIN
|
||||
|| args.length == ARGS_COUNT_CLIENT_WITH_USER;
|
||||
default -> false;
|
||||
};
|
||||
}
|
||||
@@ -45,7 +57,7 @@ public final class Main {
|
||||
"""
|
||||
Usage:
|
||||
java -jar xyz.jar server <listenPort>
|
||||
java -jar xyz.jar client <serverIp>:<serverPort>
|
||||
java -jar xyz.jar client <serverIp>:<serverPort> [<username>]
|
||||
""");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +1,51 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.client;
|
||||
|
||||
/**
|
||||
* Entry point for the Casono client application. Handles client startup and connection parameters.
|
||||
*/
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.network.ClientService;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.network.LobbyClient;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.network.LoginResult;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.ui.Launcher;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
/**
|
||||
* Entry point for the Casono client application. Handles client startup and connection parameters.
|
||||
* Entry point and bootstrap helper for the Casono client application.
|
||||
*
|
||||
* <p>Default constructor for the application.
|
||||
* <p>This class is responsible for two tasks: - performing an optional startup LOGIN when a
|
||||
* username is supplied on the command line, and - providing a shared {@link ClientService} instance
|
||||
* that the UI can reuse so the initial connection remains active.
|
||||
*/
|
||||
public class ClientApp {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger(ClientApp.class);
|
||||
|
||||
/** Shared client connection used when a username is provided at startup. */
|
||||
private static volatile ClientService sharedClientService;
|
||||
|
||||
/** Default constructor. */
|
||||
public ClientApp() {
|
||||
// Default constructor
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the shared {@link ClientService} instance created during startup, or {@code null} if
|
||||
* none exists. The UI may call this to reuse the connection that already performed the initial
|
||||
* LOGIN.
|
||||
*/
|
||||
public static ClientService getSharedClientService() {
|
||||
return sharedClientService;
|
||||
}
|
||||
|
||||
private static void setSharedClientService(ClientService cs) {
|
||||
sharedClientService = cs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the client application with the given address.
|
||||
*
|
||||
* @param arg Address in the format "ip:port".
|
||||
* @throws IllegalArgumentException if the address format is invalid.
|
||||
*/
|
||||
public static void start(String arg) {
|
||||
public static void start(String arg, String username) {
|
||||
String[] parts = arg.split(":", 2);
|
||||
if (parts.length != 2) {
|
||||
throw new IllegalArgumentException("Address must be in format <ip>:<port>.");
|
||||
@@ -36,16 +54,69 @@ public class ClientApp {
|
||||
int port = Integer.parseInt(parts[1]);
|
||||
|
||||
LOGGER.info("You've selected the client. It will connect port {} at host {}", port, host);
|
||||
// Expose the chosen host/port to the UI via system properties so controllers
|
||||
// (which read System.getProperty("casono.server.host"/"casono.server.port"))
|
||||
// can obtain the correct connection information.
|
||||
System.setProperty("casono.server.host", host);
|
||||
System.setProperty("casono.server.port", Integer.toString(port));
|
||||
// Forward the original address argument to the launcher as well.
|
||||
Launcher.main(new String[] {arg});
|
||||
System.setProperty("casono.server.port", String.valueOf(port));
|
||||
// If a username was provided, create a shared connection and perform
|
||||
// the initial LOGIN asynchronously on that connection. The connection
|
||||
// is intentionally NOT closed so the UI can reuse it.
|
||||
if (username != null && !username.isBlank()) {
|
||||
try {
|
||||
ClientService clientService = new ClientService(host, port);
|
||||
setSharedClientService(clientService);
|
||||
|
||||
java.util.concurrent.ExecutorService bg =
|
||||
java.util.concurrent.Executors.newSingleThreadExecutor(
|
||||
r -> {
|
||||
Thread t = new Thread(r);
|
||||
t.setDaemon(true);
|
||||
t.setName("casono-startup-login");
|
||||
return t;
|
||||
});
|
||||
bg.submit(
|
||||
() -> {
|
||||
try {
|
||||
LobbyClient lobbyClient = new LobbyClient(clientService);
|
||||
LoginResult res = lobbyClient.login(username);
|
||||
LOGGER.info(
|
||||
"Assigned username='{}' id={}",
|
||||
res.getUsername(),
|
||||
res.getId());
|
||||
} catch (RuntimeException e) {
|
||||
LOGGER.warn("Startup login failed: {}", e.getMessage());
|
||||
} catch (Exception e) {
|
||||
LOGGER.warn("Unexpected error during startup login", e);
|
||||
} finally {
|
||||
bg.shutdown();
|
||||
}
|
||||
});
|
||||
} catch (RuntimeException e) {
|
||||
LOGGER.warn(
|
||||
"Could not establish initial connection for startup login: {}",
|
||||
e.getMessage());
|
||||
// UI will create its own connection when it initializes
|
||||
}
|
||||
}
|
||||
|
||||
if (username != null && !username.isBlank()) {
|
||||
Launcher.main(new String[] {arg, username});
|
||||
} else {
|
||||
Launcher.main(new String[] {arg});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Main entry point. {@code <host:port>} (e.g. {@code 127.0.0.1:1234})
|
||||
*
|
||||
* <p>If a username is provided the application attempts a startup LOGIN on the shared
|
||||
* connection; the UI will still start and will reuse the connection when possible. The username
|
||||
* is not propagated via a system property for UI display.
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
start(args[0]);
|
||||
if (args == null || args.length == 0) {
|
||||
throw new IllegalArgumentException("Address argument required: <host:port>");
|
||||
}
|
||||
// Optional username argument
|
||||
String username = args.length > 1 && args[1] != null && !args[1].isBlank() ? args[1] : null;
|
||||
start(args[0], username);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,33 +138,64 @@ public class ClientService {
|
||||
() -> {
|
||||
try {
|
||||
writeToTransport(message);
|
||||
String responseText = null;
|
||||
String responseText = clienttcptransport.read().payload();
|
||||
logger.info("Raw message '{}'", responseText);
|
||||
|
||||
responseText = clienttcptransport.read().payload();
|
||||
logger.info("Raw message '" + responseText + "'");
|
||||
Boolean success = null;
|
||||
int count = 0;
|
||||
for (String line : responseText.split("\n")) {
|
||||
if (success == null) {
|
||||
boolean hasStatus = false;
|
||||
boolean success = false;
|
||||
for (String rawLine : responseText.split("\n")) {
|
||||
String line = rawLine;
|
||||
if (!hasStatus) {
|
||||
if ("+OK".equals(line)) {
|
||||
success = true;
|
||||
hasStatus = true;
|
||||
continue;
|
||||
|
||||
} else if (("-ERROR").equals(responseText)) {
|
||||
success = false;
|
||||
}
|
||||
if (line.startsWith("-ERR") || line.startsWith("-ERROR")) {
|
||||
success = false;
|
||||
hasStatus = true;
|
||||
continue;
|
||||
}
|
||||
// ignore any lines before the status indicator
|
||||
continue;
|
||||
} else if ("END".equals(line)) {
|
||||
}
|
||||
|
||||
if ("END".equals(line)) {
|
||||
break;
|
||||
}
|
||||
line = line.replaceFirst("^\t", "");
|
||||
|
||||
// strip a single leading tab if present (protocol formatting)
|
||||
if (line.startsWith("\t")) {
|
||||
line = line.substring(1);
|
||||
}
|
||||
response.add(line);
|
||||
}
|
||||
if (success != null && success) {
|
||||
return;
|
||||
} else {
|
||||
throw new RuntimeException("Error in " + message + ": " + response);
|
||||
|
||||
if (!hasStatus) {
|
||||
// Fallback for servers that do not place the status on a
|
||||
// dedicated line: try to detect status markers anywhere
|
||||
// in the payload to remain compatible with older servers.
|
||||
if (responseText.contains("+OK")) {
|
||||
success = true;
|
||||
hasStatus = true;
|
||||
} else if (responseText.contains("-ERR")
|
||||
|| responseText.contains("-ERROR")) {
|
||||
success = false;
|
||||
hasStatus = true;
|
||||
} else {
|
||||
throw new RuntimeException(
|
||||
"No status line in response for '"
|
||||
+ message
|
||||
+ "': "
|
||||
+ responseText);
|
||||
}
|
||||
}
|
||||
|
||||
if (success) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new RuntimeException("Error in " + message + ": " + response);
|
||||
} catch (Exception e) {
|
||||
throw getRuntimeException(e);
|
||||
}
|
||||
@@ -200,15 +231,14 @@ public class ClientService {
|
||||
* @return A RuntimeException representing the cause of the original exception.
|
||||
*/
|
||||
private static RuntimeException getRuntimeException(Exception e) {
|
||||
Throwable reason = e.getCause();
|
||||
RuntimeException re;
|
||||
if (reason == null) {
|
||||
reason = e;
|
||||
} else if (reason instanceof RuntimeException rte) {
|
||||
re = rte;
|
||||
Throwable cause = e.getCause();
|
||||
if (cause == null) {
|
||||
return e instanceof RuntimeException ? (RuntimeException) e : new RuntimeException(e);
|
||||
}
|
||||
re = new RuntimeException(reason);
|
||||
return re;
|
||||
if (cause instanceof RuntimeException) {
|
||||
return (RuntimeException) cause;
|
||||
}
|
||||
return new RuntimeException(cause);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.client.network;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.parsing.RequestParameter;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The LobbyClient class is responsible for communicating with the server to manage game lobbies. It
|
||||
* provides methods to create a lobby, join a lobby, and fetch the current status of a lobby by
|
||||
@@ -65,8 +68,23 @@ public class LobbyClient {
|
||||
* Logs in to the server with the given username by sending a "LOGIN" command.
|
||||
*
|
||||
* @param user The username to log in with.
|
||||
* @return a {@link LoginResult} containing the assigned username and id as returned by the
|
||||
* server
|
||||
*/
|
||||
public void login(String user) {
|
||||
client.processCommand("LOGIN USERNAME=" + user);
|
||||
public LoginResult login(String user) {
|
||||
List<String> lines = client.processCommand("LOGIN USERNAME=" + user);
|
||||
|
||||
List<RequestParameter> params = ClientService.convertToRequestParameters(lines);
|
||||
|
||||
String assigned = user;
|
||||
String id = null;
|
||||
for (RequestParameter p : params) {
|
||||
if ("USERNAME".equalsIgnoreCase(p.key())) {
|
||||
assigned = p.value();
|
||||
} else if ("ID".equalsIgnoreCase(p.key())) {
|
||||
id = p.value();
|
||||
}
|
||||
}
|
||||
return new LoginResult(assigned, id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.client.network;
|
||||
|
||||
/**
|
||||
* Result of a successful login request.
|
||||
*
|
||||
* <p>Holds the username assigned by the server and the server-side id as a string (UUID). The
|
||||
* client previously attempted to parse the id as an integer which failed when the server returned a
|
||||
* UUID; this class therefore stores the id as a {@link String}.
|
||||
*/
|
||||
public class LoginResult {
|
||||
private final String username;
|
||||
private final String id;
|
||||
|
||||
/**
|
||||
* Create a login result.
|
||||
*
|
||||
* @param username the assigned username
|
||||
* @param id the assigned id (UUID string) returned by the server
|
||||
*/
|
||||
public LoginResult(String username, String id) {
|
||||
this.username = username;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the assigned username
|
||||
*/
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the assigned id as returned by the server (UUID string)
|
||||
*/
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
+24
-13
@@ -1,5 +1,6 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.ClientApp;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.network.ClientService;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.network.LobbyClient;
|
||||
import javafx.application.Platform;
|
||||
@@ -36,12 +37,16 @@ public class CasinomainuiController {
|
||||
private int nextButtonId = 1;
|
||||
private LobbyClient lobbyClient;
|
||||
|
||||
/** Default constructor for dependency injection by FXMLLoader. */
|
||||
/** Default constructor used by FXMLLoader. */
|
||||
public CasinomainuiController() {
|
||||
// Default constructor
|
||||
}
|
||||
|
||||
/** Initializes the UI components and sets default values. */
|
||||
/**
|
||||
* Initializes the UI components and sets default values. If a shared {@link
|
||||
* ch.unibas.dmi.dbis.cs108.casono.client.network.ClientService} exists (created at application
|
||||
* start), the controller reuses it so the connection remains open and already-logged-in.
|
||||
*/
|
||||
@FXML
|
||||
public void initialize() {
|
||||
titleLabel.setText("Casono");
|
||||
@@ -51,16 +56,18 @@ public class CasinomainuiController {
|
||||
translationManager = LobbyButtonTranslationManager.getInstance();
|
||||
String host = System.getProperty("casono.server.host");
|
||||
int port = Integer.parseInt(System.getProperty("casono.server.port"));
|
||||
ClientService clientService;
|
||||
try {
|
||||
clientService = new ClientService(host, port);
|
||||
} catch (RuntimeException e) {
|
||||
LOGGER.warn(
|
||||
"Could not connect to server {}:{} — starting in offline mode: {}",
|
||||
host,
|
||||
port,
|
||||
e.getMessage());
|
||||
clientService = new ClientService(true); // offline mode
|
||||
ClientService clientService = ClientApp.getSharedClientService();
|
||||
if (clientService == null) {
|
||||
try {
|
||||
clientService = new ClientService(host, port);
|
||||
} catch (RuntimeException e) {
|
||||
LOGGER.warn(
|
||||
"Could not connect to server {}:{} — starting in offline mode: {}",
|
||||
host,
|
||||
port,
|
||||
e.getMessage());
|
||||
clientService = new ClientService(true); // offline mode
|
||||
}
|
||||
}
|
||||
gridManager =
|
||||
new LobbyButtonGridManager(
|
||||
@@ -117,7 +124,11 @@ public class CasinomainuiController {
|
||||
Platform.exit();
|
||||
}
|
||||
|
||||
/** Handles creation of a new lobby button. */
|
||||
/**
|
||||
* Handles creation of a new lobby button. Attempts to create a lobby on the server via the
|
||||
* {@link LobbyButtonGridManager} and registers the new button in the local translation manager.
|
||||
* Errors are logged and displayed as an informational alert.
|
||||
*/
|
||||
@FXML
|
||||
public void handleCreateLobbyButton() {
|
||||
if (translationManager.isFull()) {
|
||||
|
||||
@@ -13,7 +13,7 @@ import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
/** Represents an authenticated user on the server. */
|
||||
public class User {
|
||||
private final UserId id;
|
||||
private final String name;
|
||||
private String name;
|
||||
private SessionId sessionId;
|
||||
private Instant disconnectedAt;
|
||||
private final Queue<Message> messages;
|
||||
@@ -47,10 +47,20 @@ public class User {
|
||||
*
|
||||
* @return the user name
|
||||
*/
|
||||
public String getName() {
|
||||
public synchronized String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a new display name for this user. Thread-safe; callers must ensure the registry is
|
||||
* updated to maintain uniqueness when needed.
|
||||
*
|
||||
* @param newName the new display name
|
||||
*/
|
||||
public synchronized void setName(String newName) {
|
||||
this.name = newName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the session currently associated with this user, if any.
|
||||
*
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.domain.user;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.SessionId;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
/** Creates new users, resolving name conflicts automatically. */
|
||||
public class UserFactory {
|
||||
private final UserRegistry registry;
|
||||
private final AtomicInteger anonymousCounter = new AtomicInteger(1);
|
||||
private static final Logger LOGGER = LogManager.getLogger(UserFactory.class);
|
||||
|
||||
/**
|
||||
* Creates a new UserFactory backed by the given registry.
|
||||
@@ -18,16 +23,40 @@ public class UserFactory {
|
||||
/**
|
||||
* Creates and registers a new user with the given name and session. If the name is already
|
||||
* taken, a numeric suffix is appended and incremented until a free name is found (e.g.
|
||||
* Lars_001, Lars_002, ...).
|
||||
* Lars_001, Lars_002, ...). If the desiredName is null or empty, an automatic name of the form
|
||||
* "playerN" is assigned, incrementing N until a free name is found.
|
||||
*
|
||||
* @param desiredName the preferred display name
|
||||
* @param sessionId the session to associate with the new user
|
||||
* @param sessionId the session to associate with the new userwas
|
||||
* @return the newly created and registered user
|
||||
*/
|
||||
public User create(String desiredName, SessionId sessionId) {
|
||||
if (desiredName == null || desiredName.isBlank()) {
|
||||
// assign playerN names for anonymous logins
|
||||
while (true) {
|
||||
String candidate = "player" + anonymousCounter.getAndIncrement();
|
||||
var result = registry.registerIfAvailable(candidate, sessionId);
|
||||
if (result.isPresent()) {
|
||||
var user = result.get();
|
||||
LOGGER.info(
|
||||
"Registered user '{}' with id {} for session {}",
|
||||
user.getName(),
|
||||
user.getId().value(),
|
||||
sessionId == null ? "null" : sessionId.value());
|
||||
return user;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var result = registry.registerIfAvailable(desiredName, sessionId);
|
||||
if (result.isPresent()) {
|
||||
return result.get();
|
||||
var user = result.get();
|
||||
LOGGER.info(
|
||||
"Registered user '{}' with id {} for session {}",
|
||||
user.getName(),
|
||||
user.getId().value(),
|
||||
sessionId == null ? "null" : sessionId.value());
|
||||
return user;
|
||||
}
|
||||
|
||||
int suffix = 1;
|
||||
@@ -35,7 +64,13 @@ public class UserFactory {
|
||||
String candidate = desiredName + "_" + String.format("%03d", suffix);
|
||||
result = registry.registerIfAvailable(candidate, sessionId);
|
||||
if (result.isPresent()) {
|
||||
return result.get();
|
||||
var user = result.get();
|
||||
LOGGER.info(
|
||||
"Registered user '{}' with id {} for session {}",
|
||||
user.getName(),
|
||||
user.getId().value(),
|
||||
sessionId == null ? "null" : sessionId.value());
|
||||
return user;
|
||||
}
|
||||
suffix++;
|
||||
}
|
||||
|
||||
@@ -185,4 +185,29 @@ public class UserRegistry {
|
||||
public Collection<User> getAllUsers() {
|
||||
return byId.values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to change the username of an existing user. Ensures the new name is not already
|
||||
* taken and updates internal indices atomically.
|
||||
*
|
||||
* @param userId the id of the user to rename
|
||||
* @param newName the desired new name
|
||||
* @return true if the rename succeeded, false if the name was already taken or user not found
|
||||
*/
|
||||
public synchronized boolean changeUsername(UserId userId, String newName) {
|
||||
User user = byId.get(userId);
|
||||
if (user == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (byName.containsKey(newName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// remove old mapping and put new mapping
|
||||
byName.remove(user.getName());
|
||||
user.setName(newName);
|
||||
byName.put(newName, user);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.domain.user;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.SessionId;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/** Tests for {@link UserFactory} and {@link UserRegistry} name assignment behavior. */
|
||||
public class UserFactoryTest {
|
||||
|
||||
// Logger removed as per patch requirement
|
||||
|
||||
@Test
|
||||
public void createUsersWithDuplicateDesiredNamesAssignsUniqueNames() {
|
||||
UserRegistry registry = new UserRegistry();
|
||||
UserFactory factory = new UserFactory(registry);
|
||||
|
||||
SessionId s1 = new SessionId();
|
||||
SessionId s2 = new SessionId();
|
||||
SessionId s3 = new SessionId();
|
||||
|
||||
// Create first user with desired name "Alice"
|
||||
User u1 = factory.create("Alice", s1);
|
||||
// Create second user requesting the same name
|
||||
User u2 = factory.create("Alice", s2);
|
||||
// Create third user requesting the same name again
|
||||
User u3 = factory.create("Alice", s3);
|
||||
|
||||
assertNotNull(u1);
|
||||
assertNotNull(u2);
|
||||
assertNotNull(u3);
|
||||
|
||||
// All assigned names must be distinct
|
||||
String n1 = u1.getName();
|
||||
String n2 = u2.getName();
|
||||
String n3 = u3.getName();
|
||||
|
||||
// assigned names: n1, n2, n3
|
||||
|
||||
assertNotEquals(n1, n2);
|
||||
assertNotEquals(n1, n3);
|
||||
assertNotEquals(n2, n3);
|
||||
|
||||
// Registry should contain all assigned names
|
||||
assertTrue(registry.getByUsername(n1).isPresent());
|
||||
assertTrue(registry.getByUsername(n2).isPresent());
|
||||
assertTrue(registry.getByUsername(n3).isPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createUserWithNullDesiredAssignsPlayerN() {
|
||||
UserRegistry registry = new UserRegistry();
|
||||
UserFactory factory = new UserFactory(registry);
|
||||
|
||||
SessionId s1 = new SessionId();
|
||||
|
||||
User u = factory.create(null, s1);
|
||||
assertNotNull(u);
|
||||
String name = u.getName();
|
||||
// assigned anonymous name: name
|
||||
assertTrue(name.startsWith("player"), "expected automatic name starting with 'player'");
|
||||
assertTrue(registry.getByUsername(name).isPresent());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user