"""工具层测试:notifier 推送逻辑(mock外部HTTP)""" import json from unittest.mock import patch, MagicMock import pytest from app.utils.notifier import ( send_wecom_robot, send_wecom_app, send_mail, push_alert, _build_content, push_pending_alerts, ) class TestWecomRobot: """企业微信群机器人推送测试(mock HTTP)""" @patch("urllib.request.urlopen") def test_send_success(self, mock_urlopen): """推送成功""" mock_resp = MagicMock() mock_resp.read.return_value = json.dumps({"errcode": 0}).encode() mock_urlopen.return_value.__enter__.return_value = mock_resp result = send_wecom_robot("https://qyapi.weixin.qq.com/webhook/test", "测试标题", "测试内容", "red") assert result["success"] is True assert "已推送" in result["message"] @patch("urllib.request.urlopen") def test_send_api_error(self, mock_urlopen): """企微API返回错误""" mock_resp = MagicMock() mock_resp.read.return_value = json.dumps({"errcode": 40001, "errmsg": "invalid webhook"}).encode() mock_urlopen.return_value.__enter__.return_value = mock_resp result = send_wecom_robot("https://qyapi.weixin.qq.com/webhook/test", "测试", "内容", "yellow") assert result["success"] is False assert "推送失败" in result["message"] @patch("urllib.request.urlopen") def test_send_network_error(self, mock_urlopen): """网络异常""" mock_urlopen.side_effect = Exception("Connection refused") result = send_wecom_robot("https://qyapi.weixin.qq.com/webhook/test", "测试", "内容", "green") assert result["success"] is False assert "推送异常" in result["message"] def test_build_content(self): """_build_content 内容构建""" alert = { "kpi_name": "销售总额", "period": "2026-06", "actual_value": "800,000", "target_value": "1,000,000", "alert_level": "red", "resolution": "加强销售推广", } content = _build_content(alert) assert "销售总额" in content assert "2026-06" in content assert "800,000" in content assert "加强销售推广" in content assert "紧急" in content class TestPushAlert: """主推送函数测试""" @patch("app.utils.notifier.send_wecom_robot") def test_push_to_wecom_robot(self, mock_send): """推送到企微机器人渠道""" mock_send.return_value = {"success": True, "message": "ok"} channels = [{ "name": "测试群", "channel_type": "wecom", "enabled": True, "config": {"webhook_url": "https://qyapi.weixin.qq.com/webhook/abc"} }] results = push_alert({"alert_level": "yellow", "alert_message": "测试"}, channels) assert len(results) == 1 assert results[0]["channel"] == "wecom" assert results[0]["success"] is True @patch("app.utils.notifier.send_wecom_app") def test_push_to_wecom_app(self, mock_send): """推送到企微应用消息""" mock_send.return_value = {"success": True, "message": "ok"} channels = [{ "name": "应用通知", "channel_type": "wecom_app", "enabled": True, "config": {"corp_id": "xxx", "corp_secret": "yyy", "agent_id": "1000002", "touser": "@all"} }] results = push_alert({"alert_level": "yellow", "alert_message": "测试"}, channels) assert len(results) == 1 assert results[0]["channel"] == "wecom_app" @patch("app.utils.notifier.send_mail") def test_push_to_mail(self, mock_send): """推送到邮件""" mock_send.return_value = {"success": True, "message": "ok"} channels = [{ "name": "通知邮箱", "channel_type": "mail", "enabled": True, "config": {"to": ["admin@example.com"]} }] results = push_alert({"alert_level": "red", "alert_message": "测试"}, channels) assert len(results) == 1 assert results[0]["channel"] == "mail" def test_disabled_channel_skipped(self): """已禁用的渠道跳过""" channels = [{ "name": "禁用渠道", "channel_type": "wecom", "enabled": False, "config": {} }] results = push_alert({"alert_level": "yellow", "alert_message": "测试"}, channels) assert len(results) == 0 def test_empty_channels(self): """无渠道 → 空结果""" results = push_alert({"alert_level": "yellow", "alert_message": "测试"}, []) assert results == [] class TestSendWecomApp: """企微应用消息测试""" @patch("requests.get") @patch("requests.post") def test_app_send_success(self, mock_post, mock_get): """企微应用消息发送成功""" mock_get.return_value.json.return_value = {"errcode": 0, "access_token": "fake_token"} mock_post.return_value.json.return_value = {"errcode": 0} result = send_wecom_app("corp123", "secret456", "1000001", "@all", "标题", "内容", "yellow") assert result["success"] is True @patch("requests.get") def test_app_token_fail(self, mock_get): """获取token失败""" mock_get.return_value.json.return_value = {"errcode": 40013, "errmsg": "invalid corpid"} result = send_wecom_app("bad_corp", "bad_secret", "1000001", "@all", "标题", "内容", "yellow") assert result["success"] is False assert "获取token失败" in result["message"] class TestSendMail: """邮件推送测试(mock SMTP)""" @patch("smtplib.SMTP_SSL") def test_mail_send_success(self, mock_smtp): """邮件发送成功""" mock_server = MagicMock() mock_smtp.return_value = mock_server config = {"host": "smtp.example.com", "port": 465, "user": "user@example.com", "password": "pass", "use_ssl": True, "from_addr": "user@example.com"} result = send_mail(config, ["admin@example.com"], "测试标题", "测试内容") assert result["success"] is True mock_server.login.assert_called_once() mock_server.sendmail.assert_called_once() @patch("smtplib.SMTP_SSL") def test_mail_send_failure(self, mock_smtp): """邮件发送异常""" mock_smtp.side_effect = Exception("SMTP server error") config = {"host": "smtp.example.com", "port": 465, "user": "user@example.com", "password": "pass", "use_ssl": True, "from_addr": "user@example.com"} result = send_mail(config, ["admin@example.com"], "测试标题", "测试内容") assert result["success"] is False assert "邮件发送失败" in result["message"] class TestPushPendingAlerts: """push_pending_alerts 测试""" def test_no_channels(self, db): """没有渠道 → 返回0""" count = push_pending_alerts(db) assert count == 0 def test_no_pending_alerts(self, db): """没有待处理预警 → 返回0""" from app.models import NotificationChannel db.add(NotificationChannel(name="测试群", channel_type="wecom", config={}, enabled=True)) db.commit() count = push_pending_alerts(db) assert count == 0 @patch("app.utils.notifier.push_alert") def test_push_pending_with_data(self, mock_push, db): """有渠道+待处理预警 → 尝试推送""" from app.models import NotificationChannel, KPIAlert, KPIDefinition kpi = KPIDefinition(kpi_code="TEST_PUSH", kpi_name="推送测试", dimension="finance", status="active") db.add(kpi) db.commit() db.add(NotificationChannel(name="测试群", channel_type="wecom", config={"webhook_url": "http://test"}, enabled=True)) db.add(KPIAlert(kpi_id=kpi.id, alert_level="red", alert_message="测试预警", status="pending")) db.commit() mock_push.return_value = [{"channel": "wecom", "channel_name": "测试群", "success": True, "message": "ok"}] count = push_pending_alerts(db) mock_push.assert_called_once() assert count == 1