from unittest.mock import patch from django.test import TestCase from accounts.services import SMSDeliveryError from .models import Notification from .services import notify class NotifyInAppChannelTests(TestCase): """ channel='in_app' (پیش‌فرض) باید دقیقاً همون رفتارِ قبلیِ notify() رو داشته باشه: صرفاً یک Notification.objects.create ساده، بدونِ فراخوانیِ send_sms. """ @patch('notifications.services.send_sms') def test_default_channel_creates_notification_and_does_not_send_sms(self, mock_send_sms): result = notify(user_id=1, type='order_paid', title='t', body='b', related_id=5) self.assertIsInstance(result, Notification) self.assertEqual(Notification.objects.count(), 1) notification = Notification.objects.get() self.assertEqual(notification.user_id, 1) self.assertEqual(notification.type, 'order_paid') self.assertEqual(notification.title, 't') self.assertEqual(notification.body, 'b') self.assertEqual(notification.related_id, 5) mock_send_sms.assert_not_called() @patch('notifications.services.send_sms') def test_explicit_in_app_channel_same_as_default(self, mock_send_sms): result = notify(user_id=2, type='xp_earned', title='t', channel='in_app') self.assertIsInstance(result, Notification) self.assertEqual(Notification.objects.count(), 1) mock_send_sms.assert_not_called() class NotifySmsChannelTests(TestCase): """ channel='sms' نباید هیچ Notificationی بسازه؛ فقط باید accounts.services.send_sms رو صدا بزنه (بدونِ تکرارِ منطقِ provider). """ @patch('notifications.services.send_sms') def test_sms_channel_calls_send_sms_with_body_and_creates_no_notification(self, mock_send_sms): result = notify( user_id=3, type='order_paid', title='t', body='پیام پیامکی', channel='sms', phone='0910000000', ) mock_send_sms.assert_called_once_with(phone='0910000000', message='پیام پیامکی') self.assertIsNone(result) self.assertEqual(Notification.objects.count(), 0) @patch('notifications.services.send_sms') def test_sms_channel_falls_back_to_title_when_body_empty(self, mock_send_sms): notify(user_id=3, type='order_paid', title='fallback-title', channel='sms', phone='0910000000') mock_send_sms.assert_called_once_with(phone='0910000000', message='fallback-title') def test_sms_channel_without_phone_raises_value_error(self): with self.assertRaises(ValueError): notify(user_id=3, type='order_paid', title='t', channel='sms') self.assertEqual(Notification.objects.count(), 0) @patch('notifications.services.send_sms') def test_sms_delivery_error_propagates_and_creates_no_notification(self, mock_send_sms): mock_send_sms.side_effect = SMSDeliveryError('boom') with self.assertRaises(SMSDeliveryError): notify(user_id=3, type='order_paid', title='t', channel='sms', phone='0910000000') self.assertEqual(Notification.objects.count(), 0) class NotifyInvalidChannelTests(TestCase): def test_unsupported_channel_raises_value_error_and_creates_nothing(self): with self.assertRaises(ValueError): notify(user_id=3, type='order_paid', title='t', channel='email') self.assertEqual(Notification.objects.count(), 0) from django.core.cache import cache from rest_framework.test import APIClient from accounts.authentication import issue_access_token from accounts.models import User class NotificationListCapTests(TestCase): def setUp(self): cache.clear() self.client = APIClient() self.user = User.objects.create(device_id='dev-notif-cap', is_guest=True, name='n') self.token = issue_access_token(self.user.id) def test_returns_at_most_100_newest_with_correct_unread_count(self): Notification.objects.bulk_create([ Notification(user_id=self.user.id, type='xp_earned', title=f't{i}', is_read=(i % 2 == 0)) for i in range(150) ]) r = self.client.get('/api/notifications', HTTP_AUTHORIZATION=f'Bearer {self.token}') self.assertEqual(r.status_code, 200) self.assertEqual(len(r.data['items']), 100) self.assertEqual(r.data['unreadCount'], 75) # از کلِ ۱۵۰، نه از ۱۰۰ برگشتی # جدیدترین اول (Meta.ordering = -created_at؛ اینجا هم‌زمان → -id غالب) ids = [it['id'] if isinstance(it, dict) else it.id for it in r.data['items']] self.assertEqual(ids, sorted(ids, reverse=True))