""" 消息通知模块单元测试 覆盖短信、邮件、应用内通知发送和调度功能 """ import unittest from unittest.mock import Mock, patch, MagicMock from datetime import datetime, timedelta import json from src.notification.models import Notification, NotificationTemplate, NotificationSchedule from src.notification.services import ( SMSService, EmailService, InAppService, NotificationScheduler, NotificationManager ) class TestSMSService(unittest.TestCase): """短信服务测试""" def setUp(self): self.sms_service = SMSService() @patch('src.notification.services.requests') def test_send_sms(self, mock_requests): """测试发送短信""" phone_number = "+8613800138000" message = "您的水费账单已生成,金额:100.00元,请及时缴纳。" mock_response = Mock() mock_response.status_code = 200 mock_response.json.return_value = { "success": True, "message_id": "sms_001", "cost": 0.1 } mock_requests.post.return_value = mock_response result = self.sms_service.send_sms(phone_number, message) self.assertTrue(result["success"]) self.assertEqual(result["message_id"], "sms_001") self.assertEqual(result["cost"], 0.1) mock_requests.post.assert_called_once() def test_phone_number_validation(self): """测试手机号验证""" # 有效的手机号 valid_phone = "+8613800138000" is_valid = self.sms_service.validate_phone_number(valid_phone) self.assertTrue(is_valid) # 无效的手机号 invalid_phone = "12345" is_valid = self.sms_service.validate_phone_number(invalid_phone) self.assertFalse(is_valid) # 手机号格式不对 wrong_format = "13800138000" # 缺少国家代码 is_valid = self.sms_service.validate_phone_number(wrong_format) self.assertFalse(is_valid) @patch('src.notification.services.requests') def test_sms_batch_send(self, mock_requests): """测试批量短信发送""" recipients = [ {"phone": "+8613800138000", "name": "张三"}, {"phone": "+8613900139000", "name": "李四"}, {"phone": "+8614000140000", "name": "王五"} ] message = "尊敬的{name},您的水费账单已生成,请及时查看。" mock_response = Mock() mock_response.status_code = 200 mock_response.json.return_value = { "success": True, "total_sent": 3, "failed": [], "cost": 0.3 } mock_requests.post.return_value = mock_response result = self.sms_service.batch_send_sms(recipients, message) self.assertTrue(result["success"]) self.assertEqual(result["total_sent"], 3) self.assertEqual(result["cost"], 0.3) self.assertEqual(len(result["failed"]), 0) @patch('src.notification.services.requests') def test_sms_scheduling(self, mock_requests): """测试短信定时发送""" phone_number = "+8613800138000" message = "定时发送的短信" scheduled_time = datetime.now() + timedelta(hours=1) mock_response = Mock() mock_response.status_code = 200 mock_response.json.return_value = { "success": True, "schedule_id": "schedule_001", "scheduled_time": scheduled_time.isoformat() } mock_requests.post.return_value = mock_response result = self.sms_service.schedule_sms(phone_number, message, scheduled_time) self.assertTrue(result["success"]) self.assertEqual(result["schedule_id"], "schedule_001") self.assertIsNotNone(result["scheduled_time"]) class TestEmailService(unittest.TestCase): """邮件服务测试""" def setUp(self): self.email_service = EmailService() @patch('src.notification.services.smtplib') def test_send_email(self, mock_smtp): """测试发送邮件""" email_data = { "to": "customer@example.com", "subject": "水费账单通知", "body": "尊敬的客户,您的6月份水费账单已生成,金额:100.00元。", "is_html": False } mock_server = Mock() mock_smtp.SMTP.return_value = mock_server mock_server.sendmail.return_value = {} result = self.email_service.send_email(email_data) self.assertTrue(result["success"]) self.assertEqual(result["message_id"], "email_001") mock_server.sendmail.assert_called_once() @patch('src.notification.services.smtplib') def test_send_html_email(self, mock_smtp): """测试发送HTML邮件""" email_data = { "to": "customer@example.com", "subject": "水费账单通知", "body": "

水费账单

您的6月份水费账单已生成,金额:100.00元。

