import pytest from banban.service.sms_notification import SmsNotificationService class FakeSession: async def execute(self, statement, params): del statement, params return FakeResult() async def close(self): pass class FakeResult: def mappings(self): return self def all(self): return [ {"phone": "13800138000"}, {"phone": "13800138000"}, {"phone": ""}, {"phone": "13900139000"}, ] def first(self): return { "child_name": "孩子", "address": "杭州市", "lat": None, "lng": None, } def configure_enabled_sms(monkeypatch): monkeypatch.setattr("banban.service.sms_notification.settings.sms_enabled", True) monkeypatch.setattr("banban.service.sms_notification.settings.sms_provider", "aliyun") monkeypatch.setattr("banban.service.sms_notification.settings.sms_dry_run", False) monkeypatch.setattr("banban.service.sms_notification.settings.sms_alarm_dedup_seconds", 300) monkeypatch.setattr("banban.service.sms_notification.settings.sms_leave_message_dedup_seconds", 60) monkeypatch.setattr("banban.service.sms_notification.settings.aliyun_sms_access_key_id", "access-key-id") monkeypatch.setattr("banban.service.sms_notification.settings.aliyun_sms_access_key_secret", "access-key-secret") monkeypatch.setattr("banban.service.sms_notification.settings.aliyun_sms_sign_name", "短信签名") monkeypatch.setattr("banban.service.sms_notification.settings.aliyun_sms_template_code", "SMS_507185027") monkeypatch.setattr("banban.service.sms_notification.settings.aliyun_sms_alarm_template_code", "") monkeypatch.setattr("banban.service.sms_notification.settings.aliyun_sms_leave_message_template_code", "") @pytest.mark.asyncio async def test_notify_alarm_sends_to_unique_family_phone_numbers(monkeypatch): configure_enabled_sms(monkeypatch) service = SmsNotificationService() sent = [] async def fake_get_session(): return FakeSession() async def fake_send_sms(*, phone_number, template_code, template_params): sent.append( { "phone_number": phone_number, "template_code": template_code, "template_params": template_params, } ) return {"ok": True, "code": "OK", "message": "OK", "request_id": "request-id"} monkeypatch.setattr(service, "get_session", fake_get_session) monkeypatch.setattr(service, "_send_sms", fake_send_sms) await service.notify_alarm( device_id="TalkingQ_XQSN00001005", alarm_id=7, child_name="孩子", address="杭州市", ) assert [item["phone_number"] for item in sent] == ["13800138000", "13900139000"] assert all(item["template_code"] == "SMS_507185027" for item in sent) assert sent[0]["template_params"]["conference"] == "孩子" assert sent[0]["template_params"]["address"] == "杭州市" @pytest.mark.asyncio async def test_notify_alarm_sanitizes_address_for_sms_template(monkeypatch): configure_enabled_sms(monkeypatch) service = SmsNotificationService() sent = [] async def fake_get_session(): return FakeSession() async def fake_send_sms(*, phone_number, template_code, template_params): del phone_number, template_code sent.append(template_params) return {"ok": True, "code": "OK", "message": "OK", "request_id": "request-id"} monkeypatch.setattr(service, "get_session", fake_get_session) monkeypatch.setattr(service, "_send_sms", fake_send_sms) await service.notify_alarm( device_id="TalkingQ_XQSN00001005", alarm_id=7, child_name="孩子", address="浙江省杭州市萧山区盈丰街道扬帆路顺发·美哉美城", ) assert sent[0]["address"] == "浙江省杭州市萧山区盈丰街道扬帆路附近" def test_sms_address_sanitizer_falls_back_for_coordinates(): service = SmsNotificationService() assert service._sanitize_sms_address("30.245,120.215") == "请打开小程序查看" @pytest.mark.asyncio async def test_notify_alarm_dedupes_same_device_and_phone(monkeypatch): configure_enabled_sms(monkeypatch) service = SmsNotificationService() sent = [] async def fake_get_session(): return FakeSession() async def fake_send_sms(*, phone_number, template_code, template_params): del template_code, template_params sent.append(phone_number) return {"ok": True, "code": "OK", "message": "OK", "request_id": "request-id"} monkeypatch.setattr(service, "get_session", fake_get_session) monkeypatch.setattr(service, "_send_sms", fake_send_sms) await service.notify_alarm(device_id="TalkingQ_XQSN00001005", alarm_id=7) await service.notify_alarm(device_id="TalkingQ_XQSN00001005", alarm_id=8) assert sent == ["13800138000", "13900139000"] @pytest.mark.asyncio async def test_notify_leave_message_disabled_skips_database_and_send(monkeypatch): monkeypatch.setattr("banban.service.sms_notification.settings.sms_enabled", False) service = SmsNotificationService() async def fail_get_session(): raise AssertionError("disabled SMS should not query database") monkeypatch.setattr(service, "get_session", fail_get_session) await service.notify_leave_message(device_id="TalkingQ_XQSN00001005", child_name="孩子") @pytest.mark.asyncio async def test_best_effort_swallows_send_errors(monkeypatch): configure_enabled_sms(monkeypatch) service = SmsNotificationService() async def fake_get_session(): return FakeSession() async def fake_send_sms(*, phone_number, template_code, template_params): del phone_number, template_code, template_params raise RuntimeError("provider failed") monkeypatch.setattr(service, "get_session", fake_get_session) monkeypatch.setattr(service, "_send_sms", fake_send_sms) await service.notify_alarm_best_effort(device_id="TalkingQ_XQSN00001005", alarm_id=7)