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.cms.models.post import ( PostCategory, PostTag, Post, PostComment, PostLike, MediaAsset, ) from apps.profiles.models.institution import Institution User = get_user_model() class Phase4CMSTests(TestCase): """ Automated test suite for Phase 4: - Multilingual Post Creation, Auto-Slug & Reading Time Calculation - Multilingual Post Listing & Filtering (language, post_type, category, tag, search) - Post Detail View & Automated Views Counter Increment - Post Update & Role-Based Permissions (author vs forbidden user) - Post Like / Unlike Toggle with Counter Sync - Post Reader Comments Submission & Listing - Category & Tag Taxonomy Endpoints - Media Asset Library Upload & Metadata Tracking """ def setUp(self): self.client = APIClient() # Users self.author_user = User.objects.create_user( email="dr.razavi@shiahub.org", password="SecurePassword123!", fullname="Dr. Mahmoud Razavi", user_type=User.UserType.EDITOR, country="Iran", city="Mashhad" ) self.other_user = User.objects.create_user( email="reader@community.org", password="SecurePassword123!", fullname="Hassan Reader", user_type=User.UserType.CLIENT, country="Lebanon", city="Beirut" ) # Institution self.institution = Institution.objects.create( name="Astan Quds Razavi International Center", type=Institution.InstitutionType.INSTITUTE, country="Iran", city="Mashhad" ) # Category & Tag self.category_en = PostCategory.objects.create( name="Cultural Diplomacy", language="en", description="Inter-faith and cultural cooperation articles." ) self.category_fa = PostCategory.objects.create( name="دیپلماسی فرهنگی", language="fa", description="مقالات همکاری های بین المللی و گفتگوی ادیان." ) self.tag_diplomacy = PostTag.objects.create(name="Diplomacy") self.tag_dialogue = PostTag.objects.create(name="Interfaith") # Sample Published Post self.post = Post.objects.create( title="The Role of Razavi Teachings in Global Cultural Diplomacy", post_type=Post.PostType.ARTICLE, category=self.category_en, language=Post.LanguageChoices.EN, status=Post.Status.PUBLISHED, author=self.author_user, institution=self.institution, author_role_label="Senior Fellow in Islamic Diplomacy", excerpt="Analyzing contemporary methods for promoting intercultural understanding through Razavi traditions.", content=" ".join(["Razavi teachings emphasize compassion, rational dialogue, and mutual respect."] * 100), tags=["Diplomacy", "Interfaith", "Razavi Studies"], is_featured=True ) def test_post_creation_and_auto_attributes(self): self.client.force_authenticate(user=self.author_user) payload = { "title": "Interfaith Centers in Modern Europe: A Comparative Analysis", "post_type": "report", "category": self.category_en.id, "language": "en", "status": "published", "excerpt": "Field report examining 50 Islamic cultural centers across Western Europe.", "content": " ".join(["Comprehensive analysis of Islamic centers in Germany, France, and Austria."] * 250), "tags": ["Europe", "Cultural Centers", "Field Report"], "is_featured": False } response = self.client.post("/api/v1/cms/posts/", payload, format="json") self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.data["title"], payload["title"]) self.assertTrue(response.data["slug"]) self.assertEqual(response.data["author"]["email"], self.author_user.email) # Word count: 250 * 9 = 2250 words -> approx 12 mins self.assertGreaterEqual(response.data["reading_time_minutes"], 2) def test_post_listing_and_multilingual_filtering(self): # Create an Arabic post Post.objects.create( title="دور المراكز الإسلامية في تعزيز الحوار الحضاري", post_type=Post.PostType.NEWS, language=Post.LanguageChoices.AR, status=Post.Status.PUBLISHED, author=self.author_user, content="تقرير إخباري حول مؤتمر الحوار في مشهد المقدسة." ) # 1. Base list response = self.client.get("/api/v1/cms/posts/") self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertGreaterEqual(len(response.data["results"]), 2) # 2. Filter by Language resp_en = self.client.get("/api/v1/cms/posts/?language=en") self.assertEqual(len(resp_en.data["results"]), 1) self.assertEqual(resp_en.data["results"][0]["language"], "en") resp_ar = self.client.get("/api/v1/cms/posts/?language=ar") self.assertEqual(len(resp_ar.data["results"]), 1) self.assertEqual(resp_ar.data["results"][0]["language"], "ar") # 3. Filter by Post Type resp_type = self.client.get("/api/v1/cms/posts/?post_type=article") self.assertEqual(len(resp_type.data["results"]), 1) self.assertEqual(resp_type.data["results"][0]["post_type"], "article") # 4. Search Filter resp_search = self.client.get("/api/v1/cms/posts/?search=Razavi") self.assertEqual(len(resp_search.data["results"]), 1) def test_post_detail_retrieval_and_views_increment(self): initial_views = self.post.views_count # 1. Fetch by Slug response = self.client.get(f"/api/v1/cms/posts/{self.post.slug}/") self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data["id"], self.post.id) self.assertEqual(response.data["views_count"], initial_views + 1) # 2. Fetch by ID resp_id = self.client.get(f"/api/v1/cms/posts/{self.post.id}/") self.assertEqual(resp_id.status_code, status.HTTP_200_OK) self.assertEqual(resp_id.data["views_count"], initial_views + 2) def test_post_patch_update_and_permissions(self): patch_payload = { "title": "The Role of Razavi Teachings in Global Cultural Diplomacy (Updated Edition)" } # 1. Unauthenticated user receives 401 unauth_resp = self.client.patch(f"/api/v1/cms/posts/{self.post.id}/", patch_payload, format="json") self.assertEqual(unauth_resp.status_code, status.HTTP_401_UNAUTHORIZED) # 2. Non-author user receives 403 self.client.force_authenticate(user=self.other_user) forbidden_resp = self.client.patch(f"/api/v1/cms/posts/{self.post.id}/", patch_payload, format="json") self.assertEqual(forbidden_resp.status_code, status.HTTP_403_FORBIDDEN) # 3. Author can successfully update self.client.force_authenticate(user=self.author_user) author_resp = self.client.patch(f"/api/v1/cms/posts/{self.post.id}/", patch_payload, format="json") self.assertEqual(author_resp.status_code, status.HTTP_200_OK) self.assertEqual(author_resp.data["title"], patch_payload["title"]) def test_post_like_toggle(self): self.client.force_authenticate(user=self.other_user) # 1. Like like_resp = self.client.post(f"/api/v1/cms/posts/{self.post.id}/like/") self.assertEqual(like_resp.status_code, status.HTTP_200_OK) self.assertTrue(like_resp.data["is_liked"]) self.assertEqual(like_resp.data["likes_count"], 1) # Check detail includes is_liked = True detail_resp = self.client.get(f"/api/v1/cms/posts/{self.post.id}/") self.assertTrue(detail_resp.data["is_liked"]) # 2. Unlike unlike_resp = self.client.post(f"/api/v1/cms/posts/{self.post.id}/like/") self.assertEqual(unlike_resp.status_code, status.HTTP_200_OK) self.assertFalse(unlike_resp.data["is_liked"]) self.assertEqual(unlike_resp.data["likes_count"], 0) def test_post_comments_submission_and_listing(self): # 1. Anonymous / Visitor Comment comment_payload = { "author_name": "Dr. Ali Sadr", "author_email": "ali.sadr@research.org", "content": "Outstanding analysis on the foundational pillars of Islamic cultural diplomacy." } comment_resp = self.client.post(f"/api/v1/cms/posts/{self.post.id}/comments/", comment_payload, format="json") self.assertEqual(comment_resp.status_code, status.HTTP_201_CREATED) self.assertEqual(comment_resp.data["author_name"], "Dr. Ali Sadr") # 2. Authenticated User Comment self.client.force_authenticate(user=self.other_user) auth_comment_payload = { "content": "Very insightful article, thank you for sharing." } auth_comment_resp = self.client.post(f"/api/v1/cms/posts/{self.post.id}/comments/", auth_comment_payload, format="json") self.assertEqual(auth_comment_resp.status_code, status.HTTP_201_CREATED) # 3. List Comments list_resp = self.client.get(f"/api/v1/cms/posts/{self.post.id}/comments/") self.assertEqual(list_resp.status_code, status.HTTP_200_OK) self.assertEqual(len(list_resp.data), 2) def test_categories_and_tags_endpoints(self): # 1. Categories cat_resp = self.client.get("/api/v1/cms/categories/") self.assertEqual(cat_resp.status_code, status.HTTP_200_OK) self.assertGreaterEqual(len(cat_resp.data), 2) # Filter by language cat_lang_resp = self.client.get("/api/v1/cms/categories/?language=fa") self.assertEqual(len(cat_lang_resp.data), 1) self.assertEqual(cat_lang_resp.data[0]["name"], "دیپلماسی فرهنگی") # 2. Tags tag_resp = self.client.get("/api/v1/cms/tags/") self.assertEqual(tag_resp.status_code, status.HTTP_200_OK) self.assertGreaterEqual(len(tag_resp.data), 2) def test_media_asset_upload_and_listing(self): self.client.force_authenticate(user=self.author_user) mock_file = SimpleUploadedFile("diplomacy_infographic.png", b"mock_png_image_data", content_type="image/png") upload_resp = self.client.post( "/api/v1/cms/media/", { "file": mock_file, "title": "Razavi Cultural Map 2026", "media_type": "image", "dimensions": "1920x1080" }, format="multipart" ) self.assertEqual(upload_resp.status_code, status.HTTP_201_CREATED) self.assertEqual(upload_resp.data["title"], "Razavi Cultural Map 2026") self.assertEqual(upload_resp.data["uploaded_by_email"], self.author_user.email) self.assertGreater(upload_resp.data["file_size"], 0) # List Media Assets list_resp = self.client.get("/api/v1/cms/media/") self.assertEqual(list_resp.status_code, status.HTTP_200_OK) self.assertEqual(len(list_resp.data), 1)