[SPA] Shared contract for lobby/game phase view-model #155
@@ -986,6 +986,85 @@ class SessionDetailRoundQuestionTests(TestCase):
|
|||||||
self.assertEqual(payload["round_question"]["prompt"], self.question.prompt)
|
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):
|
class SmokeStagingCommandTests(TestCase):
|
||||||
def test_smoke_staging_command_runs_full_flow(self):
|
def test_smoke_staging_command_runs_full_flow(self):
|
||||||
call_command("smoke_staging")
|
call_command("smoke_staging")
|
||||||
|
|||||||
@@ -58,6 +58,45 @@ def _create_unique_session_code() -> str:
|
|||||||
raise RuntimeError("Could not generate unique session code")
|
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
|
@require_POST
|
||||||
@login_required
|
@login_required
|
||||||
def create_session(request: HttpRequest) -> JsonResponse:
|
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 [])],
|
"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(
|
return JsonResponse(
|
||||||
{
|
{
|
||||||
"session": {
|
"session": {
|
||||||
@@ -166,6 +211,7 @@ def session_detail(request: HttpRequest, code: str) -> JsonResponse:
|
|||||||
},
|
},
|
||||||
"players": players,
|
"players": players,
|
||||||
"round_question": round_question_payload,
|
"round_question": round_question_payload,
|
||||||
|
"phase_view_model": phase_view_model,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user