v0.10.0 fix to engine game state mgmt, client and web ui updates to match

This commit is contained in:
2026-07-23 14:03:09 -04:00
parent a23ed1fb21
commit 21dea47694
17 changed files with 741 additions and 549 deletions
+21 -19
View File
@@ -225,28 +225,31 @@ test_case "2.3: Login with Username"
RESPONSE=$(api_request POST "$API_URL/auth/login" \
-H "Content-Type: application/json" \
-d "{\"identifier\": \"$TEST_USER1\", \"password\": \"$TEST_PASS1\"}")
TOKEN_ALICE=$(echo "$RESPONSE" | jq -r '.token' 2>/dev/null)
TOKEN_ALICE_S1=$(echo "$RESPONSE" | jq -r '.token' 2>/dev/null) # kept for 2.4b
USER_ID_ALICE=$(echo "$RESPONSE" | jq -r '.userId' 2>/dev/null)
if [ -n "$TOKEN_ALICE" ] && [ "$TOKEN_ALICE" != "null" ]; then
echo -e "${GREEN} ✓ Login successful for $TEST_USER1${NC}"
((PASS++))
if [ -n "$TOKEN_ALICE_S1" ] && [ "$TOKEN_ALICE_S1" != "null" ]; then
echo -e "${GREEN} ✓ Login successful for $TEST_USER1${NC}"; ((PASS++))
else
echo -e "${RED} ✗ Login failed${NC}"
((FAIL++))
echo -e "${RED} ✗ Login failed${NC}"; ((FAIL++))
fi
test_case "2.4: Login with Email"
test_case "2.4: Login with Email (re-login: supersedes previous session)"
RESPONSE=$(api_request POST "$API_URL/auth/login" \
-H "Content-Type: application/json" \
-d "{\"identifier\": \"$TEST_EMAIL1\", \"password\": \"$TEST_PASS1\"}")
if echo "$RESPONSE" | jq -r '.token' 2>/dev/null | grep -q "^ey"; then
echo -e "${GREEN} ✓ Email login successful${NC}"
((PASS++))
TOKEN_ALICE=$(echo "$RESPONSE" | jq -r '.token' 2>/dev/null) # ONLY this token is valid from here on
if echo "$TOKEN_ALICE" | grep -q "^ey"; then
echo -e "${GREEN} ✓ Email login successful${NC}"; ((PASS++))
else
echo -e "${RED} ✗ Email login failed${NC}"
((FAIL++))
echo -e "${RED} ✗ Email login failed${NC}"; ((FAIL++))
fi
test_case "2.4b: Single-Session Enforcement (prior token invalidated by re-login)"
STATUS=$(api_request GET "$API_URL/auth/me" \
-o /dev/null -w "%{http_code}" \
-H "Authorization: Bearer $TOKEN_ALICE_S1")
assert_status 401 "$STATUS" "Superseded session token rejected"
test_case "2.5: Invalid Credentials"
STATUS=$(api_request POST "$API_URL/auth/login" \
-o /dev/null -w "%{http_code}" \
@@ -298,20 +301,19 @@ else
((FAIL++))
fi
test_case "3.3: Both Players Same Authenticated User"
test_case "3.3: HvH Creation Claims Only One Slot for Creator"
RESPONSE=$(api_request POST "$API_URL/games" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN_ALICE" \
-d '{"white": {"type": 1}, "black": {"type": 1}}')
WHITE_ID=$(echo "$RESPONSE" | jq -r '.players.white.id' 2>/dev/null)
BLACK_ID=$(echo "$RESPONSE" | jq -r '.players.black.id' 2>/dev/null)
BLACK_CLAIMED=$(echo "$RESPONSE" | jq -r '.players.black.claimedBy // empty' 2>/dev/null)
if [ "$WHITE_ID" = "$USER_ID_ALICE" ] && [ "$BLACK_ID" = "$USER_ID_ALICE" ]; then
echo -e "${GREEN}Same user can play both sides${NC}"
((PASS++))
if [ "$WHITE_ID" = "$USER_ID_ALICE" ] && [ "$BLACK_ID" != "$USER_ID_ALICE" ] && [ -z "$BLACK_CLAIMED" ]; then
echo -e "${GREEN}Creator claims white only; black remains claimable${NC}"; ((PASS++))
else
echo -e "${RED}Both sides should be same user${NC}"
((FAIL++))
echo -e "${RED}Slot assignment wrong: white=$WHITE_ID black=$BLACK_ID claimedBy=$BLACK_CLAIMED${NC}"; ((FAIL++))
fi
# ==============================================================================
@@ -459,4 +461,4 @@ if [ $FAIL -eq 0 ]; then
else
echo -e "\n${RED}⚠️ Some tests failed. Review the output above.${NC}"
exit 1
fi
fi
+94
View File
@@ -0,0 +1,94 @@
#!/usr/bin/env bash
BASE_URL="${BASE_URL:-http://localhost:8080}"
API_URL="${BASE_URL}/api/v1"
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m'
PASS=0; FAIL=0
t() { echo -e "\n${YELLOW}▶ TEST: $1${NC}"; }
ok() { echo -e "${GREEN}$1${NC}"; ((PASS++)); }
ko() { echo -e "${RED}$1${NC}"; ((FAIL++)); }
req(){ local m=$1 u=$2; shift 2; curl -s "$@" -X "$m" "$u"; }
assert_field() { # json field expected name
local a
a=$(echo "$1" | jq -r "$2" 2>/dev/null)
if [ "$a" = "$3" ]; then
ok "$4: $2 = '$a'"
else
ko "$4: expected $2 = '$3', got '$a'"
fi
}
MATE_FEN="rnb1kbnr/pppp1ppp/8/4p3/6Pq/5P2/PPPPP2P/RNBQKBNR w KQkq - 1 3" # fool's mate: white to move, mated
STALE_FEN="7k/5Q2/6K1/8/8/8/8/8 b - - 0 1" # black to move, stalemate
PREMATE_W="7k/5Q2/5K2/8/8/8/8/8 w - - 0 1" # f7g7 mates
PREMATE_B="rnbqkbnr/pppp1ppp/8/4p3/6P1/5P2/PPPPP2P/RNBQKBNR b KQkq - 0 2" # black computer: d8h4#
t "E1: Terminal FEN at creation (HvH)"
R=$(req POST "$API_URL/games" -H "Content-Type: application/json" \
-d "{\"white\":{\"type\":1},\"black\":{\"type\":1},\"fen\":\"$MATE_FEN\"}")
assert_field "$R" '.state' "black wins" "Creation response carries mate"
G=$(echo "$R" | jq -r '.gameId'); [ "$G" != "null" ] && req DELETE "$API_URL/games/$G" >/dev/null
t "E2: Terminal FEN at creation (human white vs computer) — original repro case 1"
R=$(req POST "$API_URL/games" -H "Content-Type: application/json" \
-d "{\"white\":{\"type\":1},\"black\":{\"type\":2,\"searchTime\":100},\"fen\":\"$MATE_FEN\"}")
assert_field "$R" '.state' "black wins" "Mate detected, no client trigger needed"
G=$(echo "$R" | jq -r '.gameId'); [ "$G" != "null" ] && req DELETE "$API_URL/games/$G" >/dev/null
t "E3: Terminal FEN at creation (computer white vs human) — original repro case 2"
R=$(req POST "$API_URL/games" -H "Content-Type: application/json" \
-d "{\"white\":{\"type\":2,\"searchTime\":100},\"black\":{\"type\":1},\"fen\":\"$MATE_FEN\"}")
assert_field "$R" '.state' "black wins" "Mate detected at creation, not via cccc path"
G=$(echo "$R" | jq -r '.gameId'); [ "$G" != "null" ] && req DELETE "$API_URL/games/$G" >/dev/null
t "E4: Stalemate FEN at creation"
R=$(req POST "$API_URL/games" -H "Content-Type: application/json" \
-d "{\"white\":{\"type\":1},\"black\":{\"type\":1},\"fen\":\"$STALE_FEN\"}")
assert_field "$R" '.state' "stalemate" "Stalemate not misclassified as mate"
G=$(echo "$R" | jq -r '.gameId'); [ "$G" != "null" ] && req DELETE "$API_URL/games/$G" >/dev/null
t "E5: Mate delivered by human move"
R=$(req POST "$API_URL/games" -H "Content-Type: application/json" \
-d "{\"white\":{\"type\":1},\"black\":{\"type\":1},\"fen\":\"$PREMATE_W\"}")
G=$(echo "$R" | jq -r '.gameId')
R=$(req POST "$API_URL/games/$G/moves" -H "Content-Type: application/json" -d '{"move":"f7g7"}')
assert_field "$R" '.state' "white wins" "Move response carries terminal state"
assert_field "$R" '.lastMove.move' "f7g7" "LastMove present in terminal response"
req DELETE "$API_URL/games/$G" >/dev/null
t "E6: Mate delivered by computer + long-poll wakes with settled state (<5s)"
R=$(req POST "$API_URL/games" -H "Content-Type: application/json" \
-d "{\"white\":{\"type\":1},\"black\":{\"type\":2,\"level\":20,\"searchTime\":1000},\"fen\":\"$PREMATE_B\"}")
G=$(echo "$R" | jq -r '.gameId')
N=$(echo "$R" | jq -r '.moves | length')
req POST "$API_URL/games/$G/moves" -H "Content-Type: application/json" -d '{"move":"cccc"}' >/dev/null
S=$(date +%s)
R=$(req GET "$API_URL/games/$G?wait=true&moveCount=$N")
E=$(( $(date +%s) - S ))
assert_field "$R" '.state' "black wins" "Computer mate classified"
[ "$E" -lt 5 ] && ok "Poll woke in ${E}s (state-aware notify, no 30s stall)" \
|| ko "Poll took ${E}s — state-only/atomic wake regressed"
req DELETE "$API_URL/games/$G" >/dev/null
t "E7: Delete during long-poll wakes waiter promptly"
R=$(req POST "$API_URL/games" -H "Content-Type: application/json" \
-d '{"white":{"type":1},"black":{"type":1}}')
G=$(echo "$R" | jq -r '.gameId')
S=$(date +%s)
req GET "$API_URL/games/$G?wait=true&moveCount=0" > /tmp/es_poll.json &
P=$!
sleep 1
req DELETE "$API_URL/games/$G" >/dev/null
wait $P
E=$(( $(date +%s) - S ))
CODE=$(jq -r '.code' /tmp/es_poll.json 2>/dev/null)
if [ "$E" -lt 5 ]; then
ok "Poll woke in ${E}s (state-aware notify, no 30s stall)"
else
ko "Poll took ${E}s — state-only/atomic wake regressed"
fi
echo -e "\n${CYAN}Passed: $PASS Failed: $FAIL${NC}"
[ $FAIL -eq 0 ]
+5 -5
View File
@@ -125,7 +125,7 @@ test_multiple_waiters() {
# Test 3: Timeout behavior
test_timeout() {
log_test "Timeout behavior (this takes 25 seconds)"
log_test "Timeout behavior (this takes 30 seconds)"
# Create a game
GAME_ID=$(create_game 1 1)
@@ -139,10 +139,10 @@ test_timeout() {
elapsed=$((end_time - start_time))
# Check timeout was ~25 seconds
if [ "$elapsed" -ge 24 ] && [ "$elapsed" -le 26 ]; then
log_info "✓ Request timed out after ~25 seconds"
if [ "$elapsed" -ge 29 ] && [ "$elapsed" -le 31 ]; then
log_info "✓ Request timed out after ~30 seconds"
else
log_error "✗ Timeout was $elapsed seconds (expected ~25)"
log_error "✗ Timeout was $elapsed seconds (expected ~30)"
exit 1
fi
@@ -209,4 +209,4 @@ else
fi
echo ""
log_info "All tests passed! ✓"
log_info "All tests passed! ✓"