317 lines
13 KiB
Python
317 lines
13 KiB
Python
|
|
"""
|
||
|
|
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 == []
|