From d728900c24ef98e833d4168935e51e2fdd0f1ba6 Mon Sep 17 00:00:00 2001 From: Disco DeDisco Date: Wed, 29 Apr 2026 12:07:41 -0400 Subject: [PATCH] =?UTF-8?q?fix=20Nomad=20icon=20fa-hat-cowboy=20=E2=86=92?= =?UTF-8?q?=20fa-hat-cowboy-side;=20setup=5Fsea=5Fsession=20command?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - migration 0011 ICONS dict corrected for fresh installs - migration 0013 data fix for existing DBs (filters on old value to be safe) - TarotCardSuitIconTest updated to assert fa-hat-cowboy-side - setup_sea_session: single-gamer PICK SEA dev session w. pre-auth URL Code architected by Disco DeDisco Git commit message Co-Authored-By: Claude Sonnet 4.6 --- .../migrations/0011_nomad_schizo_icons.py | 2 +- .../epic/migrations/0013_fix_nomad_icon.py | 25 ++++ src/apps/epic/tests/unit/test_models.py | 2 +- .../management/commands/setup_sea_session.py | 112 ++++++++++++++++++ 4 files changed, 139 insertions(+), 2 deletions(-) create mode 100644 src/apps/epic/migrations/0013_fix_nomad_icon.py create mode 100644 src/functional_tests/management/commands/setup_sea_session.py diff --git a/src/apps/epic/migrations/0011_nomad_schizo_icons.py b/src/apps/epic/migrations/0011_nomad_schizo_icons.py index c5b2a99..ef968cf 100644 --- a/src/apps/epic/migrations/0011_nomad_schizo_icons.py +++ b/src/apps/epic/migrations/0011_nomad_schizo_icons.py @@ -4,7 +4,7 @@ All other Major Arcana already have fa-hand-dots from migration 0010. """ from django.db import migrations -ICONS = {0: 'fa-hat-cowboy', 1: 'fa-hat-wizard'} +ICONS = {0: 'fa-hat-cowboy-side', 1: 'fa-hat-wizard'} def assign_icons(apps, schema_editor): diff --git a/src/apps/epic/migrations/0013_fix_nomad_icon.py b/src/apps/epic/migrations/0013_fix_nomad_icon.py new file mode 100644 index 0000000..a327cd1 --- /dev/null +++ b/src/apps/epic/migrations/0013_fix_nomad_icon.py @@ -0,0 +1,25 @@ +"""Fix The Nomad icon: fa-hat-cowboy → fa-hat-cowboy-side.""" +from django.db import migrations + + +def fix_nomad_icon(apps, schema_editor): + TarotCard = apps.get_model("epic", "TarotCard") + DeckVariant = apps.get_model("epic", "DeckVariant") + try: + earthman = DeckVariant.objects.get(slug="earthman") + except DeckVariant.DoesNotExist: + return + TarotCard.objects.filter( + deck_variant=earthman, arcana="MAJOR", number=0, icon="fa-hat-cowboy" + ).update(icon="fa-hat-cowboy-side") + + +class Migration(migrations.Migration): + + dependencies = [ + ("epic", "0012_delete_stray_pentacles"), + ] + + operations = [ + migrations.RunPython(fix_nomad_icon, reverse_code=migrations.RunPython.noop), + ] diff --git a/src/apps/epic/tests/unit/test_models.py b/src/apps/epic/tests/unit/test_models.py index cb1258c..796c57f 100644 --- a/src/apps/epic/tests/unit/test_models.py +++ b/src/apps/epic/tests/unit/test_models.py @@ -51,7 +51,7 @@ class TarotCardSuitIconTest(SimpleTestCase): """TarotCard.suit_icon — icon class resolution.""" def test_major_with_icon_returns_icon(self): - self.assertEqual(_card('MAJOR', 0, icon='fa-hat-cowboy').suit_icon, 'fa-hat-cowboy') + self.assertEqual(_card('MAJOR', 0, icon='fa-hat-cowboy-side').suit_icon, 'fa-hat-cowboy-side') def test_major_without_icon_returns_empty(self): self.assertEqual(_card('MAJOR', 5).suit_icon, '') diff --git a/src/functional_tests/management/commands/setup_sea_session.py b/src/functional_tests/management/commands/setup_sea_session.py new file mode 100644 index 0000000..b21e620 --- /dev/null +++ b/src/functional_tests/management/commands/setup_sea_session.py @@ -0,0 +1,112 @@ +""" +Management command for manual single-gamer PICK SEA testing. + +Creates a room at SKY_SELECT with one seated gamer whose sky is already +confirmed, so the PICK SEA overlay is immediately visible on page load. + +Usage: + python src/manage.py setup_sea_session + python src/manage.py setup_sea_session --base-url http://192.168.1.42:8000 + python src/manage.py setup_sea_session --room # reuse existing room +""" + +from django.contrib.auth import BACKEND_SESSION_KEY, HASH_SESSION_KEY, SESSION_KEY +from django.contrib.sessions.backends.db import SessionStore +from django.core.management.base import BaseCommand +from django.utils import timezone + +from apps.epic.models import Character, DeckVariant, GateSlot, Room, TableSeat, TarotCard +from apps.lyric.models import User + + +GAMER_EMAIL = "founder@test.io" + + +def _make_session(user): + session = SessionStore() + session[SESSION_KEY] = str(user.pk) + session[BACKEND_SESSION_KEY] = "apps.lyric.authentication.PasswordlessAuthenticationBackend" + session[HASH_SESSION_KEY] = user.get_session_auth_hash() + session.save() + return session.session_key + + +class Command(BaseCommand): + help = "Set up a SKY_SELECT room with sky confirmed and print a PICK SEA URL" + + def add_arguments(self, parser): + parser.add_argument("--base-url", default="http://localhost:8000") + parser.add_argument("--room", default=None, help="UUID of an existing room to reuse") + + def handle(self, *args, **options): + base_url = options["base_url"].rstrip("/") + earthman = DeckVariant.objects.get(slug="earthman") + + # ── User ───────────────────────────────────────────────────────────── + user, _ = User.objects.get_or_create(email=GAMER_EMAIL) + user.is_staff = True + user.is_superuser = True + user.equipped_deck = None + user.save() + user.unlocked_decks.add(earthman) + + # ── Room ───────────────────────────────────────────────────────────── + if options["room"]: + room = Room.objects.get(pk=options["room"]) + else: + room = Room.objects.create( + name="Sea Select Test Room", + owner=user, + visibility=Room.PUBLIC, + ) + + # ── Gate slot ──────────────────────────────────────────────────────── + slot = room.gate_slots.get(slot_number=1) + slot.gamer = user + slot.status = GateSlot.FILLED + slot.save() + + room.gate_status = Room.OPEN + room.table_status = Room.SKY_SELECT + room.save() + + # ── Significator ───────────────────────────────────────────────────── + sig_card = TarotCard.objects.filter( + deck_variant=earthman, arcana="MAJOR" + ).first() + + # ── Table seat (PC = levity pole) ──────────────────────────────────── + seat, _ = TableSeat.objects.update_or_create( + room=room, slot_number=1, + defaults={ + "gamer": user, + "role": "PC", + "role_revealed": True, + "deck_variant": earthman, + "significator": sig_card, + }, + ) + + # ── Confirmed Character (sky already done) ─────────────────────────── + char, created = Character.objects.get_or_create( + seat=seat, + retired_at__isnull=True, + defaults={ + "significator": sig_card, + "confirmed_at": timezone.now(), + }, + ) + if not created and char.confirmed_at is None: + char.confirmed_at = timezone.now() + char.significator = sig_card + char.save() + + # ── URL ────────────────────────────────────────────────────────────── + room_path = f"/gameboard/room/{room.pk}/" + session_key = _make_session(user) + url = f"{base_url}/lyric/dev-login/{session_key}/?next={room_path}" + + self.stdout.write(f"\nRoom: {base_url}{room_path}") + self.stdout.write(f"Gamer: {GAMER_EMAIL} (PC / levity / earthman)") + self.stdout.write(f"Sig: {sig_card}") + self.stdout.write(f"\nURL:\n {url}\n")