You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

256 lines
11 KiB

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")