From 8240de6b454b5e31a00fb34c0f510840796c2084 Mon Sep 17 00:00:00 2001 From: Disco DeDisco Date: Tue, 12 May 2026 19:23:08 -0400 Subject: [PATCH] =?UTF-8?q?functional=5Ftests/room=5Fpage.py:=20extract=20?= =?UTF-8?q?shared=20FT=20helpers=20into=20a=20dedicated=20module=20so=20re?= =?UTF-8?q?naming=20a=20test=5FX.py=20file=20doesn't=20cascade-break=20sib?= =?UTF-8?q?ling=20imports=20=E2=80=94=20`=5Ffill=5Froom=5Fvia=5Form`=20(wa?= =?UTF-8?q?s=20in=20test=5Froom=5Frole=5Fselect,=20imported=20by=20test=5F?= =?UTF-8?q?room=5Ftray=20+=20test=5Fdeck=5Fcontribution=20+=20test=5Fgame?= =?UTF-8?q?=5Finvite=20+=20test=5Froom=5Fgatekeeper=20+=20test=5Froom=5Fsi?= =?UTF-8?q?g=5Fselect),=20`=5Fassign=5Fall=5Froles`=20(was=20in=20test=5Fr?= =?UTF-8?q?oom=5Fsig=5Fselect,=20imported=20by=20test=5Froom=5Ftray=20+=20?= =?UTF-8?q?test=5Fdeck=5Fcontribution),=20and=20`=5Fequip=5Fearthman=5Fdec?= =?UTF-8?q?k`=20(duplicated=20verbatim=20in=20test=5Froom=5Frole=5Fselect?= =?UTF-8?q?=20+=20test=5Fcomponent=5Ftray=5Ftooltip=20=E2=80=94=20the=20te?= =?UTF-8?q?st=5Fcomponent=5Ftray=5Ftooltip=20copy=20of=20`=5Ffill=5Froom?= =?UTF-8?q?=5Fvia=5Form`=20was=20also=20a=20near-duplicate,=20missing=20on?= =?UTF-8?q?ly=20the=20gamers-return=20both=20call=20sites=20discarded=20an?= =?UTF-8?q?yway);=20`SIG=5FSEAT=5FORDER`=20constant=20moves=20along=20sinc?= =?UTF-8?q?e=20`=5Fassign=5Fall=5Froles`=20depends=20on=20it;=20mirrors=20?= =?UTF-8?q?the=20existing=20post=5Fpage.py=20pattern;=20underscored=20help?= =?UTF-8?q?er=20names=20kept=20so=20the=20"test=20infrastructure,=20not=20?= =?UTF-8?q?API"=20signal=20survives=20the=20relocation;=20dropped=20now-un?= =?UTF-8?q?used=20per-file=20imports=20(DeckVariant=20from=20test=5Froom?= =?UTF-8?q?=5Frole=5Fselect;=20Note/DeckVariant/TableSeat/TarotCard=20from?= =?UTF-8?q?=20test=5Froom=5Fsig=5Fselect;=20GateSlot=20from=20test=5Fcompo?= =?UTF-8?q?nent=5Ftray=5Ftooltip);=20regression=20gate:=20GatekeeperTest?= =?UTF-8?q?=20(8=20FTs)=20green=20via=20the=20new=20helper=20home;=20smoke?= =?UTF-8?q?-imports=20green=20across=20all=208=20touched=20modules?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Code architected by Disco DeDisco Git commit message Co-Authored-By: Claude Opus 4.7 (1M context) --- src/functional_tests/room_page.py | 112 ++++++++++++++++++ .../test_component_tray_tooltip.py | 21 +--- .../test_deck_contribution.py | 3 +- src/functional_tests/test_game_invite.py | 2 +- src/functional_tests/test_room_gatekeeper.py | 2 +- src/functional_tests/test_room_role_select.py | 29 +---- src/functional_tests/test_room_sig_select.py | 73 +----------- src/functional_tests/test_room_tray.py | 3 +- 8 files changed, 124 insertions(+), 121 deletions(-) create mode 100644 src/functional_tests/room_page.py diff --git a/src/functional_tests/room_page.py b/src/functional_tests/room_page.py new file mode 100644 index 0000000..1aafdd3 --- /dev/null +++ b/src/functional_tests/room_page.py @@ -0,0 +1,112 @@ +"""Room-page FT helpers — extracted from the per-page FT modules so the +helpers can be imported without coupling consumer FTs to a sibling test +file's lifecycle (renames, deletions). Mirrors the post_page.py pattern. + +Naming: underscored to signal "test infrastructure, not API surface", but +they're public within `functional_tests/` — direct imports are fine. + +Consumers: + test_room_role_select / test_room_sig_select — define-and-use + test_component_tray_tooltip — _equip_earthman_deck + test_room_tray / test_deck_contribution — both core helpers + test_game_invite / test_room_gatekeeper — _fill_room_via_orm +""" +from django.utils import timezone + +from apps.drama.models import Note +from apps.epic.models import ( + DeckVariant, GateSlot, Room, TableSeat, TarotCard, +) +from apps.lyric.models import User + + +SIG_SEAT_ORDER = ["PC", "NC", "EC", "SC", "AC", "BC"] + + +def _equip_earthman_deck(user): + # get_or_create: TransactionTestCase.flush() wipes migration-seeded + # DeckVariants between tests, so subsequent tests in the same run can't + # find it via filter(). + deck, _ = DeckVariant.objects.get_or_create( + slug="earthman", + defaults={"name": "Earthman", "card_count": 106, "is_default": True}, + ) + user.equipped_deck = deck + user.save(update_fields=["equipped_deck"]) + + +def _fill_room_via_orm(room, emails): + """Fill all 6 gate slots and set gate_status=OPEN. Returns list of gamers.""" + gamers = [] + for i, email in enumerate(emails, start=1): + gamer, _ = User.objects.get_or_create(email=email) + slot = room.gate_slots.get(slot_number=i) + slot.gamer = gamer + slot.status = GateSlot.FILLED + slot.save() + gamers.append(gamer) + room.gate_status = Room.OPEN + room.save() + return gamers + + +def _assign_all_roles(room, role_order=None): + """Assign roles to all slots, reveal them, and advance to SIG_SELECT. + Also ensures all gamers have an equipped_deck (required for sig_deck_cards).""" + if role_order is None: + role_order = SIG_SEAT_ORDER[:] + earthman, _ = DeckVariant.objects.get_or_create( + slug="earthman", + defaults={"name": "Earthman Deck", "card_count": 108, "is_default": True}, + ) + # Seed the 18 sig deck cards (migration data is flushed in TransactionTestCase FTs). + # _sig_unique_cards() filters arcana=MIDDLE, suits BRANDS/CROWNS/BLADES/GRAILS (Earthman). + _NAME = {11: "Maid", 12: "Jack", 13: "Queen", 14: "King"} + for suit in ("BRANDS", "CROWNS", "BLADES", "GRAILS"): + for number in (11, 12, 13, 14): + TarotCard.objects.get_or_create( + deck_variant=earthman, + slug=f"{_NAME[number].lower()}-of-{suit.lower()}-em", + defaults={"arcana": "MIDDLE", "suit": suit, "number": number, + "name": f"{_NAME[number]} of {suit.capitalize()}", + "levity_qualifier": "Elevated", + "gravity_qualifier": "Graven"}, + ) + # Numbers 0–1 are the sig deck's Major Arcana (unlocked via Note). + # Seed them with correct Earthman names and qualifiers, then unlock for all gamers. + for number, name, slug in [ + (0, "The Nomad", "the-nomad"), + (1, "The Schizo", "the-schizo"), + ]: + TarotCard.objects.get_or_create( + deck_variant=earthman, + slug=slug, + defaults={"arcana": "MAJOR", "number": number, "name": name, + "levity_qualifier": "Enlightened", + "gravity_qualifier": "Engraven"}, + ) + for slot in room.gate_slots.order_by("slot_number"): + if slot.gamer: + Note.objects.get_or_create( + user=slot.gamer, slug="super-nomad", + defaults={"earned_at": timezone.now()}, + ) + Note.objects.get_or_create( + user=slot.gamer, slug="super-schizo", + defaults={"earned_at": timezone.now()}, + ) + for slot in room.gate_slots.order_by("slot_number"): + if slot.gamer and not slot.gamer.equipped_deck: + slot.gamer.equipped_deck = earthman + slot.gamer.save(update_fields=["equipped_deck"]) + TableSeat.objects.update_or_create( + room=room, + slot_number=slot.slot_number, + defaults={ + "gamer": slot.gamer, + "role": role_order[slot.slot_number - 1], + "role_revealed": True, + }, + ) + room.table_status = Room.SIG_SELECT + room.save() diff --git a/src/functional_tests/test_component_tray_tooltip.py b/src/functional_tests/test_component_tray_tooltip.py index 7955a01..3eef3c7 100644 --- a/src/functional_tests/test_component_tray_tooltip.py +++ b/src/functional_tests/test_component_tray_tooltip.py @@ -14,28 +14,11 @@ from selenium.webdriver.common.by import By from .base import FunctionalTest from apps.applets.models import Applet -from apps.epic.models import DeckVariant, GateSlot, Room, TableSeat, TarotCard +from apps.epic.models import DeckVariant, Room, TableSeat, TarotCard from apps.lyric.models import User -def _equip_earthman_deck(user): - deck, _ = DeckVariant.objects.get_or_create( - slug="earthman", - defaults={"name": "Earthman", "card_count": 106, "is_default": True}, - ) - user.equipped_deck = deck - user.save(update_fields=["equipped_deck"]) - - -def _fill_room_via_orm(room, emails): - for i, email in enumerate(emails, start=1): - gamer, _ = User.objects.get_or_create(email=email) - slot = room.gate_slots.get(slot_number=i) - slot.gamer = gamer - slot.status = GateSlot.FILLED - slot.save() - room.gate_status = Room.OPEN - room.save() +from .room_page import _equip_earthman_deck, _fill_room_via_orm class TrayRoleCardTooltipTest(FunctionalTest): diff --git a/src/functional_tests/test_deck_contribution.py b/src/functional_tests/test_deck_contribution.py index 866a426..5b69225 100644 --- a/src/functional_tests/test_deck_contribution.py +++ b/src/functional_tests/test_deck_contribution.py @@ -22,8 +22,7 @@ from apps.applets.models import Applet from apps.epic.models import DeckVariant, GateSlot, Room, TableSeat from apps.lyric.models import User from functional_tests.base import FunctionalTest -from functional_tests.test_room_role_select import _fill_room_via_orm -from functional_tests.test_room_sig_select import _assign_all_roles +from functional_tests.room_page import _fill_room_via_orm FOUNDER_EMAIL = "founder@test.io" GAMER_EMAIL = "gamer@test.io" diff --git a/src/functional_tests/test_game_invite.py b/src/functional_tests/test_game_invite.py index 2769c28..469cecf 100644 --- a/src/functional_tests/test_game_invite.py +++ b/src/functional_tests/test_game_invite.py @@ -47,7 +47,7 @@ except ImportError: _BILLPOST_READY = False from apps.lyric.models import User from functional_tests.base import FunctionalTest -from functional_tests.test_room_role_select import _fill_room_via_orm +from functional_tests.room_page import _fill_room_via_orm FOUNDER_EMAIL = "founder@test.io" INVITEE_EMAIL = "invitee@test.io" diff --git a/src/functional_tests/test_room_gatekeeper.py b/src/functional_tests/test_room_gatekeeper.py index 0f238fa..ab0dbd2 100644 --- a/src/functional_tests/test_room_gatekeeper.py +++ b/src/functional_tests/test_room_gatekeeper.py @@ -8,7 +8,7 @@ from .base import FunctionalTest from apps.applets.models import Applet from apps.epic.models import Room, GateSlot, select_token from apps.lyric.models import Token, User -from .test_room_role_select import _fill_room_via_orm +from .room_page import _fill_room_via_orm class GatekeeperTest(FunctionalTest): diff --git a/src/functional_tests/test_room_role_select.py b/src/functional_tests/test_room_role_select.py index 09c7bb9..0f021f1 100644 --- a/src/functional_tests/test_room_role_select.py +++ b/src/functional_tests/test_room_role_select.py @@ -8,37 +8,12 @@ from selenium.webdriver.common.by import By from .base import FunctionalTest, ChannelsFunctionalTest from .management.commands.create_session import create_pre_authenticated_session +from .room_page import _equip_earthman_deck, _fill_room_via_orm from apps.applets.models import Applet -from apps.epic.models import DeckVariant, Room, GateSlot, TableSeat +from apps.epic.models import Room, GateSlot, TableSeat from apps.lyric.models import User -def _equip_earthman_deck(user): - # get_or_create: TransactionTestCase.flush() wipes migration-seeded DeckVariants - # between tests, so subsequent tests in the same run can't find it via filter(). - deck, _ = DeckVariant.objects.get_or_create( - slug="earthman", - defaults={"name": "Earthman", "card_count": 106, "is_default": True}, - ) - user.equipped_deck = deck - user.save(update_fields=["equipped_deck"]) - - -def _fill_room_via_orm(room, emails): - """Fill all 6 gate slots and set gate_status=OPEN. Returns list of gamers.""" - gamers = [] - for i, email in enumerate(emails, start=1): - gamer, _ = User.objects.get_or_create(email=email) - slot = room.gate_slots.get(slot_number=i) - slot.gamer = gamer - slot.status = GateSlot.FILLED - slot.save() - gamers.append(gamer) - room.gate_status = Room.OPEN - room.save() - return gamers - - class RoleSelectTest(FunctionalTest): def setUp(self): diff --git a/src/functional_tests/test_room_sig_select.py b/src/functional_tests/test_room_sig_select.py index 86e743c..9ff8493 100644 --- a/src/functional_tests/test_room_sig_select.py +++ b/src/functional_tests/test_room_sig_select.py @@ -7,13 +7,13 @@ from selenium.webdriver.common.by import By from .base import FunctionalTest, ChannelsFunctionalTest from .management.commands.create_session import create_pre_authenticated_session +from .room_page import ( + SIG_SEAT_ORDER, _assign_all_roles, _fill_room_via_orm, +) from apps.applets.models import Applet -from apps.drama.models import Note -from apps.epic.models import DeckVariant, Room, TableSeat, TarotCard +from apps.epic.models import Room from apps.lyric.models import User -from .test_room_role_select import _fill_room_via_orm - # ── Significator Selection ──────────────────────────────────────────────────── # @@ -23,71 +23,6 @@ from .test_room_role_select import _fill_room_via_orm # # ───────────────────────────────────────────────────────────────────────────── -SIG_SEAT_ORDER = ["PC", "NC", "EC", "SC", "AC", "BC"] - - -def _assign_all_roles(room, role_order=None): - """Assign roles to all slots, reveal them, and advance to SIG_SELECT. - Also ensures all gamers have an equipped_deck (required for sig_deck_cards).""" - if role_order is None: - role_order = SIG_SEAT_ORDER[:] - earthman, _ = DeckVariant.objects.get_or_create( - slug="earthman", - defaults={"name": "Earthman Deck", "card_count": 108, "is_default": True}, - ) - # Seed the 18 sig deck cards (migration data is flushed in TransactionTestCase FTs). - # _sig_unique_cards() filters arcana=MIDDLE, suits BRANDS/CROWNS/BLADES/GRAILS (Earthman). - _NAME = {11: "Maid", 12: "Jack", 13: "Queen", 14: "King"} - for suit in ("BRANDS", "CROWNS", "BLADES", "GRAILS"): - for number in (11, 12, 13, 14): - TarotCard.objects.get_or_create( - deck_variant=earthman, - slug=f"{_NAME[number].lower()}-of-{suit.lower()}-em", - defaults={"arcana": "MIDDLE", "suit": suit, "number": number, - "name": f"{_NAME[number]} of {suit.capitalize()}", - "levity_qualifier": "Elevated", - "gravity_qualifier": "Graven"}, - ) - # Numbers 0–1 are the sig deck's Major Arcana (unlocked via Note). - # Seed them with correct Earthman names and qualifiers, then unlock for all gamers. - from django.utils import timezone - for number, name, slug in [ - (0, "The Nomad", "the-nomad"), - (1, "The Schizo", "the-schizo"), - ]: - TarotCard.objects.get_or_create( - deck_variant=earthman, - slug=slug, - defaults={"arcana": "MAJOR", "number": number, "name": name, - "levity_qualifier": "Enlightened", - "gravity_qualifier": "Engraven"}, - ) - for slot in room.gate_slots.order_by("slot_number"): - if slot.gamer: - Note.objects.get_or_create( - user=slot.gamer, slug="super-nomad", - defaults={"earned_at": timezone.now()}, - ) - Note.objects.get_or_create( - user=slot.gamer, slug="super-schizo", - defaults={"earned_at": timezone.now()}, - ) - for slot in room.gate_slots.order_by("slot_number"): - if slot.gamer and not slot.gamer.equipped_deck: - slot.gamer.equipped_deck = earthman - slot.gamer.save(update_fields=["equipped_deck"]) - TableSeat.objects.update_or_create( - room=room, - slot_number=slot.slot_number, - defaults={ - "gamer": slot.gamer, - "role": role_order[slot.slot_number - 1], - "role_revealed": True, - }, - ) - room.table_status = Room.SIG_SELECT - room.save() - class SigSelectTest(FunctionalTest): """Significator Selection — non-WebSocket tests.""" diff --git a/src/functional_tests/test_room_tray.py b/src/functional_tests/test_room_tray.py index f272c60..c15beb6 100644 --- a/src/functional_tests/test_room_tray.py +++ b/src/functional_tests/test_room_tray.py @@ -5,8 +5,7 @@ from django.test import tag from selenium.webdriver.common.by import By from .base import FunctionalTest -from .test_room_role_select import _fill_room_via_orm -from .test_room_sig_select import _assign_all_roles +from .room_page import _assign_all_roles, _fill_room_via_orm from apps.epic.models import Room from apps.lyric.models import User