diff --git a/lobby/tests.py b/lobby/tests.py index 6b156f3..1aefac6 100644 --- a/lobby/tests.py +++ b/lobby/tests.py @@ -360,6 +360,8 @@ class LieSubmissionTests(TestCase): ) self.assertEqual(response.status_code, 400) + self.assertEqual(response.json()["error_code"], "lie_submission_closed") + self.assertEqual(response.json()["locale"], "en") self.assertEqual(response.json()["error"], "Lie submission window has closed") def test_submit_lie_rejects_duplicate_submission(self): @@ -381,6 +383,8 @@ class LieSubmissionTests(TestCase): ) self.assertEqual(response.status_code, 409) + self.assertEqual(response.json()["error_code"], "lie_already_submitted") + self.assertEqual(response.json()["locale"], "en") self.assertEqual(response.json()["error"], "Lie already submitted for this player") def test_submit_lie_requires_session_token(self): @@ -401,6 +405,8 @@ class LieSubmissionTests(TestCase): ) self.assertEqual(response.status_code, 400) + self.assertEqual(response.json()["error_code"], "session_token_required") + self.assertEqual(response.json()["locale"], "en") self.assertEqual(response.json()["error"], "session_token is required") def test_submit_lie_rejects_invalid_session_token(self): @@ -421,8 +427,33 @@ class LieSubmissionTests(TestCase): ) self.assertEqual(response.status_code, 403) + self.assertEqual(response.json()["error_code"], "invalid_player_session_token") + self.assertEqual(response.json()["locale"], "en") self.assertEqual(response.json()["error"], "Invalid player session token") + def test_submit_lie_uses_danish_locale_payload_from_accept_language(self): + round_question = RoundQuestion.objects.create( + session=self.session, + round_number=1, + question=self.question, + correct_answer=self.question.correct_answer, + ) + + response = self.client.post( + reverse( + "lobby:submit_lie", + kwargs={"code": self.session.code, "round_question_id": round_question.id}, + ), + data={"player_id": self.player.id, "session_token": "invalid-token", "text": "Sydney"}, + content_type="application/json", + HTTP_ACCEPT_LANGUAGE="da-DK,da;q=0.9,en;q=0.1", + ) + + self.assertEqual(response.status_code, 403) + self.assertEqual(response.json()["error_code"], "invalid_player_session_token") + self.assertEqual(response.json()["locale"], "da") + self.assertEqual(response.json()["error"], "Ugyldigt spiller-session-token") + class MixAnswersTests(TestCase): def setUp(self): self.host = User.objects.create_user(username="host", password="secret123") @@ -582,6 +613,8 @@ class GuessSubmissionTests(TestCase): ) self.assertEqual(response.status_code, 400) + self.assertEqual(response.json()["error_code"], "guess_submission_invalid_phase") + self.assertEqual(response.json()["locale"], "en") self.assertEqual(response.json()["error"], "Guess submission is only allowed in guess phase") def test_submit_guess_rejects_unknown_answer(self): @@ -595,6 +628,8 @@ class GuessSubmissionTests(TestCase): ) self.assertEqual(response.status_code, 400) + self.assertEqual(response.json()["error_code"], "selected_answer_invalid") + self.assertEqual(response.json()["locale"], "en") self.assertEqual(response.json()["error"], "Selected answer is not part of this round") def test_submit_guess_rejects_duplicate_submission(self): @@ -610,6 +645,8 @@ class GuessSubmissionTests(TestCase): ) self.assertEqual(response.status_code, 409) + self.assertEqual(response.json()["error_code"], "guess_already_submitted") + self.assertEqual(response.json()["locale"], "en") self.assertEqual(response.json()["error"], "Guess already submitted for this player") def test_submit_guess_rejects_after_deadline(self): @@ -626,6 +663,8 @@ class GuessSubmissionTests(TestCase): ) self.assertEqual(response.status_code, 400) + self.assertEqual(response.json()["error_code"], "guess_submission_closed") + self.assertEqual(response.json()["locale"], "en") self.assertEqual(response.json()["error"], "Guess submission window has closed") @@ -641,6 +680,8 @@ class GuessSubmissionTests(TestCase): ) self.assertEqual(response.status_code, 400) + self.assertEqual(response.json()["error_code"], "session_token_required") + self.assertEqual(response.json()["locale"], "en") self.assertEqual(response.json()["error"], "session_token is required") def test_submit_guess_rejects_invalid_session_token(self): @@ -654,6 +695,8 @@ class GuessSubmissionTests(TestCase): ) self.assertEqual(response.status_code, 403) + self.assertEqual(response.json()["error_code"], "invalid_player_session_token") + self.assertEqual(response.json()["locale"], "en") self.assertEqual(response.json()["error"], "Invalid player session token") @@ -735,6 +778,8 @@ class ScoreCalculationTests(TestCase): ) self.assertEqual(response.status_code, 403) + self.assertEqual(response.json()["error_code"], "host_only_calculate_scores") + self.assertEqual(response.json()["locale"], "en") self.assertEqual(response.json()["error"], "Only host can calculate scores") def test_calculate_scores_rejects_duplicate_calculation(self): @@ -756,6 +801,8 @@ class ScoreCalculationTests(TestCase): self.assertEqual(first.status_code, 200) self.assertEqual(second.status_code, 409) + self.assertEqual(second.json()["error_code"], "scores_already_calculated") + self.assertEqual(second.json()["locale"], "en") self.assertEqual(second.json()["error"], "Scores already calculated for this round question") @@ -793,6 +840,8 @@ class RevealRoundFlowTests(TestCase): ) self.assertEqual(response.status_code, 403) + self.assertEqual(response.json()["error_code"], "host_only_view_scoreboard") + self.assertEqual(response.json()["locale"], "en") self.assertEqual(response.json()["error"], "Only host can view scoreboard") def test_host_can_finish_game_from_reveal(self): @@ -825,6 +874,8 @@ class RevealRoundFlowTests(TestCase): ) self.assertEqual(response.status_code, 403) + self.assertEqual(response.json()["error_code"], "host_only_finish_game") + self.assertEqual(response.json()["locale"], "en") self.assertEqual(response.json()["error"], "Only host can finish game") def test_finish_game_rejects_wrong_phase(self): @@ -840,6 +891,8 @@ class RevealRoundFlowTests(TestCase): ) self.assertEqual(response.status_code, 400) + self.assertEqual(response.json()["error_code"], "finish_game_invalid_phase") + self.assertEqual(response.json()["locale"], "en") self.assertEqual(response.json()["error"], "Game can only be finished from reveal phase") def test_host_can_start_next_round_from_reveal(self): @@ -874,8 +927,26 @@ class RevealRoundFlowTests(TestCase): ) self.assertEqual(response.status_code, 400) + self.assertEqual(response.json()["error_code"], "next_round_invalid_phase") + self.assertEqual(response.json()["locale"], "en") self.assertEqual(response.json()["error"], "Next round can only start from reveal phase") + def test_reveal_scoreboard_unsupported_locale_falls_back_to_en_deterministically(self): + self.client.login(username="other_reveal", password="secret123") + + response = self.client.get( + reverse( + "lobby:reveal_scoreboard", + kwargs={"code": self.session.code}, + ), + HTTP_ACCEPT_LANGUAGE="fr-FR,fr;q=0.9", + ) + + self.assertEqual(response.status_code, 403) + self.assertEqual(response.json()["error_code"], "host_only_view_scoreboard") + self.assertEqual(response.json()["locale"], "en") + self.assertEqual(response.json()["error"], "Only host can view scoreboard") + class UiScreenTests(TestCase): def setUp(self): self.host = User.objects.create_user(username="host_ui", password="secret123") @@ -1342,6 +1413,15 @@ class I18nResolverTests(TestCase): self.assertEqual(result, "missing_key") self.assertTrue(any("i18n key missing in shared catalog" in entry for entry in logs.output)) + def test_missing_backend_error_code_is_logged_with_context(self): + from lobby.i18n import resolve_error_key + + with self.assertLogs("lobby.i18n", level="WARNING") as logs: + result = resolve_error_key("missing_code") + + self.assertEqual(result, "missing_code") + self.assertTrue(any("i18n error code missing in shared catalog" in entry for entry in logs.output)) + def test_missing_locale_translation_falls_back_to_default_locale(self): with patch( "lobby.i18n.lobby_i18n_error_messages",