401 lines
16 KiB
Python
401 lines
16 KiB
Python
from datetime import timedelta
|
||
from django.test import TestCase
|
||
from django.urls import reverse
|
||
from django.utils import timezone
|
||
|
||
from apps.lyric.models import Token, User
|
||
from apps.epic.models import GateSlot, Room, RoomInvite
|
||
|
||
|
||
class RoomCreationViewTest(TestCase):
|
||
def setUp(self):
|
||
self.user = User.objects.create(email="founder@test.io")
|
||
self.client.force_login(self.user)
|
||
|
||
def test_post_creates_room_and_redirects_to_gatekeeper(self):
|
||
response = self.client.post(
|
||
reverse("epic:create_room"),
|
||
data={"name": "Test Room"},
|
||
)
|
||
room = Room.objects.get(owner=self.user)
|
||
self.assertRedirects(
|
||
response, reverse(
|
||
"epic:gatekeeper",
|
||
args=[room.id],
|
||
)
|
||
)
|
||
|
||
def test_post_requires_login(self):
|
||
self.client.logout()
|
||
response = self.client.post(
|
||
reverse("epic:create_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):
|
||
def setUp(self):
|
||
self.user = User.objects.create(email="gamer@example.com")
|
||
self.client.force_login(self.user)
|
||
|
||
def test_gameboard_context_includes_owned_rooms(self):
|
||
room = Room.objects.create(name="Durango", owner=self.user)
|
||
response = self.client.get("/gameboard/")
|
||
self.assertIn(room, response.context["my_games"])
|
||
|
||
def test_gameboard_context_includes_rooms_with_filled_slot(self):
|
||
other = User.objects.create(email="friend@example.com")
|
||
room = Room.objects.create(name="Their Room", owner=other)
|
||
slot = room.gate_slots.get(slot_number=2)
|
||
slot.gamer = self.user
|
||
slot.status = "FILLED"
|
||
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")
|
||
|
||
|
||
class DropTokenViewTest(TestCase):
|
||
def setUp(self):
|
||
self.gamer = User.objects.create(email="gamer@test.io")
|
||
self.client.force_login(self.gamer)
|
||
owner = User.objects.create(email="owner@test.io")
|
||
self.room = Room.objects.create(name="Test Room", owner=owner)
|
||
|
||
def test_drop_token_reserves_lowest_empty_slot(self):
|
||
self.client.post(reverse("epic:drop_token", kwargs={"room_id": self.room.id}))
|
||
slot = self.room.gate_slots.get(slot_number=1)
|
||
self.assertEqual(slot.status, GateSlot.RESERVED)
|
||
self.assertEqual(slot.gamer, self.gamer)
|
||
|
||
def test_drop_token_skips_already_filled_slots(self):
|
||
other = User.objects.create(email="other@test.io")
|
||
slot1 = self.room.gate_slots.get(slot_number=1)
|
||
slot1.gamer = other
|
||
slot1.status = GateSlot.FILLED
|
||
slot1.save()
|
||
self.client.post(reverse("epic:drop_token", kwargs={"room_id": self.room.id}))
|
||
slot2 = self.room.gate_slots.get(slot_number=2)
|
||
self.assertEqual(slot2.status, GateSlot.RESERVED)
|
||
self.assertEqual(slot2.gamer, self.gamer)
|
||
|
||
def test_drop_token_blocked_when_another_slot_reserved(self):
|
||
other = User.objects.create(email="other@test.io")
|
||
slot1 = self.room.gate_slots.get(slot_number=1)
|
||
slot1.gamer = other
|
||
slot1.status = GateSlot.RESERVED
|
||
slot1.reserved_at = timezone.now()
|
||
slot1.save()
|
||
self.client.post(reverse("epic:drop_token", kwargs={"room_id": self.room.id}))
|
||
# Slot 2 should remain EMPTY — lock held by other user
|
||
slot2 = self.room.gate_slots.get(slot_number=2)
|
||
self.assertEqual(slot2.status, GateSlot.EMPTY)
|
||
|
||
def test_drop_token_blocked_when_user_already_has_filled_slot(self):
|
||
slot1 = self.room.gate_slots.get(slot_number=1)
|
||
slot1.gamer = self.gamer
|
||
slot1.status = GateSlot.FILLED
|
||
slot1.save()
|
||
self.client.post(reverse("epic:drop_token", kwargs={"room_id": self.room.id}))
|
||
slot2 = self.room.gate_slots.get(slot_number=2)
|
||
self.assertEqual(slot2.status, GateSlot.EMPTY)
|
||
|
||
def test_drop_token_sets_reserved_at(self):
|
||
self.client.post(reverse("epic:drop_token", kwargs={"room_id": self.room.id}))
|
||
slot = self.room.gate_slots.get(slot_number=1)
|
||
self.assertIsNotNone(slot.reserved_at)
|
||
|
||
def test_drop_token_redirects_to_gatekeeper(self):
|
||
response = self.client.post(
|
||
reverse("epic:drop_token", kwargs={"room_id": self.room.id})
|
||
)
|
||
self.assertRedirects(
|
||
response, reverse("epic:gatekeeper", args=[self.room.id])
|
||
)
|
||
|
||
|
||
class ConfirmTokenViewTest(TestCase):
|
||
def setUp(self):
|
||
self.gamer = User.objects.create(email="gamer@test.io")
|
||
self.client.force_login(self.gamer)
|
||
owner = User.objects.create(email="owner@test.io")
|
||
self.room = Room.objects.create(name="Test Room", owner=owner)
|
||
self.slot = self.room.gate_slots.get(slot_number=1)
|
||
self.slot.gamer = self.gamer
|
||
self.slot.status = GateSlot.RESERVED
|
||
self.slot.reserved_at = timezone.now()
|
||
self.slot.save()
|
||
Token.objects.create(user=self.gamer, token_type=Token.FREE)
|
||
|
||
def test_confirm_marks_slot_filled(self):
|
||
self.client.post(
|
||
reverse("epic:confirm_token", kwargs={"room_id": self.room.id})
|
||
)
|
||
self.slot.refresh_from_db()
|
||
self.assertEqual(self.slot.status, GateSlot.FILLED)
|
||
|
||
def test_confirm_sets_gate_open_when_all_slots_filled(self):
|
||
# Fill slots 2–6 via ORM
|
||
for i in range(2, 7):
|
||
other = User.objects.create(email=f"g{i}@test.io")
|
||
s = self.room.gate_slots.get(slot_number=i)
|
||
s.gamer = other
|
||
s.status = GateSlot.FILLED
|
||
s.save()
|
||
self.client.post(
|
||
reverse("epic:confirm_token", kwargs={"room_id": self.room.id})
|
||
)
|
||
self.room.refresh_from_db()
|
||
self.assertEqual(self.room.gate_status, Room.OPEN)
|
||
|
||
def test_confirm_redirects_to_gatekeeper(self):
|
||
response = self.client.post(
|
||
reverse("epic:confirm_token", kwargs={"room_id": self.room.id})
|
||
)
|
||
self.assertRedirects(
|
||
response, reverse("epic:gatekeeper", args=[self.room.id])
|
||
)
|
||
|
||
def test_confirm_does_nothing_without_reserved_slot(self):
|
||
self.slot.status = GateSlot.EMPTY
|
||
self.slot.gamer = None
|
||
self.slot.save()
|
||
self.client.post(
|
||
reverse("epic:confirm_token", kwargs={"room_id": self.room.id})
|
||
)
|
||
self.slot.refresh_from_db()
|
||
self.assertEqual(self.slot.status, GateSlot.EMPTY)
|
||
|
||
|
||
class ReturnTokenViewTest(TestCase):
|
||
def setUp(self):
|
||
self.gamer = User.objects.create(email="gamer@test.io")
|
||
self.client.force_login(self.gamer)
|
||
owner = User.objects.create(email="owner@test.io")
|
||
self.room = Room.objects.create(name="Test Room", owner=owner)
|
||
self.slot = self.room.gate_slots.get(slot_number=1)
|
||
self.slot.gamer = self.gamer
|
||
self.slot.status = GateSlot.RESERVED
|
||
self.slot.reserved_at = timezone.now()
|
||
self.slot.save()
|
||
|
||
def test_return_clears_reserved_slot(self):
|
||
self.client.post(
|
||
reverse("epic:return_token", kwargs={"room_id": self.room.id})
|
||
)
|
||
self.slot.refresh_from_db()
|
||
self.assertEqual(self.slot.status, GateSlot.EMPTY)
|
||
self.assertIsNone(self.slot.gamer)
|
||
self.assertIsNone(self.slot.reserved_at)
|
||
|
||
def test_return_after_confirm_clears_filled_slot(self):
|
||
self.slot.status = GateSlot.FILLED
|
||
self.slot.save()
|
||
self.client.post(
|
||
reverse("epic:return_token", kwargs={"room_id": self.room.id})
|
||
)
|
||
self.slot.refresh_from_db()
|
||
self.assertEqual(self.slot.status, GateSlot.EMPTY)
|
||
self.assertIsNone(self.slot.gamer)
|
||
|
||
def test_return_redirects_to_gatekeeper(self):
|
||
response = self.client.post(
|
||
reverse("epic:return_token", kwargs={"room_id": self.room.id})
|
||
)
|
||
self.assertRedirects(
|
||
response, reverse("epic:gatekeeper", args=[self.room.id])
|
||
)
|
||
|
||
def test_return_restores_coin_token(self):
|
||
coin = Token.objects.get(user=self.gamer, token_type=Token.COIN)
|
||
coin.current_room = self.room
|
||
coin.next_ready_at = timezone.now() + timedelta(days=7)
|
||
coin.save()
|
||
self.slot.status = GateSlot.FILLED
|
||
self.slot.debited_token_type = Token.COIN
|
||
self.slot.save()
|
||
self.client.post(
|
||
reverse("epic:return_token", kwargs={"room_id": self.room.id})
|
||
)
|
||
coin.refresh_from_db()
|
||
self.assertIsNone(coin.current_room)
|
||
self.assertIsNone(coin.next_ready_at)
|
||
|
||
def test_return_restores_free_token(self):
|
||
Token.objects.filter(user=self.gamer, token_type=Token.FREE).delete()
|
||
expires = timezone.now() + timedelta(days=3)
|
||
self.slot.status = GateSlot.FILLED
|
||
self.slot.debited_token_type = Token.FREE
|
||
self.slot.debited_token_expires_at = expires
|
||
self.slot.save()
|
||
self.client.post(
|
||
reverse("epic:return_token", kwargs={"room_id": self.room.id})
|
||
)
|
||
restored = Token.objects.filter(user=self.gamer, token_type=Token.FREE).first()
|
||
self.assertIsNotNone(restored)
|
||
self.assertEqual(restored.expires_at, expires)
|
||
|
||
def test_return_restores_tithe_token(self):
|
||
self.slot.status = GateSlot.FILLED
|
||
self.slot.debited_token_type = Token.TITHE
|
||
self.slot.save()
|
||
self.client.post(
|
||
reverse("epic:return_token", kwargs={"room_id": self.room.id})
|
||
)
|
||
self.assertTrue(
|
||
Token.objects.filter(user=self.gamer, token_type=Token.TITHE).exists()
|
||
)
|
||
|
||
|
||
class DropTokenAvailabilityViewTest(TestCase):
|
||
def setUp(self):
|
||
self.gamer = User.objects.create(email="gamer@test.io")
|
||
self.client.force_login(self.gamer)
|
||
owner = User.objects.create(email="owner@test.io")
|
||
self.room = Room.objects.create(name="Test Room", owner=owner)
|
||
self.other_room = Room.objects.create(name="Other Room", owner=owner)
|
||
self.coin = Token.objects.get(user=self.gamer, token_type=Token.COIN)
|
||
|
||
def test_drop_reserves_slot_when_tokens_available(self):
|
||
self.client.post(reverse("epic:drop_token", kwargs={"room_id": self.room.id}))
|
||
slot = self.room.gate_slots.get(slot_number=1)
|
||
self.assertEqual(slot.status, GateSlot.RESERVED)
|
||
# token not debited yet — that happens at confirm
|
||
self.coin.refresh_from_db()
|
||
self.assertIsNone(self.coin.current_room)
|
||
|
||
def test_drop_returns_402_when_all_tokens_depleted(self):
|
||
self.coin.current_room = self.other_room
|
||
self.coin.save()
|
||
Token.objects.filter(user=self.gamer, token_type=Token.FREE).delete()
|
||
response = self.client.post(
|
||
reverse("epic:drop_token", kwargs={"room_id": self.room.id})
|
||
)
|
||
self.assertEqual(response.status_code, 402)
|
||
|
||
|
||
class ConfirmTokenPriorityViewTest(TestCase):
|
||
def setUp(self):
|
||
self.gamer = User.objects.create(email="gamer@test.io")
|
||
self.client.force_login(self.gamer)
|
||
owner = User.objects.create(email="owner@test.io")
|
||
self.room = Room.objects.create(name="Test Room", owner=owner)
|
||
self.other_room = Room.objects.create(name="Other Room", owner=owner)
|
||
self.slot = self.room.gate_slots.get(slot_number=1)
|
||
self.slot.gamer = self.gamer
|
||
self.slot.status = GateSlot.RESERVED
|
||
self.slot.reserved_at = timezone.now()
|
||
self.slot.save()
|
||
self.coin = Token.objects.get(user=self.gamer, token_type=Token.COIN)
|
||
|
||
def test_confirm_leases_coin_to_room(self):
|
||
self.client.post(reverse("epic:confirm_token", kwargs={"room_id": self.room.id}))
|
||
self.coin.refresh_from_db()
|
||
self.assertEqual(self.coin.current_room, self.room)
|
||
self.assertTrue(Token.objects.filter(pk=self.coin.pk).exists())
|
||
|
||
def test_confirm_uses_free_token_when_coin_in_use(self):
|
||
self.coin.current_room = self.other_room
|
||
self.coin.save()
|
||
self.client.post(reverse("epic:confirm_token", kwargs={"room_id": self.room.id}))
|
||
self.assertEqual(
|
||
Token.objects.filter(user=self.gamer, token_type=Token.FREE).count(), 0
|
||
)
|
||
self.coin.refresh_from_db()
|
||
self.assertEqual(self.coin.current_room, self.other_room)
|
||
|
||
def test_confirm_uses_tithe_when_free_tokens_exhausted(self):
|
||
self.coin.current_room = self.other_room
|
||
self.coin.save()
|
||
Token.objects.filter(user=self.gamer, token_type=Token.FREE).delete()
|
||
tithe = Token.objects.create(user=self.gamer, token_type=Token.TITHE)
|
||
self.client.post(reverse("epic:confirm_token", kwargs={"room_id": self.room.id}))
|
||
self.assertFalse(Token.objects.filter(pk=tithe.pk).exists())
|
||
|
||
def test_pass_not_consumed_and_coin_not_leased(self):
|
||
self.gamer.is_staff = True
|
||
self.gamer.save()
|
||
pass_token = Token.objects.create(user=self.gamer, token_type=Token.PASS)
|
||
self.client.post(reverse("epic:confirm_token", kwargs={"room_id": self.room.id}))
|
||
self.assertTrue(Token.objects.filter(pk=pass_token.pk).exists())
|
||
self.coin.refresh_from_db()
|
||
self.assertIsNone(self.coin.current_room)
|
||
|
||
|
||
class RoomActionsViewTest(TestCase):
|
||
def setUp(self):
|
||
self.owner = User.objects.create(email="owner@test.io")
|
||
self.gamer = User.objects.create(email="gamer@test.io")
|
||
self.room = Room.objects.create(name="Test Room", owner=self.owner)
|
||
self.slot = self.room.gate_slots.get(slot_number=2)
|
||
self.slot.gamer = self.gamer
|
||
self.slot.status = "FILLED"
|
||
self.slot.save()
|
||
RoomInvite.objects.create(
|
||
room=self.room, inviter=self.owner,
|
||
invitee_email=self.gamer.email
|
||
)
|
||
|
||
def test_owner_delete_removes_room(self):
|
||
self.client.force_login(self.owner)
|
||
self.client.post(reverse("epic:delete_room", kwargs={"room_id": self.room.id}))
|
||
self.assertFalse(Room.objects.filter(pk=self.room.pk).exists())
|
||
|
||
def test_non_owner_delete_does_not_remove_room(self):
|
||
self.client.force_login(self.gamer)
|
||
self.client.post(reverse("epic:delete_room", kwargs={"room_id": self.room.id}))
|
||
self.assertTrue(Room.objects.filter(pk=self.room.pk).exists())
|
||
|
||
def test_delete_redirects_to_gameboard(self):
|
||
self.client.force_login(self.owner)
|
||
response = self.client.post(
|
||
reverse("epic:delete_room", kwargs={"room_id": self.room.id})
|
||
)
|
||
self.assertRedirects(response, "/gameboard/")
|
||
|
||
def test_abandon_clears_slot(self):
|
||
self.client.force_login(self.gamer)
|
||
self.client.post(reverse("epic:abandon_room", kwargs={"room_id": self.room.id}))
|
||
self.slot.refresh_from_db()
|
||
self.assertEqual(self.slot.status, "EMPTY")
|
||
self.assertIsNone(self.slot.gamer)
|
||
|
||
def test_abandon_deletes_pending_invite(self):
|
||
self.client.force_login(self.gamer)
|
||
self.client.post(reverse("epic:abandon_room", kwargs={"room_id": self.room.id}))
|
||
self.assertFalse(
|
||
RoomInvite.objects.filter(
|
||
room=self.room, invitee_email=self.gamer.email
|
||
).exists()
|
||
)
|
||
|
||
def test_abandon_redirects_to_gameboard(self):
|
||
self.client.force_login(self.gamer)
|
||
response = self.client.post(
|
||
reverse("epic:abandon_room", kwargs={"room_id": self.room.id})
|
||
)
|
||
self.assertRedirects(response, "/gameboard/")
|