covered some test lacunae; gatekeeper now waits for +6 gamers to commit tokens to unblock game room

This commit is contained in:
Disco DeDisco
2026-03-13 22:51:42 -04:00
parent e0d1f51bf1
commit dddffd22d5
11 changed files with 121 additions and 8 deletions

View File

@@ -3,6 +3,7 @@ source = apps
omit =
*/migrations/*
*/tests/*
*/routing.py
[report]
show_missing = true

View File

@@ -96,7 +96,8 @@ def create_gate_slots(sender, instance, created, **kwargs):
def debit_token(user, slot, token):
if token.token_type == Token.COIN:
token.current_room = slot.room
token.next_ready_at = timezone.now() + slot.room.renewal_period
period = slot.room.renewal_period or timedelta(days=7)
token.next_ready_at = timezone.now() + period
token.save()
else:
token.delete()
@@ -104,3 +105,8 @@ def debit_token(user, slot, token):
slot.status = GateSlot.FILLED
slot.filled_at = timezone.now()
slot.save()
room = slot.room
if not room.gate_slots.filter(status=GateSlot.EMPTY).exists():
room.gate_status = Room.OPEN
room.save()

View File

@@ -40,6 +40,18 @@ class DebitTokenTest(TestCase):
self.assertEqual(self.slot.status, GateSlot.FILLED)
self.assertEqual(self.slot.gamer, self.owner)
def test_debit_fills_last_slot_and_opens_gate(self):
for i in range(2, 7):
gamer = User.objects.create(email=f"g{i}@test.io")
slot = self.room.gate_slots.get(slot_number=i)
slot.gamer = gamer
slot.status = GateSlot.FILLED
slot.save()
free_token = Token.objects.get(user=self.owner, token_type=Token.FREE)
debit_token(self.owner, self.slot, free_token)
self.room.refresh_from_db()
self.assertEqual(self.room.gate_status, Room.OPEN)
class CoinTokenInUseTest(TestCase):
def setUp(self):

View File

@@ -30,6 +30,10 @@ class RoomCreationViewTest(TestCase):
data={"name": "Test Room"},
)
def test_create_room_get_redirects_to_gameboard(self):
response = self.client.get(reverse("epic:create_room"))
self.assertRedirects(response, "/gameboard/")
class MyGamesContextTest(TestCase):
def setUp(self):
@@ -50,3 +54,23 @@ class MyGamesContextTest(TestCase):
slot.save()
response = self.client.get("/gameboard/")
self.assertIn(room, response.context["my_games"])
class GateStatusViewTest(TestCase):
def setUp(self):
self.owner = User.objects.create(email="founder@test.io")
self.client.force_login(self.owner)
self.room = Room.objects.create(name="Test Room", owner=self.owner)
def test_gate_status_returns_empty_when_open(self):
self.room.gate_status = Room.OPEN
self.room.save()
response = self.client.get(reverse("epic:gate_status", kwargs={"room_id": self.room.id}))
self.assertEqual(response.content, b"")
def test_gate_status_returns_partial_when_gathering(self):
response = self.client.get(
reverse("epic:gate_status", kwargs={"room_id": self.room.id})
)
self.assertEqual(response.status_code, 200)
self.assertContains(response, "gate-modal")

View File

@@ -9,5 +9,6 @@ urlpatterns = [
path('room/<uuid:room_id>/gate/', views.gatekeeper, name='gatekeeper'),
path('room/<uuid:room_id>/gate/<int:slot_number>/drop_token', views.drop_token, name='drop_token'),
path('room/<uuid:room_id>/gate/invite', views.invite_gamer, name='invite_gamer'),
path('room/<uuid:room_id>/gate/status', views.gate_status, name='gate_status'),
]

View File

@@ -1,4 +1,5 @@
from django.contrib.auth.decorators import login_required
from django.http import HttpResponse
from django.shortcuts import redirect, render
from apps.epic.models import Room, RoomInvite, debit_token
@@ -12,7 +13,7 @@ def create_room(request):
if name:
room = Room.objects.create(name=name, owner=request.user)
return redirect("epic:gatekeeper", room_id=room.id)
return redirect("gameboard:index")
return redirect("/gameboard/")
def gatekeeper(request, room_id):
room = Room.objects.get(id=room_id)
@@ -54,3 +55,18 @@ def invite_gamer(request, room_id):
defaults={"status": RoomInvite.PENDING}
)
return redirect("epic:gatekeeper", room_id=room_id)
def gate_status(request, room_id):
room = Room.objects.get(id=room_id)
if room.gate_status == Room.OPEN:
return HttpResponse("")
slots = room.gate_slots.order_by("slot_number")
user_has_slot = (
request.user.is_authenticated
and slots.filter(gamer=request.user).exists()
)
return render(request, "apps/gameboard/_partials/_gatekeeper.html", {
"room": room,
"slots": slots,
"user_has_slot": user_has_slot,
})

