fix(lobby): guard auto score calculation
This commit is contained in:
@@ -788,6 +788,88 @@ class CanonicalRoundFlowTests(TestCase):
|
||||
self.assertEqual([entry["nickname"] for entry in payload["scoreboard"]], ["Luna", "Nora", "Mads"])
|
||||
self.assertEqual(payload["reveal"]["correct_answer"], "Shakespeare")
|
||||
|
||||
@patch("lobby.views.sync_broadcast_phase_event")
|
||||
@patch("lobby.views._resolve_scores")
|
||||
@patch("lobby.views.GameSession.objects.get")
|
||||
def test_submit_guess_skips_rescore_when_locked_session_is_already_revealing(
|
||||
self,
|
||||
mock_session_get,
|
||||
mock_resolve_scores,
|
||||
mock_sync_broadcast,
|
||||
):
|
||||
round_config = RoundConfig.objects.create(
|
||||
session=self.session,
|
||||
number=1,
|
||||
category=self.category,
|
||||
points_correct=5,
|
||||
points_bluff=2,
|
||||
)
|
||||
round_question = RoundQuestion.objects.create(
|
||||
session=self.session,
|
||||
round_number=1,
|
||||
question=self.question,
|
||||
correct_answer="Shakespeare",
|
||||
)
|
||||
LieAnswer.objects.create(round_question=round_question, player=self.players[0], text="Marlowe")
|
||||
Guess.objects.create(
|
||||
round_question=round_question,
|
||||
player=self.players[0],
|
||||
selected_text="Shakespeare",
|
||||
is_correct=True,
|
||||
)
|
||||
Guess.objects.create(
|
||||
round_question=round_question,
|
||||
player=self.players[1],
|
||||
selected_text="Marlowe",
|
||||
is_correct=False,
|
||||
fooled_player=self.players[0],
|
||||
)
|
||||
self.players[0].score = round_config.points_correct + round_config.points_bluff
|
||||
self.players[0].save(update_fields=["score"])
|
||||
ScoreEvent.objects.create(
|
||||
session=self.session,
|
||||
player=self.players[0],
|
||||
delta=round_config.points_correct,
|
||||
reason="guess_correct",
|
||||
meta={"round_question_id": round_question.id, "guess_id": 1},
|
||||
)
|
||||
ScoreEvent.objects.create(
|
||||
session=self.session,
|
||||
player=self.players[0],
|
||||
delta=round_config.points_bluff,
|
||||
reason="bluff_success",
|
||||
meta={"round_question_id": round_question.id, "fooled_count": 1},
|
||||
)
|
||||
self.session.status = GameSession.Status.REVEAL
|
||||
self.session.save(update_fields=["status"])
|
||||
|
||||
stale_session = GameSession(
|
||||
pk=self.session.pk,
|
||||
host=self.host,
|
||||
code=self.session.code,
|
||||
status=GameSession.Status.GUESS,
|
||||
current_round=self.session.current_round,
|
||||
)
|
||||
mock_session_get.return_value = stale_session
|
||||
|
||||
response = self.client.post(
|
||||
reverse("lobby:submit_guess", kwargs={"code": self.session.code, "round_question_id": round_question.id}),
|
||||
data={
|
||||
"player_id": self.players[2].id,
|
||||
"session_token": self.players[2].session_token,
|
||||
"selected_text": "Shakespeare",
|
||||
},
|
||||
content_type="application/json",
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 201)
|
||||
self.assertEqual(response.json()["session"]["status"], GameSession.Status.REVEAL)
|
||||
self.assertTrue(response.json()["phase_transition"]["auto_advanced"])
|
||||
self.assertIsNotNone(response.json()["reveal"])
|
||||
mock_resolve_scores.assert_not_called()
|
||||
mock_sync_broadcast.assert_not_called()
|
||||
self.assertEqual(ScoreEvent.objects.filter(session=self.session, meta__round_question_id=round_question.id).count(), 2)
|
||||
|
||||
|
||||
class ScoreCalculationTests(TestCase):
|
||||
def setUp(self):
|
||||
|
||||
@@ -954,22 +954,46 @@ def submit_guess(request: HttpRequest, code: str, round_question_id: int) -> Jso
|
||||
leaderboard = None
|
||||
|
||||
if players_count > 0 and guess_count >= players_count:
|
||||
score_events = []
|
||||
should_broadcast_scores = False
|
||||
|
||||
with transaction.atomic():
|
||||
locked_session = GameSession.objects.select_for_update().get(pk=session.pk)
|
||||
|
||||
if locked_session.status == GameSession.Status.GUESS:
|
||||
already_calculated = ScoreEvent.objects.filter(
|
||||
session=session,
|
||||
session=locked_session,
|
||||
meta__round_question_id=round_question.id,
|
||||
).exists()
|
||||
if not already_calculated:
|
||||
score_events, leaderboard = _resolve_scores(session, round_question, round_config)
|
||||
score_events, leaderboard = _resolve_scores(locked_session, round_question, round_config)
|
||||
should_broadcast_scores = True
|
||||
else:
|
||||
score_events = list(
|
||||
ScoreEvent.objects.filter(session=session, meta__round_question_id=round_question.id).select_related("player")
|
||||
ScoreEvent.objects.filter(
|
||||
session=locked_session,
|
||||
meta__round_question_id=round_question.id,
|
||||
).select_related("player")
|
||||
)
|
||||
leaderboard = _build_leaderboard(session)
|
||||
leaderboard = _build_leaderboard(locked_session)
|
||||
|
||||
locked_session.status = GameSession.Status.REVEAL
|
||||
locked_session.save(update_fields=["status"])
|
||||
|
||||
elif locked_session.status == GameSession.Status.REVEAL:
|
||||
score_events = list(
|
||||
ScoreEvent.objects.filter(
|
||||
session=locked_session,
|
||||
meta__round_question_id=round_question.id,
|
||||
).select_related("player")
|
||||
)
|
||||
leaderboard = _build_leaderboard(locked_session)
|
||||
|
||||
session_status = locked_session.status
|
||||
|
||||
session.status = GameSession.Status.REVEAL
|
||||
session.save(update_fields=["status"])
|
||||
session_status = session.status
|
||||
reveal_payload = _build_reveal_payload(round_question)
|
||||
|
||||
if should_broadcast_scores:
|
||||
score_deltas = [
|
||||
{"player_id": ev.player_id, "delta": ev.delta, "reason": ev.reason}
|
||||
for ev in score_events
|
||||
|
||||
Reference in New Issue
Block a user