823 lines
34 KiB
Python
823 lines
34 KiB
Python
from datetime import timedelta
|
|
|
|
from django.contrib.auth import get_user_model
|
|
from django.test import TestCase
|
|
from django.urls import reverse
|
|
from django.utils import timezone
|
|
|
|
from fupogfakta.models import (
|
|
Category,
|
|
GameSession,
|
|
Guess,
|
|
LieAnswer,
|
|
Player,
|
|
Question,
|
|
RoundConfig,
|
|
RoundQuestion,
|
|
)
|
|
|
|
User = get_user_model()
|
|
|
|
|
|
class LobbyFlowTests(TestCase):
|
|
def setUp(self):
|
|
self.host = User.objects.create_user(username="host", password="secret123")
|
|
|
|
def test_create_session_requires_login(self):
|
|
response = self.client.post(reverse("lobby:create_session"))
|
|
|
|
self.assertEqual(response.status_code, 302)
|
|
self.assertEqual(GameSession.objects.count(), 0)
|
|
|
|
def test_host_can_create_session(self):
|
|
self.client.login(username="host", password="secret123")
|
|
|
|
response = self.client.post(reverse("lobby:create_session"))
|
|
|
|
self.assertEqual(response.status_code, 201)
|
|
body = response.json()
|
|
self.assertEqual(body["session"]["status"], GameSession.Status.LOBBY)
|
|
self.assertEqual(len(body["session"]["code"]), 6)
|
|
|
|
session = GameSession.objects.get(code=body["session"]["code"])
|
|
self.assertEqual(session.host, self.host)
|
|
|
|
def test_player_can_join_with_code(self):
|
|
session = GameSession.objects.create(host=self.host, code="ABCD23")
|
|
|
|
response = self.client.post(
|
|
reverse("lobby:join_session"),
|
|
data={"code": "abcd23", "nickname": "Luna"},
|
|
content_type="application/json",
|
|
)
|
|
|
|
self.assertEqual(response.status_code, 201)
|
|
body = response.json()
|
|
self.assertEqual(body["session"]["code"], "ABCD23")
|
|
self.assertEqual(body["player"]["nickname"], "Luna")
|
|
self.assertIn("session_token", body["player"])
|
|
self.assertTrue(body["player"]["session_token"])
|
|
self.assertTrue(Player.objects.filter(session=session, nickname="Luna").exists())
|
|
|
|
def test_join_rejects_duplicate_nickname_case_insensitive(self):
|
|
session = GameSession.objects.create(host=self.host, code="QWER12")
|
|
Player.objects.create(session=session, nickname="Luna")
|
|
|
|
response = self.client.post(
|
|
reverse("lobby:join_session"),
|
|
data={"code": "QWER12", "nickname": "lUna"},
|
|
content_type="application/json",
|
|
)
|
|
|
|
self.assertEqual(response.status_code, 409)
|
|
self.assertEqual(response.json()["error"], "Nickname already taken")
|
|
|
|
def test_join_rejects_non_joinable_session(self):
|
|
GameSession.objects.create(host=self.host, code="ZXCV98", status=GameSession.Status.FINISHED)
|
|
|
|
response = self.client.post(
|
|
reverse("lobby:join_session"),
|
|
data={"code": "ZXCV98", "nickname": "Kai"},
|
|
content_type="application/json",
|
|
)
|
|
|
|
self.assertEqual(response.status_code, 400)
|
|
self.assertEqual(response.json()["error"], "Session is not joinable")
|
|
|
|
def test_session_detail_returns_players(self):
|
|
session = GameSession.objects.create(host=self.host, code="LMNO45")
|
|
Player.objects.create(session=session, nickname="Mia", score=7)
|
|
Player.objects.create(session=session, nickname="Bo", score=2)
|
|
|
|
response = self.client.get(reverse("lobby:session_detail", kwargs={"code": "lmno45"}))
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
payload = response.json()
|
|
self.assertEqual(payload["session"]["players_count"], 2)
|
|
self.assertEqual([p["nickname"] for p in payload["players"]], ["Bo", "Mia"])
|
|
|
|
|
|
class StartRoundTests(TestCase):
|
|
def setUp(self):
|
|
self.host = User.objects.create_user(username="host", password="secret123")
|
|
self.other_user = User.objects.create_user(username="other", password="secret123")
|
|
self.session = GameSession.objects.create(host=self.host, code="ABCD23")
|
|
self.category = Category.objects.create(name="Historie", slug="historie", is_active=True)
|
|
Question.objects.create(
|
|
category=self.category,
|
|
prompt="Hvilket år faldt muren?",
|
|
correct_answer="1989",
|
|
is_active=True,
|
|
)
|
|
|
|
def test_host_can_start_round_with_selected_category(self):
|
|
self.client.login(username="host", password="secret123")
|
|
|
|
response = self.client.post(
|
|
reverse("lobby:start_round", kwargs={"code": self.session.code}),
|
|
data={"category_slug": self.category.slug},
|
|
content_type="application/json",
|
|
)
|
|
|
|
self.assertEqual(response.status_code, 201)
|
|
body = response.json()
|
|
self.assertEqual(body["session"]["status"], GameSession.Status.LIE)
|
|
self.assertEqual(body["round"]["number"], 1)
|
|
self.assertEqual(body["round"]["category"]["slug"], self.category.slug)
|
|
|
|
self.session.refresh_from_db()
|
|
self.assertEqual(self.session.status, GameSession.Status.LIE)
|
|
round_config = RoundConfig.objects.get(session=self.session, number=1)
|
|
self.assertEqual(round_config.category, self.category)
|
|
|
|
def test_start_round_requires_host(self):
|
|
self.client.login(username="other", password="secret123")
|
|
|
|
response = self.client.post(
|
|
reverse("lobby:start_round", kwargs={"code": self.session.code}),
|
|
data={"category_slug": self.category.slug},
|
|
content_type="application/json",
|
|
)
|
|
|
|
self.assertEqual(response.status_code, 403)
|
|
self.assertEqual(response.json()["error"], "Only host can start round")
|
|
|
|
def test_start_round_requires_existing_active_category_with_questions(self):
|
|
self.client.login(username="host", password="secret123")
|
|
|
|
response = self.client.post(
|
|
reverse("lobby:start_round", kwargs={"code": self.session.code}),
|
|
data={"category_slug": "ukendt"},
|
|
content_type="application/json",
|
|
)
|
|
self.assertEqual(response.status_code, 404)
|
|
|
|
empty_category = Category.objects.create(name="Sport", slug="sport", is_active=True)
|
|
response = self.client.post(
|
|
reverse("lobby:start_round", kwargs={"code": self.session.code}),
|
|
data={"category_slug": empty_category.slug},
|
|
content_type="application/json",
|
|
)
|
|
self.assertEqual(response.status_code, 400)
|
|
self.assertEqual(response.json()["error"], "Category has no active questions")
|
|
|
|
def test_start_round_rejects_non_lobby_session(self):
|
|
self.client.login(username="host", password="secret123")
|
|
self.session.status = GameSession.Status.GUESS
|
|
self.session.save(update_fields=["status"])
|
|
|
|
response = self.client.post(
|
|
reverse("lobby:start_round", kwargs={"code": self.session.code}),
|
|
data={"category_slug": self.category.slug},
|
|
content_type="application/json",
|
|
)
|
|
|
|
self.assertEqual(response.status_code, 400)
|
|
self.assertEqual(response.json()["error"], "Round can only be started from lobby")
|
|
|
|
|
|
class LieSubmissionTests(TestCase):
|
|
def setUp(self):
|
|
self.host = User.objects.create_user(username="host", password="secret123")
|
|
self.session = GameSession.objects.create(host=self.host, code="ABCD23", status=GameSession.Status.LIE)
|
|
self.category = Category.objects.create(name="Geografi", slug="geografi", is_active=True)
|
|
self.question = Question.objects.create(
|
|
category=self.category,
|
|
prompt="Hvad er hovedstaden i Australien?",
|
|
correct_answer="Canberra",
|
|
is_active=True,
|
|
)
|
|
RoundConfig.objects.create(
|
|
session=self.session,
|
|
number=1,
|
|
category=self.category,
|
|
lie_seconds=45,
|
|
)
|
|
self.player = Player.objects.create(session=self.session, nickname="Luna")
|
|
|
|
def test_host_can_show_question_and_get_lie_deadline(self):
|
|
self.client.login(username="host", password="secret123")
|
|
|
|
response = self.client.post(reverse("lobby:show_question", kwargs={"code": self.session.code}))
|
|
|
|
self.assertEqual(response.status_code, 201)
|
|
payload = response.json()
|
|
self.assertEqual(payload["config"]["lie_seconds"], 45)
|
|
self.assertIn("lie_deadline_at", payload["round_question"])
|
|
self.assertTrue(RoundQuestion.objects.filter(session=self.session, round_number=1).exists())
|
|
|
|
def test_player_can_submit_lie_before_deadline(self):
|
|
round_question = RoundQuestion.objects.create(
|
|
session=self.session,
|
|
round_number=1,
|
|
question=self.question,
|
|
correct_answer=self.question.correct_answer,
|
|
)
|
|
|
|
response = self.client.post(
|
|
reverse(
|
|
"lobby:submit_lie",
|
|
kwargs={"code": self.session.code, "round_question_id": round_question.id},
|
|
),
|
|
data={"player_id": self.player.id, "session_token": self.player.session_token, "text": "Sydney"},
|
|
content_type="application/json",
|
|
)
|
|
|
|
self.assertEqual(response.status_code, 201)
|
|
self.assertTrue(LieAnswer.objects.filter(round_question=round_question, player=self.player).exists())
|
|
|
|
def test_submit_lie_rejects_after_time_window(self):
|
|
round_question = RoundQuestion.objects.create(
|
|
session=self.session,
|
|
round_number=1,
|
|
question=self.question,
|
|
correct_answer=self.question.correct_answer,
|
|
)
|
|
round_question.shown_at = timezone.now() - timedelta(seconds=46)
|
|
round_question.save(update_fields=["shown_at"])
|
|
|
|
response = self.client.post(
|
|
reverse(
|
|
"lobby:submit_lie",
|
|
kwargs={"code": self.session.code, "round_question_id": round_question.id},
|
|
),
|
|
data={"player_id": self.player.id, "session_token": self.player.session_token, "text": "Melbourne"},
|
|
content_type="application/json",
|
|
)
|
|
|
|
self.assertEqual(response.status_code, 400)
|
|
self.assertEqual(response.json()["error"], "Lie submission window has closed")
|
|
|
|
def test_submit_lie_rejects_duplicate_submission(self):
|
|
round_question = RoundQuestion.objects.create(
|
|
session=self.session,
|
|
round_number=1,
|
|
question=self.question,
|
|
correct_answer=self.question.correct_answer,
|
|
)
|
|
LieAnswer.objects.create(round_question=round_question, player=self.player, text="Perth")
|
|
|
|
response = self.client.post(
|
|
reverse(
|
|
"lobby:submit_lie",
|
|
kwargs={"code": self.session.code, "round_question_id": round_question.id},
|
|
),
|
|
data={"player_id": self.player.id, "session_token": self.player.session_token, "text": "Brisbane"},
|
|
content_type="application/json",
|
|
)
|
|
|
|
self.assertEqual(response.status_code, 409)
|
|
self.assertEqual(response.json()["error"], "Lie already submitted for this player")
|
|
|
|
def test_submit_lie_requires_session_token(self):
|
|
round_question = RoundQuestion.objects.create(
|
|
session=self.session,
|
|
round_number=1,
|
|
question=self.question,
|
|
correct_answer=self.question.correct_answer,
|
|
)
|
|
|
|
response = self.client.post(
|
|
reverse(
|
|
"lobby:submit_lie",
|
|
kwargs={"code": self.session.code, "round_question_id": round_question.id},
|
|
),
|
|
data={"player_id": self.player.id, "text": "Sydney"},
|
|
content_type="application/json",
|
|
)
|
|
|
|
self.assertEqual(response.status_code, 400)
|
|
self.assertEqual(response.json()["error"], "session_token is required")
|
|
|
|
def test_submit_lie_rejects_invalid_session_token(self):
|
|
round_question = RoundQuestion.objects.create(
|
|
session=self.session,
|
|
round_number=1,
|
|
question=self.question,
|
|
correct_answer=self.question.correct_answer,
|
|
)
|
|
|
|
response = self.client.post(
|
|
reverse(
|
|
"lobby:submit_lie",
|
|
kwargs={"code": self.session.code, "round_question_id": round_question.id},
|
|
),
|
|
data={"player_id": self.player.id, "session_token": "invalid-token", "text": "Sydney"},
|
|
content_type="application/json",
|
|
)
|
|
|
|
self.assertEqual(response.status_code, 403)
|
|
self.assertEqual(response.json()["error"], "Invalid player session token")
|
|
|
|
class MixAnswersTests(TestCase):
|
|
def setUp(self):
|
|
self.host = User.objects.create_user(username="host", password="secret123")
|
|
self.other_user = User.objects.create_user(username="other", password="secret123")
|
|
self.session = GameSession.objects.create(host=self.host, code="ABCD23", status=GameSession.Status.LIE)
|
|
self.category = Category.objects.create(name="Historie", slug="historie", is_active=True)
|
|
self.question = Question.objects.create(
|
|
category=self.category,
|
|
prompt="Hvilken by er Danmarks hovedstad?",
|
|
correct_answer="København",
|
|
is_active=True,
|
|
)
|
|
RoundConfig.objects.create(session=self.session, number=1, category=self.category)
|
|
self.round_question = RoundQuestion.objects.create(
|
|
session=self.session,
|
|
round_number=1,
|
|
question=self.question,
|
|
correct_answer="København",
|
|
)
|
|
self.player_one = Player.objects.create(session=self.session, nickname="Luna")
|
|
self.player_two = Player.objects.create(session=self.session, nickname="Mads")
|
|
|
|
def test_host_can_mix_answers_and_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")
|
|
response = self.client.post(
|
|
reverse(
|
|
"lobby:mix_answers",
|
|
kwargs={"code": self.session.code, "round_question_id": self.round_question.id},
|
|
)
|
|
)
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
payload = response.json()
|
|
answer_texts = [entry["text"] for entry in payload["answers"]]
|
|
self.assertEqual(set(answer_texts), {"København", "Aarhus", "Odense"})
|
|
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")
|
|
|
|
response = self.client.post(
|
|
reverse(
|
|
"lobby:mix_answers",
|
|
kwargs={"code": self.session.code, "round_question_id": self.round_question.id},
|
|
)
|
|
)
|
|
|
|
self.assertEqual(response.status_code, 403)
|
|
self.assertEqual(response.json()["error"], "Only host can mix answers")
|
|
|
|
def test_mix_answers_deduplicates_case_insensitive_lies(self):
|
|
LieAnswer.objects.create(round_question=self.round_question, player=self.player_one, text="københavn")
|
|
LieAnswer.objects.create(round_question=self.round_question, player=self.player_two, text="Aarhus")
|
|
|
|
self.client.login(username="host", password="secret123")
|
|
response = self.client.post(
|
|
reverse(
|
|
"lobby:mix_answers",
|
|
kwargs={"code": self.session.code, "round_question_id": self.round_question.id},
|
|
)
|
|
)
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
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):
|
|
self.host = User.objects.create_user(username="host_guess", password="secret123")
|
|
self.session = GameSession.objects.create(host=self.host, code="GU3551", status=GameSession.Status.GUESS)
|
|
self.category = Category.objects.create(name="Videnskab", slug="videnskab", is_active=True)
|
|
self.question = Question.objects.create(
|
|
category=self.category,
|
|
prompt="Hvilken planet kaldes den røde planet?",
|
|
correct_answer="Mars",
|
|
is_active=True,
|
|
)
|
|
self.round_config = RoundConfig.objects.create(
|
|
session=self.session,
|
|
number=1,
|
|
category=self.category,
|
|
lie_seconds=45,
|
|
guess_seconds=30,
|
|
)
|
|
self.round_question = RoundQuestion.objects.create(
|
|
session=self.session,
|
|
round_number=1,
|
|
question=self.question,
|
|
correct_answer="Mars",
|
|
)
|
|
self.player = Player.objects.create(session=self.session, nickname="Luna")
|
|
self.liar = Player.objects.create(session=self.session, nickname="Mads")
|
|
LieAnswer.objects.create(round_question=self.round_question, player=self.liar, text="Jupiter")
|
|
|
|
def test_player_can_submit_guess_in_guess_phase(self):
|
|
response = self.client.post(
|
|
reverse(
|
|
"lobby:submit_guess",
|
|
kwargs={"code": self.session.code, "round_question_id": self.round_question.id},
|
|
),
|
|
data={"player_id": self.player.id, "session_token": self.player.session_token, "selected_text": "Mars"},
|
|
content_type="application/json",
|
|
)
|
|
|
|
self.assertEqual(response.status_code, 201)
|
|
payload = response.json()
|
|
self.assertTrue(payload["guess"]["is_correct"])
|
|
self.assertIsNone(payload["guess"]["fooled_player_id"])
|
|
self.assertIn("guess_deadline_at", payload["window"])
|
|
|
|
def test_submit_guess_rejects_when_not_in_guess_phase(self):
|
|
self.session.status = GameSession.Status.LIE
|
|
self.session.save(update_fields=["status"])
|
|
|
|
response = self.client.post(
|
|
reverse(
|
|
"lobby:submit_guess",
|
|
kwargs={"code": self.session.code, "round_question_id": self.round_question.id},
|
|
),
|
|
data={"player_id": self.player.id, "session_token": self.player.session_token, "selected_text": "Mars"},
|
|
content_type="application/json",
|
|
)
|
|
|
|
self.assertEqual(response.status_code, 400)
|
|
self.assertEqual(response.json()["error"], "Guess submission is only allowed in guess phase")
|
|
|
|
def test_submit_guess_rejects_unknown_answer(self):
|
|
response = self.client.post(
|
|
reverse(
|
|
"lobby:submit_guess",
|
|
kwargs={"code": self.session.code, "round_question_id": self.round_question.id},
|
|
),
|
|
data={"player_id": self.player.id, "session_token": self.player.session_token, "selected_text": "Venus"},
|
|
content_type="application/json",
|
|
)
|
|
|
|
self.assertEqual(response.status_code, 400)
|
|
self.assertEqual(response.json()["error"], "Selected answer is not part of this round")
|
|
|
|
def test_submit_guess_rejects_duplicate_submission(self):
|
|
Guess.objects.create(round_question=self.round_question, player=self.player, selected_text="Mars", is_correct=True)
|
|
|
|
response = self.client.post(
|
|
reverse(
|
|
"lobby:submit_guess",
|
|
kwargs={"code": self.session.code, "round_question_id": self.round_question.id},
|
|
),
|
|
data={"player_id": self.player.id, "session_token": self.player.session_token, "selected_text": "Jupiter"},
|
|
content_type="application/json",
|
|
)
|
|
|
|
self.assertEqual(response.status_code, 409)
|
|
self.assertEqual(response.json()["error"], "Guess already submitted for this player")
|
|
|
|
def test_submit_guess_rejects_after_deadline(self):
|
|
self.round_question.shown_at = timezone.now() - timedelta(seconds=76)
|
|
self.round_question.save(update_fields=["shown_at"])
|
|
|
|
response = self.client.post(
|
|
reverse(
|
|
"lobby:submit_guess",
|
|
kwargs={"code": self.session.code, "round_question_id": self.round_question.id},
|
|
),
|
|
data={"player_id": self.player.id, "session_token": self.player.session_token, "selected_text": "Mars"},
|
|
content_type="application/json",
|
|
)
|
|
|
|
self.assertEqual(response.status_code, 400)
|
|
self.assertEqual(response.json()["error"], "Guess submission window has closed")
|
|
|
|
|
|
|
|
def test_submit_guess_requires_session_token(self):
|
|
response = self.client.post(
|
|
reverse(
|
|
"lobby:submit_guess",
|
|
kwargs={"code": self.session.code, "round_question_id": self.round_question.id},
|
|
),
|
|
data={"player_id": self.player.id, "selected_text": "Mars"},
|
|
content_type="application/json",
|
|
)
|
|
|
|
self.assertEqual(response.status_code, 400)
|
|
self.assertEqual(response.json()["error"], "session_token is required")
|
|
|
|
def test_submit_guess_rejects_invalid_session_token(self):
|
|
response = self.client.post(
|
|
reverse(
|
|
"lobby:submit_guess",
|
|
kwargs={"code": self.session.code, "round_question_id": self.round_question.id},
|
|
),
|
|
data={"player_id": self.player.id, "session_token": "wrong-token", "selected_text": "Mars"},
|
|
content_type="application/json",
|
|
)
|
|
|
|
self.assertEqual(response.status_code, 403)
|
|
self.assertEqual(response.json()["error"], "Invalid player session token")
|
|
|
|
|
|
class ScoreCalculationTests(TestCase):
|
|
def setUp(self):
|
|
self.host = User.objects.create_user(username="host_score", password="secret123")
|
|
self.other_user = User.objects.create_user(username="other_score", password="secret123")
|
|
self.session = GameSession.objects.create(host=self.host, code="SC0RE1", status=GameSession.Status.GUESS)
|
|
self.category = Category.objects.create(name="Sport", slug="sport", is_active=True)
|
|
self.question = Question.objects.create(
|
|
category=self.category,
|
|
prompt="Hvilken sport spiller man i Wimbledon?",
|
|
correct_answer="Tennis",
|
|
is_active=True,
|
|
)
|
|
RoundConfig.objects.create(
|
|
session=self.session,
|
|
number=1,
|
|
category=self.category,
|
|
points_correct=5,
|
|
points_bluff=2,
|
|
)
|
|
self.round_question = RoundQuestion.objects.create(
|
|
session=self.session,
|
|
round_number=1,
|
|
question=self.question,
|
|
correct_answer="Tennis",
|
|
)
|
|
self.player_one = Player.objects.create(session=self.session, nickname="Luna")
|
|
self.player_two = Player.objects.create(session=self.session, nickname="Mads")
|
|
self.player_three = Player.objects.create(session=self.session, nickname="Nora")
|
|
|
|
def test_host_can_calculate_scores_and_transition_to_reveal(self):
|
|
Guess.objects.create(round_question=self.round_question, player=self.player_one, selected_text="Tennis", is_correct=True)
|
|
Guess.objects.create(
|
|
round_question=self.round_question,
|
|
player=self.player_two,
|
|
selected_text="Padel",
|
|
is_correct=False,
|
|
fooled_player=self.player_three,
|
|
)
|
|
Guess.objects.create(
|
|
round_question=self.round_question,
|
|
player=self.player_three,
|
|
selected_text="Padel",
|
|
is_correct=False,
|
|
fooled_player=self.player_three,
|
|
)
|
|
|
|
self.client.login(username="host_score", password="secret123")
|
|
response = self.client.post(
|
|
reverse(
|
|
"lobby:calculate_scores",
|
|
kwargs={"code": self.session.code, "round_question_id": self.round_question.id},
|
|
)
|
|
)
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
payload = response.json()
|
|
self.assertEqual(payload["session"]["status"], GameSession.Status.REVEAL)
|
|
self.assertEqual(payload["events_created"], 2)
|
|
|
|
self.player_one.refresh_from_db()
|
|
self.player_three.refresh_from_db()
|
|
self.session.refresh_from_db()
|
|
|
|
self.assertEqual(self.player_one.score, 5)
|
|
self.assertEqual(self.player_three.score, 4)
|
|
self.assertEqual(self.session.status, GameSession.Status.REVEAL)
|
|
|
|
def test_calculate_scores_requires_host(self):
|
|
self.client.login(username="other_score", password="secret123")
|
|
|
|
response = self.client.post(
|
|
reverse(
|
|
"lobby:calculate_scores",
|
|
kwargs={"code": self.session.code, "round_question_id": self.round_question.id},
|
|
)
|
|
)
|
|
|
|
self.assertEqual(response.status_code, 403)
|
|
self.assertEqual(response.json()["error"], "Only host can calculate scores")
|
|
|
|
def test_calculate_scores_rejects_duplicate_calculation(self):
|
|
Guess.objects.create(round_question=self.round_question, player=self.player_one, selected_text="Tennis", is_correct=True)
|
|
|
|
self.client.login(username="host_score", password="secret123")
|
|
first = self.client.post(
|
|
reverse(
|
|
"lobby:calculate_scores",
|
|
kwargs={"code": self.session.code, "round_question_id": self.round_question.id},
|
|
)
|
|
)
|
|
second = self.client.post(
|
|
reverse(
|
|
"lobby:calculate_scores",
|
|
kwargs={"code": self.session.code, "round_question_id": self.round_question.id},
|
|
)
|
|
)
|
|
|
|
self.assertEqual(first.status_code, 200)
|
|
self.assertEqual(second.status_code, 409)
|
|
self.assertEqual(second.json()["error"], "Scores already calculated for this round question")
|
|
|
|
|
|
class RevealRoundFlowTests(TestCase):
|
|
def setUp(self):
|
|
self.host = User.objects.create_user(username="host_reveal", password="secret123")
|
|
self.other_user = User.objects.create_user(username="other_reveal", password="secret123")
|
|
self.session = GameSession.objects.create(host=self.host, code="RVL123", status=GameSession.Status.REVEAL)
|
|
self.player_one = Player.objects.create(session=self.session, nickname="Luna", score=9)
|
|
self.player_two = Player.objects.create(session=self.session, nickname="Mads", score=3)
|
|
|
|
def test_host_can_get_reveal_scoreboard(self):
|
|
self.client.login(username="host_reveal", password="secret123")
|
|
|
|
response = self.client.get(
|
|
reverse(
|
|
"lobby:reveal_scoreboard",
|
|
kwargs={"code": self.session.code},
|
|
)
|
|
)
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
payload = response.json()
|
|
self.assertEqual(payload["session"]["status"], GameSession.Status.REVEAL)
|
|
self.assertEqual([item["nickname"] for item in payload["leaderboard"]], ["Luna", "Mads"])
|
|
|
|
def test_reveal_scoreboard_requires_host(self):
|
|
self.client.login(username="other_reveal", password="secret123")
|
|
|
|
response = self.client.get(
|
|
reverse(
|
|
"lobby:reveal_scoreboard",
|
|
kwargs={"code": self.session.code},
|
|
)
|
|
)
|
|
|
|
self.assertEqual(response.status_code, 403)
|
|
self.assertEqual(response.json()["error"], "Only host can view scoreboard")
|
|
|
|
def test_host_can_finish_game_from_reveal(self):
|
|
self.client.login(username="host_reveal", password="secret123")
|
|
|
|
response = self.client.post(
|
|
reverse(
|
|
"lobby:finish_game",
|
|
kwargs={"code": self.session.code},
|
|
)
|
|
)
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
payload = response.json()
|
|
self.assertEqual(payload["session"]["status"], GameSession.Status.FINISHED)
|
|
self.assertEqual(payload["winner"]["nickname"], "Luna")
|
|
self.assertEqual([item["nickname"] for item in payload["leaderboard"]], ["Luna", "Mads"])
|
|
|
|
self.session.refresh_from_db()
|
|
self.assertEqual(self.session.status, GameSession.Status.FINISHED)
|
|
|
|
def test_finish_game_requires_host(self):
|
|
self.client.login(username="other_reveal", password="secret123")
|
|
|
|
response = self.client.post(
|
|
reverse(
|
|
"lobby:finish_game",
|
|
kwargs={"code": self.session.code},
|
|
)
|
|
)
|
|
|
|
self.assertEqual(response.status_code, 403)
|
|
self.assertEqual(response.json()["error"], "Only host can finish game")
|
|
|
|
def test_finish_game_rejects_wrong_phase(self):
|
|
self.client.login(username="host_reveal", password="secret123")
|
|
self.session.status = GameSession.Status.GUESS
|
|
self.session.save(update_fields=["status"])
|
|
|
|
response = self.client.post(
|
|
reverse(
|
|
"lobby:finish_game",
|
|
kwargs={"code": self.session.code},
|
|
)
|
|
)
|
|
|
|
self.assertEqual(response.status_code, 400)
|
|
self.assertEqual(response.json()["error"], "Game can only be finished from reveal phase")
|
|
|
|
def test_host_can_start_next_round_from_reveal(self):
|
|
self.client.login(username="host_reveal", password="secret123")
|
|
|
|
response = self.client.post(
|
|
reverse(
|
|
"lobby:start_next_round",
|
|
kwargs={"code": self.session.code},
|
|
)
|
|
)
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
payload = response.json()
|
|
self.assertEqual(payload["session"]["status"], GameSession.Status.LOBBY)
|
|
self.assertEqual(payload["session"]["current_round"], 2)
|
|
|
|
self.session.refresh_from_db()
|
|
self.assertEqual(self.session.status, GameSession.Status.LOBBY)
|
|
self.assertEqual(self.session.current_round, 2)
|
|
|
|
def test_start_next_round_rejects_wrong_phase(self):
|
|
self.client.login(username="host_reveal", password="secret123")
|
|
self.session.status = GameSession.Status.GUESS
|
|
self.session.save(update_fields=["status"])
|
|
|
|
response = self.client.post(
|
|
reverse(
|
|
"lobby:start_next_round",
|
|
kwargs={"code": self.session.code},
|
|
)
|
|
)
|
|
|
|
self.assertEqual(response.status_code, 400)
|
|
self.assertEqual(response.json()["error"], "Next round can only start from reveal phase")
|
|
|
|
class UiScreenTests(TestCase):
|
|
def setUp(self):
|
|
self.host = User.objects.create_user(username="host_ui", password="secret123")
|
|
|
|
def test_host_screen_requires_login(self):
|
|
response = self.client.get(reverse("lobby:host_screen"))
|
|
self.assertEqual(response.status_code, 302)
|
|
|
|
def test_host_screen_renders_for_logged_in_user(self):
|
|
self.client.login(username="host_ui", password="secret123")
|
|
response = self.client.get(reverse("lobby:host_screen"))
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertContains(response, "Host panel")
|
|
|
|
def test_player_screen_is_public(self):
|
|
response = self.client.get(reverse("lobby:player_screen"))
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertContains(response, "Player panel")
|
|
self.assertContains(response, "id=\"sessionToken\"")
|
|
self.assertContains(response, "session_token")
|
|
self.assertContains(response, "id=\"answerOptions\"")
|
|
self.assertContains(response, "renderAnswerOptions")
|
|
self.assertContains(response, "availableAnswers")
|
|
self.assertContains(response, "guessStorageKey")
|
|
self.assertContains(response, "persistGuessState")
|
|
self.assertContains(response, "id=\"lieSubmitBtn\"")
|
|
self.assertContains(response, "id=\"lieStatus\"")
|
|
self.assertContains(response, "persistLieState")
|
|
self.assertContains(response, "updateLieSubmitState")
|
|
self.assertContains(response, "hasSubmitContext")
|
|
self.assertContains(response, "missing_submit_context")
|
|
self.assertContains(response, "invalid_client_guess")
|
|
|
|
|
|
class SessionDetailRoundQuestionTests(TestCase):
|
|
def setUp(self):
|
|
self.host = User.objects.create_user(username="host_detail", password="secret123")
|
|
self.session = GameSession.objects.create(host=self.host, code="ABCDE1", status=GameSession.Status.LIE)
|
|
self.category = Category.objects.create(name="Historie", slug="historie-2", is_active=True)
|
|
self.question = Question.objects.create(
|
|
category=self.category,
|
|
prompt="Hvem opfandt pæren?",
|
|
correct_answer="Edison",
|
|
is_active=True,
|
|
)
|
|
|
|
def test_session_detail_includes_current_round_question_when_available(self):
|
|
round_question = RoundQuestion.objects.create(
|
|
session=self.session,
|
|
round_number=1,
|
|
question=self.question,
|
|
correct_answer=self.question.correct_answer,
|
|
)
|
|
|
|
response = self.client.get(reverse("lobby:session_detail", kwargs={"code": self.session.code}))
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
payload = response.json()
|
|
self.assertEqual(payload["round_question"]["id"], round_question.id)
|
|
self.assertEqual(payload["round_question"]["prompt"], self.question.prompt)
|