fix(gameplay): restore scoreboard phase error contract
Some checks failed
CI / test-and-quality (push) Failing after 2m30s
CI / test-and-quality (pull_request) Failing after 2m32s

This commit is contained in:
2026-03-15 08:52:35 +00:00
parent 97b366d1e9
commit 8fa39adc2b
4 changed files with 193 additions and 24 deletions

View File

@@ -782,7 +782,8 @@ class RevealRoundFlowTests(TestCase):
self.player_one = Player.objects.create(session=self.session, nickname="Luna", score=9)
self.player_two = Player.objects.create(session=self.session, nickname="Mads", score=3)
def test_host_can_get_reveal_scoreboard(self):
@patch("lobby.views.sync_broadcast_phase_event")
def test_host_can_get_reveal_scoreboard(self, mock_sync_broadcast_phase_event):
self.client.login(username="host_reveal", password="secret123")
response = self.client.get(
@@ -796,6 +797,17 @@ class RevealRoundFlowTests(TestCase):
payload = response.json()
self.assertEqual(payload["session"]["status"], GameSession.Status.SCOREBOARD)
self.assertEqual([item["nickname"] for item in payload["leaderboard"]], ["Luna", "Mads"])
mock_sync_broadcast_phase_event.assert_called_once_with(
self.session.code,
"phase.scoreboard",
{
"leaderboard": [
{"id": self.player_one.id, "nickname": "Luna", "score": 9},
{"id": self.player_two.id, "nickname": "Mads", "score": 3},
],
"current_round": 1,
},
)
self.session.refresh_from_db()
self.assertEqual(self.session.status, GameSession.Status.SCOREBOARD)
@@ -807,11 +819,16 @@ class RevealRoundFlowTests(TestCase):
reverse(
"lobby:reveal_scoreboard",
kwargs={"code": self.session.code},
)
),
HTTP_ACCEPT_LANGUAGE="fr",
)
self.assertEqual(response.status_code, 403)
self.assertEqual(response.json()["error"], "Only host can view scoreboard")
self.assertEqual(response.json(), {
"error": "Only host can view scoreboard",
"error_code": "host_only_view_scoreboard",
"locale": "en",
})
def test_reveal_scoreboard_is_idempotent_in_scoreboard_phase(self):
self.session.status = GameSession.Status.SCOREBOARD
@@ -825,7 +842,8 @@ class RevealRoundFlowTests(TestCase):
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):
@patch("lobby.views.sync_broadcast_phase_event")
def test_host_can_finish_game_from_scoreboard(self, _mock_sync_broadcast_phase_event):
self.client.login(username="host_reveal", password="secret123")
self.client.get(reverse("lobby:reveal_scoreboard", kwargs={"code": self.session.code}))
@@ -852,11 +870,16 @@ class RevealRoundFlowTests(TestCase):
reverse(
"lobby:finish_game",
kwargs={"code": self.session.code},
)
),
HTTP_ACCEPT_LANGUAGE="da",
)
self.assertEqual(response.status_code, 403)
self.assertEqual(response.json()["error"], "Only host can finish game")
self.assertEqual(response.json(), {
"error": "Kun værten kan afslutte spillet",
"error_code": "host_only_finish_game",
"locale": "da",
})
def test_finish_game_rejects_wrong_phase(self):
self.client.login(username="host_reveal", password="secret123")
@@ -867,13 +890,19 @@ class RevealRoundFlowTests(TestCase):
reverse(
"lobby:finish_game",
kwargs={"code": self.session.code},
)
),
HTTP_ACCEPT_LANGUAGE="fr",
)
self.assertEqual(response.status_code, 400)
self.assertEqual(response.json()["error"], "Game can only be finished from scoreboard phase")
self.assertEqual(response.json(), {
"error": "Game can only be finished from scoreboard phase",
"error_code": "finish_game_invalid_phase",
"locale": "en",
})
def test_host_can_start_next_round_from_scoreboard(self):
@patch("lobby.views.sync_broadcast_phase_event")
def test_host_can_start_next_round_from_scoreboard(self, _mock_sync_broadcast_phase_event):
self.client.login(username="host_reveal", password="secret123")
self.client.get(reverse("lobby:reveal_scoreboard", kwargs={"code": self.session.code}))
@@ -893,7 +922,28 @@ class RevealRoundFlowTests(TestCase):
self.assertEqual(self.session.status, GameSession.Status.LOBBY)
self.assertEqual(self.session.current_round, 2)
def test_reveal_scoreboard_allows_repeated_reads_after_promotion(self):
def test_start_next_round_requires_host(self):
self.session.status = GameSession.Status.SCOREBOARD
self.session.save(update_fields=["status"])
self.client.login(username="other_reveal", password="secret123")
response = self.client.post(
reverse(
"lobby:start_next_round",
kwargs={"code": self.session.code},
),
HTTP_ACCEPT_LANGUAGE="fr",
)
self.assertEqual(response.status_code, 403)
self.assertEqual(response.json(), {
"error": "Only host can start next round",
"error_code": "host_only_start_next_round",
"locale": "en",
})
@patch("lobby.views.sync_broadcast_phase_event")
def test_reveal_scoreboard_allows_repeated_reads_after_promotion(self, mock_sync_broadcast_phase_event):
self.client.login(username="host_reveal", password="secret123")
first_response = self.client.get(
@@ -914,6 +964,7 @@ class RevealRoundFlowTests(TestCase):
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"])
self.assertEqual(mock_sync_broadcast_phase_event.call_count, 1)
def test_start_next_round_rejects_wrong_phase(self):
self.client.login(username="host_reveal", password="secret123")
@@ -924,11 +975,16 @@ class RevealRoundFlowTests(TestCase):
reverse(
"lobby:start_next_round",
kwargs={"code": self.session.code},
)
),
HTTP_ACCEPT_LANGUAGE="da",
)
self.assertEqual(response.status_code, 400)
self.assertEqual(response.json()["error"], "Next round can only start from scoreboard phase")
self.assertEqual(response.json(), {
"error": "Næste runde kan kun starte fra scoreboard-fasen",
"error_code": "next_round_invalid_phase",
"locale": "da",
})
class UiScreenTests(TestCase):
def setUp(self):