284 lines
12 KiB
Python
284 lines
12 KiB
Python
from datetime import timedelta
|
|
from unittest.mock import patch
|
|
|
|
from django.contrib.auth import get_user_model
|
|
from django.test import TestCase
|
|
from django.utils import timezone
|
|
|
|
from fupogfakta.models import Category, GameSession, Guess, LieAnswer, Player, Question, RoundConfig, RoundQuestion, ScoreEvent
|
|
from fupogfakta.payloads import build_lie_started_payload, build_reveal_payload
|
|
from fupogfakta.services import (
|
|
finish_game,
|
|
get_current_round_question,
|
|
prepare_mixed_answers,
|
|
promote_reveal_to_scoreboard,
|
|
resolve_scores,
|
|
select_round_question,
|
|
start_next_round,
|
|
)
|
|
|
|
User = get_user_model()
|
|
|
|
|
|
class FupOgFaktaExtractionSliceTests(TestCase):
|
|
def setUp(self):
|
|
self.host = User.objects.create_user(username="host", password="secret123")
|
|
self.session = GameSession.objects.create(host=self.host, code="ABCD23")
|
|
self.category = Category.objects.create(name="Historie", slug="historie", is_active=True)
|
|
self.question_one = Question.objects.create(
|
|
category=self.category,
|
|
prompt="Hvornår faldt muren?",
|
|
correct_answer="1989",
|
|
is_active=True,
|
|
)
|
|
self.question_two = Question.objects.create(
|
|
category=self.category,
|
|
prompt="Hvornår kom euroen?",
|
|
correct_answer="1999",
|
|
is_active=True,
|
|
)
|
|
self.round_config = RoundConfig.objects.create(session=self.session, number=1, category=self.category)
|
|
self.alice = Player.objects.create(session=self.session, nickname="Alice")
|
|
self.bob = Player.objects.create(session=self.session, nickname="Bob")
|
|
self.clara = Player.objects.create(session=self.session, nickname="Clara")
|
|
|
|
def test_select_round_question_skips_already_used_questions_for_session(self):
|
|
RoundQuestion.objects.create(
|
|
session=self.session,
|
|
round_number=99,
|
|
question=self.question_one,
|
|
correct_answer=self.question_one.correct_answer,
|
|
)
|
|
|
|
round_question = select_round_question(self.session, self.round_config)
|
|
|
|
self.assertEqual(round_question.question, self.question_two)
|
|
self.assertEqual(get_current_round_question(self.session), round_question)
|
|
|
|
def test_prepare_mixed_answers_dedupes_blank_and_case_variants(self):
|
|
round_question = RoundQuestion.objects.create(
|
|
session=self.session,
|
|
round_number=1,
|
|
question=self.question_one,
|
|
correct_answer="1989",
|
|
)
|
|
LieAnswer.objects.create(round_question=round_question, player=self.alice, text=" 1989 ")
|
|
LieAnswer.objects.create(round_question=round_question, player=self.bob, text="Nitten niogfirs")
|
|
LieAnswer.objects.create(round_question=round_question, player=self.clara, text=" ")
|
|
|
|
with patch("fupogfakta.services.random.shuffle", side_effect=lambda answers: None):
|
|
answers = prepare_mixed_answers(round_question)
|
|
|
|
self.assertEqual(answers, ["1989", "Nitten niogfirs"])
|
|
round_question.refresh_from_db()
|
|
self.assertEqual(round_question.mixed_answers, answers)
|
|
|
|
def test_start_next_round_moves_scoreboard_transition_into_service(self):
|
|
self.session.status = GameSession.Status.SCOREBOARD
|
|
self.session.save(update_fields=["status"])
|
|
|
|
result = start_next_round(self.session)
|
|
|
|
self.session.refresh_from_db()
|
|
self.assertTrue(result.should_broadcast)
|
|
self.assertEqual(result.session.status, GameSession.Status.LIE)
|
|
self.assertEqual(result.session.current_round, 2)
|
|
self.assertEqual(result.round_config.number, 2)
|
|
self.assertTrue(result.round_config.started_from_scoreboard)
|
|
self.assertEqual(result.round_question.round_number, 2)
|
|
|
|
def test_start_next_round_rejects_plain_lie_without_scoreboard_marker(self):
|
|
self.session.status = GameSession.Status.LIE
|
|
self.session.current_round = 2
|
|
self.session.save(update_fields=["status", "current_round"])
|
|
RoundConfig.objects.create(session=self.session, number=2, category=self.category, started_from_scoreboard=False)
|
|
RoundQuestion.objects.create(
|
|
session=self.session,
|
|
round_number=2,
|
|
question=self.question_two,
|
|
correct_answer=self.question_two.correct_answer,
|
|
)
|
|
|
|
with self.assertRaisesMessage(ValueError, "next_round_invalid_phase"):
|
|
start_next_round(self.session)
|
|
|
|
def test_start_next_round_refreshes_shown_at_for_reused_bootstrap_question(self):
|
|
self.session.status = GameSession.Status.SCOREBOARD
|
|
self.session.save(update_fields=["status"])
|
|
stale_shown_at = timezone.now() - timedelta(minutes=10)
|
|
stale_round_question = RoundQuestion.objects.create(
|
|
session=self.session,
|
|
round_number=2,
|
|
question=self.question_two,
|
|
correct_answer=self.question_two.correct_answer,
|
|
shown_at=stale_shown_at,
|
|
mixed_answers=["Stale truth", "Stale lie"],
|
|
)
|
|
LieAnswer.objects.create(round_question=stale_round_question, player=self.alice, text="Stale lie")
|
|
Guess.objects.create(
|
|
round_question=stale_round_question,
|
|
player=self.bob,
|
|
selected_text="Stale truth",
|
|
is_correct=True,
|
|
)
|
|
|
|
before_transition = timezone.now()
|
|
result = start_next_round(self.session)
|
|
after_transition = timezone.now()
|
|
|
|
stale_round_question.refresh_from_db()
|
|
self.assertEqual(result.round_question.id, stale_round_question.id)
|
|
self.assertGreaterEqual(stale_round_question.shown_at, before_transition)
|
|
self.assertLessEqual(stale_round_question.shown_at, after_transition)
|
|
self.assertNotEqual(stale_round_question.shown_at, stale_shown_at)
|
|
self.assertEqual(stale_round_question.mixed_answers, [])
|
|
self.assertEqual(stale_round_question.lies.count(), 0)
|
|
self.assertEqual(stale_round_question.guesses.count(), 0)
|
|
|
|
def test_start_next_round_reuses_existing_bootstrap_round_config_with_fresh_canonical_values(self):
|
|
self.session.status = GameSession.Status.SCOREBOARD
|
|
self.session.save(update_fields=["status"])
|
|
stale_category = Category.objects.create(name="Sport", slug="sport", is_active=True)
|
|
stale_round_config = RoundConfig.objects.create(
|
|
session=self.session,
|
|
number=2,
|
|
category=stale_category,
|
|
lie_seconds=12,
|
|
guess_seconds=18,
|
|
points_correct=9,
|
|
points_bluff=7,
|
|
started_from_scoreboard=False,
|
|
)
|
|
stale_round_question = RoundQuestion.objects.create(
|
|
session=self.session,
|
|
round_number=2,
|
|
question=self.question_two,
|
|
correct_answer=self.question_two.correct_answer,
|
|
shown_at=timezone.now() - timedelta(minutes=10),
|
|
mixed_answers=["Stale truth"],
|
|
)
|
|
|
|
result = start_next_round(self.session)
|
|
|
|
stale_round_config.refresh_from_db()
|
|
stale_round_question.refresh_from_db()
|
|
self.assertEqual(result.round_config.id, stale_round_config.id)
|
|
self.assertEqual(RoundConfig.objects.filter(session=self.session, number=2).count(), 1)
|
|
self.assertEqual(stale_round_config.category_id, self.round_config.category_id)
|
|
self.assertEqual(stale_round_config.lie_seconds, self.round_config.lie_seconds)
|
|
self.assertEqual(stale_round_config.guess_seconds, self.round_config.guess_seconds)
|
|
self.assertEqual(stale_round_config.points_correct, self.round_config.points_correct)
|
|
self.assertEqual(stale_round_config.points_bluff, self.round_config.points_bluff)
|
|
self.assertTrue(stale_round_config.started_from_scoreboard)
|
|
self.assertEqual(result.round_question.id, stale_round_question.id)
|
|
self.assertEqual(stale_round_question.mixed_answers, [])
|
|
|
|
def test_finish_game_moves_scoreboard_transition_into_service(self):
|
|
self.session.status = GameSession.Status.SCOREBOARD
|
|
self.session.save(update_fields=["status"])
|
|
|
|
result = finish_game(self.session)
|
|
|
|
self.session.refresh_from_db()
|
|
self.assertTrue(result.should_broadcast)
|
|
self.assertEqual(result.session.status, GameSession.Status.FINISHED)
|
|
self.assertEqual(self.session.status, GameSession.Status.FINISHED)
|
|
|
|
def test_promote_reveal_to_scoreboard_moves_transition_into_service(self):
|
|
round_question = RoundQuestion.objects.create(
|
|
session=self.session,
|
|
round_number=1,
|
|
question=self.question_one,
|
|
correct_answer=self.question_one.correct_answer,
|
|
)
|
|
self.session.status = GameSession.Status.REVEAL
|
|
self.session.save(update_fields=["status"])
|
|
|
|
LieAnswer.objects.create(round_question=round_question, player=self.alice, text="Elbil")
|
|
Guess.objects.create(
|
|
round_question=round_question,
|
|
player=self.bob,
|
|
selected_text="Elbil",
|
|
is_correct=False,
|
|
fooled_player=self.alice,
|
|
)
|
|
ScoreEvent.objects.create(
|
|
session=self.session,
|
|
player=self.alice,
|
|
delta=5,
|
|
reason="bluff_success",
|
|
meta={"round_question_id": round_question.id},
|
|
)
|
|
self.alice.score = 5
|
|
self.alice.save(update_fields=["score"])
|
|
|
|
result = promote_reveal_to_scoreboard(self.session)
|
|
|
|
self.session.refresh_from_db()
|
|
self.assertTrue(result.should_broadcast)
|
|
self.assertEqual(result.session.status, GameSession.Status.SCOREBOARD)
|
|
self.assertEqual(result.leaderboard[0]["nickname"], self.alice.nickname)
|
|
|
|
def test_resolve_scores_applies_correct_and_bluff_points(self):
|
|
round_question = RoundQuestion.objects.create(
|
|
session=self.session,
|
|
round_number=1,
|
|
question=self.question_one,
|
|
correct_answer="1989",
|
|
)
|
|
Guess.objects.create(
|
|
round_question=round_question,
|
|
player=self.alice,
|
|
selected_text="1989",
|
|
is_correct=True,
|
|
)
|
|
Guess.objects.create(
|
|
round_question=round_question,
|
|
player=self.bob,
|
|
selected_text="Berlin",
|
|
is_correct=False,
|
|
fooled_player=self.clara,
|
|
)
|
|
Guess.objects.create(
|
|
round_question=round_question,
|
|
player=self.clara,
|
|
selected_text="Berlin",
|
|
is_correct=False,
|
|
fooled_player=self.clara,
|
|
)
|
|
|
|
score_events, leaderboard = resolve_scores(self.session, round_question, self.round_config)
|
|
|
|
self.assertEqual(len(score_events), 2)
|
|
self.alice.refresh_from_db()
|
|
self.clara.refresh_from_db()
|
|
self.assertEqual(self.alice.score, self.round_config.points_correct)
|
|
self.assertEqual(self.clara.score, self.round_config.points_bluff * 2)
|
|
self.assertEqual(ScoreEvent.objects.filter(session=self.session, meta__round_question_id=round_question.id).count(), 2)
|
|
self.assertEqual([entry["nickname"] for entry in leaderboard], ["Alice", "Clara", "Bob"])
|
|
|
|
def test_payload_builders_expose_fupogfakta_round_contract(self):
|
|
round_question = RoundQuestion.objects.create(
|
|
session=self.session,
|
|
round_number=1,
|
|
question=self.question_one,
|
|
correct_answer="1989",
|
|
)
|
|
lie = LieAnswer.objects.create(round_question=round_question, player=self.bob, text="1991")
|
|
Guess.objects.create(
|
|
round_question=round_question,
|
|
player=self.alice,
|
|
selected_text="1991",
|
|
is_correct=False,
|
|
fooled_player=self.bob,
|
|
)
|
|
|
|
lie_payload = build_lie_started_payload(self.session, self.round_config, round_question)
|
|
reveal_payload = build_reveal_payload(round_question)
|
|
|
|
self.assertEqual(lie_payload["category"], {"slug": self.category.slug, "name": self.category.name})
|
|
self.assertEqual(lie_payload["round_question_id"], round_question.id)
|
|
self.assertEqual(reveal_payload["correct_answer"], "1989")
|
|
self.assertEqual(reveal_payload["lies"][0]["player_id"], lie.player_id)
|
|
self.assertEqual(reveal_payload["guesses"][0]["fooled_player_nickname"], self.bob.nickname)
|