", "is_html": True } mock_server = Mock() mock_smtp.SMTP.return_value = mock_server mock_server.sendmail.return_value = {} result = self.email_service.send_email(email_data) self.assertTrue(result["success"]) self.assertEqual(result["is_html"], True) def test_email_validation(self): """测试邮箱验证""" # 有效的邮箱 valid_email = "customer@example.com" is_valid = self.email_service.validate_email(valid_email) self.assertTrue(is_valid) # 无效的邮箱 invalid_email = "invalid-email" is_valid = self.email_service.validate_email(invalid_email) self.assertFalse(is_valid) @patch('src.notification.services.smtplib') def test_email_with_attachment(self, mock_smtp): """测试带附件的邮件""" email_data = { "to": "customer@example.com", "subject": "水费账单详情", "body": "请查收附件中的账单详情。", "is_html": False, "attachments": [ {"filename": "bill.pdf", "content": b"PDF content"}, {"filename": "invoice.xlsx", "content": b"Excel content"} ] } mock_server = Mock() mock_smtp.SMTP.return_value = mock_server mock_server.send_message.return_value = {} result = self.email_service.send_email(email_data) self.assertTrue(result["success"]) self.assertEqual(len(result["attachments"]), 2) mock_server.send_message.assert_called_once() class TestInAppService(unittest.TestCase): """应用内通知服务测试""" def setUp(self): self.inapp_service = InAppService() @patch('src.notification.services.requests') def test_send_inapp_notification(self, mock_requests): """测试发送应用内通知""" notification_data = { "user_id": "user_001", "title": "系统通知", "message": "您的水费账单已生成", "type": "BILL_GENERATED", "priority": "normal" } mock_response = Mock() mock_response.status_code = 200 mock_response.json.return_value = { "success": True, "notification_id": "notif_001", "delivered_at": datetime.now().isoformat() } mock_requests.post.return_value = mock_response result = self.inapp_service.send_notification(notification_data) self.assertTrue(result["success"]) self.assertEqual(result["notification_id"], "notif_001") self.assertIsNotNone(result["delivered_at"]) def test_notification_template_rendering(self): """测试通知模板渲染""" template = { "title": "账单通知 - {month}月", "message": "尊敬的{customer_name},您{month}月份的水费账单已生成,金额:{amount}元。", "data": { "month": "6", "customer_name": "张三", "amount": "100.00" } } rendered = self.inapp_service.render_template(template) self.assertEqual(rendered["title"], "账单通知 - 6月") self.assertIn("张三", rendered["message"]) self.assertIn("100.00", rendered["message"]) @patch('src.notification.services.requests') def test_notification_batch_send(self, mock_requests): """测试批量应用内通知""" notifications = [ { "user_id": "user_001", "title": "系统更新", "message": "系统将于今晚进行维护" }, { "user_id": "user_002", "title": "新功能上线", "message": "移动端新增缴费功能" } ] mock_response = Mock() mock_response.status_code = 200 mock_response.json.return_value = { "success": True, "total_delivered": 2, "failed": [] } mock_requests.post.return_value = mock_response result = self.inapp_service.batch_send_notifications(notifications) self.assertTrue(result["success"]) self.assertEqual(result["total_delivered"], 2) self.assertEqual(len(result["failed"]), 0) def test_notification_priority_handling(self): """测试通知优先级处理""" high_priority_notification = { "user_id": "user_001", "title": "紧急通知", "message": "您的账户余额不足", "priority": "high" } low_priority_notification = { "user_id": "user_002", "title": "常规通知", "message": "系统正常维护通知", "priority": "low" } # 高优先级通知应该立即发送 with patch.object(self.inapp_service, 'send_immediately') as mock_send: mock_send.return_value = {"success": True} result = self.inapp_service.handle_notification(high_priority_notification) mock_send.assert_called_once() # 低优先级通知可以批量处理 with patch.object(self.inapp_service, 'batch_process') as mock_batch: mock_batch.return_value = {"success": True} result = self.inapp_service.handle_notification(low_priority_notification) mock_batch.assert_called_once() class TestNotificationScheduler(unittest.TestCase): """通知调度器测试""" def setUp(self): self.scheduler = NotificationScheduler() @patch('src.notification.services.datetime') def test_schedule_notification(self, mock_datetime): """测试通知调度""" notification_data = { "user_id": "user_001", "title": "账单提醒", "message": "您的水费账单即将到期", "scheduled_time": "2026-06-20T09:00:00Z" } mock_now = Mock() mock_now.return_value = datetime.now() mock_datetime.datetime.now.return_value = mock_now.return_value() schedule_id = self.scheduler.schedule_notification(notification_data) self.assertIsNotNone(schedule_id) self.assertIsInstance(schedule_id, str) def test_get_scheduled_notifications(self): """测试获取已调度通知""" # 模拟一些已调度的通知 self.scheduler.schedules = { "schedule_001": { "notification_id": "notif_001", "scheduled_time": "2026-06-17T09:00:00Z", "status": "pending" }, "schedule_002": { "notification_id": "notif_002", "scheduled_time": "2026-06-18T10:00:00Z", "status": "pending" } } pending_schedules = self.scheduler.get_pending_schedules() self.assertEqual(len(pending_schedules), 2) # 检查是否按时排序 self.assertLess( datetime.fromisoformat(pending_schedules[0]["scheduled_time"]), datetime.fromisoformat(pending_schedules[1]["scheduled_time"]) ) def test_reschedule_notification(self): """测试重新调度通知""" schedule_id = "schedule_001" new_time = "2026-06-25T09:00:00Z" # 先调度一个通知 self.scheduler.schedules = { schedule_id: { "notification_id": "notif_001", "scheduled_time": "2026-06-20T09:00:00Z", "status": "pending" } } result = self.scheduler.reschedule_notification(schedule_id, new_time) self.assertTrue(result["success"]) self.assertEqual(self.scheduler.schedules[schedule_id]["scheduled_time"], new_time) def test_cancel_scheduled_notification(self): """测试取消调度通知""" schedule_id = "schedule_001" # 先调度一个通知 self.scheduler.schedules = { schedule_id: { "notification_id": "notif_001", "scheduled_time": "2026-06-20T09:00:00Z", "status": "pending" } } result = self.scheduler.cancel_notification(schedule_id) self.assertTrue(result["success"]) self.assertEqual(self.scheduler.schedules[schedule_id]["status"], "cancelled") class TestNotificationManager(unittest.TestCase): """通知管理器测试""" def setUp(self): self.manager = NotificationManager() @patch.object(NotificationManager, 'send_sms') @patch.object(NotificationManager, 'send_email') @patch.object(NotificationManager, 'send_inapp') def test_multi_channel_notification(self, mock_inapp, mock_email, mock_sms): """测试多渠道通知""" notification_data = { "channels": ["sms", "email", "inapp"], "user_id": "user_001", "message": "您的账单已生成", "phone": "+8613800138000", "email": "customer@example.com" } mock_sms.return_value = {"success": True} mock_email.return_value = {"success": True} mock_inapp.return_value = {"success": True} result = self.manager.send_multi_channel_notification(notification_data) self.assertTrue(result["success"]) self.assertEqual(result["delivered_channels"], 3) mock_sms.assert_called_once() mock_email.assert_called_once() mock_inapp.assert_called_once() def test_notification_routing(self): """测试通知路由""" customer_data = { "user_id": "user_001", "name": "张三", "phone": "+8613800138000", "email": "customer@example.com", "preferences": { "notification_channels": ["sms", "email"], "notification_types": ["BILL", "REMINDER"] } } notification_type = "BILL" routed_channels = self.manager.route_notification(customer_data, notification_type) # 应该返回客户偏好的渠道 self.assertIn("sms", routed_channels) self.assertIn("email", routed_channels) self.assertNotIn("inapp", routed_channels) # 客户没有偏好 def test_notification_tracking(self): """测试通知跟踪""" notification_id = "notif_001" # 模拟发送通知 result = self.manager.track_notification_delivery(notification_id, "sms", "delivered") self.assertTrue(result["success"]) self.assertEqual(result["channel"], "sms") self.assertEqual(result["status"], "delivered") # 检查跟踪结果 tracking_info = self.manager.get_notification_tracking(notification_id) self.assertEqual(tracking_info["total_attempts"], 1) self.assertEqual(tracking_info["delivered_channels"], ["sms"]) def test_notification_analytics(self): """测试通知分析""" # 模拟一些通知数据 notification_data = [ { "id": "notif_001", "channel": "sms", "status": "delivered", "timestamp": "2026-06-16T10:00:00Z" }, { "id": "notif_002", "channel": "email", "status": "failed", "timestamp": "2026-06-16T10:01:00Z" }, { "id": "notif_003", "channel": "sms", "status": "delivered", "timestamp": "2026-06-16T10:02:00Z" } ] analytics = self.manager.analyze_notifications(notification_data) self.assertEqual(analytics["total_sent"], 3) self.assertEqual(analytics["total_delivered"], 2) self.assertEqual(analytics["total_failed"], 1) self.assertEqual(analytics["delivery_rate"], 2/3) if __name__ == '__main__': unittest.main()