diff --git a/src/.coveragerc b/src/.coveragerc index 78d398c..cf2fe04 100644 --- a/src/.coveragerc +++ b/src/.coveragerc @@ -3,6 +3,7 @@ source = apps omit = */migrations/* */tests/* + */routing.py [report] show_missing = true \ No newline at end of file diff --git a/src/apps/epic/models.py b/src/apps/epic/models.py index 0743240..eb33134 100644 --- a/src/apps/epic/models.py +++ b/src/apps/epic/models.py @@ -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() diff --git a/src/apps/epic/tests/integrated/test_models.py b/src/apps/epic/tests/integrated/test_models.py index 24f6f99..1dd5a00 100644 --- a/src/apps/epic/tests/integrated/test_models.py +++ b/src/apps/epic/tests/integrated/test_models.py @@ -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): diff --git a/src/apps/epic/tests/integrated/test_views.py b/src/apps/epic/tests/integrated/test_views.py index 9199dc2..48ebd18 100644 --- a/src/apps/epic/tests/integrated/test_views.py +++ b/src/apps/epic/tests/integrated/test_views.py @@ -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") diff --git a/src/apps/epic/urls.py b/src/apps/epic/urls.py index 40db1a8..39ad946 100644 --- a/src/apps/epic/urls.py +++ b/src/apps/epic/urls.py @@ -9,5 +9,6 @@ urlpatterns = [ path('room//gate/', views.gatekeeper, name='gatekeeper'), path('room//gate//drop_token', views.drop_token, name='drop_token'), path('room//gate/invite', views.invite_gamer, name='invite_gamer'), + path('room//gate/status', views.gate_status, name='gate_status'), ] diff --git a/src/apps/epic/views.py b/src/apps/epic/views.py index 5e1991d..c29af63 100644 --- a/src/apps/epic/views.py +++ b/src/apps/epic/views.py @@ -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, + }) diff --git a/src/apps/lyric/tests/integrated/test_models.py b/src/apps/lyric/tests/integrated/test_models.py index cacf67e..7f7b953 100644 --- a/src/apps/lyric/tests/integrated/test_models.py +++ b/src/apps/lyric/tests/integrated/test_models.py @@ -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): diff --git a/src/core/urls.py b/src/core/urls.py index d4ad285..9ead4bb 100644 --- a/src/core/urls.py +++ b/src/core/urls.py @@ -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 diff --git a/src/functional_tests/test_gatekeeper.py b/src/functional_tests/test_gatekeeper.py index ff7a558..b9b28fc 100644 --- a/src/functional_tests/test_gatekeeper.py +++ b/src/functional_tests/test_gatekeeper.py @@ -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) diff --git a/src/templates/apps/gameboard/_partials/_gatekeeper.html b/src/templates/apps/gameboard/_partials/_gatekeeper.html index 4525141..1d11da7 100644 --- a/src/templates/apps/gameboard/_partials/_gatekeeper.html +++ b/src/templates/apps/gameboard/_partials/_gatekeeper.html @@ -1,4 +1,4 @@ -
+ - {% if room.gate_status == "GATHERING" %} -
- {% include "apps/gameboard/_partials/_gatekeeper.html" %} +
+ {% if room.gate_status == "GATHERING" %} +
+ {% include "apps/gameboard/_partials/_gatekeeper.html" %} +
+ {% endif %}
- {% endif %}
{% endblock content %} \ No newline at end of file