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.
 
 

408 lines
18 KiB

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="[email protected]",
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="[email protected]",
password="SecurePassword123!",
fullname="Zainab Media Specialist",
user_type=User.UserType.EDITOR,
country="Germany",
city="Berlin"
)
self.regular_user = User.objects.create_user(
email="[email protected]",
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="[email protected]",
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": "[email protected]", "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": "[email protected]",
"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)