| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493 |
- """
- 消息通知模块单元测试
- 覆盖短信、邮件、应用内通知发送和调度功能
- """
- 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": "<html><body><h1>水费账单</h1><p>您的6月份水费账单已生成,金额:100.00元。</p></body></html>",
- "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()
|