test(i18n): cover locale fallback and backend error payloads
All checks were successful
CI / test-and-quality (push) Successful in 2m48s
CI / test-and-quality (pull_request) Successful in 2m40s

This commit is contained in:
2026-03-13 09:16:23 +00:00
parent 80520bad51
commit db7be0dfc6

View File

@@ -360,6 +360,8 @@ class LieSubmissionTests(TestCase):
) )
self.assertEqual(response.status_code, 400) 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") self.assertEqual(response.json()["error"], "Lie submission window has closed")
def test_submit_lie_rejects_duplicate_submission(self): def test_submit_lie_rejects_duplicate_submission(self):
@@ -381,6 +383,8 @@ class LieSubmissionTests(TestCase):
) )
self.assertEqual(response.status_code, 409) 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") self.assertEqual(response.json()["error"], "Lie already submitted for this player")
def test_submit_lie_requires_session_token(self): def test_submit_lie_requires_session_token(self):
@@ -401,6 +405,8 @@ class LieSubmissionTests(TestCase):
) )
self.assertEqual(response.status_code, 400) 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") self.assertEqual(response.json()["error"], "session_token is required")
def test_submit_lie_rejects_invalid_session_token(self): 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.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") 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): class MixAnswersTests(TestCase):
def setUp(self): def setUp(self):
self.host = User.objects.create_user(username="host", password="secret123") 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.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") self.assertEqual(response.json()["error"], "Guess submission is only allowed in guess phase")
def test_submit_guess_rejects_unknown_answer(self): def test_submit_guess_rejects_unknown_answer(self):
@@ -595,6 +628,8 @@ class GuessSubmissionTests(TestCase):
) )
self.assertEqual(response.status_code, 400) 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") self.assertEqual(response.json()["error"], "Selected answer is not part of this round")
def test_submit_guess_rejects_duplicate_submission(self): def test_submit_guess_rejects_duplicate_submission(self):
@@ -610,6 +645,8 @@ class GuessSubmissionTests(TestCase):
) )
self.assertEqual(response.status_code, 409) 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") self.assertEqual(response.json()["error"], "Guess already submitted for this player")
def test_submit_guess_rejects_after_deadline(self): def test_submit_guess_rejects_after_deadline(self):
@@ -626,6 +663,8 @@ class GuessSubmissionTests(TestCase):
) )
self.assertEqual(response.status_code, 400) 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") 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.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") self.assertEqual(response.json()["error"], "session_token is required")
def test_submit_guess_rejects_invalid_session_token(self): 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.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") 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.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") self.assertEqual(response.json()["error"], "Only host can calculate scores")
def test_calculate_scores_rejects_duplicate_calculation(self): def test_calculate_scores_rejects_duplicate_calculation(self):
@@ -756,6 +801,8 @@ class ScoreCalculationTests(TestCase):
self.assertEqual(first.status_code, 200) self.assertEqual(first.status_code, 200)
self.assertEqual(second.status_code, 409) 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") 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.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") self.assertEqual(response.json()["error"], "Only host can view scoreboard")
def test_host_can_finish_game_from_reveal(self): 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.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") self.assertEqual(response.json()["error"], "Only host can finish game")
def test_finish_game_rejects_wrong_phase(self): def test_finish_game_rejects_wrong_phase(self):
@@ -840,6 +891,8 @@ class RevealRoundFlowTests(TestCase):
) )
self.assertEqual(response.status_code, 400) 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") self.assertEqual(response.json()["error"], "Game can only be finished from reveal phase")
def test_host_can_start_next_round_from_reveal(self): 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.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") 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): class UiScreenTests(TestCase):
def setUp(self): def setUp(self):
self.host = User.objects.create_user(username="host_ui", password="secret123") self.host = User.objects.create_user(username="host_ui", password="secret123")
@@ -1342,6 +1413,15 @@ class I18nResolverTests(TestCase):
self.assertEqual(result, "missing_key") self.assertEqual(result, "missing_key")
self.assertTrue(any("i18n key missing in shared catalog" in entry for entry in logs.output)) 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): def test_missing_locale_translation_falls_back_to_default_locale(self):
with patch( with patch(
"lobby.i18n.lobby_i18n_error_messages", "lobby.i18n.lobby_i18n_error_messages",