F3: Slutresultat-endpoint (afslut spil + final leaderboard) #15
2
TODO.md
2
TODO.md
@@ -60,7 +60,7 @@ Byg **Weirsøe Party Protocol**: en dansk party-webapp platform ala Jackbox, hvo
|
||||
- [x] Guessfase: alle gætter inden Z sek
|
||||
- [x] Pointudregning (konfigurerbar pr. runde)
|
||||
- [x] Scoreboard + næste spørgsmål/runde
|
||||
- [ ] Slutresultat
|
||||
- [x] Slutresultat
|
||||
|
||||
### Fase 4 — Voice-acting (platformkrav)
|
||||
- [ ] Definér TTS provider-interface
|
||||
|
||||
@@ -584,6 +584,53 @@ class RevealRoundFlowTests(TestCase):
|
||||
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")
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ urlpatterns = [
|
||||
name="calculate_scores",
|
||||
),
|
||||
path("sessions/<str:code>/scoreboard", views.reveal_scoreboard, name="reveal_scoreboard"),
|
||||
path("sessions/<str:code>/finish", views.finish_game, name="finish_game"),
|
||||
path("sessions/<str:code>/rounds/next", views.start_next_round, name="start_next_round"),
|
||||
]
|
||||
|
||||
|
||||
@@ -567,6 +567,48 @@ def start_next_round(request: HttpRequest, code: str) -> JsonResponse:
|
||||
}
|
||||
)
|
||||
|
||||
@require_POST
|
||||
@login_required
|
||||
def finish_game(request: HttpRequest, code: str) -> JsonResponse:
|
||||
session_code = code.strip().upper()
|
||||
|
||||
try:
|
||||
session = GameSession.objects.get(code=session_code)
|
||||
except GameSession.DoesNotExist:
|
||||
return JsonResponse({"error": "Session not found"}, status=404)
|
||||
|
||||
if session.host_id != request.user.id:
|
||||
return JsonResponse({"error": "Only host can finish game"}, status=403)
|
||||
|
||||
with transaction.atomic():
|
||||
locked_session = GameSession.objects.select_for_update().get(pk=session.pk)
|
||||
if locked_session.status != GameSession.Status.REVEAL:
|
||||
return JsonResponse({"error": "Game can only be finished from reveal phase"}, status=400)
|
||||
|
||||
locked_session.status = GameSession.Status.FINISHED
|
||||
locked_session.save(update_fields=["status"])
|
||||
|
||||
leaderboard = list(
|
||||
Player.objects.filter(session=session)
|
||||
.order_by("-score", "nickname")
|
||||
.values("id", "nickname", "score")
|
||||
)
|
||||
|
||||
winner = leaderboard[0] if leaderboard else None
|
||||
|
||||
return JsonResponse(
|
||||
{
|
||||
"session": {
|
||||
"code": session.code,
|
||||
"status": GameSession.Status.FINISHED,
|
||||
"current_round": session.current_round,
|
||||
},
|
||||
"winner": winner,
|
||||
"leaderboard": leaderboard,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@require_POST
|
||||
@login_required
|
||||
def calculate_scores(request: HttpRequest, code: str, round_question_id: int) -> JsonResponse:
|
||||
|
||||
Reference in New Issue
Block a user