from django.test import TestCase from django.core.files.uploadedfile import SimpleUploadedFile 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, InstitutionTimeline, InstitutionMedia, InstitutionFollower, ) User = get_user_model() class Phase2InstitutionProfileTests(TestCase): """ Automated test suite for Phase 2: - Institution Registration, Auto Admin Membership & Slug Generation - Institution List & Query Filtering (country, city, type, tag, search) - Institution Retrieval by ID and Unicode Slug - Institution Profile Updates & Role-Based Permissions - Historical Timeline Milestones CRUD - Photo/Video Media Gallery Upload & Deletion - Follower Subscriptions & Follower Count Tracking - Team Member Role Delegation & Management """ def setUp(self): self.client = APIClient() # Users self.admin_user = User.objects.create_user( email="imam.ali.center@berlin.de", password="SecurePassword123!", fullname="Sheikh Hassan Al-Berlini", user_type=User.UserType.INSTITUTION_ADMIN, is_staff=True, country="Germany", city="Berlin" ) self.editor_user = User.objects.create_user( email="editor@berlin.de", password="SecurePassword123!", fullname="Zainab Media Specialist", user_type=User.UserType.EDITOR, country="Germany", city="Berlin" ) self.regular_user = User.objects.create_user( email="member@community.org", password="SecurePassword123!", fullname="Ali Community Member", user_type=User.UserType.CLIENT, country="Germany", city="Frankfurt" ) # Region self.region = Region.objects.create( name="Central Europe", slug="central-europe", code="CEU", description="Central European zone" ) # Primary Institution self.institution = Institution.objects.create( name="Imam Ali Islamic Center Berlin", type=Institution.InstitutionType.CULTURAL_CENTER, region=self.region, country="Germany", city="Berlin", address="Flughafenstrasse 42, 12053 Berlin", latitude=52.4839, longitude=13.4325, description="Leading cultural and religious center serving the international community in Berlin.", website="https://imam-ali-berlin.de", email="contact@imam-ali-berlin.de", phone="+49 30 620000", established_year=1998, tags=["Youth Programs", "Interfaith Dialogue", "Quran Academy"], verification_status=Institution.VerificationStatus.APPROVED, is_featured=True, created_by=self.admin_user ) # Member roles InstitutionMember.objects.create( institution=self.institution, user=self.admin_user, role=InstitutionMember.MemberRole.ADMIN, title="Director General" ) InstitutionMember.objects.create( institution=self.institution, user=self.editor_user, role=InstitutionMember.MemberRole.EDITOR, title="Content Coordinator" ) def test_institution_registration_and_auto_membership(self): self.client.force_authenticate(user=self.admin_user) payload = { "name": "Al-Zahra Cultural Center Vienna", "type": "cultural_center", "country": "Austria", "city": "Vienna", "address": "Favoritenstrasse 88", "latitude": 48.1751, "longitude": 16.3776, "description": "Center dedicated to cultural and scientific exchange.", "established_year": 2005, "tags": ["Library", "Language Courses"] } # 1. Omitting region must fail validation (every institution must have a region) fail_response = self.client.post("/api/v1/profiles/", payload, format="json") self.assertEqual(fail_response.status_code, status.HTTP_400_BAD_REQUEST) error_fields = [e.get('field') for e in fail_response.data.get('errors', [])] if 'errors' in fail_response.data else list(fail_response.data.keys()) self.assertIn("region", error_fields) # 2. Including region creates institution successfully payload["region"] = self.region.id response = self.client.post("/api/v1/profiles/", payload, format="json") self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.data["name"], "Al-Zahra Cultural Center Vienna") self.assertTrue(response.data["slug"]) self.assertEqual(response.data["region"]["id"], self.region.id) self.assertEqual(response.data["verification_status"], "pending") # Verify Creator is registered as Admin member inst_id = response.data["id"] member = InstitutionMember.objects.get(institution_id=inst_id, user=self.admin_user) self.assertEqual(member.role, InstitutionMember.MemberRole.ADMIN) def test_institution_listing_and_filtering(self): # 1. Base list response = self.client.get("/api/v1/profiles/") self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertGreaterEqual(len(response.data["results"]), 1) # 2. Filter by country resp_de = self.client.get("/api/v1/profiles/?country=Germany") self.assertEqual(len(resp_de.data["results"]), 1) resp_fr = self.client.get("/api/v1/profiles/?country=France") self.assertEqual(len(resp_fr.data["results"]), 0) # 3. Filter by search resp_search = self.client.get("/api/v1/profiles/?search=Berlin") self.assertEqual(len(resp_search.data["results"]), 1) # 4. Filter by tag resp_tag = self.client.get("/api/v1/profiles/?tag=Youth Programs") self.assertEqual(len(resp_tag.data["results"]), 1) def test_institution_detail_by_id_and_slug(self): # 1. Fetch by ID resp_id = self.client.get(f"/api/v1/profiles/{self.institution.id}/") self.assertEqual(resp_id.status_code, status.HTTP_200_OK) self.assertEqual(resp_id.data["name"], "Imam Ali Islamic Center Berlin") self.assertEqual(resp_id.data["members_count"], 2) # 2. Fetch by Slug resp_slug = self.client.get(f"/api/v1/profiles/{self.institution.slug}/") self.assertEqual(resp_slug.status_code, status.HTTP_200_OK) self.assertEqual(resp_slug.data["id"], self.institution.id) def test_institution_patch_update_permissions(self): patch_data = { "description": "Updated international description with expanded multi-language community center.", "phone": "+49 30 777888" } # 1. Unauthorized user cannot update unauth_resp = self.client.patch(f"/api/v1/profiles/{self.institution.id}/", patch_data, format="json") self.assertEqual(unauth_resp.status_code, status.HTTP_401_UNAUTHORIZED) # 2. Regular non-member user receives 403 self.client.force_authenticate(user=self.regular_user) forbidden_resp = self.client.patch(f"/api/v1/profiles/{self.institution.id}/", patch_data, format="json") self.assertEqual(forbidden_resp.status_code, status.HTTP_403_FORBIDDEN) # 3. Institution Editor can update self.client.force_authenticate(user=self.editor_user) editor_resp = self.client.patch(f"/api/v1/profiles/{self.institution.id}/", patch_data, format="json") self.assertEqual(editor_resp.status_code, status.HTTP_200_OK) self.assertEqual(editor_resp.data["phone"], "+49 30 777888") # 4. Institution Admin can update self.client.force_authenticate(user=self.admin_user) admin_resp = self.client.patch(f"/api/v1/profiles/{self.institution.id}/", {"city": "Berlin Capital"}, format="json") self.assertEqual(admin_resp.status_code, status.HTTP_200_OK) self.assertEqual(admin_resp.data["city"], "Berlin Capital") def test_institution_timeline_crud(self): self.client.force_authenticate(user=self.editor_user) # 1. Add Timeline entry payload = { "year": "1998", "title": "Foundation and Inauguration", "description": "First official opening ceremony with community leaders.", "order": 1 } create_resp = self.client.post(f"/api/v1/profiles/{self.institution.id}/timeline/", payload, format="json") self.assertEqual(create_resp.status_code, status.HTTP_201_CREATED) entry_id = create_resp.data["id"] # 2. List Timeline list_resp = self.client.get(f"/api/v1/profiles/{self.institution.id}/timeline/") self.assertEqual(list_resp.status_code, status.HTTP_200_OK) self.assertEqual(len(list_resp.data), 1) self.assertEqual(list_resp.data[0]["title"], "Foundation and Inauguration") # 3. Delete Timeline entry del_resp = self.client.delete(f"/api/v1/profiles/{self.institution.id}/timeline/{entry_id}/") self.assertEqual(del_resp.status_code, status.HTTP_204_NO_CONTENT) self.assertEqual(InstitutionTimeline.objects.filter(id=entry_id).count(), 0) def test_institution_media_upload_and_delete(self): self.client.force_authenticate(user=self.editor_user) mock_image = SimpleUploadedFile("center_hall.jpg", b"mock_image_bytes", content_type="image/jpeg") upload_resp = self.client.post( f"/api/v1/profiles/{self.institution.id}/media/", { "file": mock_image, "media_type": "image", "title": "Main Prayer and Conference Hall", "caption": "Capacity for 800 attendees." }, format="multipart" ) self.assertEqual(upload_resp.status_code, status.HTTP_201_CREATED) media_id = upload_resp.data["id"] # List media list_resp = self.client.get(f"/api/v1/profiles/{self.institution.id}/media/") self.assertEqual(list_resp.status_code, status.HTTP_200_OK) self.assertEqual(len(list_resp.data), 1) # Delete media del_resp = self.client.delete(f"/api/v1/profiles/{self.institution.id}/media/{media_id}/") self.assertEqual(del_resp.status_code, status.HTTP_204_NO_CONTENT) self.assertEqual(InstitutionMedia.objects.filter(id=media_id).count(), 0) def test_institution_follow_and_unfollow(self): self.client.force_authenticate(user=self.regular_user) # 1. Initial State self.assertEqual(self.institution.follower_count, 0) # 2. Follow follow_resp = self.client.post(f"/api/v1/profiles/{self.institution.id}/follow/") self.assertEqual(follow_resp.status_code, status.HTTP_200_OK) self.assertTrue(follow_resp.data["is_following"]) self.assertEqual(follow_resp.data["follower_count"], 1) # 3. Check Institution Detail reflects following state detail_resp = self.client.get(f"/api/v1/profiles/{self.institution.id}/") self.assertTrue(detail_resp.data["is_following"]) # 4. Unfollow unfollow_resp = self.client.delete(f"/api/v1/profiles/{self.institution.id}/follow/") self.assertEqual(unfollow_resp.status_code, status.HTTP_200_OK) self.assertFalse(unfollow_resp.data["is_following"]) self.assertEqual(unfollow_resp.data["follower_count"], 0) def test_institution_member_delegation(self): # 1. Non-admin cannot delegate roles self.client.force_authenticate(user=self.regular_user) forbid_resp = self.client.post( f"/api/v1/profiles/{self.institution.id}/members/", {"email": "member@community.org", "role": "editor"}, format="json" ) self.assertEqual(forbid_resp.status_code, status.HTTP_403_FORBIDDEN) # 2. Admin adds regular_user as Viewer self.client.force_authenticate(user=self.admin_user) add_resp = self.client.post( f"/api/v1/profiles/{self.institution.id}/members/", { "email": "member@community.org", "role": "viewer", "title": "Community Representative" }, format="json" ) self.assertEqual(add_resp.status_code, status.HTTP_201_CREATED) member_id = add_resp.data["id"] self.assertEqual(add_resp.data["role"], "viewer") # 3. List Members members_resp = self.client.get(f"/api/v1/profiles/{self.institution.id}/members/") self.assertEqual(members_resp.status_code, status.HTTP_200_OK) self.assertEqual(len(members_resp.data), 3) # 4. Remove Member del_member_resp = self.client.delete(f"/api/v1/profiles/{self.institution.id}/members/{member_id}/") self.assertEqual(del_member_resp.status_code, status.HTTP_204_NO_CONTENT) self.assertEqual(InstitutionMember.objects.filter(id=member_id).count(), 0) def test_region_crud_and_management(self): # 1. Anonymous can list active regions (for dropdowns / create form) resp = self.client.get("/api/v1/profiles/regions/") self.assertEqual(resp.status_code, status.HTTP_200_OK) self.assertGreaterEqual(len(resp.data), 1) # 2. Staff user can create a new region self.client.force_authenticate(user=self.admin_user) new_region_data = { "name": "Scandinavia & Baltics", "code": "SCA", "description": "Northern European countries" } create_resp = self.client.post("/api/v1/profiles/regions/", new_region_data, format="json") self.assertEqual(create_resp.status_code, status.HTTP_201_CREATED) new_region_id = create_resp.data["id"] self.assertEqual(create_resp.data["slug"], "scandinavia-baltics") # 3. Regular user cannot create region self.client.force_authenticate(user=self.regular_user) forbid_resp = self.client.post("/api/v1/profiles/regions/", {"name": "Invalid Region"}, format="json") self.assertEqual(forbid_resp.status_code, status.HTTP_403_FORBIDDEN) # 4. Deleting a region with institutions assigned must fail self.client.force_authenticate(user=self.admin_user) del_fail = self.client.delete(f"/api/v1/profiles/regions/{self.region.id}/") self.assertEqual(del_fail.status_code, status.HTTP_400_BAD_REQUEST) self.assertTrue(Region.objects.filter(id=self.region.id).exists()) # 5. Deleting a region without institutions succeeds del_ok = self.client.delete(f"/api/v1/profiles/regions/{new_region_id}/") self.assertEqual(del_ok.status_code, status.HTTP_204_NO_CONTENT) self.assertFalse(Region.objects.filter(id=new_region_id).exists()) def test_institution_region_filtering(self): # Create second region and institution mena_region = Region.objects.create(name="Middle East", slug="middle-east", code="MEA") Institution.objects.create( name="Tehran Grand Center", type=Institution.InstitutionType.MOSQUE, region=mena_region, country="Iran", city="Tehran", created_by=self.admin_user ) # 1. Filter by region ID resp_id = self.client.get(f"/api/v1/profiles/?region={mena_region.id}") self.assertEqual(resp_id.status_code, status.HTTP_200_OK) self.assertEqual(len(resp_id.data["results"]), 1) self.assertEqual(resp_id.data["results"][0]["name"], "Tehran Grand Center") # 2. Filter by region slug resp_slug = self.client.get("/api/v1/profiles/?region=central-europe") self.assertEqual(resp_slug.status_code, status.HTTP_200_OK) self.assertEqual(len(resp_slug.data["results"]), 1) self.assertEqual(resp_slug.data["results"][0]["name"], "Imam Ali Islamic Center Berlin") def test_institution_region_edit_form(self): self.client.force_authenticate(user=self.admin_user) new_region = Region.objects.create(name="Western Europe", slug="western-europe", code="WEU") # 1. Update institution region via PATCH patch_resp = self.client.patch( f"/api/v1/profiles/{self.institution.id}/", {"region": new_region.id}, format="json" ) self.assertEqual(patch_resp.status_code, status.HTTP_200_OK) self.assertEqual(patch_resp.data["region"]["id"], new_region.id) self.assertEqual(patch_resp.data["region"]["name"], "Western Europe") # Verify DB updated self.institution.refresh_from_db() self.assertEqual(self.institution.region_id, new_region.id) def test_region_admin_panel_configuration(self): from utils.admin import project_admin_site from apps.profiles.admin import RegionAdmin, InstitutionAdmin # Verify Region and Institution are registered in the project admin site self.assertIn(Region, project_admin_site._registry) self.assertIn(Institution, project_admin_site._registry) reg_admin = project_admin_site._registry[Region] inst_admin = project_admin_site._registry[Institution] self.assertIsInstance(reg_admin, RegionAdmin) self.assertIsInstance(inst_admin, InstitutionAdmin) # Verify region field in InstitutionAdmin self.assertIn('region', inst_admin.list_filter) self.assertIn('region_display', inst_admin.list_display) # Verify region in fieldsets fieldset_fields = [] for _, config in inst_admin.fieldsets: fieldset_fields.extend(config.get('fields', ())) self.assertIn('region', fieldset_fields)