mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-21 20:11:10 -05:00
- Update pghistory dependency from 0007 to 0006 in account migrations - Add docstrings and remove unused imports in htmx_forms.py - Add DJANGO_SETTINGS_MODULE bash commands to Claude settings - Add state transition definitions for ride statuses
528 lines
18 KiB
Python
528 lines
18 KiB
Python
from django.test import TestCase, Client
|
|
from django.contrib.auth import get_user_model
|
|
from django.contrib.auth.models import AnonymousUser
|
|
from django.contrib.contenttypes.models import ContentType
|
|
from django.core.files.uploadedfile import SimpleUploadedFile
|
|
from django.http import JsonResponse, HttpRequest
|
|
from .models import EditSubmission
|
|
from .mixins import (
|
|
EditSubmissionMixin,
|
|
PhotoSubmissionMixin,
|
|
ModeratorRequiredMixin,
|
|
AdminRequiredMixin,
|
|
InlineEditMixin,
|
|
HistoryMixin,
|
|
)
|
|
from apps.parks.models import Company as Operator
|
|
from django.views.generic import DetailView
|
|
from django.test import RequestFactory
|
|
import json
|
|
|
|
User = get_user_model()
|
|
|
|
|
|
class TestView(
|
|
EditSubmissionMixin,
|
|
PhotoSubmissionMixin,
|
|
InlineEditMixin,
|
|
HistoryMixin,
|
|
DetailView,
|
|
):
|
|
model = Operator
|
|
template_name = "test.html"
|
|
pk_url_kwarg = "pk"
|
|
slug_url_kwarg = "slug"
|
|
|
|
def get_context_data(self, **kwargs):
|
|
if not hasattr(self, "object"):
|
|
self.object = self.get_object()
|
|
return super().get_context_data(**kwargs)
|
|
|
|
def setup(self, request: HttpRequest, *args, **kwargs):
|
|
super().setup(request, *args, **kwargs)
|
|
self.request = request
|
|
|
|
|
|
class ModerationMixinsTests(TestCase):
|
|
def setUp(self):
|
|
self.client = Client()
|
|
self.factory = RequestFactory()
|
|
|
|
# Create users with different roles
|
|
self.user = User.objects.create_user(
|
|
username="testuser",
|
|
email="test@example.com",
|
|
password="testpass123",
|
|
)
|
|
self.moderator = User.objects.create_user(
|
|
username="moderator",
|
|
email="moderator@example.com",
|
|
password="modpass123",
|
|
role="MODERATOR",
|
|
)
|
|
self.admin = User.objects.create_user(
|
|
username="admin",
|
|
email="admin@example.com",
|
|
password="adminpass123",
|
|
role="ADMIN",
|
|
)
|
|
|
|
# Create test company
|
|
self.operator = Operator.objects.create(
|
|
name="Test Operator",
|
|
website="http://example.com",
|
|
description="Test Description",
|
|
)
|
|
|
|
def test_edit_submission_mixin_unauthenticated(self):
|
|
"""Test edit submission when not logged in"""
|
|
view = TestView()
|
|
request = self.factory.post(f"/test/{self.operator.pk}/")
|
|
request.user = AnonymousUser()
|
|
view.setup(request, pk=self.operator.pk)
|
|
view.kwargs = {"pk": self.operator.pk}
|
|
response = view.handle_edit_submission(request, {})
|
|
self.assertIsInstance(response, JsonResponse)
|
|
self.assertEqual(response.status_code, 403)
|
|
|
|
def test_edit_submission_mixin_no_changes(self):
|
|
"""Test edit submission with no changes"""
|
|
view = TestView()
|
|
request = self.factory.post(
|
|
f"/test/{self.operator.pk}/",
|
|
data=json.dumps({}),
|
|
content_type="application/json",
|
|
)
|
|
request.user = self.user
|
|
view.setup(request, pk=self.operator.pk)
|
|
view.kwargs = {"pk": self.operator.pk}
|
|
response = view.post(request)
|
|
self.assertIsInstance(response, JsonResponse)
|
|
self.assertEqual(response.status_code, 400)
|
|
|
|
def test_edit_submission_mixin_invalid_json(self):
|
|
"""Test edit submission with invalid JSON"""
|
|
view = TestView()
|
|
request = self.factory.post(
|
|
f"/test/{self.operator.pk}/",
|
|
data="invalid json",
|
|
content_type="application/json",
|
|
)
|
|
request.user = self.user
|
|
view.setup(request, pk=self.operator.pk)
|
|
view.kwargs = {"pk": self.operator.pk}
|
|
response = view.post(request)
|
|
self.assertIsInstance(response, JsonResponse)
|
|
self.assertEqual(response.status_code, 400)
|
|
|
|
def test_edit_submission_mixin_regular_user(self):
|
|
"""Test edit submission as regular user"""
|
|
view = TestView()
|
|
request = self.factory.post(f"/test/{self.operator.pk}/")
|
|
request.user = self.user
|
|
view.setup(request, pk=self.operator.pk)
|
|
view.kwargs = {"pk": self.operator.pk}
|
|
changes = {"name": "New Name"}
|
|
response = view.handle_edit_submission(
|
|
request, changes, "Test reason", "Test source"
|
|
)
|
|
self.assertIsInstance(response, JsonResponse)
|
|
self.assertEqual(response.status_code, 200)
|
|
data = json.loads(response.content.decode())
|
|
self.assertFalse(data["auto_approved"])
|
|
|
|
def test_edit_submission_mixin_moderator(self):
|
|
"""Test edit submission as moderator"""
|
|
view = TestView()
|
|
request = self.factory.post(f"/test/{self.operator.pk}/")
|
|
request.user = self.moderator
|
|
view.setup(request, pk=self.operator.pk)
|
|
view.kwargs = {"pk": self.operator.pk}
|
|
changes = {"name": "New Name"}
|
|
response = view.handle_edit_submission(
|
|
request, changes, "Test reason", "Test source"
|
|
)
|
|
self.assertIsInstance(response, JsonResponse)
|
|
self.assertEqual(response.status_code, 200)
|
|
data = json.loads(response.content.decode())
|
|
self.assertTrue(data["auto_approved"])
|
|
|
|
def test_photo_submission_mixin_unauthenticated(self):
|
|
"""Test photo submission when not logged in"""
|
|
view = TestView()
|
|
view.kwargs = {"pk": self.operator.pk}
|
|
view.object = self.operator
|
|
|
|
request = self.factory.post(
|
|
f"/test/{self.operator.pk}/", data={}, format="multipart"
|
|
)
|
|
request.user = AnonymousUser()
|
|
view.setup(request, pk=self.operator.pk)
|
|
response = view.handle_photo_submission(request)
|
|
self.assertIsInstance(response, JsonResponse)
|
|
self.assertEqual(response.status_code, 403)
|
|
|
|
def test_photo_submission_mixin_no_photo(self):
|
|
"""Test photo submission with no photo"""
|
|
view = TestView()
|
|
view.kwargs = {"pk": self.operator.pk}
|
|
view.object = self.operator
|
|
|
|
request = self.factory.post(
|
|
f"/test/{self.operator.pk}/", data={}, format="multipart"
|
|
)
|
|
request.user = self.user
|
|
view.setup(request, pk=self.operator.pk)
|
|
response = view.handle_photo_submission(request)
|
|
self.assertIsInstance(response, JsonResponse)
|
|
self.assertEqual(response.status_code, 400)
|
|
|
|
def test_photo_submission_mixin_regular_user(self):
|
|
"""Test photo submission as regular user"""
|
|
view = TestView()
|
|
view.kwargs = {"pk": self.operator.pk}
|
|
view.object = self.operator
|
|
|
|
# Create a test photo file
|
|
photo = SimpleUploadedFile(
|
|
"test.gif",
|
|
b"GIF87a\x01\x00\x01\x00\x80\x01\x00\x00\x00\x00ccc,\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02D\x01\x00;",
|
|
content_type="image/gif",
|
|
)
|
|
|
|
request = self.factory.post(
|
|
f"/test/{self.operator.pk}/",
|
|
data={
|
|
"photo": photo,
|
|
"caption": "Test Photo",
|
|
"date_taken": "2024-01-01",
|
|
},
|
|
format="multipart",
|
|
)
|
|
request.user = self.user
|
|
view.setup(request, pk=self.operator.pk)
|
|
|
|
response = view.handle_photo_submission(request)
|
|
self.assertIsInstance(response, JsonResponse)
|
|
self.assertEqual(response.status_code, 200)
|
|
data = json.loads(response.content.decode())
|
|
self.assertFalse(data["auto_approved"])
|
|
|
|
def test_photo_submission_mixin_moderator(self):
|
|
"""Test photo submission as moderator"""
|
|
view = TestView()
|
|
view.kwargs = {"pk": self.operator.pk}
|
|
view.object = self.operator
|
|
|
|
# Create a test photo file
|
|
photo = SimpleUploadedFile(
|
|
"test.gif",
|
|
b"GIF87a\x01\x00\x01\x00\x80\x01\x00\x00\x00\x00ccc,\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02D\x01\x00;",
|
|
content_type="image/gif",
|
|
)
|
|
|
|
request = self.factory.post(
|
|
f"/test/{self.operator.pk}/",
|
|
data={
|
|
"photo": photo,
|
|
"caption": "Test Photo",
|
|
"date_taken": "2024-01-01",
|
|
},
|
|
format="multipart",
|
|
)
|
|
request.user = self.moderator
|
|
view.setup(request, pk=self.operator.pk)
|
|
|
|
response = view.handle_photo_submission(request)
|
|
self.assertIsInstance(response, JsonResponse)
|
|
self.assertEqual(response.status_code, 200)
|
|
data = json.loads(response.content.decode())
|
|
self.assertTrue(data["auto_approved"])
|
|
|
|
def test_moderator_required_mixin(self):
|
|
"""Test moderator required mixin"""
|
|
|
|
class TestModeratorView(ModeratorRequiredMixin):
|
|
pass
|
|
|
|
view = TestModeratorView()
|
|
|
|
# Test unauthenticated user
|
|
request = self.factory.get("/test/")
|
|
request.user = AnonymousUser()
|
|
view.request = request
|
|
self.assertFalse(view.test_func())
|
|
|
|
# Test regular user
|
|
request.user = self.user
|
|
view.request = request
|
|
self.assertFalse(view.test_func())
|
|
|
|
# Test moderator
|
|
request.user = self.moderator
|
|
view.request = request
|
|
self.assertTrue(view.test_func())
|
|
|
|
# Test admin
|
|
request.user = self.admin
|
|
view.request = request
|
|
self.assertTrue(view.test_func())
|
|
|
|
def test_admin_required_mixin(self):
|
|
"""Test admin required mixin"""
|
|
|
|
class TestAdminView(AdminRequiredMixin):
|
|
pass
|
|
|
|
view = TestAdminView()
|
|
|
|
# Test unauthenticated user
|
|
request = self.factory.get("/test/")
|
|
request.user = AnonymousUser()
|
|
view.request = request
|
|
self.assertFalse(view.test_func())
|
|
|
|
# Test regular user
|
|
request.user = self.user
|
|
view.request = request
|
|
self.assertFalse(view.test_func())
|
|
|
|
# Test moderator
|
|
request.user = self.moderator
|
|
view.request = request
|
|
self.assertFalse(view.test_func())
|
|
|
|
# Test admin
|
|
request.user = self.admin
|
|
view.request = request
|
|
self.assertTrue(view.test_func())
|
|
|
|
def test_inline_edit_mixin(self):
|
|
"""Test inline edit mixin"""
|
|
view = TestView()
|
|
view.kwargs = {"pk": self.operator.pk}
|
|
view.object = self.operator
|
|
|
|
# Test unauthenticated user
|
|
request = self.factory.get(f"/test/{self.operator.pk}/")
|
|
request.user = AnonymousUser()
|
|
view.setup(request, pk=self.operator.pk)
|
|
context = view.get_context_data()
|
|
self.assertNotIn("can_edit", context)
|
|
|
|
# Test regular user
|
|
request.user = self.user
|
|
view.setup(request, pk=self.operator.pk)
|
|
context = view.get_context_data()
|
|
self.assertTrue(context["can_edit"])
|
|
self.assertFalse(context["can_auto_approve"])
|
|
|
|
# Test moderator
|
|
request.user = self.moderator
|
|
view.setup(request, pk=self.operator.pk)
|
|
context = view.get_context_data()
|
|
self.assertTrue(context["can_edit"])
|
|
self.assertTrue(context["can_auto_approve"])
|
|
|
|
def test_history_mixin(self):
|
|
"""Test history mixin"""
|
|
view = TestView()
|
|
view.kwargs = {"pk": self.operator.pk}
|
|
view.object = self.operator
|
|
request = self.factory.get(f"/test/{self.operator.pk}/")
|
|
request.user = self.user
|
|
view.setup(request, pk=self.operator.pk)
|
|
|
|
# Create some edit submissions
|
|
EditSubmission.objects.create(
|
|
user=self.user,
|
|
content_type=ContentType.objects.get_for_model(Operator),
|
|
object_id=getattr(self.operator, "id", None),
|
|
submission_type="EDIT",
|
|
changes={"name": "New Name"},
|
|
status="APPROVED",
|
|
)
|
|
|
|
context = view.get_context_data()
|
|
self.assertIn("history", context)
|
|
self.assertIn("edit_submissions", context)
|
|
self.assertEqual(len(context["edit_submissions"]), 1)
|
|
|
|
|
|
# ============================================================================
|
|
# FSM Transition Logging Tests
|
|
# ============================================================================
|
|
|
|
|
|
class TransitionLoggingTestCase(TestCase):
|
|
"""Test cases for FSM transition logging with django-fsm-log."""
|
|
|
|
def setUp(self):
|
|
"""Set up test fixtures."""
|
|
self.user = User.objects.create_user(
|
|
username='testuser',
|
|
email='test@example.com',
|
|
password='testpass123',
|
|
role='USER'
|
|
)
|
|
self.moderator = User.objects.create_user(
|
|
username='moderator',
|
|
email='moderator@example.com',
|
|
password='testpass123',
|
|
role='MODERATOR'
|
|
)
|
|
self.operator = Operator.objects.create(
|
|
name='Test Operator',
|
|
description='Test Description'
|
|
)
|
|
self.content_type = ContentType.objects.get_for_model(Operator)
|
|
|
|
def test_transition_creates_log(self):
|
|
"""Test that transitions create StateLog entries."""
|
|
from django_fsm_log.models import StateLog
|
|
|
|
# Create a submission
|
|
submission = EditSubmission.objects.create(
|
|
user=self.user,
|
|
content_type=self.content_type,
|
|
object_id=self.operator.id,
|
|
submission_type='EDIT',
|
|
changes={'name': 'Updated Name'},
|
|
status='PENDING'
|
|
)
|
|
|
|
# Perform transition
|
|
submission.transition_to_approved(user=self.moderator)
|
|
submission.save()
|
|
|
|
# Check log was created
|
|
submission_ct = ContentType.objects.get_for_model(submission)
|
|
log = StateLog.objects.filter(
|
|
content_type=submission_ct,
|
|
object_id=submission.id
|
|
).first()
|
|
|
|
self.assertIsNotNone(log, "StateLog entry should be created")
|
|
self.assertEqual(log.state, 'APPROVED')
|
|
self.assertEqual(log.by, self.moderator)
|
|
self.assertIn('approved', log.transition.lower())
|
|
|
|
def test_multiple_transitions_logged(self):
|
|
"""Test that multiple transitions are all logged."""
|
|
from django_fsm_log.models import StateLog
|
|
|
|
submission = EditSubmission.objects.create(
|
|
user=self.user,
|
|
content_type=self.content_type,
|
|
object_id=self.operator.id,
|
|
submission_type='EDIT',
|
|
changes={'name': 'Updated Name'},
|
|
status='PENDING'
|
|
)
|
|
|
|
submission_ct = ContentType.objects.get_for_model(submission)
|
|
|
|
# First transition
|
|
submission.transition_to_escalated(user=self.moderator)
|
|
submission.save()
|
|
|
|
# Second transition
|
|
submission.transition_to_approved(user=self.moderator)
|
|
submission.save()
|
|
|
|
# Check multiple logs created
|
|
logs = StateLog.objects.filter(
|
|
content_type=submission_ct,
|
|
object_id=submission.id
|
|
).order_by('timestamp')
|
|
|
|
self.assertEqual(logs.count(), 2, "Should have 2 log entries")
|
|
self.assertEqual(logs[0].state, 'ESCALATED')
|
|
self.assertEqual(logs[1].state, 'APPROVED')
|
|
|
|
def test_history_endpoint_returns_logs(self):
|
|
"""Test history API endpoint returns transition logs."""
|
|
from rest_framework.test import APIClient
|
|
from django_fsm_log.models import StateLog
|
|
|
|
api_client = APIClient()
|
|
api_client.force_authenticate(user=self.moderator)
|
|
|
|
submission = EditSubmission.objects.create(
|
|
user=self.user,
|
|
content_type=self.content_type,
|
|
object_id=self.operator.id,
|
|
submission_type='EDIT',
|
|
changes={'name': 'Updated Name'},
|
|
status='PENDING'
|
|
)
|
|
|
|
# Perform transition to create log
|
|
submission.transition_to_approved(user=self.moderator)
|
|
submission.save()
|
|
|
|
# Note: This assumes EditSubmission has a history endpoint
|
|
# Adjust URL pattern based on actual implementation
|
|
response = api_client.get(f'/api/moderation/reports/all_history/')
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
# Response should contain history data
|
|
# Actual assertions depend on response format
|
|
|
|
def test_system_transitions_without_user(self):
|
|
"""Test that system transitions work without a user."""
|
|
from django_fsm_log.models import StateLog
|
|
|
|
submission = EditSubmission.objects.create(
|
|
user=self.user,
|
|
content_type=self.content_type,
|
|
object_id=self.operator.id,
|
|
submission_type='EDIT',
|
|
changes={'name': 'Updated Name'},
|
|
status='PENDING'
|
|
)
|
|
|
|
# Perform transition without user
|
|
submission.transition_to_rejected(user=None)
|
|
submission.save()
|
|
|
|
# Check log was created even without user
|
|
submission_ct = ContentType.objects.get_for_model(submission)
|
|
log = StateLog.objects.filter(
|
|
content_type=submission_ct,
|
|
object_id=submission.id
|
|
).first()
|
|
|
|
self.assertIsNotNone(log)
|
|
self.assertEqual(log.state, 'REJECTED')
|
|
self.assertIsNone(log.by, "System transitions should have no user")
|
|
|
|
def test_transition_log_includes_description(self):
|
|
"""Test that transition logs can include descriptions."""
|
|
from django_fsm_log.models import StateLog
|
|
|
|
submission = EditSubmission.objects.create(
|
|
user=self.user,
|
|
content_type=self.content_type,
|
|
object_id=self.operator.id,
|
|
submission_type='EDIT',
|
|
changes={'name': 'Updated Name'},
|
|
status='PENDING'
|
|
)
|
|
|
|
# Perform transition
|
|
submission.transition_to_approved(user=self.moderator)
|
|
submission.save()
|
|
|
|
# Check log
|
|
submission_ct = ContentType.objects.get_for_model(submission)
|
|
log = StateLog.objects.filter(
|
|
content_type=submission_ct,
|
|
object_id=submission.id
|
|
).first()
|
|
|
|
self.assertIsNotNone(log)
|
|
# Description field exists and can be used for audit trails
|
|
self.assertTrue(hasattr(log, 'description'))
|
|
|