intent/tests/test_org_views.py

143 lines
5.5 KiB
Python
Raw Normal View History

import re
import pytest
from django.core import mail
from django.core.exceptions import ValidationError
from django.urls import reverse
from intent.orgs.models import InviteStatus, OrgInvite, Role
def members_url(org):
return reverse("orgs:members", kwargs={"org_slug": org.slug})
def test_org_page_is_public(client, org):
response = client.get(reverse("orgs:detail", kwargs={"org_slug": org.slug}))
assert response.status_code == 200
assert b"Catbriar" in response.content
def test_members_page_requires_membership(client, make_user, org):
assert client.get(members_url(org)).status_code == 403
client.force_login(make_user())
assert client.get(members_url(org)).status_code == 403
def test_collaborator_sees_members_but_cannot_invite(client, collaborator, org):
client.force_login(collaborator)
response = client.get(members_url(org))
assert response.status_code == 200
assert b"Invite someone" not in response.content
response = client.post(members_url(org), {"recipient": "x@example.com", "role": "owner"})
assert response.status_code == 403
def test_invite_by_username_then_accept_from_invitations_page(client, owner, make_user, org):
invitee = make_user("ivan")
client.force_login(owner)
response = client.post(members_url(org), {"recipient": "IVAN", "role": "collaborator"})
assert response.status_code == 302
invite = OrgInvite.objects.get(user=invitee)
assert mail.outbox == [] # in-app only; no notifications in V1
client.force_login(invitee)
assert b"Catbriar" in client.get(reverse("invites:list")).content
response = client.post(
reverse("invites:respond", kwargs={"invite_id": invite.pk}), {"action": "accept"}
)
assert response.status_code == 302
assert org.memberships.filter(user=invitee, role=Role.COLLABORATOR).exists()
def test_cannot_respond_to_someone_elses_invite(client, owner, make_user, org):
invite = OrgInvite.objects.create(org=org, user=make_user(), invited_by=owner, token_hash="x")
client.force_login(make_user())
response = client.post(
reverse("invites:respond", kwargs={"invite_id": invite.pk}), {"action": "accept"}
)
assert response.status_code == 403
def test_email_invite_link_flow(client, owner, make_user, org):
client.force_login(owner)
client.post(members_url(org), {"recipient": "new@example.com", "role": "collaborator"})
assert len(mail.outbox) == 1
path = re.search(r"https?://[^/]+(/invites/t/\S+/)", mail.outbox[0].body).group(1)
client.logout()
response = client.get(path)
assert response.status_code == 302
assert response["Location"].startswith(reverse("account_login"))
# Bearer link: any signed-in account can claim it, whatever its email.
joiner = make_user("june", email="june@elsewhere.example")
client.force_login(joiner)
invite = OrgInvite.objects.get(email="new@example.com")
show = reverse("invites:show", kwargs={"invite_id": invite.pk})
response = client.get(path)
assert response.status_code == 302 and response["Location"] == show
assert client.get(show).status_code == 200
assert invite in list(client.get(reverse("invites:list")).context["invites"])
# Once claimed, the link is dead for everyone else, even someone with the invited email.
other = make_user("newbie", email="new@example.com")
client.force_login(other)
assert client.get(path).status_code == 404
assert client.get(show).status_code == 403
assert client.get(reverse("invites:list")).context["invites"].count() == 0
client.force_login(joiner)
assert client.get(path).status_code == 404 # Token was rotated on claim.
client.post(reverse("invites:respond", kwargs={"invite_id": invite.pk}), {"action": "accept"})
assert org.memberships.filter(user=joiner).exists()
invite.refresh_from_db()
assert invite.status == InviteStatus.ACCEPTED and invite.accepted_by == joiner
def test_invite_sending_is_throttled(owner, org):
from intent.orgs import services
for _ in range(3):
services.invite(org, owner, email="same@example.com")
with pytest.raises(ValidationError, match="Too many"):
services.invite(org, owner, email="same@example.com")
# A different address still works; the per-sender cap is higher.
services.invite(org, owner, email="other@example.com")
def test_invalid_token_is_404(client, make_user):
client.force_login(make_user())
assert client.get(reverse("invites:by_token", kwargs={"token": "nope"})).status_code == 404
def test_owner_updates_role_and_grant(client, owner, collaborator, org):
membership = org.memberships.get(user=collaborator)
client.force_login(owner)
client.post(
reverse(
"orgs:member_update", kwargs={"org_slug": org.slug, "membership_id": membership.pk}
),
{"role": "collaborator", "can_create_projects": "on"},
)
membership.refresh_from_db()
assert membership.can_create_projects
def test_last_owner_leaving_shows_error(client, owner, org):
membership = org.memberships.get(user=owner)
client.force_login(owner)
response = client.post(
reverse(
"orgs:member_remove", kwargs={"org_slug": org.slug, "membership_id": membership.pk}
),
follow=True,
)
assert b"at least one owner" in response.content
assert org.memberships.filter(user=owner).exists()
def test_home_lists_orgs_and_invites(client, owner, make_user, org):
client.force_login(owner)
assert b"Catbriar" in client.get("/").content