View File

@@ -143,6 +143,10 @@ class TokenTooltipTest(TestCase):
free = Token.objects.get(user=self.user, token_type=Token.FREE)
self.assertIsNone(free.tooltip_shoptalk())
def test_tooltip_room_html_returns_empty_when_no_room(self):
token = Token.objects.get(user=self.user, token_type=Token.COIN)
self.assertEqual(token.tooltip_room_html(), "")
class PaymentMethodTest(TestCase):
def setUp(self):

View File

@@ -12,7 +12,7 @@ urlpatterns = [
path('dashboard/', include('apps.dashboard.urls')),
path('lyric/', include('apps.lyric.urls')),
path('gameboard/', include('apps.gameboard.urls')),
path('gameboard/', include('apps.epic.urls', 'epic')),
path('gameboard/', include('apps.epic.urls')),
]
# Please remove the following urlpattern

View File

@@ -2,6 +2,8 @@ from selenium.webdriver.common.by import By
from .base import FunctionalTest
from apps.applets.models import Applet
from apps.epic.models import Room, GateSlot
from apps.lyric.models import User
class GatekeeperTest(FunctionalTest):
@@ -134,3 +136,43 @@ class GatekeeperTest(FunctionalTest):
len(self.browser.find_elements(By.CSS_SELECTOR, ".gate-slot.filled")), 2
)
)
def test_gate_opens_when_all_slots_filled(self):
# 1. Founder creates room
self.create_pre_authenticated_session("founder@test.io")
self.browser.get(self.live_server_url + "/gameboard/")
self.wait_for(
lambda: self.browser.find_element(By.ID, "id_new_game_name")
)
self.browser.find_element(By.ID, "id_new_game_name").send_keys("Dragon's Den")
self.browser.find_element(By.ID, "id_create_game_btn").click()
self.wait_for(
lambda: self.assertIn("/gate/", self.browser.current_url)
)
room_url = self.browser.current_url
# 2. Fill all 6 slots directly via ORM (founder + 5 extras)
self.browser.find_element(By.CSS_SELECTOR, ".drop-token-btn").click()
self.wait_for(
lambda: self.browser.find_element(By.CSS_SELECTOR, ".gate-slot.filled")
)
room = Room.objects.get(name="Dragon's Den")
for i, email in enumerate([
"g2@test.io", "g3@test.io", "g4@test.io", "g5@test.io", "g6@test.io"
], start=2):
gamer = User.objects.create(email=email)
slot = room.gate_slots.get(slot_number=i)
slot.gamer = gamer
slot.status = GateSlot.FILLED
slot.save()
room.refresh_from_db()
room.gate_status = Room.OPEN
room.save()
# 3. Gatekeeper disappears via htmx
self.wait_for(
lambda: self.assertEqual(
len(self.browser.find_elements(By.CSS_SELECTOR, ".gate-modal")), 0
)
)
# Restore the following once room built
# body = self.browser.find_element(By.TAG_NAME, "body")
# self.assertIn("OPEN", body.text)

View File

@@ -1,4 +1,4 @@
<div class="gate-modal" role"dialog" aria-label="Gatekeeper">
<div class="gate-modal" role="dialog" aria-label="Gatekeeper">
<header class="gate-header">
<h1>{{ room.name }}</h1>
<span class="gate-status">{{ room.gate_status }}</span>

View File

@@ -7,10 +7,17 @@
<div class="room-table"></div>
</div>
<div
id="id_gate_wrapper"
hx-get="{% url 'epic:gate_status' room.id %}"
hx-trigger="every 3s"
hx-swap="outerHTML"
>
{% if room.gate_status == "GATHERING" %}
<div class="gate-overlay">
{% include "apps/gameboard/_partials/_gatekeeper.html" %}
</div>
{% endif %}
</div>
</div>
{% endblock content %}