Split backend into tavolo-platform, tavolo-scopone and tavolo-app packages
Move the game-independent machinery (lobby, live-game store, websocket, deadline scheduler, match history, leaderboards) into a new tavolo-platform distribution behind a GameEngine contract, the scopone scientifico rules plus a platform adapter into tavolo-scopone, and keep only the composition root in tavolo-app. The three distributions share the tavolo namespace (PEP 420, kaya-style monorepo). Match history becomes fully generic: Match carries the engine's result JSON and MatchPlayer points/details instead of scopone-shaped team columns (migration 3 backfills existing rows). Lobby creation takes an opaque per-game options object and websocket actions dispatch to the session's engine. Tests: platform suite runs against a DummyEngine toy game, scopone keeps the rules tests plus new adapter tests, server/tests covers the wired stack end to end (194 tests, was 143).
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
"""Game lobby route tests via kaya's ASGI transport."""
|
||||
"""Game lobby route tests via kaya's ASGI transport (scopone game)."""
|
||||
from __future__ import annotations
|
||||
|
||||
import unittest
|
||||
@@ -23,7 +23,9 @@ class GamesRouteTest(unittest.TestCase):
|
||||
transport = ASGITransport(app=app)
|
||||
async with AsyncClient(transport=transport, base_url="http://127.0.0.1") as client:
|
||||
with oidc_user("alice"):
|
||||
created = await client.post("/api/games", json={"target_score": 16})
|
||||
created = await client.post(
|
||||
"/api/games", json={"options": {"target_score": 16}}
|
||||
)
|
||||
self.assertEqual(201, created.status_code)
|
||||
body = created.json()
|
||||
self.assertEqual("lobby", body["phase"])
|
||||
@@ -80,12 +82,16 @@ class GamesRouteTest(unittest.TestCase):
|
||||
self.assertTrue(default.json()["napola"])
|
||||
|
||||
with oidc_user("alice"):
|
||||
disabled = await client.post("/api/games", json={"napola": False})
|
||||
disabled = await client.post(
|
||||
"/api/games", json={"options": {"napola": False}}
|
||||
)
|
||||
self.assertEqual(201, disabled.status_code)
|
||||
self.assertFalse(disabled.json()["napola"])
|
||||
|
||||
with oidc_user("alice"):
|
||||
invalid = await client.post("/api/games", json={"napola": "yes"})
|
||||
invalid = await client.post(
|
||||
"/api/games", json={"options": {"napola": "yes"}}
|
||||
)
|
||||
self.assertEqual(400, invalid.status_code)
|
||||
|
||||
@async_test
|
||||
@@ -120,9 +126,15 @@ class GamesRouteTest(unittest.TestCase):
|
||||
transport = ASGITransport(app=app)
|
||||
async with AsyncClient(transport=transport, base_url="http://127.0.0.1") as client:
|
||||
with oidc_user("alice"):
|
||||
zero = await client.post("/api/games", json={"target_score": 0})
|
||||
text = await client.post("/api/games", json={"target_score": "eleven"})
|
||||
huge = await client.post("/api/games", json={"target_score": 1000})
|
||||
zero = await client.post(
|
||||
"/api/games", json={"options": {"target_score": 0}}
|
||||
)
|
||||
text = await client.post(
|
||||
"/api/games", json={"options": {"target_score": "eleven"}}
|
||||
)
|
||||
huge = await client.post(
|
||||
"/api/games", json={"options": {"target_score": 1000}}
|
||||
)
|
||||
self.assertEqual(400, zero.status_code)
|
||||
self.assertEqual(400, text.status_code)
|
||||
self.assertEqual(400, huge.status_code)
|
||||
@@ -147,6 +159,10 @@ class GameTypesRouteTest(unittest.TestCase):
|
||||
self.assertEqual(["scopone_scientifico"], [g["id"] for g in results])
|
||||
self.assertEqual("Scopone scientifico", results[0]["name"])
|
||||
self.assertTrue(results[0]["description"])
|
||||
self.assertEqual(4, results[0]["min_players"])
|
||||
self.assertEqual(4, results[0]["max_players"])
|
||||
self.assertIn("target_score", results[0]["options_schema"]["properties"])
|
||||
self.assertIn("napola", results[0]["options_schema"]["properties"])
|
||||
|
||||
@async_test
|
||||
async def test_create_defaults_game_type(self) -> None:
|
||||
|
||||
Reference in New Issue
Block a user