fix(gameplay): make scoreboard reads idempotent
All checks were successful
CI / test-and-quality (push) Successful in 2m40s
CI / test-and-quality (pull_request) Successful in 2m42s

This commit is contained in:
2026-03-15 08:05:21 +00:00
parent 558f8fe245
commit 97b366d1e9
2 changed files with 15 additions and 10 deletions

View File

@@ -813,15 +813,17 @@ class RevealRoundFlowTests(TestCase):
self.assertEqual(response.status_code, 403)
self.assertEqual(response.json()["error"], "Only host can view scoreboard")
def test_reveal_scoreboard_rejects_scoreboard_phase(self):
def test_reveal_scoreboard_is_idempotent_in_scoreboard_phase(self):
self.session.status = GameSession.Status.SCOREBOARD
self.session.save(update_fields=["status"])
self.client.login(username="host_reveal", password="secret123")
response = self.client.get(reverse("lobby:reveal_scoreboard", kwargs={"code": self.session.code}))
self.assertEqual(response.status_code, 400)
self.assertEqual(response.json()["error"], "Scoreboard is only available in reveal phase")
self.assertEqual(response.status_code, 200)
payload = response.json()
self.assertEqual(payload["session"]["status"], GameSession.Status.SCOREBOARD)
self.assertEqual([item["nickname"] for item in payload["leaderboard"]], ["Luna", "Mads"])
def test_host_can_finish_game_from_scoreboard(self):
self.client.login(username="host_reveal", password="secret123")
@@ -891,7 +893,7 @@ class RevealRoundFlowTests(TestCase):
self.assertEqual(self.session.status, GameSession.Status.LOBBY)
self.assertEqual(self.session.current_round, 2)
def test_reveal_scoreboard_rejects_repeated_reads_after_promotion(self):
def test_reveal_scoreboard_allows_repeated_reads_after_promotion(self):
self.client.login(username="host_reveal", password="secret123")
first_response = self.client.get(
@@ -908,8 +910,10 @@ class RevealRoundFlowTests(TestCase):
)
self.assertEqual(first_response.status_code, 200)
self.assertEqual(second_response.status_code, 400)
self.assertEqual(second_response.json()["error"], "Scoreboard is only available in reveal phase")
self.assertEqual(second_response.status_code, 200)
self.assertEqual(first_response.json()["session"]["status"], GameSession.Status.SCOREBOARD)
self.assertEqual(second_response.json()["session"]["status"], GameSession.Status.SCOREBOARD)
self.assertEqual([item["nickname"] for item in second_response.json()["leaderboard"]], ["Luna", "Mads"])
def test_start_next_round_rejects_wrong_phase(self):
self.client.login(username="host_reveal", password="secret123")

View File

@@ -714,11 +714,12 @@ def reveal_scoreboard(request: HttpRequest, code: str) -> JsonResponse:
with transaction.atomic():
locked_session = GameSession.objects.select_for_update().get(pk=session.pk)
if locked_session.status != GameSession.Status.REVEAL:
return JsonResponse({"error": "Scoreboard is only available in reveal phase"}, status=400)
if locked_session.status not in {GameSession.Status.REVEAL, GameSession.Status.SCOREBOARD}:
return JsonResponse({"error": "Scoreboard is only available in reveal/scoreboard phase"}, status=400)
locked_session.status = GameSession.Status.SCOREBOARD
locked_session.save(update_fields=["status"])
if locked_session.status == GameSession.Status.REVEAL:
locked_session.status = GameSession.Status.SCOREBOARD
locked_session.save(update_fields=["status"])
leaderboard = list(
Player.objects.filter(session=session)