fix CI FT regression: My Sign + My Sea setUpClass ContentType collision — pipeline #313
Sprint 4a-cont (400762c) + 4b (cd0add1) introduced `serialized_rollback = True` on `MySignPickerTest`, `MySignBackupDeckTest`, and `MySeaSignGateTest` to keep migration-seeded `DeckVariant` + `TarotCard` rows alive across TransactionTestCase flushes. Locally each file ran clean, but in the full pipeline #313 `test-FTs-non-room` stage all three classes errored in setUpClass: django.db.utils.IntegrityError: UNIQUE constraint failed: django_content_type.app_label, django_content_type.model Mechanism: every prior `TransactionTestCase`-derived class in the bucket flushed without `inhibit_post_migrate`, so Django's post-migrate signal recreated `django_content_type` rows. When these three classes hit `_fixture_setup` & tried to deserialize the saved DB snapshot, the inserts collided on `(app_label, model)`. No serialized rollback ⇒ no collision; mixing in the same DB run is the trap. Fix: drop `serialized_rollback = True` from all 3 classes & inline-reseed via `get_or_create` per the canonical pattern already used by [test_admin_tarot.py](src/functional_tests/test_admin_tarot.py) & [room_page.py](src/functional_tests/room_page.py) (see [[feedback_transactiontestcase_flush]]) — new `_seed_earthman_sig_pile()` module-level helper in each file restores `DeckVariant(slug='earthman')` + the 16 MIDDLE court cards (Maid/Jack/Queen/King × BRANDS/CROWNS/BLADES/GRAILS) that `personal_sig_cards(user)` returns. Adjacent bug uncovered once the snapshot reload was gone: [test_game_my_sea.py](src/functional_tests/test_game_my_sea.py) `_seed_gameboard_applets` had been seeding `my-palette` w. `context='gameboard'` — but `my-palette` is not a gameboard applet (it's a dashboard slug; the real palette applet is `palette` per migration 0003). The applets template iterates every applet & includes `apps/<context>/_partials/_applet-<slug>.html`, so seeding the bogus row made /gameboard/ try to load a partial that doesn't exist → TemplateDoesNotExist 500 → `#id_applet_my_sea` never rendered. `serialized_rollback = True` had been masking this because the snapshot restored the migration-correct applet rows (which never had my-palette as a gameboard entry to begin with). Swapped `my-palette` for the real `new-game` gameboard applet & corrected grid-cols/rows on `game-kit` to match the 0003 seed (4×3, not 4×6). Tests: full sweep across all three classes runs green locally (17/17 in 142s). Code architected by Disco DeDisco <discodedisco@outlook.com> Git commit message Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -11,7 +11,7 @@ from selenium.webdriver.common.by import By
|
|||||||
|
|
||||||
from .base import FunctionalTest
|
from .base import FunctionalTest
|
||||||
from apps.applets.models import Applet
|
from apps.applets.models import Applet
|
||||||
from apps.epic.models import personal_sig_cards
|
from apps.epic.models import DeckVariant, TarotCard, personal_sig_cards
|
||||||
from apps.lyric.models import User
|
from apps.lyric.models import User
|
||||||
|
|
||||||
|
|
||||||
@@ -23,19 +23,37 @@ 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):
|
class MySignPickerTest(FunctionalTest):
|
||||||
"""Happy-path picker: a user with the Earthman deck equipped lands at
|
"""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
|
/billboard/my-sign/, picks a card, clicks SAVE SIGN, and sees the sig
|
||||||
propagate to the Game Sign applet on /billboard/."""
|
propagate to the Game Sign applet on /billboard/."""
|
||||||
|
|
||||||
# StaticLiveServerTestCase → TransactionTestCase flushes DB between tests,
|
|
||||||
# wiping migration-seeded DeckVariant + TarotCard rows. Without this flag,
|
|
||||||
# personal_sig_cards(user) returns [] because the signal that auto-equips
|
|
||||||
# Earthman can't find the deck. See [[feedback_transactiontestcase_flush]].
|
|
||||||
serialized_rollback = True
|
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
|
_seed_earthman_sig_pile()
|
||||||
_seed_my_sign_applet()
|
_seed_my_sign_applet()
|
||||||
# Seed the rest of the billboard applets so /billboard/ renders
|
# Seed the rest of the billboard applets so /billboard/ renders
|
||||||
# without missing-applet errors.
|
# without missing-applet errors.
|
||||||
@@ -339,10 +357,9 @@ class MySignBackupDeckTest(FunctionalTest):
|
|||||||
NVM dismisses + picker stays usable w. backup deck cards; FYI links
|
NVM dismisses + picker stays usable w. backup deck cards; FYI links
|
||||||
to the gameboard (Game Kit applet)."""
|
to the gameboard (Game Kit applet)."""
|
||||||
|
|
||||||
serialized_rollback = True
|
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
|
_seed_earthman_sig_pile()
|
||||||
for slug, name in [
|
for slug, name in [
|
||||||
("my-sign", "Game Sign"), ("my-scrolls", "My Scrolls"),
|
("my-sign", "Game Sign"), ("my-scrolls", "My Scrolls"),
|
||||||
("my-buds", "My Buds"), ("most-recent-scroll", "Most Recent Scroll"),
|
("my-buds", "My Buds"), ("most-recent-scroll", "Most Recent Scroll"),
|
||||||
|
|||||||
@@ -10,17 +10,19 @@ from selenium.webdriver.common.by import By
|
|||||||
|
|
||||||
from .base import FunctionalTest
|
from .base import FunctionalTest
|
||||||
from apps.applets.models import Applet
|
from apps.applets.models import Applet
|
||||||
from apps.epic.models import personal_sig_cards
|
from apps.epic.models import DeckVariant, TarotCard, personal_sig_cards
|
||||||
from apps.lyric.models import User
|
from apps.lyric.models import User
|
||||||
|
|
||||||
|
|
||||||
def _seed_gameboard_applets():
|
def _seed_gameboard_applets():
|
||||||
"""My Sea + the rest of the gameboard applets so /gameboard/ renders
|
"""My Sea + the rest of the gameboard applets so /gameboard/ renders
|
||||||
without missing-applet errors during the applet-side assertions."""
|
without missing-applet errors during the applet-side assertions.
|
||||||
|
Mirrors the migration seed (0003 + 0008) — every slug must have a
|
||||||
|
matching _applet-<slug>.html partial under apps/gameboard/_partials/."""
|
||||||
for slug, name, cols, rows, ctx in [
|
for slug, name, cols, rows, ctx in [
|
||||||
("my-sea", "My Sea", 12, 4, "gameboard"),
|
("my-sea", "My Sea", 12, 4, "gameboard"),
|
||||||
("game-kit", "Game Kit", 4, 6, "gameboard"),
|
("game-kit", "Game Kit", 4, 3, "gameboard"),
|
||||||
("my-palette", "My Palette", 4, 4, "gameboard"),
|
("new-game", "New Game", 4, 3, "gameboard"),
|
||||||
("my-games", "My Games", 4, 4, "gameboard"),
|
("my-games", "My Games", 4, 4, "gameboard"),
|
||||||
]:
|
]:
|
||||||
Applet.objects.get_or_create(
|
Applet.objects.get_or_create(
|
||||||
@@ -30,15 +32,37 @@ 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):
|
class MySeaSignGateTest(FunctionalTest):
|
||||||
"""Sign-gate UX on the standalone /gameboard/my-sea/ page + the
|
"""Sign-gate UX on the standalone /gameboard/my-sea/ page + the
|
||||||
/gameboard/ My Sea applet. User without a saved sig sees a Look!-
|
/gameboard/ My Sea applet. User without a saved sig sees a Look!-
|
||||||
formatted nudge w. FYI to the picker + BACK to the gameboard."""
|
formatted nudge w. FYI to the picker + BACK to the gameboard."""
|
||||||
|
|
||||||
serialized_rollback = True
|
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
|
_seed_earthman_sig_pile()
|
||||||
_seed_gameboard_applets()
|
_seed_gameboard_applets()
|
||||||
self.email = "sea@test.io"
|
self.email = "sea@test.io"
|
||||||
self.gamer = User.objects.create(email=self.email)
|
self.gamer = User.objects.create(email=self.email)
|
||||||
|
|||||||
Reference in New Issue
Block a user