Files
tavolo/server/tests/test_store.py
T
woggioni 6932a3272c
CI / Build and push docker image (push) Successful in 3m12s
Rename the app from scopa to tavolo
The platform now hosts multiple card games, with scopone scientifico as
the first one. Rename the brand wherever it is not a game rule:

- move the Python package to server/src/tavolo and update imports
- rename the Postgres database/user, OIDC issuer path, client id and
  Redis key prefixes to tavolo (clean break: existing pgdata volumes and
  live games are not migrated)
- rename the Cargo package to tavolo-web and set the page title to Tavolo
- update docs and the Docker image path to woggioni/tavolo

The scopa game term (clearing the table) in the engine, state and web UI
is intentionally left untouched.
2026-09-16 21:46:06 +08:00

96 lines
3.0 KiB
Python

"""In-memory game store behaviour (the Redis store shares this interface)."""
from __future__ import annotations
import asyncio
import unittest
from pwo import async_test
from tavolo.game import engine
from tavolo.store import InMemoryGameStore
class InMemoryGameStoreTest(unittest.TestCase):
@async_test
async def test_save_load_roundtrip(self) -> None:
store = InMemoryGameStore()
state = engine.create_game("g1", "CODE01", "alice", "alice", target_score=16)
engine.join_game(state, "bob", "bob")
await store.save(state)
loaded = await store.load("g1")
self.assertIsNotNone(loaded)
assert loaded is not None
self.assertEqual("CODE01", loaded.join_code)
self.assertEqual(16, loaded.target_score)
self.assertEqual(["alice", "bob"], [p.sub for p in loaded.players])
@async_test
async def test_load_missing_returns_none(self) -> None:
store = InMemoryGameStore()
self.assertIsNone(await store.load("nope"))
self.assertIsNone(await store.find_by_code("NOPE01"))
@async_test
async def test_find_by_code(self) -> None:
store = InMemoryGameStore()
state = engine.create_game("g2", "CODE02", "alice", "alice")
await store.save(state)
found = await store.find_by_code("code02") # case-insensitive
self.assertIsNotNone(found)
assert found is not None
self.assertEqual("g2", found.id)
@async_test
async def test_load_returns_a_copy(self) -> None:
store = InMemoryGameStore()
state = engine.create_game("g3", "CODE03", "alice", "alice")
await store.save(state)
first = await store.load("g3")
assert first is not None
first.phase = "tampered"
second = await store.load("g3")
assert second is not None
self.assertEqual("lobby", second.phase)
@async_test
async def test_publish_reaches_subscriber(self) -> None:
store = InMemoryGameStore()
state = engine.create_game("g4", "CODE04", "alice", "alice")
await store.save(state)
received = []
async with store.subscribe("g4") as events:
await store.publish("g4")
async for _ in events:
received.append(True)
break
self.assertEqual([True], received)
@async_test
async def test_lock_serializes_concurrent_mutations(self) -> None:
store = InMemoryGameStore()
order = []
async def holder() -> None:
async with store.lock("g5"):
order.append("holder-enter")
await asyncio.sleep(0.05)
order.append("holder-exit")
async def contender() -> None:
await asyncio.sleep(0.01)
async with store.lock("g5"):
order.append("contender")
await asyncio.gather(holder(), contender())
self.assertEqual(
["holder-enter", "holder-exit", "contender"], order
)
if __name__ == "__main__":
unittest.main()