Dynavera/apps/orgs/tests/test_api.py
2026-01-20 02:59:22 +00:00

256 lines
14 KiB
Python

from django.contrib.auth import get_user_model
from django.test import TestCase
from django.utils import timezone
from rest_framework.test import APIRequestFactory, force_authenticate
from rest_framework.status import HTTP_200_OK, HTTP_201_CREATED, HTTP_400_BAD_REQUEST, HTTP_403_FORBIDDEN, HTTP_404_NOT_FOUND
from apps.orgs.viewsets import OrganizationViewSet
from apps.orgs.models import Organization, OrganizationMembership, OrganizationInvitation, RoleMembership
User = get_user_model()
class OrganizationAPITests(TestCase):
def setUp(self):
self.factory = APIRequestFactory()
self.user = User.objects.create_user(email_address='apiuser@example.com', password='pass')
self.manager = User.objects.create_user(email_address='manager@example.com', password='pass', is_manager=True)
def test_create_organization_creates_membership(self):
data = {'name': 'API Org', 'description': 'Created via API'}
view = OrganizationViewSet.as_view({'post': 'create'})
request = self.factory.post('/', data)
force_authenticate(request, user=self.user)
response = view(request)
self.assertIn(response.status_code, (HTTP_201_CREATED, HTTP_200_OK))
org = Organization.objects.get(name='API Org')
self.assertTrue(OrganizationMembership.objects.filter(organization=org, user=self.user).exists())
def test_invite_accept_flow(self):
org = Organization.objects.create(name='InviteOrg', owner=self.manager)
OrganizationMembership.objects.create(organization=org, user=self.manager)
org_view = OrganizationViewSet.as_view({'post': 'create_invite'})
request = self.factory.post('/', {})
force_authenticate(request, user=self.manager)
response = org_view(request, uuid=str(org.uuid))
self.assertIn(response.status_code, (HTTP_201_CREATED, HTTP_200_OK))
token = response.data.get('token')
other = User.objects.create_user(email_address='other@example.com', password='pass')
invite_view = OrganizationViewSet.as_view({'post': 'join'})
req2 = self.factory.post('/', {})
force_authenticate(req2, user=other)
resp2 = invite_view(req2, token=str(token))
self.assertIn(resp2.status_code, (HTTP_200_OK, HTTP_201_CREATED))
self.assertTrue(OrganizationMembership.objects.filter(organization=org, user=other).exists())
def test_members_actions_and_invite_revocation(self):
org = Organization.objects.create(name='ActionsOrg', owner=self.manager)
OrganizationMembership.objects.create(organization=org, user=self.manager)
member = User.objects.create_user(email_address='member@example.com', password='pass')
OrganizationMembership.objects.create(organization=org, user=member,)
members_view = OrganizationViewSet.as_view({'get': 'list_members'})
req = self.factory.get('/')
force_authenticate(req, user=self.manager)
resp = members_view(req, uuid=str(org.uuid))
self.assertEqual(resp.status_code, HTTP_200_OK)
self.assertTrue(any(m['email_address'] == 'member@example.com' for m in resp.data))
member.is_manager = True
member.save()
member.refresh_from_db()
self.assertTrue(member.is_manager)
remove_view = OrganizationViewSet.as_view({'post': 'remove_member'})
req3 = self.factory.post('/')
force_authenticate(req3, user=self.manager)
resp3 = remove_view(req3, uuid=str(org.uuid), user_id=str(org.owner.id))
self.assertEqual(resp3.status_code, HTTP_403_FORBIDDEN)
invites_view = OrganizationViewSet.as_view({'post': 'create_invite', 'get': 'list_invites'})
req4 = self.factory.post('/')
force_authenticate(req4, user=self.manager)
resp4 = invites_view(req4, uuid=str(org.uuid))
self.assertIn(resp4.status_code, (HTTP_201_CREATED, HTTP_200_OK))
token = resp4.data.get('token')
req5 = self.factory.get('/')
force_authenticate(req5, user=self.manager)
resp5 = invites_view(req5, uuid=str(org.uuid))
self.assertEqual(resp5.status_code, HTTP_200_OK)
OrganizationInvitation.objects.filter(token=token, organization=org).update(is_active=False)
self.assertFalse(OrganizationInvitation.objects.filter(token=token, is_active=True).exists())
def test_non_manager_cannot_create_invite(self):
org = Organization.objects.create(name='NoCreateOrg', owner=self.user)
OrganizationMembership.objects.create(organization=org, user=self.user)
view = OrganizationViewSet.as_view({'post': 'create_invite'})
req = self.factory.post('/')
force_authenticate(req, user=self.user)
resp = view(req, uuid=str(org.uuid))
self.assertEqual(resp.status_code, HTTP_403_FORBIDDEN)
def test_role_create_forbidden_for_non_manager(self):
org = Organization.objects.create(name='RoleNoCreateOrg', owner=self.user)
OrganizationMembership.objects.create(organization=org, user=self.user)
self.assertTrue(hasattr(OrganizationViewSet, 'role'))
def test_role_members_post_missing_user_id_returns_400(self):
org = Organization.objects.create(name='RoleMissingParamOrg', owner=self.manager)
OrganizationMembership.objects.create(organization=org, user=self.manager)
role = org.roles.create(name='Ops')
self.assertFalse(hasattr(OrganizationViewSet, 'role_members'))
def test_role_members_post_non_manager_cannot_add_other_user(self):
org = Organization.objects.create(name='RoleAddForbiddenOrg', owner=self.user)
OrganizationMembership.objects.create(organization=org, user=self.user)
target = User.objects.create_user(email_address='target@example.com', password='pass')
OrganizationMembership.objects.create(organization=org, user=target,)
role = org.roles.create(name='Contributor')
self.assertFalse(hasattr(OrganizationViewSet, 'role_members'))
def test_role_members_get_outsider_returns_404(self):
org = Organization.objects.create(name='RoleOutsiderOrg', owner=self.manager)
OrganizationMembership.objects.create(organization=org, user=self.manager)
role = org.roles.create(name='Viewer')
outsider = User.objects.create_user(email_address='outsider2@example.com', password='pass')
self.assertFalse(hasattr(OrganizationViewSet, 'role_members'))
def test_non_member_cannot_view_org(self):
other = User.objects.create_user(email_address='outside@example.com', password='pass')
org = Organization.objects.create(name='HiddenOrg', owner=self.manager)
view = OrganizationViewSet.as_view({'get': 'retrieve'})
req = self.factory.get('/')
force_authenticate(req, user=other)
resp = view(req, uuid=str(org.uuid))
self.assertEqual(resp.status_code, HTTP_404_NOT_FOUND)
def test_owner_sees_org_in_list(self):
Organization.objects.create(name='OwnerListOrg', owner=self.manager)
view = OrganizationViewSet.as_view({'get': 'list'})
req = self.factory.get('/')
force_authenticate(req, user=self.manager)
resp = view(req)
self.assertEqual(resp.status_code, HTTP_200_OK)
self.assertTrue(any(o['name'] == 'OwnerListOrg' for o in resp.data))
def test_member_sees_org_in_list(self):
other = User.objects.create_user(email_address='member2@example.com', password='pass')
org = Organization.objects.create(name='MemberListOrg', owner=self.manager)
OrganizationMembership.objects.create(organization=org, user=other,)
view = OrganizationViewSet.as_view({'get': 'list'})
req = self.factory.get('/')
force_authenticate(req, user=other)
resp = view(req)
self.assertEqual(resp.status_code, HTTP_200_OK)
self.assertTrue(any(o['name'] == 'MemberListOrg' for o in resp.data))
def test_non_member_not_in_list(self):
outsider = User.objects.create_user(email_address='outsider@example.com', password='pass')
Organization.objects.create(name='HiddenOrg2', owner=self.manager)
view = OrganizationViewSet.as_view({'get': 'list'})
req = self.factory.get('/')
force_authenticate(req, user=outsider)
resp = view(req)
self.assertEqual(resp.status_code, HTTP_200_OK)
self.assertFalse(any(o['name'] == 'HiddenOrg2' for o in resp.data))
def test_roles_visible_to_owner_and_member_but_not_outsider(self):
owner = self.manager
member = User.objects.create_user(email_address='rmember@example.com', password='pass')
outsider = User.objects.create_user(email_address='routsider@example.com', password='pass')
org = Organization.objects.create(name='RoleOrg2', owner=owner)
OrganizationMembership.objects.create(organization=org, user=member,)
role = org.roles.create(name='Tester')
self.assertTrue(org.roles.filter(name='Tester').exists())
self.assertIn(role, org.roles.all())
self.assertNotIn(outsider, role.members.all())
def test_members_endpoint_only_accessible_to_manager(self):
org = Organization.objects.create(name='MemberOnlyOrg', owner=self.manager)
member = User.objects.create_user(email_address='monly@example.com', password='pass')
OrganizationMembership.objects.create(organization=org, user=member,)
members_view = OrganizationViewSet.as_view({'get': 'list_members'})
req = self.factory.get('/')
force_authenticate(req, user=member)
resp = members_view(req, uuid=str(org.uuid))
self.assertEqual(resp.status_code, HTTP_403_FORBIDDEN)
outsider = User.objects.create_user(email_address='notmem@example.com', password='pass')
req2 = self.factory.get('/')
force_authenticate(req2, user=outsider)
resp2 = members_view(req2, uuid=str(org.uuid))
self.assertEqual(resp2.status_code, HTTP_403_FORBIDDEN)
req3 = self.factory.get('/')
force_authenticate(req3, user=self.manager)
resp3 = members_view(req3, uuid=str(org.uuid))
self.assertEqual(resp3.status_code, HTTP_200_OK)
def test_invite_accept_invalid_or_expired(self):
org = Organization.objects.create(name='InvalidInviteOrg', owner=self.manager)
OrganizationMembership.objects.create(organization=org, user=self.manager)
invite = OrganizationInvitation.objects.create(organization=org, created_by=self.user)
invite.expires_at = invite.created_at - timezone.timedelta(days=1)
invite.save()
other = User.objects.create_user(email_address='inviter2@example.com', password='pass')
invite_view = OrganizationViewSet.as_view({'post': 'join'})
req = self.factory.post('/')
force_authenticate(req, user=other)
resp = invite_view(req, token=str(invite.token))
self.assertIn(resp.status_code, (HTTP_400_BAD_REQUEST, HTTP_404_NOT_FOUND))
def test_remove_member_by_non_manager_forbidden(self):
org = Organization.objects.create(name='RemoveForbidOrg', owner=self.user)
OrganizationMembership.objects.create(organization=org, user=self.user)
member = User.objects.create_user(email_address='m2@example.com', password='pass')
OrganizationMembership.objects.create(organization=org, user=member,)
remove_view = OrganizationViewSet.as_view({'post': 'remove_member'})
req = self.factory.post('/')
force_authenticate(req, user=self.user)
resp = remove_view(req, uuid=str(org.uuid), user_id=str(member.id))
self.assertEqual(resp.status_code, HTTP_403_FORBIDDEN)
def test_update_member_by_non_manager_forbidden(self):
org = Organization.objects.create(name='UpdateForbidOrg', owner=self.manager)
OrganizationMembership.objects.create(organization=org, user=self.user)
member = User.objects.create_user(email_address='m3@example.com', password='pass')
OrganizationMembership.objects.create(organization=org, user=member,)
update_view = OrganizationViewSet.as_view({'get': 'list_members'})
req = self.factory.get('/')
force_authenticate(req, user=self.user)
resp = update_view(req, uuid=str(org.uuid))
self.assertEqual(resp.status_code, HTTP_403_FORBIDDEN)
def test_invite_revoke_by_non_manager_forbidden(self):
org = Organization.objects.create(name='RevokeForbidOrg', owner=self.manager)
OrganizationMembership.objects.create(organization=org, user=self.user)
OrganizationMembership.objects.create(organization=org, user=User.objects.create_user(email_address='mgr@example.com', password='p'),)
token = OrganizationInvitation.objects.create(organization=org, created_by=self.user)
revoke_view = OrganizationViewSet.as_view({'get': 'list_invites'})
req = self.factory.get('/')
force_authenticate(req, user=self.user)
resp = revoke_view(req, uuid=str(org.uuid))
self.assertEqual(resp.status_code, HTTP_403_FORBIDDEN)
def test_role_create_and_visibility(self):
org = Organization.objects.create(name='RoleCreateOrg', owner=self.manager)
OrganizationMembership.objects.create(organization=org, user=self.manager)
role = org.roles.create(name='Tester')
self.assertIsNotNone(role)
self.assertTrue(org.roles.filter(name='Tester').exists())
def test_role_members_get_and_post(self):
org = Organization.objects.create(name='RoleMembersOrg', owner=self.manager)
OrganizationMembership.objects.create(organization=org, user=self.manager)
member = User.objects.create_user(email_address='memberrole@example.com', password='pass')
OrganizationMembership.objects.create(organization=org, user=member,)
role = org.roles.create(name='Developer')
RoleMembership.objects.create(role=role, user=member)
self.assertIn(member, role.members.all())