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 =
|
omit =
|
||||||
*/migrations/*
|
*/migrations/*
|
||||||
*/tests/*
|
*/tests/*
|
||||||
|
*/routing.py
|
||||||
|
|
||||||
[report]
|
[report]
|
||||||
show_missing = true
|
show_missing = true
|
||||||
@@ -96,7 +96,8 @@ def create_gate_slots(sender, instance, created, **kwargs):
|
|||||||
def debit_token(user, slot, token):
|
def debit_token(user, slot, token):
|
||||||
if token.token_type == Token.COIN:
|
if token.token_type == Token.COIN:
|
||||||
token.current_room = slot.room
|
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()
|
token.save()
|
||||||
else:
|
else:
|
||||||
token.delete()
|
token.delete()
|
||||||
@@ -104,3 +105,8 @@ def debit_token(user, slot, token):
|
|||||||
slot.status = GateSlot.FILLED
|
slot.status = GateSlot.FILLED
|
||||||
slot.filled_at = timezone.now()
|
slot.filled_at = timezone.now()
|
||||||
slot.save()
|
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.status, GateSlot.FILLED)
|
||||||
self.assertEqual(self.slot.gamer, self.owner)
|
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):
|
class CoinTokenInUseTest(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|||||||
@@ -30,6 +30,10 @@ class RoomCreationViewTest(TestCase):
|
|||||||
data={"name": "Test Room"},
|
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):
|
class MyGamesContextTest(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@@ -50,3 +54,23 @@ class MyGamesContextTest(TestCase):
|
|||||||
slot.save()
|
slot.save()
|
||||||
response = self.client.get("/gameboard/")
|
response = self.client.get("/gameboard/")
|
||||||
self.assertIn(room, response.context["my_games"])
|
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/', 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/<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/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.contrib.auth.decorators import login_required
|
||||||
|
from django.http import HttpResponse
|
||||||
from django.shortcuts import redirect, render
|
from django.shortcuts import redirect, render
|
||||||
|
|
||||||
from apps.epic.models import Room, RoomInvite, debit_token
|
from apps.epic.models import Room, RoomInvite, debit_token
|
||||||
@@ -12,7 +13,7 @@ def create_room(request):
|
|||||||
if name:
|
if name:
|
||||||
room = Room.objects.create(name=name, owner=request.user)
|
room = Room.objects.create(name=name, owner=request.user)
|
||||||
return redirect("epic:gatekeeper", room_id=room.id)
|
return redirect("epic:gatekeeper", room_id=room.id)
|
||||||
return redirect("gameboard:index")
|
return redirect("/gameboard/")
|
||||||
|
|
||||||
def gatekeeper(request, room_id):
|
def gatekeeper(request, room_id):
|
||||||
room = Room.objects.get(id=room_id)
|
room = Room.objects.get(id=room_id)
|
||||||
@@ -54,3 +55,18 @@ def invite_gamer(request, room_id):
|
|||||||
defaults={"status": RoomInvite.PENDING}
|
defaults={"status": RoomInvite.PENDING}
|
||||||
)
|
)
|
||||||
return redirect("epic:gatekeeper", room_id=room_id)
|
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)
|
free = Token.objects.get(user=self.user, token_type=Token.FREE)
|
||||||
self.assertIsNone(free.tooltip_shoptalk())
|
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):
|
class PaymentMethodTest(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ urlpatterns = [
|
|||||||
path('dashboard/', include('apps.dashboard.urls')),
|
path('dashboard/', include('apps.dashboard.urls')),
|
||||||
path('lyric/', include('apps.lyric.urls')),
|
path('lyric/', include('apps.lyric.urls')),
|
||||||
path('gameboard/', include('apps.gameboard.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
|
# Please remove the following urlpattern
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ from selenium.webdriver.common.by import By
|
|||||||
|
|
||||||
from .base import FunctionalTest
|
from .base import FunctionalTest
|
||||||
from apps.applets.models import Applet
|
from apps.applets.models import Applet
|
||||||
|
from apps.epic.models import Room, GateSlot
|
||||||
|
from apps.lyric.models import User
|
||||||
|
|
||||||
|
|
||||||
class GatekeeperTest(FunctionalTest):
|
class GatekeeperTest(FunctionalTest):
|
||||||
@@ -134,3 +136,43 @@ class GatekeeperTest(FunctionalTest):
|
|||||||
len(self.browser.find_elements(By.CSS_SELECTOR, ".gate-slot.filled")), 2
|
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">
|
<header class="gate-header">
|
||||||
<h1>{{ room.name }}</h1>
|
<h1>{{ room.name }}</h1>
|
||||||
<span class="gate-status">{{ room.gate_status }}</span>
|
<span class="gate-status">{{ room.gate_status }}</span>
|
||||||
|
|||||||
@@ -7,10 +7,17 @@
|
|||||||
<div class="room-table"></div>
|
<div class="room-table"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if room.gate_status == "GATHERING" %}
|
<div
|
||||||
<div class="gate-overlay">
|
id="id_gate_wrapper"
|
||||||
{% include "apps/gameboard/_partials/_gatekeeper.html" %}
|
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>
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
Reference in New Issue
Block a user