Add preliminary support for multiple card games
CI / Build and push docker image (push) Successful in 3m5s

A game-type registry (server/src/tavolo/games.py) is now the single
source of truth for the games the platform can host; only scopone
scientifico is registered so far. GET /api/game-types exposes it for the
lobby's new game dropdown, and POST /api/games accepts a validated
game_type (default scopone_scientifico) which is carried on the live
GameState and onto each finished Match row (new indexed column,
migration 1_20260916235833_update), so statistics can be scoped per
game: /api/me/matches and /api/leaderboard take an optional game_type
filter and every serialized match includes its game_type.

Game states serialized before this change still load with the default
game type.
This commit is contained in:
2026-09-17 08:26:46 +08:00
parent 876b4abd8b
commit ab4130a4ca
15 changed files with 376 additions and 28 deletions
+48
View File
@@ -119,5 +119,53 @@ class GamesRouteTest(unittest.TestCase):
self.assertEqual(404, response.status_code)
class GameTypesRouteTest(unittest.TestCase):
@async_test
async def test_lists_available_game_types(self) -> None:
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://127.0.0.1") as client:
response = await client.get("/api/game-types")
self.assertEqual(200, response.status_code)
results = response.json()["results"]
self.assertEqual(["scopone_scientifico"], [g["id"] for g in results])
self.assertEqual("Scopone scientifico", results[0]["name"])
self.assertTrue(results[0]["description"])
@async_test
async def test_create_defaults_game_type(self) -> None:
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={})
self.assertEqual(201, created.status_code)
self.assertEqual("scopone_scientifico", created.json()["game_type"])
@async_test
async def test_create_with_explicit_game_type(self) -> None:
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={"game_type": "scopone_scientifico"}
)
self.assertEqual(201, created.status_code)
body = created.json()
self.assertEqual("scopone_scientifico", body["game_type"])
with oidc_user("alice"):
snapshot = await client.get(f"/api/games/{body['id']}")
self.assertEqual("scopone_scientifico", snapshot.json()["game_type"])
@async_test
async def test_create_rejects_unknown_game_type(self) -> None:
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://127.0.0.1") as client:
with oidc_user("alice"):
unknown = await client.post("/api/games", json={"game_type": "briscola"})
non_string = await client.post("/api/games", json={"game_type": 42})
self.assertEqual(400, unknown.status_code)
self.assertEqual(400, non_string.status_code)
if __name__ == "__main__":
unittest.main()