v0.6.0 multi-user game support with longpoll, tests and doc updated

This commit is contained in:
2025-11-05 12:08:18 -05:00
parent a3f4db96fa
commit 52868af4ea
13 changed files with 708 additions and 120 deletions

View File

@ -1,84 +1,146 @@
# Chess API Test Suite
This directory contains comprehensive test suites for the Chess API server, covering both API functionality and database persistence.
This directory contains comprehensive test suites for the Chess API server, covering API functionality, database operations, authentication, and real-time updates.
## Prerequisites
- `jq` - JSON processor
- `curl` - HTTP client
- `sqlite3` - SQLite CLI (for database tests only)
- Compiled `chessd` binary in parent directory
- `sqlite3` - SQLite CLI (for database tests)
- `base64` - Base64 encoder (for JWT tests)
- Compiled `chessd` binary in accessible path
## Test Suites
### 1. API Functionality Tests (`test-api.sh`)
Tests all API endpoints, error handling, rate limiting, and game logic.
**Running the test:**
## Running the test server
```bash
# Start server in development mode (required for tests to pass)
../chessd -dev
# In another terminal, run tests
./test-api.sh
./run-test-server.sh
```
**Coverage:**
- Game creation and management (HvH, HvC, CvC)
- Move validation and execution
- Computer move triggering with "cccc"
- Undo functionality
- Player configuration changes
- Rate limiting (dev mode: 20 req/s)
- Security hardening and input validation
Pass binary path as first argument of the script if it's not placed in current directory `./chessd`.
Server will run with '-dev' option, enabling db WAL mode and relaxing rate limiting.
Will clean up test database and temporary files, so it's preferred for clean testing.
Can be used for all the tests.
### 2. Database Persistence Tests (`test-db.sh`)
**Outdated: test-db.sh and test-db-server.sh currently focus on user operations.**
### Pre-configured Users
| Username | Password | Email |
|----------|----------|-------|
| alice | AlicePass123 | alice@example.com |
| bob | BobPass456 | bob@example.com |
| charlie | CharliePass789 | - |
Tests database storage, async writes, and data integrity.
### Features
- Automatically initializes database schema
- Creates three test users
- Runs on port 8080 (API) and 9090 (Web UI)
- Development mode with relaxed rate limits
- Fixed JWT secret for consistent tokens
- Graceful shutdown on Ctrl+C
**Running the test:**
### Manual Testing Examples
```bash
# Terminal 1: Start server with database
./run-server-with-db.sh ../chessd
# Login as alice
curl -X POST http://localhost:8080/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"alice","password":"AlicePass123"}'
# Terminal 2: Run database tests
./test-db.sh
# When done, press Ctrl+C in Terminal 1
# Create authenticated game
TOKEN="<jwt-from-login>"
curl -X POST http://localhost:8080/games \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"white":{"type":1},"black":{"type":2,"level":10}}'
```
**Coverage:**
- Game and move persistence
- Async write buffer behavior
- Multi-game isolation
- Undo effects on database
- WAL mode verification
- Foreign key constraints
## Test Suite Overview
## Important Notes
| Test Suite | File | Coverage |
|------------|------|----------|
| API Functionality | `test-api.sh` | Game operations, moves, undo, rate limiting |
| Database & Auth | `test-db.sh` | User registration, login, JWT tokens, persistence |
| Long-Polling | `test-longpoll.sh` | Real-time updates, wait behavior, timeouts |
| Test Server | `test-db-server.sh` | Pre-populated test environment |
1. **Development Mode Required**: The server MUST be started with `-dev` flag for tests to pass. This enables:
- Relaxed rate limiting (20 req/s instead of 10)
- WAL mode for SQLite (better concurrency)
## 1. API Functionality Tests (`test-api.sh`)
2. **Database Tests**: The `run-server-with-db.sh` script automatically:
- Creates a temporary test database
- Initializes the schema
- Cleans up on exit (Ctrl+C)
Tests core game mechanics and API endpoints.
3. **Test Isolation**: Each test suite can be run independently. The database tests use a separate `test.db` file that doesn't affect production data.
### Running the test
```bash
# Terminal 1: Start server in development mode
test/run-test-server.sh ./chessd
# Direct (no cleanup required): ./chessd -dev
## Troubleshooting
# Terminal 2: Run API tests
test/test-api.sh
```
**Rate limiting failures:** Ensure server is running with `-dev` flag
**Database test failures:** Check that no other instance is using `test.db`
**Port conflicts:** Default port is 8080, ensure it's available
### Coverage
- **Game Creation**: Human vs Human, Human vs Computer, Computer vs Computer
- **Move Validation**: Legal/illegal moves, UCI notation
- **Computer Play**: Async engine moves with "cccc" trigger
- **Undo System**: Single and multiple move reversal
- **Player Configuration**: Dynamic player type changes
- **Rate Limiting**: 20 req/s in dev mode
- **Error Handling**: Invalid inputs, missing games, wrong content-types
- **Security**: Input validation, FEN injection prevention
## Exit Codes
## 2. Database & Authentication Tests (`test-db.sh`)
- `0` - All tests passed
- `1` - One or more tests failed
Tests user management, authentication, and persistence via API integration.
**Requires Running Server Script**
Check colored output for detailed pass/fail information for each test case.
### Running the test
```bash
# Terminal 1: Start test server with database
# Server is running with -dev option (WAL mode db)
test/test-db-server.sh ./chessd
# Terminal 2: Run API integration tests
test/test-db.sh ./chessd
```
### Coverage
- **User Registration**: Account creation, password hashing
- **Duplicate Prevention**: Username/email uniqueness
- **Authentication**: Login with JWT generation
- **Token Validation**: JWT parsing and claims verification
- **Password Security**: Argon2id hashing, complexity requirements
- **Case Sensitivity**: Case-insensitive username/email matching
- **Database Schema**: Table creation, constraints, indexes
### Test Flow
1. Creates temporary `test.db` with schema
2. Registers test users (alice, bob, charlie)
3. Tests authentication endpoints
4. Validates JWT tokens and claims
5. Tests duplicate user prevention
6. Cleans up test database
## 3. Long-Polling Tests (`test-longpoll.sh`)
Tests real-time game updates via HTTP long-polling.
### Running the test
```bash
# Terminal 1: Start server with storage
test/run-test-server.sh ./chessd
# Direct (test.db cleanup required): ./chessd -dev -storage-path test.db
# Terminal 2: Run long-polling tests
test/test-longpoll.sh
```
### Coverage
- **Basic Long-Polling**: Wait for game state changes
- **Multi-Client**: Multiple simultaneous waiters
- **Timeout Behavior**: 25-second timeout verification
- **Immediate Response**: No wait when state already changed
- **Connection Handling**: Client disconnect cleanup
- **Game Deletion**: Notification on game removal
- **Move Detection**: Accurate move count tracking
### Test Scenarios
1. **Single Waiter**: Client waits, receives update after move
2. **Multiple Waiters**: 3 clients wait, all receive notification
3. **Timeout**: Verify 25-second timeout with valid response
4. **Skip Wait**: Immediate return when moveCount outdated
5. **Disconnection**: Proper cleanup on client disconnect

