teecup/tests/test_tournament_round_shots.py

265 lines
10 KiB
Python
Raw Normal View History

"""
Slag-for-slag GPS-avstandsmåling for org-turneringer (ADR-103). Speiler
ADR-048s round_shot-autorisasjon (self ELLER org-admin, samme klasse som
update_hole), men org-scopet (RLS) og FK-et til
tournament_round_participant + hole_number (ikke tournament_round_hole_id
-- den raden finnes kanskje ikke ennå).
"""
import pytest
from fastapi import HTTPException
from app.auth import CurrentUser
from app.routers.tournament_round_shots import (
ShotIn,
ShotShareIn,
create_tournament_round_shot,
delete_tournament_round_shot,
list_tournament_round_shots,
share_tournament_round_shot,
)
from tests.conftest import (
add_membership,
create_course,
create_org,
create_player,
create_tee,
create_tournament,
create_tournament_participant,
create_tournament_round,
create_tournament_round_participant,
create_user,
)
_SHOT = ShotIn(
club="Driver",
distance_meters=210.5,
start_method="gps",
start_lat=59.1,
start_lng=10.5,
end_method="gps",
end_lat=59.101,
end_lng=10.501,
)
async def _setup(org_id):
tournament_id = await create_tournament(org_id, name="Slagmåling-turnering")
course_id = await create_course(org_id, name="Slagmålingsbanen")
tee_id = await create_tee(org_id, course_id, name="Gul")
round_id = await create_tournament_round(org_id, tournament_id, course_id, sequence=1)
return tournament_id, round_id, tee_id
async def _make_participant(org_id, tournament_id, round_id, tee_id, user_id):
player_id = await create_player(org_id, display_name="Slag Testesen", 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, round_id, tp_id, tee_id)
return rp_id
async def test_create_and_list_shot(pool):
org_id = await create_org()
tournament_id, round_id, tee_id = await _setup(org_id)
user_id = await create_user()
await add_membership(org_id, user_id)
rp_id = await _make_participant(org_id, tournament_id, round_id, tee_id, user_id)
created = await create_tournament_round_shot(
tournament_id, round_id, rp_id, 1, _SHOT,
organization_id=org_id, user=CurrentUser(user_id=user_id),
)
assert created.shot_number == 1
assert created.club == "Driver"
assert created.shared_tournament_round_message_id is None
listed = await list_tournament_round_shots(
tournament_id, round_id, rp_id, 1, organization_id=org_id,
)
assert [s.id for s in listed] == [created.id]
async def test_shot_number_increments_per_hole(pool):
org_id = await create_org()
tournament_id, round_id, tee_id = await _setup(org_id)
user_id = await create_user()
await add_membership(org_id, user_id)
rp_id = await _make_participant(org_id, tournament_id, round_id, tee_id, user_id)
first = await create_tournament_round_shot(
tournament_id, round_id, rp_id, 1, _SHOT, organization_id=org_id, user=CurrentUser(user_id=user_id),
)
second = await create_tournament_round_shot(
tournament_id, round_id, rp_id, 1, _SHOT, organization_id=org_id, user=CurrentUser(user_id=user_id),
)
assert first.shot_number == 1
assert second.shot_number == 2
# Et annet hull starter på nytt fra 1.
other_hole = await create_tournament_round_shot(
tournament_id, round_id, rp_id, 2, _SHOT, organization_id=org_id, user=CurrentUser(user_id=user_id),
)
assert other_hole.shot_number == 1
async def test_can_measure_before_hole_score_exists(pool):
"""Kjernebegrunnelsen for datamodellen: tournament_round_hole
opprettes FØRST ved score-innsending -- måling fungere FØR det."""
org_id = await create_org()
tournament_id, round_id, tee_id = await _setup(org_id)
user_id = await create_user()
await add_membership(org_id, user_id)
rp_id = await _make_participant(org_id, tournament_id, round_id, tee_id, user_id)
# Ingen tournament_round_hole-rad finnes -- måling skal likevel virke.
created = await create_tournament_round_shot(
tournament_id, round_id, rp_id, 5, _SHOT, organization_id=org_id, user=CurrentUser(user_id=user_id),
)
assert created.shot_number == 1
async def test_non_self_non_admin_rejected(pool):
org_id = await create_org()
tournament_id, round_id, tee_id = await _setup(org_id)
owner_id = await create_user()
other_id = await create_user()
await add_membership(org_id, owner_id, role="member")
await add_membership(org_id, other_id, role="member")
rp_id = await _make_participant(org_id, tournament_id, round_id, tee_id, owner_id)
with pytest.raises(HTTPException) as exc_info:
await create_tournament_round_shot(
tournament_id, round_id, rp_id, 1, _SHOT, organization_id=org_id, user=CurrentUser(user_id=other_id),
)
assert exc_info.value.status_code == 403
async def test_org_admin_can_measure_for_others(pool):
org_id = await create_org()
tournament_id, round_id, tee_id = await _setup(org_id)
owner_id = await create_user()
admin_id = await create_user()
await add_membership(org_id, owner_id, role="member")
await add_membership(org_id, admin_id, role="admin")
rp_id = await _make_participant(org_id, tournament_id, round_id, tee_id, owner_id)
created = await create_tournament_round_shot(
tournament_id, round_id, rp_id, 1, _SHOT, organization_id=org_id, user=CurrentUser(user_id=admin_id),
)
assert created.shot_number == 1
async def test_delete_self_only(pool):
org_id = await create_org()
tournament_id, round_id, tee_id = await _setup(org_id)
owner_id = await create_user()
other_id = await create_user()
await add_membership(org_id, owner_id, role="member")
await add_membership(org_id, other_id, role="member")
rp_id = await _make_participant(org_id, tournament_id, round_id, tee_id, owner_id)
created = await create_tournament_round_shot(
tournament_id, round_id, rp_id, 1, _SHOT, organization_id=org_id, user=CurrentUser(user_id=owner_id),
)
with pytest.raises(HTTPException) as exc_info:
await delete_tournament_round_shot(
tournament_id, round_id, created.id, organization_id=org_id, user=CurrentUser(user_id=other_id),
)
assert exc_info.value.status_code == 403
await delete_tournament_round_shot(
tournament_id, round_id, created.id, organization_id=org_id, user=CurrentUser(user_id=owner_id),
)
listed = await list_tournament_round_shots(tournament_id, round_id, rp_id, 1, organization_id=org_id)
assert listed == []
def test_invalid_distance_rejected():
with pytest.raises(Exception):
ShotIn(
club="Driver", distance_meters=0, start_method="gps",
start_lat=59.1, start_lng=10.5, end_lat=59.101, end_lng=10.501,
)
async def test_wrong_tournament_id_404(pool):
org_id = await create_org()
tournament_id, round_id, tee_id = await _setup(org_id)
other_tournament_id = await create_tournament(org_id, name="En annen turnering")
user_id = await create_user()
await add_membership(org_id, user_id)
rp_id = await _make_participant(org_id, tournament_id, round_id, tee_id, user_id)
with pytest.raises(HTTPException) as exc_info:
await create_tournament_round_shot(
other_tournament_id, round_id, rp_id, 1, _SHOT, organization_id=org_id, user=CurrentUser(user_id=user_id),
)
assert exc_info.value.status_code == 404
async def test_rls_blocks_cross_org_access(pool):
org_a = await create_org()
org_b = await create_org()
tournament_id, round_id, tee_id = await _setup(org_a)
user_id = await create_user()
await add_membership(org_a, user_id)
await add_membership(org_b, user_id)
rp_id = await _make_participant(org_a, tournament_id, round_id, tee_id, user_id)
await create_tournament_round_shot(
tournament_id, round_id, rp_id, 1, _SHOT, organization_id=org_a, user=CurrentUser(user_id=user_id),
)
with pytest.raises(HTTPException) as exc_info:
await list_tournament_round_shots(tournament_id, round_id, rp_id, 1, organization_id=org_b)
assert exc_info.value.status_code == 404
async def test_share_creates_tournament_round_message(pool):
org_id = await create_org()
tournament_id, round_id, tee_id = await _setup(org_id)
user_id = await create_user()
await add_membership(org_id, user_id)
rp_id = await _make_participant(org_id, tournament_id, round_id, tee_id, user_id)
created = await create_tournament_round_shot(
tournament_id, round_id, rp_id, 1, _SHOT, organization_id=org_id, user=CurrentUser(user_id=user_id),
)
assert created.shared_tournament_round_message_id is None
shared = await share_tournament_round_shot(
tournament_id, round_id, created.id, ShotShareIn(body="⛳ Driver, 210.5 m hull 1"),
organization_id=org_id, user=CurrentUser(user_id=user_id),
)
assert shared.shared_tournament_round_message_id is not None
# Kommentartråden (ADR-102) skal nå ha en ekte melding.
from app.routers.tournament_round_messages import list_tournament_round_messages
messages = await list_tournament_round_messages(
tournament_id, round_id, organization_id=org_id, user=CurrentUser(user_id=user_id),
)
assert any(m.id == shared.shared_tournament_round_message_id for m in messages)
assert any(m.body == "⛳ Driver, 210.5 m hull 1" for m in messages)
async def test_share_rejected_for_non_owner(pool):
org_id = await create_org()
tournament_id, round_id, tee_id = await _setup(org_id)
owner_id = await create_user()
other_id = await create_user()
await add_membership(org_id, owner_id, role="member")
await add_membership(org_id, other_id, role="member")
rp_id = await _make_participant(org_id, tournament_id, round_id, tee_id, owner_id)
created = await create_tournament_round_shot(
tournament_id, round_id, rp_id, 1, _SHOT, organization_id=org_id, user=CurrentUser(user_id=owner_id),
)
with pytest.raises(HTTPException) as exc_info:
await share_tournament_round_shot(
tournament_id, round_id, created.id, ShotShareIn(body="Test"),
organization_id=org_id, user=CurrentUser(user_id=other_id),
)
assert exc_info.value.status_code == 403