"""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 scopa.game import engine from scopa.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()