View File

@ -1,5 +1,5 @@
#!/usr/bin/env bash
# FILE: run-test-db-server.sh
# FILE: test/run-test-server.sh
set -e
@ -19,7 +19,8 @@ NC='\033[0m'
# Check executable
if [ ! -x "$CHESSD_EXEC" ]; then
echo -e "${RED}Error: chessd executable not found or not executable: $CHESSD_EXEC${NC}"
echo "Please build the application first: go build ./cmd/chessd"
echo "Provide the path to chessd binary as first argument or place it in the current directory."
echo "Build the binary if not available: go build ./cmd/chessd"
exit 1
fi
@ -91,11 +92,12 @@ echo " Executable: $CHESSD_EXEC"
echo " Database: $TEST_DB"
echo " Port: $API_PORT"
echo " Mode: Development (WAL enabled, relaxed rate limits)"
echo " Purpose: Backend for chessd tests"
echo " PID File: $PID_FILE"
echo ""
echo -e "${YELLOW}Instructions:${NC}"
echo " 1. Server will start in foreground"
echo " 2. Open another terminal and run: ./test-db.sh"
echo " 1. Server will run in foreground with test database"
echo " 2. Open another terminal and run the test script or manual tests"
echo " 3. Press Ctrl+C here when testing is complete"
echo ""
echo -e "${CYAN}──────────────────────────────────────────────────────────${NC}"

View File

