Offer a Continue link for ongoing games in the lobby

A player who accidentally closes the game page can now get back to it
from the lobby.

Backend:
- GameStore.list_ongoing() returns live unfinished sessions (newest
  first), skipping expired keys and finished matches;
- GET /api/me/games lists the games the caller is seated in, with the
  game name/description, players, open seats and your seat (no join
  code);
- store, platform route and app integration tests.

Frontend:
- MyGame/MyGamesPage model plus api::my_games();
- the lobby shows a "Your games" card with phase, seats, players and a
  Continue button linking to /game/<id>; hidden when empty or logged
  out.
This commit is contained in:
2026-10-06 17:41:44 +08:00
parent b821274f0b
commit 18877b8109
9 changed files with 446 additions and 4 deletions
+46
View File
@@ -236,6 +236,52 @@ class GamesRouteTest(unittest.TestCase):
self.assertEqual(400, text.status_code)
self.assertEqual(400, huge.status_code)
@async_test
async def test_my_games_lists_my_ongoing_games(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": "briscola"}
)
game_id = created.json()["id"]
with oidc_user("alice"):
listed = await client.get("/api/me/games")
self.assertEqual(200, listed.status_code)
# The app suite shares one in-memory store, so other tests'
# games may be listed too: pick ours by id.
by_id = {g["id"]: g for g in listed.json()["results"]}
self.assertIn(game_id, by_id)
entry = by_id[game_id]
self.assertEqual("briscola", entry["game_type"])
self.assertEqual("Briscola", entry["game_name"])
self.assertTrue(entry["game_description"])
self.assertEqual("lobby", entry["phase"])
self.assertEqual(1, entry["seats_open"])
self.assertEqual(0, entry["your_seat"])
self.assertNotIn("join_code", entry)
# A logged-in non-player does not see the game.
with oidc_user("mallory"):
others = await client.get("/api/me/games")
self.assertNotIn(
game_id, [g["id"] for g in others.json()["results"]]
)
# Once the match starts it stays listed, for both players.
code = created.json()["join_code"]
with oidc_user("bob"):
await client.post("/api/games/join", json={"code": code})
with oidc_user("bob"):
started = await client.get("/api/me/games")
bob_entry = next(
g for g in started.json()["results"] if g["id"] == game_id
)
self.assertEqual("playing", bob_entry["phase"])
self.assertEqual(1, bob_entry["your_seat"])
self.assertEqual(0, bob_entry["seats_open"])
@async_test
async def test_get_unknown_game(self) -> None:
transport = ASGITransport(app=app)