F3: persist mixed answer order for stable UI reconnect
This commit is contained in:
@@ -308,7 +308,9 @@ class MixAnswersTests(TestCase):
|
||||
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")
|
||||
@@ -339,6 +341,30 @@ class MixAnswersTests(TestCase):
|
||||
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):
|
||||
|
||||
@@ -147,6 +147,7 @@ def session_detail(request: HttpRequest, code: str) -> JsonResponse:
|
||||
"round_number": current_round_question.round_number,
|
||||
"prompt": current_round_question.question.prompt,
|
||||
"shown_at": current_round_question.shown_at.isoformat(),
|
||||
"answers": [{"text": text} for text in (current_round_question.mixed_answers or [])],
|
||||
}
|
||||
|
||||
return JsonResponse(
|
||||
@@ -368,8 +369,8 @@ def mix_answers(request: HttpRequest, code: str, round_question_id: int) -> Json
|
||||
if session.host_id != request.user.id:
|
||||
return JsonResponse({"error": "Only host can mix answers"}, status=403)
|
||||
|
||||
if session.status != GameSession.Status.LIE:
|
||||
return JsonResponse({"error": "Answers can only be mixed in lie phase"}, status=400)
|
||||
if session.status not in {GameSession.Status.LIE, GameSession.Status.GUESS}:
|
||||
return JsonResponse({"error": "Answers can only be mixed in lie or guess phase"}, status=400)
|
||||
|
||||
try:
|
||||
round_question = RoundQuestion.objects.get(
|
||||
@@ -380,28 +381,34 @@ def mix_answers(request: HttpRequest, code: str, round_question_id: int) -> Json
|
||||
except RoundQuestion.DoesNotExist:
|
||||
return JsonResponse({"error": "Round question not found"}, status=404)
|
||||
|
||||
lie_texts = list(round_question.lies.values_list("text", flat=True))
|
||||
|
||||
deduped_answers = []
|
||||
seen = set()
|
||||
for text in [round_question.correct_answer, *lie_texts]:
|
||||
normalized = text.strip().casefold()
|
||||
if not normalized or normalized in seen:
|
||||
continue
|
||||
seen.add(normalized)
|
||||
deduped_answers.append(text.strip())
|
||||
|
||||
if len(deduped_answers) < 2:
|
||||
return JsonResponse({"error": "Not enough answers to mix"}, status=400)
|
||||
|
||||
random.shuffle(deduped_answers)
|
||||
|
||||
with transaction.atomic():
|
||||
locked_session = GameSession.objects.select_for_update().get(pk=session.pk)
|
||||
if locked_session.status != GameSession.Status.LIE:
|
||||
return JsonResponse({"error": "Answers can only be mixed in lie phase"}, status=400)
|
||||
locked_session.status = GameSession.Status.GUESS
|
||||
locked_session.save(update_fields=["status"])
|
||||
if locked_session.status not in {GameSession.Status.LIE, GameSession.Status.GUESS}:
|
||||
return JsonResponse({"error": "Answers can only be mixed in lie or guess phase"}, status=400)
|
||||
|
||||
locked_round_question = RoundQuestion.objects.select_for_update().get(pk=round_question.pk)
|
||||
|
||||
deduped_answers = list(locked_round_question.mixed_answers or [])
|
||||
if not deduped_answers:
|
||||
lie_texts = list(locked_round_question.lies.values_list("text", flat=True))
|
||||
seen = set()
|
||||
for text in [locked_round_question.correct_answer, *lie_texts]:
|
||||
normalized = text.strip().casefold()
|
||||
if not normalized or normalized in seen:
|
||||
continue
|
||||
seen.add(normalized)
|
||||
deduped_answers.append(text.strip())
|
||||
|
||||
if len(deduped_answers) < 2:
|
||||
return JsonResponse({"error": "Not enough answers to mix"}, status=400)
|
||||
|
||||
random.shuffle(deduped_answers)
|
||||
locked_round_question.mixed_answers = deduped_answers
|
||||
locked_round_question.save(update_fields=["mixed_answers"])
|
||||
|
||||
if locked_session.status == GameSession.Status.LIE:
|
||||
locked_session.status = GameSession.Status.GUESS
|
||||
locked_session.save(update_fields=["status"])
|
||||
|
||||
return JsonResponse(
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user