""" 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