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/")