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, uuid=str(org.uuid), 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['user']['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.assertFalse(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, uuid=str(org.uuid), 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())