from datetime import timedelta from django.contrib.auth import get_user_model from django.test import TestCase from django.urls import reverse from django.utils import timezone from fupogfakta.models import ( Category, GameSession, Guess, LieAnswer, Player, Question, RoundConfig, RoundQuestion, ) User = get_user_model() class LobbyFlowTests(TestCase): def setUp(self): self.host = User.objects.create_user(username="host", password="secret123") def test_create_session_requires_login(self): response = self.client.post(reverse("lobby:create_session")) self.assertEqual(response.status_code, 302) self.assertEqual(GameSession.objects.count(), 0) def test_host_can_create_session(self): self.client.login(username="host", password="secret123") response = self.client.post(reverse("lobby:create_session")) self.assertEqual(response.status_code, 201) body = response.json() self.assertEqual(body["session"]["status"], GameSession.Status.LOBBY) self.assertEqual(len(body["session"]["code"]), 6) session = GameSession.objects.get(code=body["session"]["code"]) self.assertEqual(session.host, self.host) def test_player_can_join_with_code(self): session = GameSession.objects.create(host=self.host, code="ABCD23") response = self.client.post( reverse("lobby:join_session"), data={"code": "abcd23", "nickname": "Luna"}, content_type="application/json", ) self.assertEqual(response.status_code, 201) body = response.json() self.assertEqual(body["session"]["code"], "ABCD23") self.assertEqual(body["player"]["nickname"], "Luna") self.assertIn("session_token", body["player"]) self.assertTrue(body["player"]["session_token"]) self.assertTrue(Player.objects.filter(session=session, nickname="Luna").exists()) def test_join_rejects_duplicate_nickname_case_insensitive(self): session = GameSession.objects.create(host=self.host, code="QWER12") Player.objects.create(session=session, nickname="Luna") response = self.client.post( reverse("lobby:join_session"), data={"code": "QWER12", "nickname": "lUna"}, content_type="application/json", ) self.assertEqual(response.status_code, 409) self.assertEqual(response.json()["error"], "Nickname already taken") def test_join_rejects_non_joinable_session(self): GameSession.objects.create(host=self.host, code="ZXCV98", status=GameSession.Status.FINISHED) response = self.client.post( reverse("lobby:join_session"), data={"code": "ZXCV98", "nickname": "Kai"}, content_type="application/json", ) self.assertEqual(response.status_code, 400) self.assertEqual(response.json()["error"], "Session is not joinable") def test_session_detail_returns_players(self): session = GameSession.objects.create(host=self.host, code="LMNO45") Player.objects.create(session=session, nickname="Mia", score=7) Player.objects.create(session=session, nickname="Bo", score=2) response = self.client.get(reverse("lobby:session_detail", kwargs={"code": "lmno45"})) self.assertEqual(response.status_code, 200) payload = response.json() self.assertEqual(payload["session"]["players_count"], 2) self.assertEqual([p["nickname"] for p in payload["players"]], ["Bo", "Mia"]) class StartRoundTests(TestCase): def setUp(self): self.host = User.objects.create_user(username="host", password="secret123") self.other_user = User.objects.create_user(username="other", password="secret123") self.session = GameSession.objects.create(host=self.host, code="ABCD23") self.category = Category.objects.create(name="Historie", slug="historie", is_active=True) Question.objects.create( category=self.category, prompt="Hvilket år faldt muren?", correct_answer="1989", is_active=True, ) def test_host_can_start_round_with_selected_category(self): self.client.login(username="host", password="secret123") response = self.client.post( reverse("lobby:start_round", kwargs={"code": self.session.code}), data={"category_slug": self.category.slug}, content_type="application/json", ) self.assertEqual(response.status_code, 201) body = response.json() self.assertEqual(body["session"]["status"], GameSession.Status.LIE) self.assertEqual(body["round"]["number"], 1) self.assertEqual(body["round"]["category"]["slug"], self.category.slug) self.session.refresh_from_db() self.assertEqual(self.session.status, GameSession.Status.LIE) round_config = RoundConfig.objects.get(session=self.session, number=1) self.assertEqual(round_config.category, self.category) def test_start_round_requires_host(self): self.client.login(username="other", password="secret123") response = self.client.post( reverse("lobby:start_round", kwargs={"code": self.session.code}), data={"category_slug": self.category.slug}, content_type="application/json", ) self.assertEqual(response.status_code, 403) self.assertEqual(response.json()["error"], "Only host can start round") def test_start_round_requires_existing_active_category_with_questions(self): self.client.login(username="host", password="secret123") response = self.client.post( reverse("lobby:start_round", kwargs={"code": self.session.code}), data={"category_slug": "ukendt"}, content_type="application/json", ) self.assertEqual(response.status_code, 404) empty_category = Category.objects.create(name="Sport", slug="sport", is_active=True) response = self.client.post( reverse("lobby:start_round", kwargs={"code": self.session.code}), data={"category_slug": empty_category.slug}, content_type="application/json", ) self.assertEqual(response.status_code, 400) self.assertEqual(response.json()["error"], "Category has no active questions") def test_start_round_rejects_non_lobby_session(self): self.client.login(username="host", password="secret123") self.session.status = GameSession.Status.GUESS self.session.save(update_fields=["status"]) response = self.client.post( reverse("lobby:start_round", kwargs={"code": self.session.code}), data={"category_slug": self.category.slug}, content_type="application/json", ) self.assertEqual(response.status_code, 400) self.assertEqual(response.json()["error"], "Round can only be started from lobby") class LieSubmissionTests(TestCase): def setUp(self): self.host = User.objects.create_user(username="host", password="secret123") self.session = GameSession.objects.create(host=self.host, code="ABCD23", status=GameSession.Status.LIE) self.category = Category.objects.create(name="Geografi", slug="geografi", is_active=True) self.question = Question.objects.create( category=self.category, prompt="Hvad er hovedstaden i Australien?", correct_answer="Canberra", is_active=True, ) RoundConfig.objects.create( session=self.session, number=1, category=self.category, lie_seconds=45, ) self.player = Player.objects.create(session=self.session, nickname="Luna") def test_host_can_show_question_and_get_lie_deadline(self): self.client.login(username="host", password="secret123") response = self.client.post(reverse("lobby:show_question", kwargs={"code": self.session.code})) self.assertEqual(response.status_code, 201) payload = response.json() self.assertEqual(payload["config"]["lie_seconds"], 45) self.assertIn("lie_deadline_at", payload["round_question"]) self.assertTrue(RoundQuestion.objects.filter(session=self.session, round_number=1).exists()) def test_player_can_submit_lie_before_deadline(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": self.player.session_token, "text": "Sydney"}, content_type="application/json", ) self.assertEqual(response.status_code, 201) self.assertTrue(LieAnswer.objects.filter(round_question=round_question, player=self.player).exists()) def test_submit_lie_rejects_after_time_window(self): round_question = RoundQuestion.objects.create( session=self.session, round_number=1, question=self.question, correct_answer=self.question.correct_answer, ) round_question.shown_at = timezone.now() - timedelta(seconds=46) round_question.save(update_fields=["shown_at"]) 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": self.player.session_token, "text": "Melbourne"}, content_type="application/json", ) self.assertEqual(response.status_code, 400) self.assertEqual(response.json()["error"], "Lie submission window has closed") def test_submit_lie_rejects_duplicate_submission(self): round_question = RoundQuestion.objects.create( session=self.session, round_number=1, question=self.question, correct_answer=self.question.correct_answer, ) LieAnswer.objects.create(round_question=round_question, player=self.player, text="Perth") 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": self.player.session_token, "text": "Brisbane"}, content_type="application/json", ) self.assertEqual(response.status_code, 409) self.assertEqual(response.json()["error"], "Lie already submitted for this player") def test_submit_lie_requires_session_token(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, "text": "Sydney"}, content_type="application/json", ) self.assertEqual(response.status_code, 400) self.assertEqual(response.json()["error"], "session_token is required") def test_submit_lie_rejects_invalid_session_token(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", ) self.assertEqual(response.status_code, 403) self.assertEqual(response.json()["error"], "Invalid player session token") class MixAnswersTests(TestCase): def setUp(self): self.host = User.objects.create_user(username="host", password="secret123") self.other_user = User.objects.create_user(username="other", password="secret123") self.session = GameSession.objects.create(host=self.host, code="ABCD23", status=GameSession.Status.LIE) self.category = Category.objects.create(name="Historie", slug="historie", is_active=True) self.question = Question.objects.create( category=self.category, prompt="Hvilken by er Danmarks hovedstad?", correct_answer="København", is_active=True, ) RoundConfig.objects.create(session=self.session, number=1, category=self.category) self.round_question = RoundQuestion.objects.create( session=self.session, round_number=1, question=self.question, correct_answer="København", ) self.player_one = Player.objects.create(session=self.session, nickname="Luna") self.player_two = Player.objects.create(session=self.session, nickname="Mads") def test_host_can_mix_answers_and_transition_to_guess(self): LieAnswer.objects.create(round_question=self.round_question, player=self.player_one, text="Aarhus") LieAnswer.objects.create(round_question=self.round_question, player=self.player_two, text="Odense") self.client.login(username="host", password="secret123") response = self.client.post( reverse( "lobby:mix_answers", kwargs={"code": self.session.code, "round_question_id": self.round_question.id}, ) ) self.assertEqual(response.status_code, 200) payload = response.json() answer_texts = [entry["text"] for entry in payload["answers"]] self.assertEqual(set(answer_texts), {"København", "Aarhus", "Odense"}) self.assertEqual(payload["session"]["status"], GameSession.Status.GUESS) self.session.refresh_from_db() self.round_question.refresh_from_db() self.assertEqual(self.session.status, GameSession.Status.GUESS) self.assertEqual(self.round_question.mixed_answers, answer_texts) def test_mix_answers_requires_host(self): self.client.login(username="other", password="secret123") response = self.client.post( reverse( "lobby:mix_answers", kwargs={"code": self.session.code, "round_question_id": self.round_question.id}, ) ) self.assertEqual(response.status_code, 403) self.assertEqual(response.json()["error"], "Only host can mix answers") def test_mix_answers_deduplicates_case_insensitive_lies(self): LieAnswer.objects.create(round_question=self.round_question, player=self.player_one, text="københavn") LieAnswer.objects.create(round_question=self.round_question, player=self.player_two, text="Aarhus") self.client.login(username="host", password="secret123") response = self.client.post( reverse( "lobby:mix_answers", kwargs={"code": self.session.code, "round_question_id": self.round_question.id}, ) ) self.assertEqual(response.status_code, 200) answer_texts = [entry["text"] for entry in response.json()["answers"]] self.assertEqual(set(answer_texts), {"København", "Aarhus"}) def test_mix_answers_is_idempotent_after_transition_to_guess(self): LieAnswer.objects.create(round_question=self.round_question, player=self.player_one, text="Aarhus") LieAnswer.objects.create(round_question=self.round_question, player=self.player_two, text="Odense") self.client.login(username="host", password="secret123") first = self.client.post(reverse("lobby:mix_answers", kwargs={"code": self.session.code, "round_question_id": self.round_question.id})) second = self.client.post(reverse("lobby:mix_answers", kwargs={"code": self.session.code, "round_question_id": self.round_question.id})) self.assertEqual(first.status_code, 200) self.assertEqual(second.status_code, 200) self.assertEqual([entry["text"] for entry in first.json()["answers"]], [entry["text"] for entry in second.json()["answers"]]) def test_session_detail_returns_persisted_mixed_answers_for_reconnect(self): LieAnswer.objects.create(round_question=self.round_question, player=self.player_one, text="Aarhus") LieAnswer.objects.create(round_question=self.round_question, player=self.player_two, text="Odense") self.client.login(username="host", password="secret123") mix_response = self.client.post(reverse("lobby:mix_answers", kwargs={"code": self.session.code, "round_question_id": self.round_question.id})) detail_response = self.client.get(reverse("lobby:session_detail", kwargs={"code": self.session.code})) self.assertEqual(mix_response.status_code, 200) self.assertEqual(detail_response.status_code, 200) self.assertEqual([entry["text"] for entry in mix_response.json()["answers"]], [entry["text"] for entry in detail_response.json()["round_question"]["answers"]]) class GuessSubmissionTests(TestCase): def setUp(self): self.host = User.objects.create_user(username="host_guess", password="secret123") self.session = GameSession.objects.create(host=self.host, code="GU3551", status=GameSession.Status.GUESS) self.category = Category.objects.create(name="Videnskab", slug="videnskab", is_active=True) self.question = Question.objects.create( category=self.category, prompt="Hvilken planet kaldes den røde planet?", correct_answer="Mars", is_active=True, ) self.round_config = RoundConfig.objects.create( session=self.session, number=1, category=self.category, lie_seconds=45, guess_seconds=30, ) self.round_question = RoundQuestion.objects.create( session=self.session, round_number=1, question=self.question, correct_answer="Mars", ) self.player = Player.objects.create(session=self.session, nickname="Luna") self.liar = Player.objects.create(session=self.session, nickname="Mads") LieAnswer.objects.create(round_question=self.round_question, player=self.liar, text="Jupiter") def test_player_can_submit_guess_in_guess_phase(self): response = self.client.post( reverse( "lobby:submit_guess", kwargs={"code": self.session.code, "round_question_id": self.round_question.id}, ), data={"player_id": self.player.id, "session_token": self.player.session_token, "selected_text": "Mars"}, content_type="application/json", ) self.assertEqual(response.status_code, 201) payload = response.json() self.assertTrue(payload["guess"]["is_correct"]) self.assertIsNone(payload["guess"]["fooled_player_id"]) self.assertIn("guess_deadline_at", payload["window"]) def test_submit_guess_rejects_when_not_in_guess_phase(self): self.session.status = GameSession.Status.LIE self.session.save(update_fields=["status"]) response = self.client.post( reverse( "lobby:submit_guess", kwargs={"code": self.session.code, "round_question_id": self.round_question.id}, ), data={"player_id": self.player.id, "session_token": self.player.session_token, "selected_text": "Mars"}, content_type="application/json", ) self.assertEqual(response.status_code, 400) self.assertEqual(response.json()["error"], "Guess submission is only allowed in guess phase") def test_submit_guess_rejects_unknown_answer(self): response = self.client.post( reverse( "lobby:submit_guess", kwargs={"code": self.session.code, "round_question_id": self.round_question.id}, ), data={"player_id": self.player.id, "session_token": self.player.session_token, "selected_text": "Venus"}, content_type="application/json", ) self.assertEqual(response.status_code, 400) self.assertEqual(response.json()["error"], "Selected answer is not part of this round") def test_submit_guess_rejects_duplicate_submission(self): Guess.objects.create(round_question=self.round_question, player=self.player, selected_text="Mars", is_correct=True) response = self.client.post( reverse( "lobby:submit_guess", kwargs={"code": self.session.code, "round_question_id": self.round_question.id}, ), data={"player_id": self.player.id, "session_token": self.player.session_token, "selected_text": "Jupiter"}, content_type="application/json", ) self.assertEqual(response.status_code, 409) self.assertEqual(response.json()["error"], "Guess already submitted for this player") def test_submit_guess_rejects_after_deadline(self): self.round_question.shown_at = timezone.now() - timedelta(seconds=76) self.round_question.save(update_fields=["shown_at"]) response = self.client.post( reverse( "lobby:submit_guess", kwargs={"code": self.session.code, "round_question_id": self.round_question.id}, ), data={"player_id": self.player.id, "session_token": self.player.session_token, "selected_text": "Mars"}, content_type="application/json", ) self.assertEqual(response.status_code, 400) self.assertEqual(response.json()["error"], "Guess submission window has closed") def test_submit_guess_requires_session_token(self): response = self.client.post( reverse( "lobby:submit_guess", kwargs={"code": self.session.code, "round_question_id": self.round_question.id}, ), data={"player_id": self.player.id, "selected_text": "Mars"}, content_type="application/json", ) self.assertEqual(response.status_code, 400) self.assertEqual(response.json()["error"], "session_token is required") def test_submit_guess_rejects_invalid_session_token(self): response = self.client.post( reverse( "lobby:submit_guess", kwargs={"code": self.session.code, "round_question_id": self.round_question.id}, ), data={"player_id": self.player.id, "session_token": "wrong-token", "selected_text": "Mars"}, content_type="application/json", ) self.assertEqual(response.status_code, 403) self.assertEqual(response.json()["error"], "Invalid player session token") class ScoreCalculationTests(TestCase): def setUp(self): self.host = User.objects.create_user(username="host_score", password="secret123") self.other_user = User.objects.create_user(username="other_score", password="secret123") self.session = GameSession.objects.create(host=self.host, code="SC0RE1", status=GameSession.Status.GUESS) self.category = Category.objects.create(name="Sport", slug="sport", is_active=True) self.question = Question.objects.create( category=self.category, prompt="Hvilken sport spiller man i Wimbledon?", correct_answer="Tennis", is_active=True, ) RoundConfig.objects.create( session=self.session, number=1, category=self.category, points_correct=5, points_bluff=2, ) self.round_question = RoundQuestion.objects.create( session=self.session, round_number=1, question=self.question, correct_answer="Tennis", ) self.player_one = Player.objects.create(session=self.session, nickname="Luna") self.player_two = Player.objects.create(session=self.session, nickname="Mads") self.player_three = Player.objects.create(session=self.session, nickname="Nora") def test_host_can_calculate_scores_and_transition_to_reveal(self): Guess.objects.create(round_question=self.round_question, player=self.player_one, selected_text="Tennis", is_correct=True) Guess.objects.create( round_question=self.round_question, player=self.player_two, selected_text="Padel", is_correct=False, fooled_player=self.player_three, ) Guess.objects.create( round_question=self.round_question, player=self.player_three, selected_text="Padel", is_correct=False, fooled_player=self.player_three, ) self.client.login(username="host_score", password="secret123") response = self.client.post( reverse( "lobby:calculate_scores", kwargs={"code": self.session.code, "round_question_id": self.round_question.id}, ) ) self.assertEqual(response.status_code, 200) payload = response.json() self.assertEqual(payload["session"]["status"], GameSession.Status.REVEAL) self.assertEqual(payload["events_created"], 2) self.player_one.refresh_from_db() self.player_three.refresh_from_db() self.session.refresh_from_db() self.assertEqual(self.player_one.score, 5) self.assertEqual(self.player_three.score, 4) self.assertEqual(self.session.status, GameSession.Status.REVEAL) def test_calculate_scores_requires_host(self): self.client.login(username="other_score", password="secret123") response = self.client.post( reverse( "lobby:calculate_scores", kwargs={"code": self.session.code, "round_question_id": self.round_question.id}, ) ) self.assertEqual(response.status_code, 403) self.assertEqual(response.json()["error"], "Only host can calculate scores") def test_calculate_scores_rejects_duplicate_calculation(self): Guess.objects.create(round_question=self.round_question, player=self.player_one, selected_text="Tennis", is_correct=True) self.client.login(username="host_score", password="secret123") first = self.client.post( reverse( "lobby:calculate_scores", kwargs={"code": self.session.code, "round_question_id": self.round_question.id}, ) ) second = self.client.post( reverse( "lobby:calculate_scores", kwargs={"code": self.session.code, "round_question_id": self.round_question.id}, ) ) self.assertEqual(first.status_code, 200) self.assertEqual(second.status_code, 409) self.assertEqual(second.json()["error"], "Scores already calculated for this round question") class RevealRoundFlowTests(TestCase): def setUp(self): self.host = User.objects.create_user(username="host_reveal", password="secret123") self.other_user = User.objects.create_user(username="other_reveal", password="secret123") self.session = GameSession.objects.create(host=self.host, code="RVL123", status=GameSession.Status.REVEAL) 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): 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, 200) payload = response.json() self.assertEqual(payload["session"]["status"], GameSession.Status.REVEAL) self.assertEqual([item["nickname"] for item in payload["leaderboard"]], ["Luna", "Mads"]) def test_reveal_scoreboard_requires_host(self): self.client.login(username="other_reveal", password="secret123") response = self.client.get( reverse( "lobby:reveal_scoreboard", kwargs={"code": self.session.code}, ) ) self.assertEqual(response.status_code, 403) self.assertEqual(response.json()["error"], "Only host can view scoreboard") def test_host_can_finish_game_from_reveal(self): self.client.login(username="host_reveal", password="secret123") response = self.client.post( reverse( "lobby:finish_game", kwargs={"code": self.session.code}, ) ) self.assertEqual(response.status_code, 200) payload = response.json() self.assertEqual(payload["session"]["status"], GameSession.Status.FINISHED) self.assertEqual(payload["winner"]["nickname"], "Luna") self.assertEqual([item["nickname"] for item in payload["leaderboard"]], ["Luna", "Mads"]) self.session.refresh_from_db() self.assertEqual(self.session.status, GameSession.Status.FINISHED) def test_finish_game_requires_host(self): self.client.login(username="other_reveal", password="secret123") response = self.client.post( reverse( "lobby:finish_game", kwargs={"code": self.session.code}, ) ) self.assertEqual(response.status_code, 403) self.assertEqual(response.json()["error"], "Only host can finish game") def test_finish_game_rejects_wrong_phase(self): self.client.login(username="host_reveal", password="secret123") self.session.status = GameSession.Status.GUESS self.session.save(update_fields=["status"]) response = self.client.post( reverse( "lobby:finish_game", kwargs={"code": self.session.code}, ) ) self.assertEqual(response.status_code, 400) self.assertEqual(response.json()["error"], "Game can only be finished from reveal phase") def test_host_can_start_next_round_from_reveal(self): self.client.login(username="host_reveal", password="secret123") response = self.client.post( reverse( "lobby:start_next_round", kwargs={"code": self.session.code}, ) ) self.assertEqual(response.status_code, 200) payload = response.json() self.assertEqual(payload["session"]["status"], GameSession.Status.LOBBY) self.assertEqual(payload["session"]["current_round"], 2) self.session.refresh_from_db() self.assertEqual(self.session.status, GameSession.Status.LOBBY) self.assertEqual(self.session.current_round, 2) def test_start_next_round_rejects_wrong_phase(self): self.client.login(username="host_reveal", password="secret123") self.session.status = GameSession.Status.GUESS self.session.save(update_fields=["status"]) response = self.client.post( reverse( "lobby:start_next_round", kwargs={"code": self.session.code}, ) ) self.assertEqual(response.status_code, 400) self.assertEqual(response.json()["error"], "Next round can only start from reveal phase") class UiScreenTests(TestCase): def setUp(self): self.host = User.objects.create_user(username="host_ui", password="secret123") def test_host_screen_requires_login(self): response = self.client.get(reverse("lobby:host_screen")) self.assertEqual(response.status_code, 302) def test_host_screen_renders_for_logged_in_user(self): self.client.login(username="host_ui", password="secret123") response = self.client.get(reverse("lobby:host_screen")) self.assertEqual(response.status_code, 200) self.assertContains(response, "Host panel") self.assertContains(response, "saveHostContext") self.assertContains(response, "restoreHostContext") self.assertContains(response, "id=\"showQuestionBtn\"") self.assertContains(response, "id=\"mixAnswersBtn\"") self.assertContains(response, "id=\"hostActionHint\"") self.assertContains(response, "id=\"categoryGuardHint\"") self.assertContains(response, "id=\"phaseStatus\"") self.assertContains(response, "updateHostActionState") self.assertContains(response, "phaseLabel") self.assertContains(response, "Opdatér session-status for fasebaserede host-actions.") self.assertContains(response, "Angiv sessionkode for at aktivere host-actions.") self.assertContains(response, "Kategori er kun redigérbar i lobby-fasen.") self.assertContains(response, "Kræver 3-5 spillere i lobbyen.") self.assertContains(response, "For mange spillere: maks 5 i MVP før runde-start.") self.assertContains(response, "Round question-id kan kun redigeres i lie/guess/reveal-faser.") self.assertContains(response, "roundQuestionInput.disabled=hostActionInFlight||!canEditRoundQuestion") self.assertContains(response, "categorySelect.disabled=hostActionInFlight||!hasCode||phase!==\"lobby\"") self.assertContains(response, "hostActionInFlight") self.assertContains(response, "withHostActionLock") self.assertContains(response, "Handling kører… afvent svar før næste klik.") self.assertContains(response, "Session-data ikke opdateret endnu.") self.assertContains(response, "Sidst opdateret:") self.assertContains(response, "Session-data kan være forældet") self.assertContains(response, "id=\"sessionDetailBtn\"") self.assertContains(response, "id=\"sessionDetailHint\"") self.assertContains(response, "updateSessionDetailState") self.assertContains(response, "sessionDetailInFlight") self.assertContains(response, "session_detail_in_flight") self.assertContains(response, "Opdaterer session-status…") self.assertContains(response, "Session-opdatering er låst mens en host-handling kører.") self.assertContains(response, "Angiv sessionkode for at opdatere session-status.") self.assertContains(response, "markSessionRefresh") self.assertContains(response, "updateLastRefreshStatus") self.assertContains(response, "isSessionDetailRead") self.assertContains(response, "showQuestionBtn.disabled=hostActionInFlight||!hasCode||phase!==") self.assertContains(response, "categorySelect.disabled=hostActionInFlight||!hasCode||phase!==") def test_player_screen_is_public(self): response = self.client.get(reverse("lobby:player_screen")) self.assertEqual(response.status_code, 200) self.assertContains(response, "Player panel") self.assertContains(response, "id=\"sessionToken\"") self.assertContains(response, "session_token") self.assertContains(response, "id=\"answerOptions\"") self.assertContains(response, "renderAnswerOptions") self.assertContains(response, "renderAnswerOptions(null)") self.assertContains(response, "availableAnswers") self.assertContains(response, "guessStorageKey") self.assertContains(response, "persistGuessState") self.assertContains(response, "savePlayerContext") self.assertContains(response, "restorePlayerContext") self.assertContains(response, "id=\"lieSubmitBtn\"") self.assertContains(response, "id=\"lieStatus\"") self.assertContains(response, "id=\"phaseStatus\"") self.assertContains(response, "currentSessionStatus") self.assertContains(response, "updatePhaseStatus") self.assertContains(response, "Løgn-input er låst i fase") self.assertContains(response, "Gæt er låst i fase") self.assertContains(response, "Afvent aktivt spørgsmål fra host før du kan gætte.") self.assertContains(response, "persistLieState") self.assertContains(response, "updateLieSubmitState") self.assertContains(response, "hasSubmitContext") self.assertContains(response, "hasRoundQuestionContext") self.assertContains(response, "canAttemptJoin") self.assertContains(response, "missing_join_input") self.assertContains(response, "Udfyld kode og nickname for at join.") self.assertContains(response, "id=\"contextLockHint\"") self.assertContains(response, "updateContextLockState") self.assertContains(response, "Spillerkontekst er låst efter join.") self.assertContains(response, "already_joined_client") self.assertContains(response, "missing_submit_context") self.assertContains(response, "invalid_client_guess") self.assertContains(response, "lieSubmitInFlight") self.assertContains(response, "guessSubmitInFlight") self.assertContains(response, "Sender løgn…") self.assertContains(response, "Sender gæt…") self.assertContains(response, "lie_submit_in_flight") self.assertContains(response, "guess_submit_in_flight") self.assertContains(response, "guess_already_submitted_client") self.assertContains(response, "id=\"playerAutoRefreshToggleBtn\"") self.assertContains(response, "id=\"playerAutoRefreshHint\"") self.assertContains(response, "id=\"playerLastRefreshStatus\"") self.assertContains(response, "id=\"sessionDetailBtn\"") self.assertContains(response, "id=\"sessionRefreshHint\"") self.assertContains(response, "id=\"roundContextHint\"") self.assertContains(response, "resetRoundContextForManualChange") self.assertContains(response, "Runde-kontekst afventer session-opdatering.") self.assertContains(response, "togglePlayerAutoRefresh") self.assertContains(response, "btn.disabled=sessionDetailInFlight||!code()") self.assertContains(response, "Auto-refresh-lås: afvent aktiv session-opdatering.") self.assertContains(response, "Auto-refresh kræver sessionkode.") self.assertContains(response, "markPlayerSessionRefresh") self.assertContains(response, "updatePlayerLastRefreshStatus") self.assertContains(response, "updateSessionDetailState") self.assertContains(response, "session_detail_in_flight") self.assertContains(response, "Opdaterer session-status…") self.assertContains(response, "Session-data ikke opdateret endnu.") self.assertContains(response, "Sidst opdateret:") self.assertContains(response, "Session-data kan være forældet") class SessionDetailRoundQuestionTests(TestCase): def setUp(self): self.host = User.objects.create_user(username="host_detail", password="secret123") self.session = GameSession.objects.create(host=self.host, code="ABCDE1", status=GameSession.Status.LIE) self.category = Category.objects.create(name="Historie", slug="historie-2", is_active=True) self.question = Question.objects.create( category=self.category, prompt="Hvem opfandt pæren?", correct_answer="Edison", is_active=True, ) def test_session_detail_includes_current_round_question_when_available(self): round_question = RoundQuestion.objects.create( session=self.session, round_number=1, question=self.question, correct_answer=self.question.correct_answer, ) response = self.client.get(reverse("lobby:session_detail", kwargs={"code": self.session.code})) self.assertEqual(response.status_code, 200) payload = response.json() self.assertEqual(payload["round_question"]["id"], round_question.id) self.assertEqual(payload["round_question"]["prompt"], self.question.prompt)