v0.6.0 multi-user game support with longpoll, tests and doc updated
This commit is contained in:
176
test/README.md
176
test/README.md
@ -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
|
||||
@ -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}"
|
||||
@ -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..."
|
||||
|
||||
|
||||
@ -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
213
test/test-longpoll.sh
Executable 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! ✓"
|
||||
Reference in New Issue
Block a user