227 lines
8.9 KiB
Python
227 lines
8.9 KiB
Python
|
|
"""
|
||
|
|
Flaggturnering: GPS-flaggplanting + runde 2+, org-individuelle turneringer
|
||
|
|
(migrasjon 070, 2026-08-14) -- speiler test_flag_plant.py (frittstående
|
||
|
|
runder) presist, men gjennom individual_tournaments.py sin self-only
|
||
|
|
autorisasjon (user_is_own_tournament_participant), ikke rounds.py sin
|
||
|
|
flight-styrte modell.
|
||
|
|
|
||
|
|
Kaller de faktiske router-funksjonene direkte (samme mønster som
|
||
|
|
test_scoring_concurrency.py) -- organization_id sendes som vanlig
|
||
|
|
funksjonsargument her, ikke via FastAPIs Depends (som kun løses når
|
||
|
|
appen faktisk kjører via HTTP).
|
||
|
|
"""
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
from fastapi import HTTPException
|
||
|
|
|
||
|
|
from app.auth import CurrentUser
|
||
|
|
from app.routers.individual_tournaments import (
|
||
|
|
FlagOverflowHoleIn,
|
||
|
|
FlagPlantIn,
|
||
|
|
HoleUpdate,
|
||
|
|
get_flag_map,
|
||
|
|
get_flag_plant,
|
||
|
|
list_flag_overflow_holes,
|
||
|
|
plant_flag,
|
||
|
|
unplant_flag,
|
||
|
|
update_flag_overflow_hole,
|
||
|
|
update_hole,
|
||
|
|
)
|
||
|
|
|
||
|
|
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,
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
async def _setup_flag_tournament():
|
||
|
|
org_id = await create_org()
|
||
|
|
user_id = await create_user()
|
||
|
|
await add_membership(org_id, user_id, role="owner")
|
||
|
|
tournament_id = await create_tournament(org_id)
|
||
|
|
course_id = await create_course(org_id)
|
||
|
|
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)
|
||
|
|
|
||
|
|
import app.db as app_db
|
||
|
|
async with app_db.org_connection(org_id) as conn:
|
||
|
|
await conn.execute(
|
||
|
|
"UPDATE tournament SET format_type = 'individual', scoring_method = 'flag' WHERE id = $1",
|
||
|
|
tournament_id,
|
||
|
|
)
|
||
|
|
|
||
|
|
tournament_round_id = await create_tournament_round(org_id, tournament_id, course_id)
|
||
|
|
player_id = await create_player(org_id, user_id=user_id)
|
||
|
|
tp_id = await create_tournament_participant(org_id, tournament_id, player_id)
|
||
|
|
rp_id = await create_tournament_round_participant(org_id, tournament_round_id, tp_id, tee_id)
|
||
|
|
return org_id, user_id, tournament_id, tournament_round_id, rp_id
|
||
|
|
|
||
|
|
|
||
|
|
async def _play_hole(tournament_id, round_id, rp_id, hole_number, score, org_id, user):
|
||
|
|
await update_hole(
|
||
|
|
tournament_id, round_id, rp_id, hole_number,
|
||
|
|
HoleUpdate(gross_strokes=score),
|
||
|
|
organization_id=org_id, user=user,
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
async def test_plant_flag_succeeds_on_first_unplayed_hole(pool):
|
||
|
|
org_id, user_id, tournament_id, round_id, rp_id = await _setup_flag_tournament()
|
||
|
|
user = CurrentUser(user_id=user_id)
|
||
|
|
await _play_hole(tournament_id, round_id, rp_id, 1, 5, org_id, user)
|
||
|
|
|
||
|
|
result = await plant_flag(
|
||
|
|
tournament_id, round_id, rp_id,
|
||
|
|
FlagPlantIn(lat=59.9, lng=10.7, hole_number=2, lap=1, on_green=False),
|
||
|
|
organization_id=org_id, user=user,
|
||
|
|
)
|
||
|
|
assert result.lap == 1
|
||
|
|
assert result.hole_number == 2
|
||
|
|
|
||
|
|
|
||
|
|
async def test_plant_flag_rejects_non_owning_player(pool):
|
||
|
|
org_id, user_id, tournament_id, round_id, rp_id = await _setup_flag_tournament()
|
||
|
|
stranger_id = await create_user()
|
||
|
|
await add_membership(org_id, stranger_id, role="member")
|
||
|
|
stranger = CurrentUser(user_id=stranger_id)
|
||
|
|
|
||
|
|
with pytest.raises(HTTPException) as exc_info:
|
||
|
|
await plant_flag(
|
||
|
|
tournament_id, round_id, rp_id,
|
||
|
|
FlagPlantIn(lat=59.9, lng=10.7, hole_number=1, lap=1, on_green=False),
|
||
|
|
organization_id=org_id, user=stranger,
|
||
|
|
)
|
||
|
|
assert exc_info.value.status_code == 403
|
||
|
|
assert exc_info.value.detail["code"] == "NOT_TOURNAMENT_PARTICIPANT"
|
||
|
|
|
||
|
|
|
||
|
|
async def test_plant_flag_rejects_mismatched_hole(pool):
|
||
|
|
org_id, user_id, tournament_id, round_id, rp_id = await _setup_flag_tournament()
|
||
|
|
user = CurrentUser(user_id=user_id)
|
||
|
|
with pytest.raises(HTTPException) as exc_info:
|
||
|
|
await plant_flag(
|
||
|
|
tournament_id, round_id, rp_id,
|
||
|
|
FlagPlantIn(lat=59.9, lng=10.7, hole_number=5, lap=1, on_green=False),
|
||
|
|
organization_id=org_id, user=user,
|
||
|
|
)
|
||
|
|
assert exc_info.value.status_code == 400
|
||
|
|
assert exc_info.value.detail["code"] == "VALIDATION_FAILED"
|
||
|
|
|
||
|
|
|
||
|
|
async def test_unplant_and_replant(pool):
|
||
|
|
org_id, user_id, tournament_id, round_id, rp_id = await _setup_flag_tournament()
|
||
|
|
user = CurrentUser(user_id=user_id)
|
||
|
|
await plant_flag(
|
||
|
|
tournament_id, round_id, rp_id,
|
||
|
|
FlagPlantIn(lat=59.9, lng=10.7, hole_number=1, lap=1, on_green=True, distance_to_pin_cm=120),
|
||
|
|
organization_id=org_id, user=user,
|
||
|
|
)
|
||
|
|
current = await get_flag_plant(tournament_id, round_id, rp_id, organization_id=org_id)
|
||
|
|
assert current is not None and current.distance_to_pin_cm == 120
|
||
|
|
|
||
|
|
await unplant_flag(tournament_id, round_id, rp_id, organization_id=org_id, user=user)
|
||
|
|
assert await get_flag_plant(tournament_id, round_id, rp_id, organization_id=org_id) is None
|
||
|
|
|
||
|
|
|
||
|
|
async def test_full_lap_two_flow(pool):
|
||
|
|
org_id, user_id, tournament_id, round_id, rp_id = await _setup_flag_tournament()
|
||
|
|
user = CurrentUser(user_id=user_id)
|
||
|
|
for n in range(1, 19):
|
||
|
|
await _play_hole(tournament_id, round_id, rp_id, n, 4, org_id, user)
|
||
|
|
|
||
|
|
with pytest.raises(HTTPException):
|
||
|
|
await plant_flag(
|
||
|
|
tournament_id, round_id, rp_id,
|
||
|
|
FlagPlantIn(lat=59.9, lng=10.7, hole_number=1, lap=1, on_green=False),
|
||
|
|
organization_id=org_id, user=user,
|
||
|
|
)
|
||
|
|
|
||
|
|
overflow_hole = await update_flag_overflow_hole(
|
||
|
|
tournament_id, round_id, rp_id, 2, 1,
|
||
|
|
FlagOverflowHoleIn(gross_strokes=5), organization_id=org_id, user=user,
|
||
|
|
)
|
||
|
|
assert overflow_hole.lap == 2
|
||
|
|
assert overflow_hole.gross_strokes == 5
|
||
|
|
|
||
|
|
holes = await list_flag_overflow_holes(tournament_id, round_id, rp_id, organization_id=org_id)
|
||
|
|
assert len(holes) == 1
|
||
|
|
|
||
|
|
lap2_plant = await plant_flag(
|
||
|
|
tournament_id, round_id, rp_id,
|
||
|
|
FlagPlantIn(lat=59.9, lng=10.7, hole_number=2, lap=2, on_green=False),
|
||
|
|
organization_id=org_id, user=user,
|
||
|
|
)
|
||
|
|
assert lap2_plant.lap == 2
|
||
|
|
assert lap2_plant.hole_number == 2
|
||
|
|
|
||
|
|
|
||
|
|
async def test_flag_map_shows_only_own_flag_when_switch_off(pool):
|
||
|
|
org_id, user_id, tournament_id, round_id, rp_id = await _setup_flag_tournament()
|
||
|
|
other_user_id = await create_user()
|
||
|
|
await add_membership(org_id, other_user_id, role="member")
|
||
|
|
other_player_id = await create_player(org_id, user_id=other_user_id)
|
||
|
|
other_tp_id = await create_tournament_participant(org_id, tournament_id, other_player_id)
|
||
|
|
|
||
|
|
import app.db as app_db
|
||
|
|
async with app_db.org_connection(org_id) as conn:
|
||
|
|
tee_id = await conn.fetchval("SELECT tee_id FROM tournament_round_participant WHERE id = $1", rp_id)
|
||
|
|
other_rp_id = await create_tournament_round_participant(org_id, round_id, other_tp_id, tee_id)
|
||
|
|
|
||
|
|
user = CurrentUser(user_id=user_id)
|
||
|
|
other = CurrentUser(user_id=other_user_id)
|
||
|
|
await plant_flag(
|
||
|
|
tournament_id, round_id, rp_id,
|
||
|
|
FlagPlantIn(lat=59.9, lng=10.7, hole_number=1, lap=1, on_green=False),
|
||
|
|
organization_id=org_id, user=user,
|
||
|
|
)
|
||
|
|
await plant_flag(
|
||
|
|
tournament_id, round_id, other_rp_id,
|
||
|
|
FlagPlantIn(lat=60.0, lng=10.8, hole_number=1, lap=1, on_green=False),
|
||
|
|
organization_id=org_id, user=other,
|
||
|
|
)
|
||
|
|
|
||
|
|
result = await get_flag_map(tournament_id, round_id, organization_id=org_id, user=user)
|
||
|
|
assert result.visible_to_all is False
|
||
|
|
assert [f.participant_id for f in result.flags] == [rp_id]
|
||
|
|
|
||
|
|
|
||
|
|
async def test_flag_map_shows_all_flags_when_switch_on(pool):
|
||
|
|
org_id, user_id, tournament_id, round_id, rp_id = await _setup_flag_tournament()
|
||
|
|
other_user_id = await create_user()
|
||
|
|
await add_membership(org_id, other_user_id, role="member")
|
||
|
|
other_player_id = await create_player(org_id, user_id=other_user_id)
|
||
|
|
other_tp_id = await create_tournament_participant(org_id, tournament_id, other_player_id)
|
||
|
|
|
||
|
|
import app.db as app_db
|
||
|
|
async with app_db.org_connection(org_id) as conn:
|
||
|
|
tee_id = await conn.fetchval("SELECT tee_id FROM tournament_round_participant WHERE id = $1", rp_id)
|
||
|
|
await conn.execute("UPDATE tournament SET flag_map_visible = true WHERE id = $1", tournament_id)
|
||
|
|
other_rp_id = await create_tournament_round_participant(org_id, round_id, other_tp_id, tee_id)
|
||
|
|
|
||
|
|
user = CurrentUser(user_id=user_id)
|
||
|
|
other = CurrentUser(user_id=other_user_id)
|
||
|
|
await plant_flag(
|
||
|
|
tournament_id, round_id, rp_id,
|
||
|
|
FlagPlantIn(lat=59.9, lng=10.7, hole_number=1, lap=1, on_green=False),
|
||
|
|
organization_id=org_id, user=user,
|
||
|
|
)
|
||
|
|
await plant_flag(
|
||
|
|
tournament_id, round_id, other_rp_id,
|
||
|
|
FlagPlantIn(lat=60.0, lng=10.8, hole_number=1, lap=1, on_green=False),
|
||
|
|
organization_id=org_id, user=other,
|
||
|
|
)
|
||
|
|
|
||
|
|
result = await get_flag_map(tournament_id, round_id, organization_id=org_id, user=other)
|
||
|
|
assert result.visible_to_all is True
|
||
|
|
assert {f.participant_id for f in result.flags} == {rp_id, other_rp_id}
|