WebSocket Message Schemas (WS-002): - Add Pydantic models for all client/server WebSocket messages - Implement discriminated unions for message type parsing - Include JoinGame, Action, Resign, Heartbeat client messages - Include GameState, ActionResult, Error, TurnStart server messages Connection Manager (WS-003): - Add Redis-backed WebSocket connection tracking - Implement user-to-sid mapping with TTL management - Support game room association and opponent lookup - Add heartbeat tracking for connection health Socket.IO Authentication (WS-004): - Add JWT-based authentication middleware - Support token extraction from multiple formats - Implement session setup with ConnectionManager integration - Add require_auth helper for event handlers Socket.IO Server Setup (WS-001): - Configure AsyncServer with ASGI mode - Register /game namespace with event handlers - Integrate with FastAPI via ASGIApp wrapper - Configure CORS from application settings Game Service (GS-001): - Add stateless GameService for game lifecycle orchestration - Create engine per-operation using rules from GameState - Implement action-based RNG seeding for deterministic replay - Add rng_seed field to GameState for replay support Architecture verified: - Core module independence (no forbidden imports) - Config from request pattern (rules in GameState) - Dependency injection (constructor deps, method config) - All 1090 tests passing Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
114 lines
3.5 KiB
Python
114 lines
3.5 KiB
Python
"""Tests for Socket.IO server setup and configuration.
|
|
|
|
These tests verify that the Socket.IO server is correctly configured
|
|
and can be mounted alongside FastAPI.
|
|
"""
|
|
|
|
|
|
class TestSocketIOSetup:
|
|
"""Tests for Socket.IO server initialization."""
|
|
|
|
def test_sio_server_exists(self) -> None:
|
|
"""
|
|
Verify that the Socket.IO AsyncServer is created.
|
|
|
|
The server instance should be available as a module-level export
|
|
and configured for ASGI mode.
|
|
"""
|
|
from app.socketio import sio
|
|
|
|
assert sio is not None
|
|
assert sio.async_mode == "asgi"
|
|
|
|
def test_game_namespace_handlers_registered(self) -> None:
|
|
"""
|
|
Verify that /game namespace event handlers are registered.
|
|
|
|
All required event handlers should be available on the sio instance
|
|
before any connections are made.
|
|
"""
|
|
from app.socketio import sio
|
|
|
|
# Check that handlers are registered for /game namespace
|
|
assert "/game" in sio.handlers
|
|
|
|
game_handlers = sio.handlers["/game"]
|
|
expected_events = [
|
|
"connect",
|
|
"disconnect",
|
|
"game:join",
|
|
"game:action",
|
|
"game:resign",
|
|
"game:heartbeat",
|
|
]
|
|
|
|
for event in expected_events:
|
|
assert event in game_handlers, f"Missing handler for {event}"
|
|
|
|
def test_create_socketio_app_returns_asgi_app(self) -> None:
|
|
"""
|
|
Verify that create_socketio_app returns a valid ASGI application.
|
|
|
|
The combined app should wrap the FastAPI app and handle both
|
|
HTTP and WebSocket connections.
|
|
"""
|
|
import socketio
|
|
from fastapi import FastAPI
|
|
|
|
from app.socketio import create_socketio_app
|
|
|
|
# Create a minimal FastAPI app for testing
|
|
test_app = FastAPI()
|
|
|
|
# Create combined ASGI app
|
|
combined = create_socketio_app(test_app)
|
|
|
|
# Verify it's a Socket.IO ASGI app
|
|
assert isinstance(combined, socketio.ASGIApp)
|
|
|
|
def test_cors_configured_from_settings(self) -> None:
|
|
"""
|
|
Verify that Socket.IO CORS settings match application settings.
|
|
|
|
The Socket.IO server should allow the same origins as FastAPI
|
|
to ensure consistent cross-origin behavior.
|
|
"""
|
|
from app.config import settings
|
|
from app.socketio import sio
|
|
|
|
# Socket.IO stores CORS origins in eio (Engine.IO) settings
|
|
# The cors_allowed_origins should match settings
|
|
assert sio.eio.cors_allowed_origins == settings.cors_origins
|
|
|
|
|
|
class TestMainAppIntegration:
|
|
"""Tests for Socket.IO integration with main app."""
|
|
|
|
def test_main_app_is_combined_asgi_app(self) -> None:
|
|
"""
|
|
Verify that the main app module exports the combined ASGI app.
|
|
|
|
The 'app' variable in main.py should be the Socket.IO wrapped
|
|
FastAPI application, not the raw FastAPI app.
|
|
"""
|
|
import socketio
|
|
|
|
from app.main import app
|
|
|
|
# The exported 'app' should be a Socket.IO ASGI app
|
|
assert isinstance(app, socketio.ASGIApp)
|
|
|
|
def test_fastapi_app_accessible(self) -> None:
|
|
"""
|
|
Verify that the underlying FastAPI app is still accessible.
|
|
|
|
The FastAPI app should be available as _fastapi_app in main
|
|
for testing and direct access when needed.
|
|
"""
|
|
from fastapi import FastAPI
|
|
|
|
from app.main import _fastapi_app
|
|
|
|
assert isinstance(_fastapi_app, FastAPI)
|
|
assert _fastapi_app.title == "Mantimon TCG"
|