@ -1,5 +1,5 @@
#!/usr/bin/env bash
# FILE: test-api.sh
# FILE: test/test-api.sh
# Chess API Robustness Test Suite
# Tests the refactored chess API with security hardening
@ -132,8 +132,9 @@ fi
print_header "Chess API Robustness Test Suite"
echo "Server: $BASE_URL"
echo "API Version: v1"
echo -e "${MAGENTA}⚠️ IMPORTANT: Server must be started with -dev flag for tests to pass!${NC}"
echo -e "${MAGENTA} Example: ./chessd -dev${NC}"
echo -e "${MAGENTA} IMPORTANT: Server must be started with -dev flag for tests to pass!${NC}"
echo -e "${MAGENTA} Start the server first: test/run-test-server.sh${NC}"
echo -e "${MAGENTA} Or directly after build: ./chessd -dev${NC}"
echo ""
echo "Starting comprehensive tests..."

View File

@ -1,8 +1,11 @@
#!/usr/bin/env bash
# FILE: test-db.sh
# FILE: test/test-db.sh
# Database and User Management Test Suite
# Tests user operations, authentication, and game persistence
# Database & Authentication API Integration Test Suite
# Tests user operations, authentication, and persistence via HTTP API
#
# REQUIRES: Server running on localhost:8080 with database storage
# Start with: test/run-test-server.sh
BASE_URL="http://localhost:8080"
API_URL="${BASE_URL}/api/v1"
@ -118,29 +121,6 @@ api_request() {
return $status
}
wait_for_state() {
local game_id=$1
local target_state=$2
local token=$3
local max_attempts=${4:-20}
local attempt=0
while [ $attempt -lt $max_attempts ]; do
local response=$(api_request GET "$API_URL/games/$game_id" \
-H "Authorization: Bearer $token")
local current_state=$(echo "$response" | jq -r '.state' 2>/dev/null)
if [ "$current_state" = "$target_state" ] || [[ "$target_state" = "!pending" && "$current_state" != "pending" ]]; then
return 0
fi
((attempt++))
sleep 0.1
done
return 1
}
# Check dependencies
for cmd in jq sqlite3 curl; do
if ! command -v $cmd &> /dev/null; then
@ -155,10 +135,18 @@ if [ ! -x "$CHESSD_EXEC" ]; then
exit 1
fi
# Verify server connectivity before running tests
if ! curl -sf "$BASE_URL/health" > /dev/null 2>&1; then
echo -e "${RED}Error: Cannot connect to server at $BASE_URL${NC}"
echo "Start the server first: test/run-test-server.sh"
exit 1
fi
# Start tests
print_header "Database & User Management Test Suite"
echo "Server: $BASE_URL"
echo "Executable: $CHESSD_EXEC"
echo "Database: $TEST_DB"
echo "Test Database (server-managed): $TEST_DB"
echo ""
# ==============================================================================

213
test/test-longpoll.sh Executable file
View File

