Files
python-tdd/src/apps/drama/tests/integrated/test_models.py

243 lines
11 KiB
Python
Raw Normal View History

from django.test import TestCase
from django.db import IntegrityError
from django.utils import timezone
from apps.drama.models import GameEvent, Recognition, 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)
class RecognitionModelTest(TestCase):
def setUp(self):
self.user = User.objects.create(email="earner@test.io")
def test_can_create_recognition(self):
recog = Recognition.objects.create(
user=self.user, slug="stargazer", earned_at=timezone.now(),
)
self.assertEqual(Recognition.objects.count(), 1)
self.assertEqual(recog.slug, "stargazer")
self.assertEqual(recog.user, self.user)
def test_palette_is_null_by_default(self):
recog = Recognition.objects.create(
user=self.user, slug="stargazer", earned_at=timezone.now(),
)
self.assertIsNone(recog.palette)
def test_palette_can_be_set(self):
recog = Recognition.objects.create(
user=self.user, slug="stargazer", earned_at=timezone.now(),
palette="palette-bardo",
)
self.assertEqual(recog.palette, "palette-bardo")
def test_unique_per_user_and_slug(self):
Recognition.objects.create(user=self.user, slug="stargazer", earned_at=timezone.now())
with self.assertRaises(IntegrityError):
Recognition.objects.create(user=self.user, slug="stargazer", earned_at=timezone.now())
def test_different_users_can_share_slug(self):
other = User.objects.create(email="other@test.io")
Recognition.objects.create(user=self.user, slug="stargazer", earned_at=timezone.now())
Recognition.objects.create(user=other, slug="stargazer", earned_at=timezone.now())
self.assertEqual(Recognition.objects.count(), 2)
def test_str_includes_slug_and_email(self):
recog = Recognition.objects.create(
user=self.user, slug="stargazer", earned_at=timezone.now(),
)
s = str(recog)
self.assertIn("stargazer", s)
self.assertIn("earner@test.io", s)
def test_grant_if_new_creates_on_first_call(self):
recog, created = Recognition.grant_if_new(self.user, "stargazer")
self.assertTrue(created)
self.assertEqual(recog.slug, "stargazer")
self.assertIsNotNone(recog.earned_at)
def test_grant_if_new_is_idempotent(self):
Recognition.grant_if_new(self.user, "stargazer")
recog, created = Recognition.grant_if_new(self.user, "stargazer")
self.assertFalse(created)
self.assertEqual(Recognition.objects.count(), 1)
def test_grant_if_new_does_not_overwrite_palette(self):
Recognition.objects.create(
user=self.user, slug="stargazer",
earned_at=timezone.now(), palette="palette-bardo",
)
recog, created = Recognition.grant_if_new(self.user, "stargazer")
self.assertFalse(created)
self.assertEqual(recog.palette, "palette-bardo")