Compare commits
2 Commits
b97c4a0508
...
af1a90e76b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af1a90e76b | ||
|
|
8240de6b45 |
@@ -11,19 +11,21 @@ services:
|
||||
|
||||
steps:
|
||||
- name: test-UTs-n-ITs
|
||||
image: python:3.13-slim
|
||||
image: gitea.earthmanrpg.me/discoman/python-tdd-ci:latest
|
||||
environment:
|
||||
DATABASE_URL: postgresql://postgres:postgres@postgres/python_tdd_test
|
||||
CELERY_BROKER_URL: redis://redis:6379/0
|
||||
REDIS_URL: redis://redis:6379/1
|
||||
# Workspace-shared pip cache — Woodpecker mounts the workspace across
|
||||
# all steps in a run, so the wheels populated here are reused by
|
||||
# test-two-browser-FTs + test-FTs (saves ~30-60s × 2 = ~1-2 min per
|
||||
# run). Cache lives only inside one run; cross-run caching would
|
||||
# need a volume plugin.
|
||||
PIP_CACHE_DIR: .pip-cache
|
||||
commands:
|
||||
- pip install -r requirements.txt
|
||||
# `requirements.dev.txt` is the pinned superset Dockerfile.ci pre-
|
||||
# installs; pinning here means pip skips resolver+download and just
|
||||
# verifies "already satisfied" (~5-10s) instead of resolving unpinned
|
||||
# requirements.txt against PyPI from scratch (~30-60s). Drift safety
|
||||
# net: if requirements.dev.txt has changed since the CI image was
|
||||
# last rebuilt + pushed, pip installs the delta — slower for that
|
||||
# run but never broken. See TDD SKILL.md § CI dependency discipline.
|
||||
- pip install -r requirements.dev.txt
|
||||
- cd ./src
|
||||
- python manage.py test apps
|
||||
when:
|
||||
@@ -45,7 +47,7 @@ steps:
|
||||
from_secret: stripe_publishable_key
|
||||
PIP_CACHE_DIR: .pip-cache
|
||||
commands:
|
||||
- pip install -r requirements.txt
|
||||
- pip install -r requirements.dev.txt
|
||||
- cd ./src
|
||||
- python manage.py collectstatic --noinput
|
||||
- python manage.py test functional_tests --tag=two-browser
|
||||
@@ -70,7 +72,7 @@ steps:
|
||||
from_secret: stripe_publishable_key
|
||||
PIP_CACHE_DIR: .pip-cache
|
||||
commands:
|
||||
- pip install -r requirements.txt
|
||||
- pip install -r requirements.dev.txt
|
||||
- cd ./src
|
||||
- python manage.py collectstatic --noinput
|
||||
- python manage.py test functional_tests --parallel --exclude-tag=channels --exclude-tag=two-browser
|
||||
|
||||
112
src/functional_tests/room_page.py
Normal file
112
src/functional_tests/room_page.py
Normal file
@@ -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()
|
||||
@@ -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):
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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."""
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user