2026-04-03 23:51:00 +03:30
|
|
|
from unittest.mock import patch
|
|
|
|
|
|
2026-04-05 00:57:25 +03:30
|
|
|
from django.conf import settings
|
2026-04-03 23:51:00 +03:30
|
|
|
from django.contrib.auth import get_user_model
|
2026-04-05 00:57:25 +03:30
|
|
|
from django.test import TestCase, override_settings
|
2026-04-03 23:51:00 +03:30
|
|
|
from rest_framework.test import APIRequestFactory, force_authenticate
|
|
|
|
|
|
2026-04-05 00:57:25 +03:30
|
|
|
from farm_hub.models import FarmHub, FarmType
|
2026-04-03 23:51:00 +03:30
|
|
|
|
2026-04-05 00:57:25 +03:30
|
|
|
from .models import FarmNotification
|
|
|
|
|
from .services import create_notification_for_farm_uuid, long_poll_notifications
|
|
|
|
|
from .views import ExternalNotificationIngestView, NotificationLongPollView
|
2026-04-03 23:51:00 +03:30
|
|
|
|
2026-04-05 00:57:25 +03:30
|
|
|
|
|
|
|
|
class NotificationServiceTests(TestCase):
|
2026-04-03 23:51:00 +03:30
|
|
|
def setUp(self):
|
|
|
|
|
self.user = get_user_model().objects.create_user(
|
2026-04-05 00:57:25 +03:30
|
|
|
username="notif-service-user",
|
2026-04-03 23:51:00 +03:30
|
|
|
password="secret123",
|
2026-04-05 00:57:25 +03:30
|
|
|
email="notif-service@example.com",
|
|
|
|
|
phone_number="09120000011",
|
|
|
|
|
)
|
|
|
|
|
self.farm_type = FarmType.objects.create(name="گلخانه")
|
|
|
|
|
self.farm = FarmHub.objects.create(
|
|
|
|
|
owner=self.user,
|
|
|
|
|
farm_type=self.farm_type,
|
|
|
|
|
name="Farm A",
|
2026-04-03 23:51:00 +03:30
|
|
|
)
|
|
|
|
|
|
2026-04-05 00:57:25 +03:30
|
|
|
def test_create_notification_for_farm_uuid_creates_record(self):
|
|
|
|
|
notification = create_notification_for_farm_uuid(
|
|
|
|
|
farm_uuid=self.farm.farm_uuid,
|
|
|
|
|
title="Irrigation alert",
|
|
|
|
|
message="Soil moisture dropped",
|
|
|
|
|
level="warning",
|
|
|
|
|
metadata={"sensor": "soil-1"},
|
2026-04-03 23:51:00 +03:30
|
|
|
)
|
|
|
|
|
|
2026-04-05 00:57:25 +03:30
|
|
|
self.assertEqual(notification.farm, self.farm)
|
|
|
|
|
self.assertEqual(notification.level, "warning")
|
|
|
|
|
self.assertEqual(notification.metadata["sensor"], "soil-1")
|
2026-04-03 23:51:00 +03:30
|
|
|
|
2026-04-05 00:57:25 +03:30
|
|
|
def test_create_notification_for_farm_uuid_raises_for_unknown_farm(self):
|
|
|
|
|
with self.assertRaisesMessage(ValueError, "Farm not found."):
|
|
|
|
|
create_notification_for_farm_uuid(
|
|
|
|
|
farm_uuid="11111111-1111-1111-1111-111111111111",
|
|
|
|
|
title="x",
|
|
|
|
|
message="y",
|
|
|
|
|
)
|
2026-04-03 23:51:00 +03:30
|
|
|
|
2026-04-05 00:57:25 +03:30
|
|
|
def test_long_poll_notifications_returns_new_notifications(self):
|
|
|
|
|
FarmNotification.objects.create(farm=self.farm, title="A", message="B")
|
2026-04-03 23:51:00 +03:30
|
|
|
|
2026-04-05 00:57:25 +03:30
|
|
|
notifications = long_poll_notifications(farm=self.farm, timeout_seconds=0)
|
2026-04-03 23:51:00 +03:30
|
|
|
|
2026-04-05 00:57:25 +03:30
|
|
|
self.assertEqual(len(notifications), 1)
|
|
|
|
|
self.assertEqual(notifications[0].title, "A")
|
2026-04-03 23:51:00 +03:30
|
|
|
|
|
|
|
|
|
2026-04-05 00:57:25 +03:30
|
|
|
class NotificationLongPollViewTests(TestCase):
|
|
|
|
|
def setUp(self):
|
|
|
|
|
self.factory = APIRequestFactory()
|
|
|
|
|
self.user = get_user_model().objects.create_user(
|
|
|
|
|
username="notif-view-user",
|
|
|
|
|
password="secret123",
|
|
|
|
|
email="notif-view@example.com",
|
|
|
|
|
phone_number="09120000012",
|
|
|
|
|
)
|
|
|
|
|
self.other_user = get_user_model().objects.create_user(
|
|
|
|
|
username="notif-other-user",
|
|
|
|
|
password="secret123",
|
|
|
|
|
email="notif-other@example.com",
|
|
|
|
|
phone_number="09120000013",
|
|
|
|
|
)
|
|
|
|
|
self.farm_type = FarmType.objects.create(name="دامداری")
|
|
|
|
|
self.farm = FarmHub.objects.create(
|
|
|
|
|
owner=self.user,
|
|
|
|
|
farm_type=self.farm_type,
|
|
|
|
|
name="Farm B",
|
|
|
|
|
)
|
2026-04-03 23:51:00 +03:30
|
|
|
|
2026-04-05 00:57:25 +03:30
|
|
|
def test_long_poll_view_returns_notifications_for_owned_farm(self):
|
|
|
|
|
FarmNotification.objects.create(farm=self.farm, title="Alert", message="Check sensor")
|
|
|
|
|
request = self.factory.get(f"/api/notifications/long-poll/?farm_uuid={self.farm.farm_uuid}&timeout=0")
|
|
|
|
|
force_authenticate(request, user=self.user)
|
|
|
|
|
|
|
|
|
|
response = NotificationLongPollView.as_view()(request)
|
|
|
|
|
|
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
|
self.assertEqual(len(response.data["data"]), 1)
|
|
|
|
|
self.assertEqual(response.data["data"][0]["title"], "Alert")
|
2026-04-03 23:51:00 +03:30
|
|
|
|
2026-04-05 00:57:25 +03:30
|
|
|
def test_long_poll_view_returns_404_for_unowned_farm(self):
|
|
|
|
|
request = self.factory.get(f"/api/notifications/long-poll/?farm_uuid={self.farm.farm_uuid}&timeout=0")
|
|
|
|
|
force_authenticate(request, user=self.other_user)
|
2026-04-03 23:51:00 +03:30
|
|
|
|
2026-04-05 00:57:25 +03:30
|
|
|
response = NotificationLongPollView.as_view()(request)
|
2026-04-03 23:51:00 +03:30
|
|
|
|
2026-04-05 00:57:25 +03:30
|
|
|
self.assertEqual(response.status_code, 404)
|
|
|
|
|
self.assertEqual(response.data["msg"], "Farm not found.")
|
|
|
|
|
|
|
|
|
|
@patch("notifications.views.long_poll_notifications")
|
|
|
|
|
def test_long_poll_view_passes_since_id(self, mocked_long_poll):
|
|
|
|
|
mocked_long_poll.return_value = []
|
|
|
|
|
request = self.factory.get(
|
|
|
|
|
f"/api/notifications/long-poll/?farm_uuid={self.farm.farm_uuid}&since_id=5&timeout=0"
|
|
|
|
|
)
|
|
|
|
|
force_authenticate(request, user=self.user)
|
|
|
|
|
|
|
|
|
|
response = NotificationLongPollView.as_view()(request)
|
|
|
|
|
|
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
|
mocked_long_poll.assert_called_once()
|
|
|
|
|
self.assertEqual(mocked_long_poll.call_args.kwargs["since_id"], 5)
|
2026-04-03 23:51:00 +03:30
|
|
|
|
2026-04-05 00:57:25 +03:30
|
|
|
|
|
|
|
|
@override_settings(EXTERNAL_NOTIFICATION_API_KEY="12345")
|
|
|
|
|
class ExternalNotificationIngestViewTests(TestCase):
|
2026-04-03 23:51:00 +03:30
|
|
|
def setUp(self):
|
|
|
|
|
self.factory = APIRequestFactory()
|
|
|
|
|
self.user = get_user_model().objects.create_user(
|
2026-04-05 00:57:25 +03:30
|
|
|
username="notif-external-user",
|
2026-04-03 23:51:00 +03:30
|
|
|
password="secret123",
|
2026-04-05 00:57:25 +03:30
|
|
|
email="notif-external@example.com",
|
|
|
|
|
phone_number="09120000014",
|
|
|
|
|
)
|
|
|
|
|
self.farm_type = FarmType.objects.create(name="آبی")
|
|
|
|
|
self.farm = FarmHub.objects.create(
|
|
|
|
|
owner=self.user,
|
|
|
|
|
farm_type=self.farm_type,
|
|
|
|
|
name="Farm C",
|
2026-04-03 23:51:00 +03:30
|
|
|
)
|
|
|
|
|
|
2026-04-05 00:57:25 +03:30
|
|
|
def test_external_ingest_requires_api_key(self):
|
|
|
|
|
request = self.factory.post(
|
|
|
|
|
"/api/notifications/external/ingest/",
|
|
|
|
|
{
|
|
|
|
|
"farm_uuid": str(self.farm.farm_uuid),
|
|
|
|
|
"title": "external",
|
|
|
|
|
"message": "payload",
|
|
|
|
|
},
|
|
|
|
|
format="json",
|
|
|
|
|
)
|
2026-04-03 23:51:00 +03:30
|
|
|
|
2026-04-05 00:57:25 +03:30
|
|
|
response = ExternalNotificationIngestView.as_view()(request)
|
2026-04-03 23:51:00 +03:30
|
|
|
|
2026-04-05 00:57:25 +03:30
|
|
|
self.assertEqual(response.status_code, 401)
|
|
|
|
|
|
|
|
|
|
def test_external_ingest_creates_notification_with_valid_api_key(self):
|
|
|
|
|
request = self.factory.post(
|
|
|
|
|
"/api/notifications/external/ingest/",
|
|
|
|
|
{
|
|
|
|
|
"farm_uuid": str(self.farm.farm_uuid),
|
|
|
|
|
"title": "Pump alert",
|
|
|
|
|
"message": "Pump disconnected",
|
|
|
|
|
"level": "critical",
|
|
|
|
|
"metadata": {"source": "external-service"},
|
|
|
|
|
},
|
|
|
|
|
format="json",
|
|
|
|
|
HTTP_X_API_KEY=settings.EXTERNAL_NOTIFICATION_API_KEY,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
response = ExternalNotificationIngestView.as_view()(request)
|
|
|
|
|
|
|
|
|
|
self.assertEqual(response.status_code, 201)
|
|
|
|
|
self.assertEqual(response.data["data"]["title"], "Pump alert")
|
|
|
|
|
self.assertTrue(
|
|
|
|
|
FarmNotification.objects.filter(farm=self.farm, title="Pump alert", level="critical").exists()
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def test_external_ingest_returns_404_for_unknown_farm(self):
|
|
|
|
|
request = self.factory.post(
|
|
|
|
|
"/api/notifications/external/ingest/",
|
|
|
|
|
{
|
|
|
|
|
"farm_uuid": "11111111-1111-1111-1111-111111111111",
|
|
|
|
|
"title": "Pump alert",
|
|
|
|
|
"message": "Pump disconnected",
|
|
|
|
|
},
|
|
|
|
|
format="json",
|
|
|
|
|
HTTP_X_API_KEY=settings.EXTERNAL_NOTIFICATION_API_KEY,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
response = ExternalNotificationIngestView.as_view()(request)
|
|
|
|
|
|
|
|
|
|
self.assertEqual(response.status_code, 404)
|
|
|
|
|
self.assertEqual(response.data["msg"], "Farm not found.")
|