2026-03-13 00:31:17 -04:00
|
|
|
|
from selenium.webdriver.common.by import By
|
|
|
|
|
|
|
|
|
|
|
|
from .base import FunctionalTest
|
|
|
|
|
|
from apps.applets.models import Applet
|
2026-03-13 22:51:42 -04:00
|
|
|
|
from apps.epic.models import Room, GateSlot
|
|
|
|
|
|
from apps.lyric.models import User
|
2026-03-13 00:31:17 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class GatekeeperTest(FunctionalTest):
|
|
|
|
|
|
def setUp(self):
|
|
|
|
|
|
super().setUp()
|
|
|
|
|
|
Applet.objects.get_or_create(
|
|
|
|
|
|
slug="new-game", defaults={"name": "New Game", "context": "gameboard"}
|
|
|
|
|
|
)
|
2026-03-13 17:31:52 -04:00
|
|
|
|
Applet.objects.get_or_create(
|
|
|
|
|
|
slug="my-games", defaults={"name": "My Games", "context": "gameboard"}
|
|
|
|
|
|
)
|
2026-03-13 00:31:17 -04:00
|
|
|
|
|
|
|
|
|
|
def test_founder_creates_room_and_sees_gatekeeper(self):
|
|
|
|
|
|
# 1. Log in, navigate to gameboard
|
|
|
|
|
|
self.create_pre_authenticated_session("founder@test.io")
|
|
|
|
|
|
self.browser.get(self.live_server_url + "/gameboard/")
|
|
|
|
|
|
# 2. New Game applet has room name input, create button
|
|
|
|
|
|
self.wait_for(
|
|
|
|
|
|
lambda: self.browser.find_element(By.ID, "id_applet_new_game")
|
|
|
|
|
|
)
|
|
|
|
|
|
self.browser.find_element(By.ID, "id_new_game_name").send_keys("Test Room")
|
|
|
|
|
|
self.browser.find_element(By.ID, "id_create_game_btn").click()
|
|
|
|
|
|
# 3. User is redirected to Gatekeeper page for new room
|
|
|
|
|
|
self.wait_for(
|
|
|
|
|
|
lambda: self.assertIn("/gameboard/room/", self.browser.current_url)
|
|
|
|
|
|
)
|
|
|
|
|
|
self.wait_for(
|
|
|
|
|
|
lambda: self.assertIn("/gate/", self.browser.current_url)
|
|
|
|
|
|
)
|
|
|
|
|
|
# 4. Page shows room name, GATHERING status
|
|
|
|
|
|
body = self.browser.find_element(By.TAG_NAME, "body")
|
|
|
|
|
|
self.assertIn("Test Room", body.text)
|
|
|
|
|
|
self.assertIn("GATHERING", body.text)
|
|
|
|
|
|
# 5. Six token slots are visible
|
|
|
|
|
|
slots = self.browser.find_elements(By.CSS_SELECTOR, ".gate-slot")
|
|
|
|
|
|
self.assertEqual(len(slots), 6)
|
|
|
|
|
|
# 6. Slot 1 has Drop Token btn; slots 2–6 show as empty
|
|
|
|
|
|
slot_1 = slots[0]
|
|
|
|
|
|
slot_1.find_element(By.CSS_SELECTOR, ".drop-token-btn")
|
|
|
|
|
|
for slot in slots[1:]:
|
|
|
|
|
|
self.assertIn("empty", slot.get_attribute("class"))
|
2026-03-13 17:31:52 -04:00
|
|
|
|
|
|
|
|
|
|
def test_founder_drops_token_and_slot_fills(self):
|
|
|
|
|
|
# 1. Set up: log in, create room, arrive at gatekeeper
|
|
|
|
|
|
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)
|
|
|
|
|
|
)
|
|
|
|
|
|
# 2. Founder clicks Drop Token on slot 1
|
|
|
|
|
|
drop_btn = self.wait_for(
|
|
|
|
|
|
lambda: self.browser.find_element(By.CSS_SELECTOR, ".drop-token-btn")
|
|
|
|
|
|
)
|
|
|
|
|
|
drop_btn.click()
|
|
|
|
|
|
|
|
|
|
|
|
# 3. Slot 1 now filled; drop btn gone
|
|
|
|
|
|
self.wait_for(
|
|
|
|
|
|
lambda: self.browser.find_element(By.CSS_SELECTOR, ".gate-slot.filled")
|
|
|
|
|
|
)
|
|
|
|
|
|
slots = self.browser.find_elements(By.CSS_SELECTOR, ".gate-slot")
|
|
|
|
|
|
self.assertIn("filled", slots[0].get_attribute("class"))
|
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
|
len(self.browser.find_elements(By.CSS_SELECTOR, ".drop-token-btn")), 0
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def test_room_appears_in_my_games_after_creation(self):
|
|
|
|
|
|
# 1. Set up founder, game room, name
|
|
|
|
|
|
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)
|
|
|
|
|
|
)
|
|
|
|
|
|
# 2. Navigate back to gameboard
|
|
|
|
|
|
self.browser.get(self.live_server_url + "/gameboard/")
|
|
|
|
|
|
my_games = self.wait_for(
|
|
|
|
|
|
lambda: self.browser.find_element(By.ID, "id_applet_my_games")
|
|
|
|
|
|
)
|
|
|
|
|
|
self.assertIn("Dragon's Den", my_games.text)
|
2026-03-13 18:37:19 -04:00
|
|
|
|
|
|
|
|
|
|
def test_second_gamer_drops_token_into_open_slot(self):
|
|
|
|
|
|
# 1. Founder creates room, fills slot 1
|
|
|
|
|
|
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
|
|
|
|
|
|
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")
|
|
|
|
|
|
)
|
|
|
|
|
|
# 2. Founder invites friend via email (duplicate invite logic from My Notes applet)
|
|
|
|
|
|
invite_input = self.wait_for(
|
|
|
|
|
|
lambda: self.browser.find_element(By.ID, "id_invite_email")
|
|
|
|
|
|
)
|
|
|
|
|
|
invite_input.send_keys("friend@test.io")
|
|
|
|
|
|
self.browser.find_element(By.ID, "id_invite_btn").click()
|
|
|
|
|
|
# 3. Friend logs in, sees invitation in My Games
|
|
|
|
|
|
self.create_pre_authenticated_session("friend@test.io")
|
|
|
|
|
|
self.browser.get(self.live_server_url + "/gameboard/")
|
|
|
|
|
|
my_games = self.wait_for(
|
|
|
|
|
|
lambda: self.browser.find_element(By.ID, "id_applet_my_games")
|
|
|
|
|
|
)
|
|
|
|
|
|
self.assertIn("Dragon's Den", my_games.text)
|
|
|
|
|
|
# 3. Friend follows link to gatekeeper
|
|
|
|
|
|
self.browser.find_element(By.LINK_TEXT, "Dragon's Den").click()
|
|
|
|
|
|
# 4. Friend sees drop btn on open slot
|
|
|
|
|
|
drop_btn = self.wait_for(
|
|
|
|
|
|
lambda: self.browser.find_element(By.CSS_SELECTOR, ".drop-token-btn")
|
|
|
|
|
|
)
|
|
|
|
|
|
drop_btn.click()
|
|
|
|
|
|
# 5. Now two slots filled
|
|
|
|
|
|
self.wait_for(
|
|
|
|
|
|
lambda: self.assertEqual(
|
|
|
|
|
|
len(self.browser.find_elements(By.CSS_SELECTOR, ".gate-slot.filled")), 2
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
2026-03-13 22:51:42 -04:00
|
|
|
|
|
|
|
|
|
|
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)
|