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.
286 lines
12 KiB
286 lines
12 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.institution import (
|
|
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,
|
|
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"
|
|
)
|
|
|
|
# Primary Institution
|
|
self.institution = Institution.objects.create(
|
|
name="Imam Ali Islamic Center Berlin",
|
|
type=Institution.InstitutionType.CULTURAL_CENTER,
|
|
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"]
|
|
}
|
|
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["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)
|