""" Rundespesifikk kommentartråd for org-turneringer (ADR-102). Ekte parallell til round_messages.py sin RoundMessages, men org-scopet (RLS) i stedet for plain_connection() -- skriverett = leserett = org-medlemskap (get_authorized_org), moderering = forfatter ELLER org-admin (is_org_admin), samme regel som Banter Board (ADR-025). Én tråd PER TURNERINGSRUNDE (hele feltet deler tråd, ikke per flight). """ import uuid import pytest from fastapi import HTTPException from app.auth import CurrentUser from app.routers.tournament_round_messages import ( CommentIn, ReactionIn, delete_tournament_round_message, delete_tournament_round_message_comment, list_tournament_round_message_comments, list_tournament_round_messages, post_tournament_round_message, post_tournament_round_message_comment, remove_tournament_round_message_reaction, set_tournament_round_message_reaction, ) from tests.conftest import ( add_membership, create_org, create_tournament, create_tournament_round, create_course, create_user, ) async def _setup(org_id): tournament_id = await create_tournament(org_id, name="Kommentar-turnering") course_id = await create_course(org_id, name="Kommentarbanen") round_id = await create_tournament_round(org_id, tournament_id, course_id, sequence=1) return tournament_id, round_id async def test_post_and_list_message(pool): org_id = await create_org() tournament_id, round_id = await _setup(org_id) author_id = await create_user(display_name="Forfatter Testesen") await add_membership(org_id, author_id) created = await post_tournament_round_message( tournament_id, round_id, body="Fin runde i dag!", image=None, client_message_id=None, organization_id=org_id, user=CurrentUser(user_id=author_id), ) assert created.body == "Fin runde i dag!" assert created.tournament_round_id == round_id assert created.author_display_name == "Forfatter Testesen" listed = await list_tournament_round_messages( tournament_id, round_id, organization_id=org_id, user=CurrentUser(user_id=author_id), ) assert [m.id for m in listed] == [created.id] async def test_client_message_id_idempotent(pool) -> None: org_id = await create_org() tournament_id, round_id = await _setup(org_id) author_id = await create_user() await add_membership(org_id, author_id) client_message_id = str(uuid.uuid4()) first = await post_tournament_round_message( tournament_id, round_id, body="Duplikat-test", image=None, client_message_id=client_message_id, organization_id=org_id, user=CurrentUser(user_id=author_id), ) second = await post_tournament_round_message( tournament_id, round_id, body="Duplikat-test", image=None, client_message_id=client_message_id, organization_id=org_id, user=CurrentUser(user_id=author_id), ) assert first.id == second.id listed = await list_tournament_round_messages( tournament_id, round_id, organization_id=org_id, user=CurrentUser(user_id=author_id), ) assert len(listed) == 1 async def test_body_or_image_required(pool) -> None: org_id = await create_org() tournament_id, round_id = await _setup(org_id) author_id = await create_user() await add_membership(org_id, author_id) with pytest.raises(HTTPException) as exc_info: await post_tournament_round_message( tournament_id, round_id, body=None, image=None, client_message_id=None, organization_id=org_id, user=CurrentUser(user_id=author_id), ) assert exc_info.value.status_code == 400 async def test_wrong_tournament_id_404(pool) -> None: org_id = await create_org() tournament_id, round_id = await _setup(org_id) other_tournament_id = await create_tournament(org_id, name="En annen turnering") author_id = await create_user() await add_membership(org_id, author_id) with pytest.raises(HTTPException) as exc_info: await post_tournament_round_message( other_tournament_id, round_id, body="Feil turnering", image=None, client_message_id=None, organization_id=org_id, user=CurrentUser(user_id=author_id), ) assert exc_info.value.status_code == 404 async def test_rls_blocks_cross_org_access(pool) -> None: org_a = await create_org() org_b = await create_org() tournament_id, round_id = await _setup(org_a) author_id = await create_user() await add_membership(org_a, author_id) await add_membership(org_b, author_id) await post_tournament_round_message( tournament_id, round_id, body="Kun org A", image=None, client_message_id=None, organization_id=org_a, user=CurrentUser(user_id=author_id), ) # Samme (tournament_id, round_id) forsøkt lest med FEIL org_id satt -- # RLS filtrerer bort tournament_round-raden, _get_tournament_round_or_404 # ser den derfor ikke og gir 404 (ikke et 0-treffs, "tom liste"-svar). with pytest.raises(HTTPException) as exc_info: await list_tournament_round_messages( tournament_id, round_id, organization_id=org_b, user=CurrentUser(user_id=author_id), ) assert exc_info.value.status_code == 404 async def test_delete_by_author(pool) -> None: org_id = await create_org() tournament_id, round_id = await _setup(org_id) author_id = await create_user() await add_membership(org_id, author_id) created = await post_tournament_round_message( tournament_id, round_id, body="Slett meg", image=None, client_message_id=None, organization_id=org_id, user=CurrentUser(user_id=author_id), ) await delete_tournament_round_message( tournament_id, round_id, created.id, organization_id=org_id, user=CurrentUser(user_id=author_id), ) listed = await list_tournament_round_messages( tournament_id, round_id, organization_id=org_id, user=CurrentUser(user_id=author_id), ) assert listed == [] async def test_delete_by_org_admin_not_author(pool) -> None: org_id = await create_org() tournament_id, round_id = await _setup(org_id) author_id = await create_user() admin_id = await create_user() await add_membership(org_id, author_id, role="member") await add_membership(org_id, admin_id, role="admin") created = await post_tournament_round_message( tournament_id, round_id, body="Slett meg som admin", image=None, client_message_id=None, organization_id=org_id, user=CurrentUser(user_id=author_id), ) await delete_tournament_round_message( tournament_id, round_id, created.id, organization_id=org_id, user=CurrentUser(user_id=admin_id), ) listed = await list_tournament_round_messages( tournament_id, round_id, organization_id=org_id, user=CurrentUser(user_id=author_id), ) assert listed == [] async def test_delete_rejected_for_unrelated_member(pool) -> None: org_id = await create_org() tournament_id, round_id = await _setup(org_id) author_id = await create_user() other_member_id = await create_user() await add_membership(org_id, author_id, role="member") await add_membership(org_id, other_member_id, role="member") created = await post_tournament_round_message( tournament_id, round_id, body="Ikke slett meg", image=None, client_message_id=None, organization_id=org_id, user=CurrentUser(user_id=author_id), ) with pytest.raises(HTTPException) as exc_info: await delete_tournament_round_message( tournament_id, round_id, created.id, organization_id=org_id, user=CurrentUser(user_id=other_member_id), ) assert exc_info.value.status_code == 403 async def test_ordinary_member_can_post(pool) -> None: """Skriverett = org-medlemskap, ikke deltaker-i-akkurat-denne-turneringen eller admin -- bekrefter at et VANLIG medlem (ikke admin/deltaker) kan poste, samme "vanlig org-medlemsnivå"-presedens som resten av individual_tournaments.py sitt oppsett (ADR-102).""" org_id = await create_org() tournament_id, round_id = await _setup(org_id) member_id = await create_user() await add_membership(org_id, member_id, role="member") created = await post_tournament_round_message( tournament_id, round_id, body="Jeg er bare medlem", image=None, client_message_id=None, organization_id=org_id, user=CurrentUser(user_id=member_id), ) assert created.body == "Jeg er bare medlem" async def test_reaction_upsert_and_remove(pool) -> None: org_id = await create_org() tournament_id, round_id = await _setup(org_id) author_id = await create_user() reactor_id = await create_user() await add_membership(org_id, author_id) await add_membership(org_id, reactor_id) created = await post_tournament_round_message( tournament_id, round_id, body="Reager på meg", image=None, client_message_id=None, organization_id=org_id, user=CurrentUser(user_id=author_id), ) summaries = await set_tournament_round_message_reaction( tournament_id, round_id, created.id, ReactionIn(emoji="👍"), organization_id=org_id, user=CurrentUser(user_id=reactor_id), ) assert len(summaries) == 1 assert summaries[0].emoji == "👍" assert summaries[0].count == 1 assert summaries[0].reacted_by_me is True # Bytte av emoji erstatter, stables ikke. summaries = await set_tournament_round_message_reaction( tournament_id, round_id, created.id, ReactionIn(emoji="❤️"), organization_id=org_id, user=CurrentUser(user_id=reactor_id), ) assert len(summaries) == 1 assert summaries[0].emoji == "❤️" summaries = await remove_tournament_round_message_reaction( tournament_id, round_id, created.id, organization_id=org_id, user=CurrentUser(user_id=reactor_id), ) assert summaries == [] async def test_invalid_emoji_rejected(pool) -> None: org_id = await create_org() tournament_id, round_id = await _setup(org_id) author_id = await create_user() await add_membership(org_id, author_id) created = await post_tournament_round_message( tournament_id, round_id, body="Test", image=None, client_message_id=None, organization_id=org_id, user=CurrentUser(user_id=author_id), ) with pytest.raises(HTTPException) as exc_info: await set_tournament_round_message_reaction( tournament_id, round_id, created.id, ReactionIn(emoji="🐸"), organization_id=org_id, user=CurrentUser(user_id=author_id), ) assert exc_info.value.status_code == 400 async def test_threaded_comment_create_list_delete(pool) -> None: org_id = await create_org() tournament_id, round_id = await _setup(org_id) author_id = await create_user() commenter_id = await create_user() admin_id = await create_user() await add_membership(org_id, author_id, role="member") await add_membership(org_id, commenter_id, role="member") await add_membership(org_id, admin_id, role="admin") message = await post_tournament_round_message( tournament_id, round_id, body="Kommenter meg", image=None, client_message_id=None, organization_id=org_id, user=CurrentUser(user_id=author_id), ) top_comment = await post_tournament_round_message_comment( tournament_id, round_id, message.id, CommentIn(body="Bra spilt!"), organization_id=org_id, user=CurrentUser(user_id=commenter_id), ) reply = await post_tournament_round_message_comment( tournament_id, round_id, message.id, CommentIn(body="Enig!", parent_comment_id=top_comment.id), organization_id=org_id, user=CurrentUser(user_id=author_id), ) comments = await list_tournament_round_message_comments( tournament_id, round_id, message.id, organization_id=org_id, ) assert {c.id for c in comments} == {top_comment.id, reply.id} assert next(c for c in comments if c.id == reply.id).parent_comment_id == top_comment.id # Ikke-forfatter-ikke-admin nektes. with pytest.raises(HTTPException) as exc_info: await delete_tournament_round_message_comment( tournament_id, round_id, message.id, top_comment.id, organization_id=org_id, user=CurrentUser(user_id=author_id), ) assert exc_info.value.status_code == 403 # Org-admin kan slette andres kommentar -- kaskade-sletter svaret under. await delete_tournament_round_message_comment( tournament_id, round_id, message.id, top_comment.id, organization_id=org_id, user=CurrentUser(user_id=admin_id), ) remaining = await list_tournament_round_message_comments( tournament_id, round_id, message.id, organization_id=org_id, ) assert remaining == []