diff --git a/apps/account/tests/notifications/test_communication_notifications.py b/apps/account/tests/notifications/test_communication_notifications.py index 8e65b57..30fc6d1 100644 --- a/apps/account/tests/notifications/test_communication_notifications.py +++ b/apps/account/tests/notifications/test_communication_notifications.py @@ -182,3 +182,59 @@ class CommunicationNotificationsFlowTests(TestCase): # Student should not receive a notification for their own message self.assertFalse(Notification.objects.filter(user=self.student).exists()) self.assertFalse(mock_send.called) + + @patch('apps.account.notification_service.send_notification') + def test_webhook_unauthorized(self, mock_send): + from django.urls import reverse + url = reverse('chat-notify-message') + response = self.client.post(url, {'message_id': 1}, HTTP_X_INTERNAL_SECRET='wrong-token') + self.assertEqual(response.status_code, 403) + + @patch('apps.account.notification_service.send_notification') + def test_webhook_authorized_success(self, mock_send): + from django.urls import reverse + from django.conf import settings + from django.db.models.signals import post_save + from apps.chat.signals import notify_on_new_chat_message + + # Create a private chat room + room = RoomMessage.objects.create( + name="Private chat with teacher", + initiator=self.student, + recipient=self.teacher, + room_type=RoomMessage.RoomTypeChoices.PRIVATE + ) + + # Temporarily disconnect the signal to avoid trigger on save, so we only test webhook execution + post_save.disconnect(notify_on_new_chat_message, sender=ChatMessage) + + try: + # Teacher sends a message + msg = ChatMessage.objects.create( + room=room, + sender=self.teacher, + content="How are you?", + content_type=ChatMessage.ChatTypeChoices.TEXT + ) + + # Clear notifications created (if any) + Notification.objects.all().delete() + mock_send.reset_mock() + + url = reverse('chat-notify-message') + secret = getattr(settings, 'INTERNAL_WEBHOOK_SECRET', 'super-secret-key-12345') + response = self.client.post( + url, + {'message_id': msg.id}, + HTTP_X_INTERNAL_SECRET=secret + ) + self.assertEqual(response.status_code, 200) + + # Verify notification was sent + notif = Notification.objects.filter(user=self.student).first() + self.assertIsNotNone(notif) + self.assertIn("How are you?", notif.message) + self.assertTrue(mock_send.called) + finally: + # Reconnect signal + post_save.connect(notify_on_new_chat_message, sender=ChatMessage) diff --git a/apps/chat/urls.py b/apps/chat/urls.py new file mode 100644 index 0000000..f2d68fb --- /dev/null +++ b/apps/chat/urls.py @@ -0,0 +1,6 @@ +from django.urls import path +from .views import ChatMessageNotifyWebhookView + +urlpatterns = [ + path('notify-message/', ChatMessageNotifyWebhookView.as_view(), name='chat-notify-message'), +] diff --git a/apps/chat/views.py b/apps/chat/views.py index 91ea44a..36318f5 100644 --- a/apps/chat/views.py +++ b/apps/chat/views.py @@ -1,3 +1,26 @@ -from django.shortcuts import render +from rest_framework.views import APIView +from rest_framework.response import Response +from rest_framework.permissions import AllowAny +from django.conf import settings +from django.shortcuts import get_object_or_404 +from apps.chat.models import ChatMessage +from apps.chat.signals import notify_on_new_chat_message -# Create your views here. +class ChatMessageNotifyWebhookView(APIView): + permission_classes = [AllowAny] + + def post(self, request, *args, **kwargs): + token = request.headers.get('X-Internal-Secret') or request.query_params.get('secret') + if token != getattr(settings, 'INTERNAL_WEBHOOK_SECRET', None): + return Response({'error': 'Unauthorized'}, status=403) + + message_id = request.data.get('message_id') + if not message_id: + return Response({'error': 'message_id is required'}, status=400) + + message = get_object_or_404(ChatMessage, id=message_id) + + # Trigger the notification logic + notify_on_new_chat_message(sender=ChatMessage, instance=message, created=True) + + return Response({'status': 'success'}) diff --git a/config/urls.py b/config/urls.py index 0aa1c48..0cdf656 100644 --- a/config/urls.py +++ b/config/urls.py @@ -85,6 +85,7 @@ api_patterns = [ path('bookmarks/', include('apps.bookmark.urls')), path('calendar/', include('apps.dobodbi_calendar.urls')), path('blog/', include('apps.blog.urls')), + path('chat/', include('apps.chat.urls')), path('admin/dashboard-stats/' , admin_dashboard.AdminDashboardStatsView.as_view()), path('professor/dashboard-stats/' , ProfessorDashboardStatsView.as_view()),