diff --git a/lobby/tests.py b/lobby/tests.py index e000eaf..84fefca 100644 --- a/lobby/tests.py +++ b/lobby/tests.py @@ -1002,6 +1002,85 @@ class SessionDetailRoundQuestionTests(TestCase): self.assertEqual(payload["round_question"]["prompt"], self.question.prompt) + + +class SessionDetailPhaseViewModelTests(TestCase): + def setUp(self): + self.host = User.objects.create_user(username="host_phase", password="secret123") + self.session = GameSession.objects.create(host=self.host, code="PHASE1", status=GameSession.Status.LOBBY) + + def test_session_detail_includes_shared_phase_view_model_contract(self): + Player.objects.create(session=self.session, nickname="P1") + Player.objects.create(session=self.session, nickname="P2") + Player.objects.create(session=self.session, nickname="P3") + + response = self.client.get(reverse("lobby:session_detail", kwargs={"code": self.session.code})) + + self.assertEqual(response.status_code, 200) + phase = response.json()["phase_view_model"] + self.assertEqual(phase["status"], GameSession.Status.LOBBY) + self.assertEqual(phase["round_number"], 1) + self.assertEqual(phase["players_count"], 3) + self.assertEqual(phase["constraints"]["min_players_to_start"], 3) + self.assertEqual(phase["constraints"]["max_players_mvp"], 5) + self.assertTrue(phase["constraints"]["min_players_reached"]) + self.assertTrue(phase["constraints"]["max_players_allowed"]) + self.assertTrue(phase["host"]["can_start_round"]) + self.assertFalse(phase["host"]["can_show_question"]) + self.assertTrue(phase["player"]["can_join"]) + self.assertFalse(phase["player"]["can_submit_lie"]) + self.assertFalse(phase["player"]["can_submit_guess"]) + + def test_phase_view_model_flags_change_with_round_phase(self): + category = Category.objects.create(name="Kultur", slug="kultur", is_active=True) + question = Question.objects.create( + category=category, + prompt="Hvilket land kommer sushi fra?", + correct_answer="Japan", + is_active=True, + ) + round_question = RoundQuestion.objects.create( + session=self.session, + round_number=1, + question=question, + correct_answer="Japan", + ) + + self.session.status = GameSession.Status.LIE + self.session.save(update_fields=["status"]) + lie_payload = self.client.get(reverse("lobby:session_detail", kwargs={"code": self.session.code})).json() + lie_phase = lie_payload["phase_view_model"] + self.assertFalse(lie_phase["host"]["can_show_question"]) + self.assertTrue(lie_phase["host"]["can_mix_answers"]) + self.assertTrue(lie_phase["player"]["can_submit_lie"]) + self.assertFalse(lie_phase["player"]["can_submit_guess"]) + + self.session.status = GameSession.Status.GUESS + self.session.save(update_fields=["status"]) + guess_payload = self.client.get(reverse("lobby:session_detail", kwargs={"code": self.session.code})).json() + guess_phase = guess_payload["phase_view_model"] + self.assertTrue(guess_phase["host"]["can_mix_answers"]) + self.assertTrue(guess_phase["host"]["can_calculate_scores"]) + self.assertFalse(guess_phase["player"]["can_submit_lie"]) + self.assertTrue(guess_phase["player"]["can_submit_guess"]) + + round_question.delete() + self.session.status = GameSession.Status.REVEAL + self.session.save(update_fields=["status"]) + reveal_payload = self.client.get(reverse("lobby:session_detail", kwargs={"code": self.session.code})).json() + reveal_phase = reveal_payload["phase_view_model"] + self.assertTrue(reveal_phase["host"]["can_reveal_scoreboard"]) + self.assertTrue(reveal_phase["host"]["can_start_next_round"]) + self.assertTrue(reveal_phase["host"]["can_finish_game"]) + self.assertFalse(reveal_phase["player"]["can_view_final_result"]) + + self.session.status = GameSession.Status.FINISHED + self.session.save(update_fields=["status"]) + finished_payload = self.client.get(reverse("lobby:session_detail", kwargs={"code": self.session.code})).json() + finished_phase = finished_payload["phase_view_model"] + self.assertFalse(finished_phase["player"]["can_join"]) + self.assertTrue(finished_phase["player"]["can_view_final_result"]) + class SmokeStagingCommandTests(TestCase): def test_smoke_staging_command_runs_full_flow(self): call_command("smoke_staging") diff --git a/lobby/views.py b/lobby/views.py index 30534ec..0a12ce6 100644 --- a/lobby/views.py +++ b/lobby/views.py @@ -58,6 +58,45 @@ def _create_unique_session_code() -> str: raise RuntimeError("Could not generate unique session code") +def _build_phase_view_model(session: GameSession, *, players_count: int, has_round_question: bool) -> dict: + status = session.status + in_lobby = status == GameSession.Status.LOBBY + in_lie = status == GameSession.Status.LIE + in_guess = status == GameSession.Status.GUESS + in_reveal = status == GameSession.Status.REVEAL + in_finished = status == GameSession.Status.FINISHED + + min_players_reached = players_count >= 3 + max_players_allowed = players_count <= 5 + + return { + "status": status, + "round_number": session.current_round, + "players_count": players_count, + "constraints": { + "min_players_to_start": 3, + "max_players_mvp": 5, + "min_players_reached": min_players_reached, + "max_players_allowed": max_players_allowed, + }, + "host": { + "can_start_round": in_lobby and min_players_reached and max_players_allowed, + "can_show_question": in_lie and not has_round_question, + "can_mix_answers": in_lie or in_guess, + "can_calculate_scores": in_guess, + "can_reveal_scoreboard": in_reveal, + "can_start_next_round": in_reveal, + "can_finish_game": in_reveal, + }, + "player": { + "can_join": status in JOINABLE_STATUSES, + "can_submit_lie": in_lie and has_round_question, + "can_submit_guess": in_guess and has_round_question, + "can_view_final_result": in_finished, + }, + } + + @require_POST @login_required def create_session(request: HttpRequest) -> JsonResponse: @@ -155,6 +194,12 @@ def session_detail(request: HttpRequest, code: str) -> JsonResponse: "answers": [{"text": text} for text in (current_round_question.mixed_answers or [])], } + phase_view_model = _build_phase_view_model( + session, + players_count=len(players), + has_round_question=bool(current_round_question), + ) + return JsonResponse( { "session": { @@ -166,6 +211,7 @@ def session_detail(request: HttpRequest, code: str) -> JsonResponse: }, "players": players, "round_question": round_question_payload, + "phase_view_model": phase_view_model, } )