@ -0,0 +1,213 @@
#!/bin/bash
# test-longpoll.sh - Test long-polling functionality
set -e
# Configuration
API_URL="${API_URL:-http://localhost:8080/api/v1}" # Updated to include /api/v1
VERBOSE="${VERBOSE:-false}"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Helper functions
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
log_test() {
echo -e "${YELLOW}[TEST]${NC} $1"
}
# Function to create a test game
create_game() {
local white_type=$1
local black_type=$2
response=$(curl -s -X POST "$API_URL/games" \
-H "Content-Type: application/json" \
-d "{\"white\": {\"type\": $white_type}, \"black\": {\"type\": $black_type}}")
echo "$response" | jq -r '.gameId'
}
# Test 1: Basic long-polling
test_basic_longpoll() {
log_test "Basic long-polling functionality"
# Create a human vs human game
GAME_ID=$(create_game 1 1)
log_info "Created game: $GAME_ID"
# Start background poller
log_info "Starting long-poll request (25s timeout)..."
curl -s -X GET "$API_URL/games/$GAME_ID?wait=true&moveCount=0" > /tmp/poll_result.json &
POLL_PID=$!
# Wait a moment then make a move
sleep 2
log_info "Making move e2e4..."
curl -s -X POST "$API_URL/games/$GAME_ID/moves" \
-H "Content-Type: application/json" \
-d '{"move":"e2e4"}' > /dev/null
# Wait for poller to complete
if wait $POLL_PID; then
# Check if we got the updated game state
moves=$(cat /tmp/poll_result.json | jq -r '.moves | length')
if [ "$moves" = "1" ]; then
log_info "✓ Long-poll received notification successfully"
else
log_error "✗ Long-poll did not receive correct state"
exit 1
fi
else
log_error "✗ Long-poll request failed"
exit 1
fi
# Cleanup
curl -s -X DELETE "$API_URL/games/$GAME_ID" > /dev/null
}
# Test 2: Multiple concurrent waiters
test_multiple_waiters() {
log_test "Multiple concurrent waiters"
# Create a game
GAME_ID=$(create_game 1 1)
log_info "Created game: $GAME_ID"
# Start multiple background pollers
log_info "Starting 3 concurrent long-poll requests..."
curl -s -X GET "$API_URL/games/$GAME_ID?wait=true&moveCount=0" > /tmp/poll1.json &
PID1=$!
curl -s -X GET "$API_URL/games/$GAME_ID?wait=true&moveCount=0" > /tmp/poll2.json &
PID2=$!
curl -s -X GET "$API_URL/games/$GAME_ID?wait=true&moveCount=0" > /tmp/poll3.json &
PID3=$!
# Make a move
sleep 1
log_info "Making move e2e4..."
curl -s -X POST "$API_URL/games/$GAME_ID/moves" \
-H "Content-Type: application/json" \
-d '{"move":"e2e4"}' > /dev/null
# Wait for all pollers
wait $PID1 $PID2 $PID3
# Check all received the update
success=true
for i in 1 2 3; do
moves=$(cat /tmp/poll$i.json | jq -r '.moves | length')
if [ "$moves" != "1" ]; then
log_error "✗ Poller $i did not receive update"
success=false
fi
done
if $success; then
log_info "✓ All waiters received notifications"
else
exit 1
fi
# Cleanup
curl -s -X DELETE "$API_URL/games/$GAME_ID" > /dev/null
}
# Test 3: Timeout behavior
test_timeout() {
log_test "Timeout behavior (this takes 25 seconds)"
# Create a game
GAME_ID=$(create_game 1 1)
log_info "Created game: $GAME_ID"
# Start poller without making any moves
log_info "Starting long-poll that will timeout..."
start_time=$(date +%s)
curl -s -X GET "$API_URL/games/$GAME_ID?wait=true&moveCount=0" > /tmp/timeout.json
end_time=$(date +%s)
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"
else
log_error "✗ Timeout was $elapsed seconds (expected ~25)"
exit 1
fi
# Should still get valid game state
game_id=$(cat /tmp/timeout.json | jq -r '.gameId')
if [ "$game_id" = "$GAME_ID" ]; then
log_info "✓ Timeout response contains valid game state"
else
log_error "✗ Timeout response invalid"
exit 1
fi
# Cleanup
curl -s -X DELETE "$API_URL/games/$GAME_ID" > /dev/null
}
# Test 4: Immediate response when state already changed
test_immediate_response() {
log_test "Immediate response when state already changed"
# Create game and make a move
GAME_ID=$(create_game 1 1)
log_info "Created game: $GAME_ID"
curl -s -X POST "$API_URL/games/$GAME_ID/moves" \
-H "Content-Type: application/json" \
-d '{"move":"e2e4"}' > /dev/null
# Poll with moveCount=0 (should return immediately)
log_info "Polling with outdated move count..."
start_time=$(date +%s)
response=$(curl -s -X GET "$API_URL/games/$GAME_ID?wait=true&moveCount=0")
end_time=$(date +%s)
elapsed=$((end_time - start_time))
if [ "$elapsed" -le 1 ]; then
log_info "✓ Immediate response when move count differs"
else
log_error "✗ Response took $elapsed seconds (should be immediate)"
exit 1
fi
# Cleanup
curl -s -X DELETE "$API_URL/games/$GAME_ID" > /dev/null
}
# Run tests
log_info "Starting long-poll tests against $API_URL"
echo ""
test_basic_longpoll
echo ""
test_multiple_waiters
echo ""
test_immediate_response
echo ""
if [ "${SKIP_TIMEOUT_TEST:-false}" = "false" ]; then
test_timeout
else
log_info "Skipping timeout test (set SKIP_TIMEOUT_TEST=false to run)"
fi
echo ""
log_info "All tests passed! ✓"