Skip to content

Commit 437c3c7

Browse files
hopshackleclaude
andcommitted
Web play: unit tests for reading a game setup and setting parameters
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent 85a8a8d commit 437c3c7

2 files changed

Lines changed: 119 additions & 3 deletions

File tree

‎WEBSERVER_PLAN.md‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -222,9 +222,12 @@ every GUI in the JVM. Instead the web session, which already drives the update t
222222

223223
## Testing
224224

225-
- **Unit tests:** protocol and session logic (action validation, stale action rejection, lifecycle and stop). They run with
226-
`-Dmaven.test.skip=false` and need a display: fine on Windows; under Xvfb in CI.
227-
- **End-to-end:** drive the real page with browser automation for each milestone.
225+
- **Unit tests** (`web.SessionConfigTest`, no display needed): reading a setup from a link, defaults, refusals with their
226+
messages, setting parameters from strings, free-entry parameters. Run with
227+
`mvn test -Dmaven.test.skip=false -Dtest=SessionConfigTest`. The existing tests of the packages touched (gui, LawnAndOrder,
228+
Terraforming Mars, Colt Express, Love Letter, Pandemic, Poker): 141 tests including these, all passing.
229+
- **End to end**: a Java WebSocket client standing in for the page (tile reassembly, scripted mouse, wheel, actions, log),
230+
Chrome for each stage's milestone, and the CI workflow for the Docker image.
228231

229232
## Risks
230233

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package web;
2+
3+
import core.AbstractParameters;
4+
import games.GameType;
5+
import games.lawnandorder.LawnAndOrderParameters;
6+
import org.junit.Test;
7+
8+
import java.util.HashMap;
9+
import java.util.List;
10+
import java.util.Map;
11+
12+
import static org.junit.Assert.*;
13+
14+
/**
15+
* Reading a game setup from the play page's query string, and setting game parameters from the strings a form sends.
16+
*/
17+
public class SessionConfigTest {
18+
19+
private final GameCatalog games = new GameCatalog("LawnAndOrder,TicTacToe");
20+
private final OpponentCatalog opponents = new OpponentCatalog("");
21+
22+
private static Map<String, List<String>> query(String... keyValues) {
23+
Map<String, List<String>> q = new HashMap<>();
24+
for (int i = 0; i < keyValues.length; i += 2)
25+
q.put(keyValues[i], List.of(keyValues[i + 1]));
26+
return q;
27+
}
28+
29+
private SessionConfig read(String... keyValues) {
30+
return SessionConfig.fromQuery(query(keyValues), games, opponents);
31+
}
32+
33+
@Test
34+
public void aFullSetupIsRead() {
35+
SessionConfig c = read("game", "LawnAndOrder", "players", "4", "seat", "2",
36+
"opponents", "mcts-100,random,you,osla", "seed", "42", "pause", "0", "p.handSize", "6", "insight", "1");
37+
assertEquals(GameType.LawnAndOrder, c.game());
38+
assertEquals(4, c.nPlayers());
39+
assertEquals(2, c.seat());
40+
assertEquals(List.of("mcts-100", "random", "you", "osla"), c.opponents());
41+
assertEquals(42, c.seed());
42+
assertEquals(0, c.turnPause());
43+
assertEquals(Map.of("handSize", "6"), c.params());
44+
assertTrue(c.insight());
45+
}
46+
47+
@Test
48+
public void defaultsFillTheGaps() {
49+
SessionConfig c = read();
50+
assertEquals(GameType.LawnAndOrder, c.game()); // the first game offered
51+
assertEquals(3, c.nPlayers());
52+
assertEquals(0, c.seat());
53+
assertEquals(List.of("you", OpponentCatalog.DEFAULT, OpponentCatalog.DEFAULT), c.opponents());
54+
assertEquals(-1, c.seed());
55+
assertEquals(SessionConfig.DEFAULT_TURN_PAUSE, c.turnPause());
56+
assertTrue(c.params().isEmpty());
57+
assertFalse(c.insight());
58+
}
59+
60+
@Test
61+
public void theDefaultPlayerCountFitsTheGame() {
62+
assertEquals(2, read("game", "TicTacToe").nPlayers());
63+
}
64+
65+
@Test
66+
public void theBrowserSeatIsAlwaysYou() {
67+
SessionConfig c = read("players", "2", "seat", "1", "opponents", "random,random");
68+
assertEquals(List.of("random", "you"), c.opponents());
69+
}
70+
71+
@Test
72+
public void badSetupsAreRefused() {
73+
assertRefused("Unknown game", "game", "Chess"); // not offered
74+
assertRefused("takes 2 to 6 players", "players", "9");
75+
assertRefused("seat must be between", "players", "3", "seat", "3");
76+
assertRefused("Unknown opponent", "opponents", "you,nobody");
77+
assertRefused("Unknown parameter", "p.noSuchThing", "1");
78+
assertRefused("Bad value for handSize", "p.handSize", "lots");
79+
assertRefused("Not a whole number for seed", "seed", "abc");
80+
assertRefused("pause must be between", "pause", "-5");
81+
}
82+
83+
private void assertRefused(String messagePart, String... keyValues) {
84+
try {
85+
read(keyValues);
86+
fail("Expected the setup to be refused: " + messagePart);
87+
} catch (IllegalArgumentException e) {
88+
assertTrue(e.getMessage() + " should mention: " + messagePart, e.getMessage().contains(messagePart));
89+
}
90+
}
91+
92+
@Test
93+
public void parametersAreSetFromStrings() {
94+
AbstractParameters params = GameType.LawnAndOrder.createParameters(1);
95+
GameCatalog.apply(params, Map.of("handSize", "7", "maxRounds", "12"));
96+
LawnAndOrderParameters p = (LawnAndOrderParameters) params;
97+
assertEquals(7, p.handSize);
98+
assertEquals(7, p.getParameterValue("handSize"));
99+
assertEquals(12, p.getParameterValue("maxRounds"));
100+
}
101+
102+
@Test
103+
public void parametersWithoutAListOfSettingsAreFreeEntry() {
104+
var described = GameCatalog.describeParameters(GameType.LawnAndOrder);
105+
for (var e : described) {
106+
var p = e.getAsJsonObject();
107+
if (p.get("name").getAsString().equals("maxRounds"))
108+
assertEquals(0, p.getAsJsonArray("values").size());
109+
if (p.get("name").getAsString().equals("handSize"))
110+
assertTrue(p.getAsJsonArray("values").size() > 1);
111+
}
112+
}
113+
}

0 commit comments

Comments
 (0)