feat(lobby): add shared phase view-model contract
This commit is contained in:
@@ -986,6 +986,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")
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user