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