From 4d1c74a2af957a5b25473728327b3566c70a7423 Mon Sep 17 00:00:00 2001 From: Disco DeDisco Date: Tue, 19 May 2026 14:22:49 -0400 Subject: [PATCH] =?UTF-8?q?FT=20helper:=20sig=5Fpage.py=20=E2=80=94=20=5Fs?= =?UTF-8?q?eed=5Fearthman=5Fsig=5Fpile=20+=20=5Fassign=5Fsig=20=E2=80=94?= =?UTF-8?q?=20Sprint=204c=20of=20My=20Sea=20roadmap=20=E2=80=94=20TDD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pure test-infra refactor. Sprint 5+ (sea-select / DRAW SEA / latest-draw rendering) all need a "user has sig" precondition without walking the picker — extracting it once now prevents Sprint 5's setUps from copy-pasting the inline assignment dance again. Two helpers in new `src/functional_tests/sig_page.py` (mirrors the `room_page.py` / `post_page.py` / `my_posts_page.py` convention — underscored to signal "test infrastructure, not API surface", public within `functional_tests/`): - `_seed_earthman_sig_pile()` — re-seeds Earthman DeckVariant + the 16 MIDDLE court cards (Maid/Jack/Queen/King × BRANDS/CROWNS/BLADES/GRAILS) that `personal_sig_cards(user)` returns. Hoisted verbatim from the duplicate definitions in `test_bill_my_sign.py` + `test_game_my_sea.py` introduced in [[sprint_serialized_rollback_ft_fix_may19]]. Major 0/1 are deliberately NOT seeded — `_filter_major_unlocks` in `personal_sig_cards()` strips them for users w.o the matching Note unlocks, which is the default state in solo FTs. - `_assign_sig(user, card=None, reversed_flag=False)` — sets `user.significator` + `significator_reversed` directly, bypassing the picker UI. Returns the assigned card so downstream assertions can use it. `card=None` defaults to `personal_sig_cards(user)[0]` (the same target the picker happy-path FT uses). Call sites updated: - `test_bill_my_sign.py` — drops the local `_seed_earthman_sig_pile` definition (32 lines); imports from `sig_page`. `MySignClearTest.setUp` now uses `_assign_sig(self.gamer)` instead of the 4-line manual sig-assignment block. - `test_game_my_sea.py` — drops the local `_seed_earthman_sig_pile` definition (22 lines); imports from `sig_page`. `MySeaSignGateTest`'s two "user w. sig" tests (#4 + #6) swap their 2-line `self.gamer.significator = ... ; .save(...)` blocks for `_assign_sig(self.gamer, self.target_card)`. Diff stat: +69 lines (new helper module), -59 lines (duplicate code removed). Net +10 LOC but the duplication trap is closed — single source of truth for sig-state FT setup. 20/20 FT green across both files in 174s post-refactor. Code architected by Disco DeDisco Git commit message Co-Authored-By: Claude Sonnet 4.6 --- src/functional_tests/sig_page.py | 69 +++++++++++++++++++++++ src/functional_tests/test_bill_my_sign.py | 35 ++---------- src/functional_tests/test_game_my_sea.py | 32 ++--------- 3 files changed, 77 insertions(+), 59 deletions(-) create mode 100644 src/functional_tests/sig_page.py diff --git a/src/functional_tests/sig_page.py b/src/functional_tests/sig_page.py new file mode 100644 index 0000000..2277056 --- /dev/null +++ b/src/functional_tests/sig_page.py @@ -0,0 +1,69 @@ +"""Sig-state FT helpers — sprint 4c of [[project-my-sea-roadmap]]. Two +public helpers: + +- `_seed_earthman_sig_pile()` — restore the Earthman DeckVariant + 16 + MIDDLE court cards that `personal_sig_cards(user)` returns. The + migration seed gets flushed by TransactionTestCase between tests + (see [[feedback_transactiontestcase_flush]]); each setUp that touches + the picker or its dependents must call this before creating a User. + +- `_assign_sig(user, card=None, reversed_flag=False)` — set + `User.significator` (+ `significator_reversed`) directly, bypassing + the picker UI. Returns the assigned card so downstream assertions can + use it. Use this in any FT that needs a "user has a sig" precondition + without walking the SCAN SIGN → click thumb → OK → SAVE SIGN flow. + +Naming follows the room_page.py / post_page.py / my_posts_page.py +convention: underscored to signal "test infrastructure, not API +surface"; public within `functional_tests/`. + +Consumers: + test_bill_my_sign — both helpers + test_game_my_sea — both helpers (gate / no-gate branches) +""" +from apps.epic.models import DeckVariant, TarotCard, personal_sig_cards + + +def _seed_earthman_sig_pile(): + """Re-seed Earthman DeckVariant + the 16 MIDDLE court cards that + `personal_sig_cards(user)` returns. Idempotent — `get_or_create` on + deck + each card slug. + + The 16 cards are Maid/Jack/Queen/King × BRANDS/CROWNS/BLADES/GRAILS. + Major 0/1 (Nomad/Schizo) are *not* seeded here — `_filter_major_unlocks` + in `personal_sig_cards()` strips them for users without the matching + Note unlocks, which is the default state in solo FTs. If a future FT + needs the Major seed (Note-unlocked path), it should seed those rows + separately or extend this helper w. a flag.""" + earthman, _ = DeckVariant.objects.get_or_create( + slug="earthman", + defaults={"name": "Earthman", "card_count": 106, "is_default": True}, + ) + _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"}, + ) + return earthman + + +def _assign_sig(user, card=None, reversed_flag=False): + """Assign `user.significator` (and optionally `significator_reversed`) + directly, bypassing the picker UI. Returns the assigned card. + + If `card` is None, defaults to the first card in + `personal_sig_cards(user)` — the same card the picker happy-path FT + targets. Caller is responsible for ensuring the sig pile is seeded + (call `_seed_earthman_sig_pile()` before User.create if needed).""" + if card is None: + card = personal_sig_cards(user)[0] + user.significator = card + user.significator_reversed = reversed_flag + user.save(update_fields=["significator", "significator_reversed"]) + return card diff --git a/src/functional_tests/test_bill_my_sign.py b/src/functional_tests/test_bill_my_sign.py index e77f652..f9fa09c 100644 --- a/src/functional_tests/test_bill_my_sign.py +++ b/src/functional_tests/test_bill_my_sign.py @@ -10,8 +10,9 @@ is branded "Sign" / "Game Sign". from selenium.webdriver.common.by import By from .base import FunctionalTest +from .sig_page import _assign_sig, _seed_earthman_sig_pile from apps.applets.models import Applet -from apps.epic.models import DeckVariant, TarotCard, personal_sig_cards +from apps.epic.models import personal_sig_cards from apps.lyric.models import User @@ -23,29 +24,6 @@ def _seed_my_sign_applet(): ) -def _seed_earthman_sig_pile(): - """Re-seed Earthman DeckVariant + the 16 MIDDLE court cards that - personal_sig_cards() returns. TransactionTestCase flushes wipe the - migration seed between tests, so each setUp must restore them. - See [[feedback_transactiontestcase_flush]].""" - earthman, _ = DeckVariant.objects.get_or_create( - slug="earthman", - defaults={"name": "Earthman", "card_count": 106, "is_default": True}, - ) - _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"}, - ) - return earthman - - class MySignPickerTest(FunctionalTest): """Happy-path picker: a user with the Earthman deck equipped lands at /billboard/my-sign/, picks a card, clicks SAVE SIGN, and sees the sig @@ -469,13 +447,8 @@ class MySignClearTest(FunctionalTest): _seed_my_sign_applet() self.email = "clear@test.io" self.gamer = User.objects.create(email=self.email) - sig_pile = personal_sig_cards(self.gamer) - self.target_card = sig_pile[0] if sig_pile else None - self.assertIsNotNone(self.target_card) - # Pre-save a sig so the CLEAR affordance is visible on landing. - self.gamer.significator = self.target_card - self.gamer.significator_reversed = False - self.gamer.save(update_fields=["significator", "significator_reversed"]) + # Pre-save a sig so the DEL affordance is visible on landing. + self.target_card = _assign_sig(self.gamer) # ── Test 1 ─────────────────────────────────────────────────────────────── diff --git a/src/functional_tests/test_game_my_sea.py b/src/functional_tests/test_game_my_sea.py index 3946ee0..802845a 100644 --- a/src/functional_tests/test_game_my_sea.py +++ b/src/functional_tests/test_game_my_sea.py @@ -9,8 +9,9 @@ mirrors the gate hint in its empty-state slot. from selenium.webdriver.common.by import By from .base import FunctionalTest +from .sig_page import _assign_sig, _seed_earthman_sig_pile from apps.applets.models import Applet -from apps.epic.models import DeckVariant, TarotCard, personal_sig_cards +from apps.epic.models import personal_sig_cards from apps.lyric.models import User @@ -32,29 +33,6 @@ def _seed_gameboard_applets(): ) -def _seed_earthman_sig_pile(): - """Re-seed Earthman DeckVariant + the 16 MIDDLE court cards that - personal_sig_cards() returns. TransactionTestCase flushes wipe the - migration seed between tests, so each setUp must restore them. - See [[feedback_transactiontestcase_flush]].""" - earthman, _ = DeckVariant.objects.get_or_create( - slug="earthman", - defaults={"name": "Earthman", "card_count": 106, "is_default": True}, - ) - _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"}, - ) - return earthman - - class MySeaSignGateTest(FunctionalTest): """Sign-gate UX on the standalone /gameboard/my-sea/ page + the /gameboard/ My Sea applet. User without a saved sig sees a Look!- @@ -134,8 +112,7 @@ class MySeaSignGateTest(FunctionalTest): def test_with_sig_skips_gate_and_renders_draw_shell(self): """User w. saved significator → no .my-sea-sign-gate on the page; draw shell renders normally (Sprint 3 placeholder).""" - self.gamer.significator = self.target_card - self.gamer.save(update_fields=["significator"]) + _assign_sig(self.gamer, self.target_card) self.create_pre_authenticated_session(self.email) self.browser.get(self.live_server_url + "/gameboard/my-sea/") self.wait_for( @@ -170,8 +147,7 @@ class MySeaSignGateTest(FunctionalTest): def test_with_sig_applet_renders_default_empty_state(self): """Applet w. saved sig → no gate, empty-state placeholder (until Sprint 7 wires up the latest-draw rendering).""" - self.gamer.significator = self.target_card - self.gamer.save(update_fields=["significator"]) + _assign_sig(self.gamer, self.target_card) self.create_pre_authenticated_session(self.email) self.browser.get(self.live_server_url + "/gameboard/") self.wait_for(