covered some test lacunae; gatekeeper now waits for +6 gamers to commit tokens to unblock game room
This commit is contained in:
@@ -3,6 +3,7 @@ source = apps
|
||||
omit =
|
||||
*/migrations/*
|
||||
*/tests/*
|
||||
*/routing.py
|
||||
|
||||
[report]
|
||||
show_missing = true
|
||||
@@ -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()
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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'),
|
||||
]
|
||||
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -7,10 +7,17 @@
|
||||
<div class="room-table"></div>
|
||||
</div>
|
||||
|
||||
{% if room.gate_status == "GATHERING" %}
|
||||
<div class="gate-overlay">
|
||||
{% include "apps/gameboard/_partials/_gatekeeper.html" %}
|
||||
<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>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock content %}
|
||||
Reference in New Issue
Block a user