2026-08-18 15:07:21 +02:00
|
|
|
"""
|
|
|
|
|
Cut (migrasjon 083, 2026-08-18): topp N og delt plass, etter et
|
|
|
|
|
organisator-valgt rundenummer, MANUELT anvendt (bruker bekreftet
|
|
|
|
|
eksplisitt -- ikke automatisk ved fullspilt runde). Kaller de ekte
|
|
|
|
|
router-funksjonene direkte (samme mønster som test_order_of_merit.py).
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
from fastapi import HTTPException
|
|
|
|
|
|
|
|
|
|
from app.auth import CurrentUser
|
|
|
|
|
from app.routers.individual_tournaments import (
|
|
|
|
|
HoleUpdate as TournamentHoleUpdate,
|
|
|
|
|
apply_cut,
|
|
|
|
|
individual_leaderboard,
|
|
|
|
|
update_hole as tournament_update_hole,
|
|
|
|
|
)
|
|
|
|
|
from app.routers.tournaments import TournamentUpdate, update_tournament
|
|
|
|
|
|
|
|
|
|
from tests.conftest import (
|
|
|
|
|
add_membership,
|
|
|
|
|
create_org,
|
|
|
|
|
create_org_hole,
|
|
|
|
|
create_course,
|
|
|
|
|
create_player,
|
|
|
|
|
create_tee,
|
|
|
|
|
create_tournament,
|
|
|
|
|
create_tournament_participant,
|
|
|
|
|
create_tournament_round,
|
|
|
|
|
create_tournament_round_participant,
|
|
|
|
|
create_user,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
import app.db as app_db
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def _score_holes(tournament_id, round_id, rp_id, org_id, user_id, scores: list[int]) -> None:
|
|
|
|
|
user = CurrentUser(user_id=user_id)
|
|
|
|
|
for n, gross in enumerate(scores, start=1):
|
|
|
|
|
await tournament_update_hole(
|
|
|
|
|
tournament_id, round_id, rp_id, n,
|
|
|
|
|
TournamentHoleUpdate(gross_strokes=gross, expected_version=None),
|
|
|
|
|
organization_id=org_id, user=user,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def _setup(org_id, owner_id, method="stroke_gross"):
|
|
|
|
|
tournament_id = await create_tournament(org_id, name="Cut-turnering")
|
|
|
|
|
course_id = await create_course(org_id, name="Cut Links")
|
|
|
|
|
for n in range(1, 19):
|
|
|
|
|
await create_org_hole(org_id, course_id, hole_number=n, par=4, stroke_index=n)
|
|
|
|
|
tee_id = await create_tee(org_id, course_id)
|
|
|
|
|
async with app_db.org_connection(org_id) as conn:
|
|
|
|
|
await conn.execute(
|
|
|
|
|
"UPDATE tournament SET format_type = 'individual', scoring_method = $2 WHERE id = $1",
|
|
|
|
|
tournament_id, method,
|
|
|
|
|
)
|
|
|
|
|
round1_id = await create_tournament_round(org_id, tournament_id, course_id, sequence=1)
|
|
|
|
|
round2_id = await create_tournament_round(org_id, tournament_id, course_id, sequence=2)
|
|
|
|
|
|
|
|
|
|
players = {}
|
|
|
|
|
for name in ["P1", "P2", "P3", "P4", "P5"]:
|
|
|
|
|
player_id = await create_player(org_id, display_name=name)
|
|
|
|
|
tp_id = await create_tournament_participant(org_id, tournament_id, player_id)
|
|
|
|
|
players[name] = tp_id
|
|
|
|
|
|
|
|
|
|
# Runde 1: P1 -4, P2/P3 uavgjort -2, P4 +3, P5 +8 (par 72 over 18 hull).
|
|
|
|
|
scores_r1 = {
|
|
|
|
|
"P1": [3, 3, 3, 3] + [4] * 14, # 68 -> -4
|
|
|
|
|
"P2": [3, 3] + [4] * 16, # 70 -> -2
|
|
|
|
|
"P3": [3, 3] + [4] * 16, # 70 -> -2 (uavgjort med P2)
|
|
|
|
|
"P4": [5, 5, 5] + [4] * 15, # 75 -> +3
|
|
|
|
|
"P5": [5] * 8 + [4] * 10, # 80 -> +8
|
|
|
|
|
}
|
|
|
|
|
rp1 = {}
|
|
|
|
|
for name, tp_id in players.items():
|
|
|
|
|
rp1[name] = await create_tournament_round_participant(org_id, round1_id, tp_id, tee_id)
|
|
|
|
|
await _score_holes(tournament_id, round1_id, rp1[name], org_id, owner_id, scores_r1[name])
|
|
|
|
|
|
|
|
|
|
# P6 opprettes som deltaker, men spiller ALDRI runde 1 -- skal kuttes
|
|
|
|
|
# uansett cut_size (ingen resultat gjennom cut-punktet).
|
|
|
|
|
p6_player_id = await create_player(org_id, display_name="P6")
|
|
|
|
|
players["P6"] = await create_tournament_participant(org_id, tournament_id, p6_player_id)
|
|
|
|
|
|
|
|
|
|
rp2 = {name: await create_tournament_round_participant(org_id, round2_id, tp_id, tee_id)
|
|
|
|
|
for name, tp_id in players.items() if name != "P6"}
|
|
|
|
|
|
|
|
|
|
return tournament_id, round1_id, round2_id, players, rp2
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def _set_cut_config(org_id, tournament_id, cut_after_round, cut_size):
|
|
|
|
|
await update_tournament(
|
|
|
|
|
tournament_id,
|
|
|
|
|
TournamentUpdate(cut_after_round=cut_after_round, cut_size=cut_size),
|
|
|
|
|
organization_id=org_id,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def test_apply_cut_top_n_and_ties_lets_extra_player_through(pool):
|
|
|
|
|
"""cut_size=2, men P2/P3 er uavgjort på grenseplassen -- begge skal
|
|
|
|
|
slippe gjennom ("delt plass"), så tre (ikke to) overlever i praksis."""
|
|
|
|
|
org_id = await create_org()
|
|
|
|
|
owner_id = await create_user()
|
|
|
|
|
await add_membership(org_id, owner_id, role="owner")
|
|
|
|
|
tournament_id, round1_id, round2_id, players, rp2 = await _setup(org_id, owner_id)
|
|
|
|
|
await _set_cut_config(org_id, tournament_id, cut_after_round=1, cut_size=2)
|
|
|
|
|
|
|
|
|
|
result = await apply_cut(tournament_id, organization_id=org_id)
|
|
|
|
|
assert result.cut_after_round == 1
|
|
|
|
|
assert result.cut_size == 2
|
|
|
|
|
assert result.survivors == 3 # P1, P2, P3 (uavgjort holdt begge inne)
|
|
|
|
|
assert result.cut_count == 3 # P4, P5, P6
|
|
|
|
|
|
|
|
|
|
async with app_db.org_connection(org_id) as conn:
|
|
|
|
|
cut_flags = {
|
|
|
|
|
name: await conn.fetchval("SELECT cut FROM tournament_participant WHERE id = $1", tp_id)
|
|
|
|
|
for name, tp_id in players.items()
|
|
|
|
|
}
|
|
|
|
|
assert cut_flags["P1"] is False
|
|
|
|
|
assert cut_flags["P2"] is False
|
|
|
|
|
assert cut_flags["P3"] is False
|
|
|
|
|
assert cut_flags["P4"] is True
|
|
|
|
|
assert cut_flags["P5"] is True
|
|
|
|
|
assert cut_flags["P6"] is True # spilte aldri runde 1 -- kuttes uansett cut_size
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def test_apply_cut_is_idempotent(pool):
|
|
|
|
|
org_id = await create_org()
|
|
|
|
|
owner_id = await create_user()
|
|
|
|
|
await add_membership(org_id, owner_id, role="owner")
|
|
|
|
|
tournament_id, round1_id, round2_id, players, rp2 = await _setup(org_id, owner_id)
|
|
|
|
|
await _set_cut_config(org_id, tournament_id, cut_after_round=1, cut_size=2)
|
|
|
|
|
|
|
|
|
|
first = await apply_cut(tournament_id, organization_id=org_id)
|
|
|
|
|
second = await apply_cut(tournament_id, organization_id=org_id)
|
|
|
|
|
assert first.cut_participant_ids == second.cut_participant_ids or (
|
|
|
|
|
set(first.cut_participant_ids) == set(second.cut_participant_ids)
|
|
|
|
|
)
|
|
|
|
|
assert second.survivors == first.survivors
|
|
|
|
|
assert second.cut_count == first.cut_count
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def test_apply_cut_requires_config_set_first(pool):
|
|
|
|
|
org_id = await create_org()
|
|
|
|
|
owner_id = await create_user()
|
|
|
|
|
await add_membership(org_id, owner_id, role="owner")
|
|
|
|
|
tournament_id, *_ = await _setup(org_id, owner_id)
|
|
|
|
|
|
|
|
|
|
with pytest.raises(HTTPException) as exc_info:
|
|
|
|
|
await apply_cut(tournament_id, organization_id=org_id)
|
|
|
|
|
assert exc_info.value.status_code == 400
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def test_cut_player_blocked_from_scoring_in_later_round(pool):
|
2026-08-21 08:04:53 +02:00
|
|
|
"""Siden 2026-08-20 fjerner apply_cut kuttede spilleres runde-2-rad
|
|
|
|
|
HELT (kaskade-vedtaket, se test_apply_cut_removes_cut_players_...) --
|
|
|
|
|
forsøk på å score for dem gir derfor 404 (raden finnes ikke), ikke
|
|
|
|
|
lenger 403. Den gamle cut-sperren (403) lever videre som forsvar i
|
|
|
|
|
dybden for det sjeldne tilfellet raden IKKE ble fjernet fordi den
|
|
|
|
|
allerede hadde scorer -- se test_apply_cut_does_not_delete_round_
|
|
|
|
|
participation_with_existing_scores for det tilfellet."""
|
2026-08-18 15:07:21 +02:00
|
|
|
org_id = await create_org()
|
|
|
|
|
owner_id = await create_user()
|
|
|
|
|
await add_membership(org_id, owner_id, role="owner")
|
|
|
|
|
tournament_id, round1_id, round2_id, players, rp2 = await _setup(org_id, owner_id)
|
|
|
|
|
await _set_cut_config(org_id, tournament_id, cut_after_round=1, cut_size=2)
|
|
|
|
|
await apply_cut(tournament_id, organization_id=org_id)
|
|
|
|
|
|
|
|
|
|
user = CurrentUser(user_id=owner_id)
|
2026-08-21 08:04:53 +02:00
|
|
|
# P4 er kuttet -- runde-2-raden er fjernet av apply_cut, så forsøket
|
|
|
|
|
# treffer "finnes ikke", ikke "blokkert".
|
2026-08-18 15:07:21 +02:00
|
|
|
with pytest.raises(HTTPException) as exc_info:
|
|
|
|
|
await tournament_update_hole(
|
|
|
|
|
tournament_id, round2_id, rp2["P4"], 1,
|
|
|
|
|
TournamentHoleUpdate(gross_strokes=4, expected_version=None),
|
|
|
|
|
organization_id=org_id, user=user,
|
|
|
|
|
)
|
2026-08-21 08:04:53 +02:00
|
|
|
assert exc_info.value.status_code == 404
|
2026-08-18 15:07:21 +02:00
|
|
|
|
|
|
|
|
# P1 overlevde cutten -- runde 2 skal fortsatt fungere som normalt.
|
|
|
|
|
result = await tournament_update_hole(
|
|
|
|
|
tournament_id, round2_id, rp2["P1"], 1,
|
|
|
|
|
TournamentHoleUpdate(gross_strokes=4, expected_version=None),
|
|
|
|
|
organization_id=org_id, user=user,
|
|
|
|
|
)
|
|
|
|
|
assert result.gross_strokes == 4
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def test_cut_player_can_still_edit_score_in_round_at_or_before_cut_point(pool):
|
|
|
|
|
"""Cutten låser IKKE historikken -- en rettelse i runde 1 (t.o.m.
|
|
|
|
|
cut_after_round) skal fortsatt være mulig for en kuttet spiller."""
|
|
|
|
|
org_id = await create_org()
|
|
|
|
|
owner_id = await create_user()
|
|
|
|
|
await add_membership(org_id, owner_id, role="owner")
|
|
|
|
|
tournament_id, round1_id, round2_id, players, rp2 = await _setup(org_id, owner_id)
|
|
|
|
|
await _set_cut_config(org_id, tournament_id, cut_after_round=1, cut_size=2)
|
|
|
|
|
await apply_cut(tournament_id, organization_id=org_id)
|
|
|
|
|
|
|
|
|
|
async with app_db.org_connection(org_id) as conn:
|
|
|
|
|
rp1_p4 = await conn.fetchval(
|
|
|
|
|
"SELECT id FROM tournament_round_participant WHERE tournament_round_id = $1 AND tournament_participant_id = $2",
|
|
|
|
|
round1_id, players["P4"],
|
|
|
|
|
)
|
|
|
|
|
user = CurrentUser(user_id=owner_id)
|
|
|
|
|
result = await tournament_update_hole(
|
|
|
|
|
tournament_id, round1_id, str(rp1_p4), 1,
|
|
|
|
|
TournamentHoleUpdate(gross_strokes=5, expected_version=1),
|
|
|
|
|
organization_id=org_id, user=user,
|
|
|
|
|
)
|
|
|
|
|
assert result.gross_strokes == 5
|
|
|
|
|
|
|
|
|
|
|
2026-08-21 08:04:53 +02:00
|
|
|
async def test_apply_cut_auto_adds_survivor_to_round_after_cut_if_missing(pool):
|
|
|
|
|
"""P1 overlevde, men har (i motsetning til _setup sitt vanlige rp2-
|
|
|
|
|
oppsett) INGEN runde 2-rad ennå -- apply_cut skal opprette den, med
|
|
|
|
|
samme tee som P1 spilte runde 1 med (2026-08-20, bruker bekreftet
|
|
|
|
|
kaskade-oppførsel)."""
|
|
|
|
|
org_id = await create_org()
|
|
|
|
|
owner_id = await create_user()
|
|
|
|
|
await add_membership(org_id, owner_id, role="owner")
|
|
|
|
|
tournament_id = await create_tournament(org_id, name="Cut-turnering")
|
|
|
|
|
course_id = await create_course(org_id, name="Cut Links")
|
|
|
|
|
for n in range(1, 19):
|
|
|
|
|
await create_org_hole(org_id, course_id, hole_number=n, par=4, stroke_index=n)
|
|
|
|
|
tee_id = await create_tee(org_id, course_id)
|
|
|
|
|
async with app_db.org_connection(org_id) as conn:
|
|
|
|
|
await conn.execute(
|
|
|
|
|
"UPDATE tournament SET format_type = 'individual', scoring_method = 'stroke_gross' WHERE id = $1",
|
|
|
|
|
tournament_id,
|
|
|
|
|
)
|
|
|
|
|
round1_id = await create_tournament_round(org_id, tournament_id, course_id, sequence=1)
|
|
|
|
|
round2_id = await create_tournament_round(org_id, tournament_id, course_id, sequence=2)
|
|
|
|
|
|
|
|
|
|
p1 = await create_tournament_participant(org_id, tournament_id, await create_player(org_id, display_name="P1"))
|
|
|
|
|
p2 = await create_tournament_participant(org_id, tournament_id, await create_player(org_id, display_name="P2"))
|
|
|
|
|
rp1_p1 = await create_tournament_round_participant(org_id, round1_id, p1, tee_id)
|
|
|
|
|
rp1_p2 = await create_tournament_round_participant(org_id, round1_id, p2, tee_id)
|
|
|
|
|
await _score_holes(tournament_id, round1_id, rp1_p1, org_id, owner_id, [3, 3, 3, 3] + [4] * 14) # -4
|
|
|
|
|
await _score_holes(tournament_id, round1_id, rp1_p2, org_id, owner_id, [5] * 8 + [4] * 10) # +8
|
|
|
|
|
|
|
|
|
|
# Ingen runde 2-rad for NOEN opprettet på forhånd -- P1 skal likevel
|
|
|
|
|
# dukke opp der etter cut, siden P1 overlever.
|
|
|
|
|
await _set_cut_config(org_id, tournament_id, cut_after_round=1, cut_size=1)
|
|
|
|
|
|
|
|
|
|
result = await apply_cut(tournament_id, organization_id=org_id)
|
|
|
|
|
assert result.added_to_later_rounds == 1
|
|
|
|
|
|
|
|
|
|
async with app_db.org_connection(org_id) as conn:
|
|
|
|
|
row = await conn.fetchrow(
|
|
|
|
|
"SELECT tee_id::text AS tee_id, course_handicap FROM tournament_round_participant "
|
|
|
|
|
"WHERE tournament_round_id = $1 AND tournament_participant_id = $2",
|
|
|
|
|
round2_id, p1,
|
|
|
|
|
)
|
|
|
|
|
p2_row = await conn.fetchval(
|
|
|
|
|
"SELECT id FROM tournament_round_participant WHERE tournament_round_id = $1 AND tournament_participant_id = $2",
|
|
|
|
|
round2_id, p2,
|
|
|
|
|
)
|
|
|
|
|
assert row is not None
|
|
|
|
|
assert row["tee_id"] == tee_id
|
|
|
|
|
assert p2_row is None # P2 ble kuttet -- ingen runde 2-rad opprettet for dem
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def test_apply_cut_removes_cut_players_round_participation_in_later_rounds(pool):
|
|
|
|
|
"""_setup() oppretter runde 2-rader for ALLE (unntatt P6) på forhånd
|
|
|
|
|
-- apply_cut skal fjerne raden for de som blir kuttet (P4, P5, P6),
|
|
|
|
|
men la P1/P2/P3 sine stå."""
|
|
|
|
|
org_id = await create_org()
|
|
|
|
|
owner_id = await create_user()
|
|
|
|
|
await add_membership(org_id, owner_id, role="owner")
|
|
|
|
|
tournament_id, round1_id, round2_id, players, rp2 = await _setup(org_id, owner_id)
|
|
|
|
|
await _set_cut_config(org_id, tournament_id, cut_after_round=1, cut_size=2)
|
|
|
|
|
|
|
|
|
|
await apply_cut(tournament_id, organization_id=org_id)
|
|
|
|
|
|
|
|
|
|
async with app_db.org_connection(org_id) as conn:
|
|
|
|
|
remaining = {
|
|
|
|
|
name: await conn.fetchval(
|
|
|
|
|
"SELECT id FROM tournament_round_participant WHERE tournament_round_id = $1 AND tournament_participant_id = $2",
|
|
|
|
|
round2_id, tp_id,
|
|
|
|
|
)
|
|
|
|
|
for name, tp_id in players.items()
|
|
|
|
|
}
|
|
|
|
|
assert remaining["P1"] is not None
|
|
|
|
|
assert remaining["P2"] is not None
|
|
|
|
|
assert remaining["P3"] is not None
|
|
|
|
|
assert remaining["P4"] is None # kuttet, ingen scorer i runde 2 -- fjernet
|
|
|
|
|
assert remaining["P5"] is None # kuttet, ingen scorer i runde 2 -- fjernet
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def test_apply_cut_does_not_delete_round_participation_with_existing_scores(pool):
|
|
|
|
|
"""Dersom en kuttet spiller likevel har scorer registrert i en senere
|
|
|
|
|
runde (kant-tilfelle -- burde normalt blokkeres av score-sperren, men
|
|
|
|
|
testes eksplisitt siden sletting av tournament_round_participant
|
|
|
|
|
kaskader til tournament_round_hole), skal raden IKKE fjernes."""
|
|
|
|
|
org_id = await create_org()
|
|
|
|
|
owner_id = await create_user()
|
|
|
|
|
await add_membership(org_id, owner_id, role="owner")
|
|
|
|
|
tournament_id, round1_id, round2_id, players, rp2 = await _setup(org_id, owner_id)
|
|
|
|
|
|
|
|
|
|
# Score inn ett hull for P4 i runde 2 FØR cutten anvendes (simulerer at
|
|
|
|
|
# data allerede finnes -- score-sperren gjelder kun EFTER cut er anvendt).
|
|
|
|
|
await _score_holes(tournament_id, round2_id, rp2["P4"], org_id, owner_id, [4])
|
|
|
|
|
|
|
|
|
|
await _set_cut_config(org_id, tournament_id, cut_after_round=1, cut_size=2)
|
|
|
|
|
await apply_cut(tournament_id, organization_id=org_id)
|
|
|
|
|
|
|
|
|
|
async with app_db.org_connection(org_id) as conn:
|
|
|
|
|
still_there = await conn.fetchval(
|
|
|
|
|
"SELECT id FROM tournament_round_participant WHERE id = $1", rp2["P4"],
|
|
|
|
|
)
|
|
|
|
|
score_still_there = await conn.fetchval(
|
|
|
|
|
"SELECT COUNT(*) FROM tournament_round_hole WHERE tournament_round_participant_id = $1", rp2["P4"],
|
|
|
|
|
)
|
|
|
|
|
assert still_there is not None # IKKE slettet -- ville kaskadert bort scoren
|
|
|
|
|
assert score_still_there == 1
|
|
|
|
|
|
|
|
|
|
# Raden lever videre, men den eksisterende cut-sperren (403, basert på
|
|
|
|
|
# tournament_participant.cut) blokkerer likevel forsøk på Å FORTSETTE
|
|
|
|
|
# å registrere flere hull for denne kuttede spilleren i runden.
|
|
|
|
|
user = CurrentUser(user_id=owner_id)
|
|
|
|
|
with pytest.raises(HTTPException) as exc_info:
|
|
|
|
|
await tournament_update_hole(
|
|
|
|
|
tournament_id, round2_id, rp2["P4"], 2,
|
|
|
|
|
TournamentHoleUpdate(gross_strokes=4, expected_version=None),
|
|
|
|
|
organization_id=org_id, user=user,
|
|
|
|
|
)
|
|
|
|
|
assert exc_info.value.status_code == 403
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def test_apply_cut_cascades_to_all_rounds_after_cut_not_just_the_next_one(pool):
|
|
|
|
|
"""Bruker bekreftet eksplisitt: har du klart cutten spiller du ALLE
|
|
|
|
|
påfølgende runder, ikke bare den aller neste. Turnering med 3 runder,
|
|
|
|
|
cut etter runde 1 -- overlever skal auto-legges til BÅDE runde 2 og 3."""
|
|
|
|
|
org_id = await create_org()
|
|
|
|
|
owner_id = await create_user()
|
|
|
|
|
await add_membership(org_id, owner_id, role="owner")
|
|
|
|
|
tournament_id = await create_tournament(org_id, name="Cut-turnering, 3 runder")
|
|
|
|
|
course_id = await create_course(org_id, name="Cut Links")
|
|
|
|
|
for n in range(1, 19):
|
|
|
|
|
await create_org_hole(org_id, course_id, hole_number=n, par=4, stroke_index=n)
|
|
|
|
|
tee_id = await create_tee(org_id, course_id)
|
|
|
|
|
async with app_db.org_connection(org_id) as conn:
|
|
|
|
|
await conn.execute(
|
|
|
|
|
"UPDATE tournament SET format_type = 'individual', scoring_method = 'stroke_gross' WHERE id = $1",
|
|
|
|
|
tournament_id,
|
|
|
|
|
)
|
|
|
|
|
round1_id = await create_tournament_round(org_id, tournament_id, course_id, sequence=1)
|
|
|
|
|
round3_id = await create_tournament_round(org_id, tournament_id, course_id, sequence=3)
|
|
|
|
|
|
|
|
|
|
p1 = await create_tournament_participant(org_id, tournament_id, await create_player(org_id, display_name="P1"))
|
|
|
|
|
p2 = await create_tournament_participant(org_id, tournament_id, await create_player(org_id, display_name="P2"))
|
|
|
|
|
rp1_p1 = await create_tournament_round_participant(org_id, round1_id, p1, tee_id)
|
|
|
|
|
rp1_p2 = await create_tournament_round_participant(org_id, round1_id, p2, tee_id)
|
|
|
|
|
await _score_holes(tournament_id, round1_id, rp1_p1, org_id, owner_id, [3, 3, 3, 3] + [4] * 14) # -4
|
|
|
|
|
await _score_holes(tournament_id, round1_id, rp1_p2, org_id, owner_id, [5] * 8 + [4] * 10) # +8
|
|
|
|
|
|
|
|
|
|
await _set_cut_config(org_id, tournament_id, cut_after_round=1, cut_size=1)
|
|
|
|
|
result = await apply_cut(tournament_id, organization_id=org_id)
|
|
|
|
|
assert result.added_to_later_rounds == 1 # kun round3 finnes (ingen round2 opprettet i dette oppsettet)
|
|
|
|
|
|
|
|
|
|
async with app_db.org_connection(org_id) as conn:
|
|
|
|
|
row = await conn.fetchval(
|
|
|
|
|
"SELECT id FROM tournament_round_participant WHERE tournament_round_id = $1 AND tournament_participant_id = $2",
|
|
|
|
|
round3_id, p1,
|
|
|
|
|
)
|
|
|
|
|
assert row is not None # P1 (overlever) auto-lagt til runde 3, selv om runde 2 ikke fantes i mellom
|
|
|
|
|
|
|
|
|
|
|
2026-08-18 15:07:21 +02:00
|
|
|
async def test_leaderboard_marks_cut_players_with_status_and_sorts_them_last(pool):
|
|
|
|
|
org_id = await create_org()
|
|
|
|
|
owner_id = await create_user()
|
|
|
|
|
await add_membership(org_id, owner_id, role="owner")
|
|
|
|
|
tournament_id, round1_id, round2_id, players, rp2 = await _setup(org_id, owner_id)
|
|
|
|
|
await _set_cut_config(org_id, tournament_id, cut_after_round=1, cut_size=2)
|
|
|
|
|
await apply_cut(tournament_id, organization_id=org_id)
|
|
|
|
|
|
|
|
|
|
entries = await individual_leaderboard(tournament_id, organization_id=org_id)
|
|
|
|
|
by_id = {e.tournament_participant_id: e for e in entries}
|
|
|
|
|
|
|
|
|
|
assert by_id[players["P4"]].total_label == "CUT"
|
|
|
|
|
assert by_id[players["P4"]].cut is True
|
|
|
|
|
assert by_id[players["P4"]].is_leader is False
|
|
|
|
|
assert by_id[players["P1"]].total_label != "CUT"
|
|
|
|
|
assert by_id[players["P1"]].cut is False
|
|
|
|
|
|
|
|
|
|
# Alle ikke-kuttede rader kommer FØR alle kuttede rader, uansett
|
|
|
|
|
# frossent til-par-tall.
|
|
|
|
|
cut_flags_in_order = [by_id[e.tournament_participant_id].cut for e in entries]
|
|
|
|
|
first_cut_index = cut_flags_in_order.index(True)
|
|
|
|
|
assert all(not c for c in cut_flags_in_order[:first_cut_index])
|
|
|
|
|
assert all(c for c in cut_flags_in_order[first_cut_index:])
|