All checks were successful
Backend-tester / test (push) Successful in 21s
Fortsetter robusthetslinjen fra ADR-057: hole_score/match_hole_result (scoring.py) hadde ingen samtidighetsvern, ren siste-skriving-vinner -- den siste dokumenterte, kjente svakheten fra den opprinnelige "harde kjerner"-vurderingen. Samme version-kolonne-mønster, tilpasset UPSERT (INSERT ... ON CONFLICT DO UPDATE ... WHERE) i stedet for ren UPDATE. Offline-kø-kjedingen generalisert til å nøkle på ressurs (resourceKey), ikke bare URL -- nødvendig siden disse to endepunktene deler samme URL for alle hull i en match. Se ADR-060/CHANGELOG punkt 69. 23/23 backend-tester grønne, tsc rent. Migrasjon 063 kjørt mot ekte teecup_db, teecup_api/teecup_frontend rebygget, ingen konsollfeil. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
241 lines
9.8 KiB
Python
241 lines
9.8 KiB
Python
"""
|
|
Delt testoppsett for backend-integrasjonstestene.
|
|
|
|
Kjøres ALLTID mot en scratch-database (se scripts/run_backend_tests.sh) --
|
|
aldri mot ekte teecup_db. Poolen initialiseres per test (funksjonsscope,
|
|
ikke sesjonsscope) for å unngå event loop-fallgruver med pytest-asyncio --
|
|
en full pool-oppstart tar millisekunder og suiten er liten nok til at det
|
|
ikke koster noe i praksis.
|
|
|
|
Hjelpefunksjonene under (create_user, create_org, ...) setter kun de
|
|
kolonnene testene faktisk trenger å kontrollere, med fornuftige defaults
|
|
for resten -- se \\d-utskrift av schema for hvilke felt som er NOT NULL.
|
|
"""
|
|
|
|
import uuid
|
|
|
|
import pytest_asyncio
|
|
|
|
from app import db as app_db
|
|
|
|
|
|
def new_id() -> str:
|
|
return str(uuid.uuid4())
|
|
|
|
|
|
@pytest_asyncio.fixture
|
|
async def pool():
|
|
await app_db.init_pool()
|
|
yield
|
|
await app_db.close_pool()
|
|
|
|
|
|
async def create_user(**overrides) -> str:
|
|
"""Egen tilkobling, samme begrunnelse som create_org -- app_user har
|
|
ingen RLS (ikke org-scopet), så det er trygt/enkelt å ikke kreve en
|
|
ferdig-åpnet `conn` fra kalleren."""
|
|
user_id = overrides.get("id", new_id())
|
|
display_name = overrides.get("display_name", f"Testbruker {user_id[:8]}")
|
|
birth_date = overrides.get("birth_date")
|
|
email = overrides.get("email", f"{user_id}@pytest.invalid")
|
|
async with app_db.plain_connection() as conn:
|
|
await conn.execute(
|
|
"""
|
|
INSERT INTO app_user (id, display_name, birth_date, email)
|
|
VALUES ($1, $2, $3, $4)
|
|
""",
|
|
user_id, display_name, birth_date, email,
|
|
)
|
|
return user_id
|
|
|
|
|
|
async def create_org(**overrides) -> str:
|
|
"""Egen tilkobling (ikke `conn`-parameter som de andre hjelperne) --
|
|
speiler den ekte selvrefererende RLS-bootstrappen i
|
|
routers/organizations.py::create_organization: `app.current_org` MÅ
|
|
være satt til organisasjonens EGEN, ennå-ikke-eksisterende id FØR
|
|
innsettingen, ellers avviser org_self-policyen den (teecup_app er
|
|
NOSUPERUSER/NOBYPASSRLS, akkurat som i produksjon)."""
|
|
org_id = overrides.get("id", new_id())
|
|
name = overrides.get("name", f"Testklubb {org_id[:8]}")
|
|
async with app_db.org_connection(org_id) as conn:
|
|
await conn.execute("INSERT INTO organization (id, name) VALUES ($1, $2)", org_id, name)
|
|
return org_id
|
|
|
|
|
|
async def add_membership(organization_id: str, user_id: str, role: str = "member") -> None:
|
|
"""organization_membership er org-scopet (RLS), samme begrunnelse som
|
|
create_org over -- må skrives med riktig app.current_org satt."""
|
|
async with app_db.org_connection(organization_id) as conn:
|
|
await conn.execute(
|
|
"INSERT INTO organization_membership (organization_id, user_id, role) VALUES ($1, $2, $3)",
|
|
organization_id, user_id, role,
|
|
)
|
|
|
|
|
|
async def create_round(conn, owner_user_id: str, **overrides) -> str:
|
|
round_id = overrides.get("id", new_id())
|
|
# course_source='teeoff' krever KUN teeoff_facility_slug/teeoff_course_id
|
|
# (fritekst, ingen FK mot en ekte teeoff-bane) -- enklere for et testfixtur
|
|
# enn 'custom', som (round_check-constrainten) også krever en ekte rad i
|
|
# personal_course.
|
|
await conn.execute(
|
|
"""
|
|
INSERT INTO round (id, owner_user_id, course_source, teeoff_facility_slug,
|
|
teeoff_course_id, course_name_snapshot, tee_name_snapshot, played_at)
|
|
VALUES ($1, $2, 'teeoff', 'test-facility', 'test-course', 'Testbanen', 'Gul', CURRENT_DATE)
|
|
""",
|
|
round_id, owner_user_id,
|
|
)
|
|
return round_id
|
|
|
|
|
|
async def create_participant(conn, round_id: str, **overrides) -> str:
|
|
participant_id = overrides.get("id", new_id())
|
|
user_id = overrides.get("user_id")
|
|
is_owner = overrides.get("is_owner", True)
|
|
course_handicap_snapshot = overrides.get("course_handicap_snapshot")
|
|
await conn.execute(
|
|
"""
|
|
INSERT INTO round_participant (id, round_id, user_id, is_owner, gender,
|
|
tee_name_snapshot, course_handicap_snapshot)
|
|
VALUES ($1, $2, $3, $4, 'x', 'Gul', $5)
|
|
""",
|
|
participant_id, round_id, user_id, is_owner, course_handicap_snapshot,
|
|
)
|
|
return participant_id
|
|
|
|
|
|
async def create_hole(conn, participant_id: str, hole_number: int, par: int = 4,
|
|
stroke_index: int = 9, **overrides) -> str:
|
|
hole_id = overrides.get("id", new_id())
|
|
version = overrides.get("version", 1)
|
|
await conn.execute(
|
|
"""
|
|
INSERT INTO round_hole (id, round_participant_id, hole_number, par, stroke_index, version)
|
|
VALUES ($1, $2, $3, $4, $5, $6)
|
|
""",
|
|
hole_id, participant_id, hole_number, par, stroke_index, version,
|
|
)
|
|
return hole_id
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Org-turnering-kjeden (organisasjon -> turnering -> lag/bane/tee -> økt ->
|
|
# match[-deltaker]) -- brukt av test_scoring_concurrency.py. Alle disse
|
|
# tabellene er org-scopet (RLS), derfor org_connection(organization_id) i
|
|
# hver hjelper i stedet for en delt `conn`-parameter (samme mønster som
|
|
# create_org/add_membership over).
|
|
# ---------------------------------------------------------------------------
|
|
|
|
async def create_tournament(organization_id: str, **overrides) -> str:
|
|
tournament_id = overrides.get("id", new_id())
|
|
name = overrides.get("name", f"Testturnering {tournament_id[:8]}")
|
|
join_code = overrides.get("join_code", tournament_id[:8])
|
|
async with app_db.org_connection(organization_id) as conn:
|
|
await conn.execute(
|
|
"INSERT INTO tournament (id, organization_id, name, join_code) VALUES ($1, $2, $3, $4)",
|
|
tournament_id, organization_id, name, join_code,
|
|
)
|
|
return tournament_id
|
|
|
|
|
|
async def create_team(organization_id: str, tournament_id: str, **overrides) -> str:
|
|
team_id = overrides.get("id", new_id())
|
|
name = overrides.get("name", f"Testlag {team_id[:8]}")
|
|
async with app_db.org_connection(organization_id) as conn:
|
|
await conn.execute(
|
|
"INSERT INTO team (id, organization_id, tournament_id, name) VALUES ($1, $2, $3, $4)",
|
|
team_id, organization_id, tournament_id, name,
|
|
)
|
|
return team_id
|
|
|
|
|
|
async def create_course(organization_id: str, **overrides) -> str:
|
|
course_id = overrides.get("id", new_id())
|
|
name = overrides.get("name", f"Testbanen {course_id[:8]}")
|
|
async with app_db.org_connection(organization_id) as conn:
|
|
await conn.execute(
|
|
"INSERT INTO course (id, organization_id, name) VALUES ($1, $2, $3)",
|
|
course_id, organization_id, name,
|
|
)
|
|
return course_id
|
|
|
|
|
|
async def create_tee(organization_id: str, course_id: str, **overrides) -> str:
|
|
tee_id = overrides.get("id", new_id())
|
|
name = overrides.get("name", "Gul")
|
|
async with app_db.org_connection(organization_id) as conn:
|
|
await conn.execute(
|
|
"INSERT INTO tee (id, organization_id, course_id, name) VALUES ($1, $2, $3, $4)",
|
|
tee_id, organization_id, course_id, name,
|
|
)
|
|
return tee_id
|
|
|
|
|
|
async def create_session(organization_id: str, tournament_id: str, course_id: str, **overrides) -> str:
|
|
session_id = overrides.get("id", new_id())
|
|
sequence = overrides.get("sequence", 1)
|
|
format_ = overrides.get("format", "singles")
|
|
scoring_mode = overrides.get("scoring_mode", "stroke")
|
|
async with app_db.org_connection(organization_id) as conn:
|
|
await conn.execute(
|
|
"""
|
|
INSERT INTO session (id, organization_id, tournament_id, sequence, format,
|
|
course_id, scoring_mode)
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
|
""",
|
|
session_id, organization_id, tournament_id, sequence, format_, course_id, scoring_mode,
|
|
)
|
|
return session_id
|
|
|
|
|
|
async def create_match(organization_id: str, session_id: str, team_a_id: str, team_b_id: str,
|
|
**overrides) -> str:
|
|
match_id = overrides.get("id", new_id())
|
|
sequence = overrides.get("sequence", 1)
|
|
async with app_db.org_connection(organization_id) as conn:
|
|
await conn.execute(
|
|
"""
|
|
INSERT INTO match (id, organization_id, session_id, sequence, team_a_id, team_b_id)
|
|
VALUES ($1, $2, $3, $4, $5, $6)
|
|
""",
|
|
match_id, organization_id, session_id, sequence, team_a_id, team_b_id,
|
|
)
|
|
return match_id
|
|
|
|
|
|
async def create_player(organization_id: str, **overrides) -> str:
|
|
player_id = overrides.get("id", new_id())
|
|
user_id = overrides.get("user_id")
|
|
display_name = overrides.get("display_name", f"Testspiller {player_id[:8]}")
|
|
async with app_db.org_connection(organization_id) as conn:
|
|
await conn.execute(
|
|
"INSERT INTO player (id, organization_id, user_id, display_name) VALUES ($1, $2, $3, $4)",
|
|
player_id, organization_id, user_id, display_name,
|
|
)
|
|
return player_id
|
|
|
|
|
|
async def create_team_roster(organization_id: str, team_id: str, player_id: str, **overrides) -> str:
|
|
roster_id = overrides.get("id", new_id())
|
|
async with app_db.org_connection(organization_id) as conn:
|
|
await conn.execute(
|
|
"INSERT INTO team_roster (id, organization_id, team_id, player_id) VALUES ($1, $2, $3, $4)",
|
|
roster_id, organization_id, team_id, player_id,
|
|
)
|
|
return roster_id
|
|
|
|
|
|
async def create_match_participant(organization_id: str, match_id: str, team_side: str,
|
|
team_roster_id: str, tee_id: str, **overrides) -> str:
|
|
participant_id = overrides.get("id", new_id())
|
|
async with app_db.org_connection(organization_id) as conn:
|
|
await conn.execute(
|
|
"""
|
|
INSERT INTO match_participant (id, organization_id, match_id, team_side, team_roster_id, tee_id)
|
|
VALUES ($1, $2, $3, $4, $5, $6)
|
|
""",
|
|
participant_id, organization_id, match_id, team_side, team_roster_id, tee_id,
|
|
)
|
|
return participant_id
|