from django.test import TestCase from django.db import IntegrityError from apps.drama.models import GameEvent, ScrollPosition, record from apps.epic.models import Room from apps.lyric.models import User class GameEventModelTest(TestCase): def setUp(self): self.user = User.objects.create(email="actor@test.io") self.room = Room.objects.create(name="Test Room", owner=self.user) def test_record_creates_game_event(self): event = record(self.room, GameEvent.SLOT_FILLED, actor=self.user, slot_number=1, token_type="tithe") self.assertEqual(GameEvent.objects.count(), 1) self.assertEqual(event.room, self.room) self.assertEqual(event.actor, self.user) self.assertEqual(event.verb, GameEvent.SLOT_FILLED) self.assertEqual(event.data, {"slot_number": 1, "token_type": "tithe"}) def test_record_without_actor(self): event = record(self.room, GameEvent.ROOM_CREATED) self.assertIsNone(event.actor) self.assertEqual(event.verb, GameEvent.ROOM_CREATED) def test_events_ordered_by_timestamp(self): record(self.room, GameEvent.ROOM_CREATED) record(self.room, GameEvent.SLOT_RESERVED, actor=self.user) record(self.room, GameEvent.SLOT_FILLED, actor=self.user) verbs = list(GameEvent.objects.values_list("verb", flat=True)) self.assertEqual(verbs, [ GameEvent.ROOM_CREATED, GameEvent.SLOT_RESERVED, GameEvent.SLOT_FILLED, ]) def test_str_includes_actor_and_verb(self): event = record(self.room, GameEvent.ROLE_SELECTED, actor=self.user, role="PC") self.assertIn("actor@test.io", str(event)) self.assertIn(GameEvent.ROLE_SELECTED, str(event)) # ── to_prose — ROLE_SELECTED ────────────────────────────────────────── def test_role_selected_prose_uses_ordinal_chair(self): for role, ordinal in [("PC", "1st"), ("NC", "2nd"), ("EC", "3rd"), ("SC", "4th"), ("AC", "5th"), ("BC", "6th")]: with self.subTest(role=role): event = record(self.room, GameEvent.ROLE_SELECTED, actor=self.user, role=role, role_display="") self.assertIn(f"assumes {ordinal} Chair", event.to_prose()) def test_role_selected_prose_includes_role_name(self): event = record(self.room, GameEvent.ROLE_SELECTED, actor=self.user, role="PC", role_display="Player") prose = event.to_prose() self.assertIn("Player", prose) self.assertIn("yo will start the game", prose) # ── to_prose — SIG_READY ───────────────────────────────────────────── def test_sig_ready_prose_embodies_card_with_rank_and_icon(self): event = record(self.room, GameEvent.SIG_READY, actor=self.user, card_name="Maid of Brands", corner_rank="M", suit_icon="fa-wand-sparkles") prose = event.to_prose() self.assertIn("embodies as yos Significator the Maid of Brands", prose) self.assertIn("(M", prose) self.assertIn("fa-wand-sparkles", prose) def test_sig_ready_prose_omits_icon_when_none(self): event = record(self.room, GameEvent.SIG_READY, actor=self.user, card_name="The Wanderer", corner_rank="0", suit_icon="") prose = event.to_prose() self.assertIn("embodies as yos Significator the The Wanderer (0)", prose) self.assertNotIn("fa-", prose) def test_sig_ready_prose_degrades_without_corner_rank(self): # Old events recorded before this change have no corner_rank key event = record(self.room, GameEvent.SIG_READY, actor=self.user, card_name="Maid of Brands") prose = event.to_prose() self.assertIn("embodies as yos Significator the Maid of Brands", prose) self.assertNotIn("(", prose) def test_str_without_actor_shows_system(self): event = record(self.room, GameEvent.ROLES_REVEALED) self.assertIn("system", str(event)) # ── to_prose — remaining verb branches ─────────────────────────────── def test_slot_reserved_prose(self): event = record(self.room, GameEvent.SLOT_RESERVED, actor=self.user) self.assertEqual(event.to_prose(), "reserves a seat") def test_slot_returned_prose(self): event = record(self.room, GameEvent.SLOT_RETURNED, actor=self.user) self.assertEqual(event.to_prose(), "withdraws from the gate") def test_slot_released_prose_includes_slot_number(self): event = record(self.room, GameEvent.SLOT_RELEASED, actor=self.user, slot_number=3) self.assertIn("slot 3", event.to_prose()) def test_invite_sent_prose(self): event = record(self.room, GameEvent.INVITE_SENT, actor=self.user) self.assertEqual(event.to_prose(), "sends an invitation") def test_role_select_started_prose(self): event = record(self.room, GameEvent.ROLE_SELECT_STARTED) self.assertEqual(event.to_prose(), "Role selection begins") def test_roles_revealed_prose(self): event = record(self.room, GameEvent.ROLES_REVEALED) self.assertEqual(event.to_prose(), "All roles assigned") def test_role_selected_prose_unknown_role_code_uses_question_mark_ordinal(self): event = record(self.room, GameEvent.ROLE_SELECTED, actor=self.user, role="XX", role_display="Unknown") self.assertIn("?", event.to_prose()) def test_sig_unready_prose(self): event = record(self.room, GameEvent.SIG_UNREADY, actor=self.user) self.assertIn("disembodies", event.to_prose()) self.assertIn("Significator", event.to_prose()) def test_unknown_verb_falls_back_to_verb_string(self): event = record(self.room, "custom_event", actor=self.user) self.assertEqual(event.to_prose(), "custom_event") def test_to_activity_returns_none_when_actor_has_no_username(self): actor = User.objects.create(email="noname@test.io") event = record(self.room, GameEvent.SLOT_FILLED, actor=actor, slot_number=1) self.assertIsNone(event.to_activity("https://example.com")) class ScrollPositionStrTest(TestCase): def setUp(self): self.user = User.objects.create(email="reader@test.io") self.room = Room.objects.create(name="Test Room", owner=self.user) def test_str_includes_email_room_and_position(self): from apps.drama.models import ScrollPosition sp = ScrollPosition.objects.create(user=self.user, room=self.room, position=42) s = str(sp) self.assertIn("reader@test.io", s) self.assertIn("Test Room", s) self.assertIn("42", s) class ScrollPositionModelTest(TestCase): def setUp(self): self.user = User.objects.create(email="reader@test.io") self.room = Room.objects.create(name="Test Room", owner=self.user) def test_can_save_scroll_position(self): sp = ScrollPosition.objects.create(user=self.user, room=self.room, position=150) self.assertEqual(ScrollPosition.objects.count(), 1) self.assertEqual(sp.position, 150) def test_default_position_is_zero(self): sp = ScrollPosition.objects.create(user=self.user, room=self.room) self.assertEqual(sp.position, 0) def test_unique_per_user_and_room(self): ScrollPosition.objects.create(user=self.user, room=self.room, position=50) with self.assertRaises(IntegrityError): ScrollPosition.objects.create(user=self.user, room=self.room, position=100) def test_upsert_updates_existing_position(self): ScrollPosition.objects.create(user=self.user, room=self.room, position=50) ScrollPosition.objects.update_or_create( user=self.user, room=self.room, defaults={"position": 200}, ) self.assertEqual(ScrollPosition.objects.get(user=self.user, room=self.room).position, 200)