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)