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.
269 lines
12 KiB
269 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.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="[email protected]",
|
|
password="SecurePassword123!",
|
|
fullname="Dr. Mahmoud Razavi",
|
|
user_type=User.UserType.EDITOR,
|
|
country="Iran",
|
|
city="Mashhad"
|
|
)
|
|
self.other_user = User.objects.create_user(
|
|
email="[email protected]",
|
|
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 (Option C returns 202 Accepted staged revision for published post)
|
|
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.assertIn(author_resp.status_code, [status.HTTP_200_OK, status.HTTP_202_ACCEPTED])
|
|
if author_resp.status_code == status.HTTP_202_ACCEPTED:
|
|
self.assertTrue(author_resp.data["has_pending_revision"])
|
|
self.assertEqual(author_resp.data["revision"]["title"], patch_payload["title"])
|
|
else:
|
|
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": "[email protected]",
|
|
"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)
|