265 lines
10 KiB
Python
265 lines
10 KiB
Python
|
|
"""
|
|||
|
|
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 må 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
|