from django.test import TestCase, RequestFactory from django.contrib.auth import get_user_model from rest_framework.test import APIClient from rest_framework import status from apps.profiles.models import Region, Institution, InstitutionMember from apps.profiles.admin import InstitutionAdmin, RegionAdmin from apps.projects.models import Project from apps.account.serializers.auth_serializers import UserMeSerializer from utils.admin import project_admin_site User = get_user_model() class RegionalAdminPermissionTests(TestCase): """ Comprehensive test suite verifying regional admin scoping and permissions: - User model can_manage_region and has_regional_perm methods. - Regional admin CRUD isolation for institutions (permitted within managed region, 403 outside). - API managed_only filtering honoring admin jurisdiction. - Project editing scoped to institution regions. - Django Admin get_queryset and formfield_for_foreignkey scoping. - UserMeSerializer exposure of managed_regions. """ def setUp(self): self.client = APIClient() self.rf = RequestFactory() # Retrieve or create regions (Europe & Asia are pre-seeded by migration 0002) self.region_europe, _ = Region.objects.get_or_create( slug="europe", defaults={"name": "Europe", "code": "EU", "description": "European Region"} ) self.region_asia, _ = Region.objects.get_or_create( slug="asia", defaults={"name": "Asia", "code": "AS", "description": "Asian Region"} ) # Create Super Admin self.super_admin = User.objects.create_superuser( email="super@domain.com", password="SecurePassword123!", fullname="Global Super Admin" ) # Create Regional Admin assigned to Europe only self.eu_admin = User.objects.create_user( email="eu.admin@domain.com", password="SecurePassword123!", fullname="Europe Regional Admin", user_type=User.UserType.REGIONAL_ADMIN, is_staff=True, regional_permissions={ "institutions": ["view", "change", "delete"], "projects": ["view", "change"] } ) self.eu_admin.managed_regions.add(self.region_europe) # Create Regular User self.regular_user = User.objects.create_user( email="regular@domain.com", password="SecurePassword123!", fullname="Regular User", user_type=User.UserType.CLIENT ) # Create Institutions in Europe and Asia self.inst_europe = Institution.objects.create( name="Berlin Islamic Center", slug="berlin-islamic-center", type="mosque", region=self.region_europe, country="Germany", city="Berlin", address="Flughafenstr 43", created_by=self.eu_admin ) self.inst_asia = Institution.objects.create( name="Tokyo Camii Center", slug="tokyo-camii-center", type="mosque", region=self.region_asia, country="Japan", city="Tokyo", address="1-19 Oyama-cho", created_by=self.super_admin ) # Create Projects self.proj_europe = Project.objects.create( title="European Youth Conference", owner_institution=self.inst_europe, created_by=self.eu_admin, status="active" ) self.proj_asia = Project.objects.create( title="Tokyo Intercultural Expo", owner_institution=self.inst_asia, created_by=self.super_admin, status="active" ) # ------------------------------------------------------------------------- # 1. Model-level jurisdiction methods # ------------------------------------------------------------------------- def test_user_can_manage_region(self): # Super admin can manage all regions self.assertTrue(self.super_admin.can_manage_region(self.region_europe)) self.assertTrue(self.super_admin.can_manage_region(self.region_asia)) # Europe admin can manage Europe, but NOT Asia self.assertTrue(self.eu_admin.can_manage_region(self.region_europe)) self.assertFalse(self.eu_admin.can_manage_region(self.region_asia)) # Regular user cannot manage any region self.assertFalse(self.regular_user.can_manage_region(self.region_europe)) self.assertFalse(self.regular_user.can_manage_region(self.region_asia)) def test_user_has_regional_perm(self): # EU admin has change permission on Europe self.assertTrue(self.eu_admin.has_regional_perm("institutions.change", self.region_europe)) # EU admin does NOT have permission on Asia self.assertFalse(self.eu_admin.has_regional_perm("institutions.change", self.region_asia)) # Super admin has all permissions on any region self.assertTrue(self.super_admin.has_regional_perm("anything", self.region_asia)) # ------------------------------------------------------------------------- # 2. Institution permission checks # ------------------------------------------------------------------------- def test_institution_is_admin_regional_scoping(self): # EU admin is admin of European institution, NOT Asian institution self.assertTrue(self.inst_europe.is_admin(self.eu_admin)) self.assertFalse(self.inst_asia.is_admin(self.eu_admin)) # Super admin is admin of both self.assertTrue(self.inst_europe.is_admin(self.super_admin)) self.assertTrue(self.inst_asia.is_admin(self.super_admin)) def test_institution_edit_api_regional_scoping(self): # Authenticate as EU admin self.client.force_authenticate(user=self.eu_admin) # 1. Edit European institution -> Success (200) res_eu = self.client.patch( f"/api/v1/profiles/{self.inst_europe.id}/", {"description": "Updated by EU admin"}, format="json" ) self.assertEqual(res_eu.status_code, status.HTTP_200_OK) self.inst_europe.refresh_from_db() self.assertEqual(self.inst_europe.description, "Updated by EU admin") # 2. Edit Asian institution -> Forbidden (403) res_asia = self.client.patch( f"/api/v1/profiles/{self.inst_asia.id}/", {"description": "Attempted update outside jurisdiction"}, format="json" ) self.assertEqual(res_asia.status_code, status.HTTP_403_FORBIDDEN) def test_institution_list_managed_only_filter(self): self.client.force_authenticate(user=self.eu_admin) # Query all institutions res_all = self.client.get("/api/v1/profiles/") self.assertEqual(res_all.status_code, status.HTTP_200_OK) all_ids = [item["id"] for item in res_all.data["results"]] self.assertIn(self.inst_europe.id, all_ids) self.assertIn(self.inst_asia.id, all_ids) # Query with managed_only=true res_managed = self.client.get("/api/v1/profiles/?managed_only=true") self.assertEqual(res_managed.status_code, status.HTTP_200_OK) managed_ids = [item["id"] for item in res_managed.data["results"]] self.assertIn(self.inst_europe.id, managed_ids) self.assertNotIn(self.inst_asia.id, managed_ids) # ------------------------------------------------------------------------- # 3. Project Scoping # ------------------------------------------------------------------------- def test_project_can_user_edit_regional_scoping(self): # EU admin can edit European project, but NOT Asian project self.assertTrue(self.proj_europe.can_user_edit(self.eu_admin)) self.assertFalse(self.proj_asia.can_user_edit(self.eu_admin)) # Super admin can edit both self.assertTrue(self.proj_europe.can_user_edit(self.super_admin)) self.assertTrue(self.proj_asia.can_user_edit(self.super_admin)) def test_project_edit_api_regional_scoping(self): self.client.force_authenticate(user=self.eu_admin) # 1. Edit European project -> Success (200) res_eu = self.client.patch( f"/api/v1/projects/{self.proj_europe.id}/", {"description": "Updated conference agenda"}, format="json" ) self.assertEqual(res_eu.status_code, status.HTTP_200_OK) # 2. Edit Asian project -> Forbidden (403) res_asia = self.client.patch( f"/api/v1/projects/{self.proj_asia.id}/", {"description": "Unauthorized edit"}, format="json" ) self.assertEqual(res_asia.status_code, status.HTTP_403_FORBIDDEN) # ------------------------------------------------------------------------- # 4. Django Admin Isolation # ------------------------------------------------------------------------- def test_admin_panel_institution_scoping(self): admin_obj = InstitutionAdmin(Institution, project_admin_site) # Request by EU admin req_eu = self.rf.get("/admin/profiles/institution/") req_eu.user = self.eu_admin qs_eu = admin_obj.get_queryset(req_eu) self.assertIn(self.inst_europe, qs_eu) self.assertNotIn(self.inst_asia, qs_eu) # Request by Super Admin req_super = self.rf.get("/admin/profiles/institution/") req_super.user = self.super_admin qs_super = admin_obj.get_queryset(req_super) self.assertIn(self.inst_europe, qs_super) self.assertIn(self.inst_asia, qs_super) def test_admin_panel_foreign_key_formfield_scoping(self): admin_obj = InstitutionAdmin(Institution, project_admin_site) req_eu = self.rf.get("/admin/profiles/institution/add/") req_eu.user = self.eu_admin db_field = Institution._meta.get_field("region") form_field = admin_obj.formfield_for_foreignkey(db_field, req_eu) # Dropdown queryset for regional admin should only contain Europe region_ids = list(form_field.queryset.values_list("id", flat=True)) self.assertIn(self.region_europe.id, region_ids) self.assertNotIn(self.region_asia.id, region_ids) # ------------------------------------------------------------------------- # 5. UserMeSerializer exposure # ------------------------------------------------------------------------- def test_user_me_serializer_exposes_managed_regions(self): serializer = UserMeSerializer(self.eu_admin) data = serializer.data self.assertIn("managed_regions", data) self.assertEqual(len(data["managed_regions"]), 1) self.assertEqual(data["managed_regions"][0]["code"], "EU") self.assertEqual(data["managed_regions"][0]["name"], "Europe")