feat(sms): send_code 仅主 503 时转创蓝补发(429/400 不转)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -36,10 +36,23 @@ def _fallback():
|
||||
|
||||
|
||||
def send_code(phone: str) -> SendResult:
|
||||
"""发码:主成功即返回;失败抛 SmsError。返回 SendResult(cooldown + 实际渠道)。"""
|
||||
"""发码:主成功即返回;仅主「供应商不可用(503)」且配置了备时转备补发。
|
||||
|
||||
429(本地冷却/超频)、400(手机号无效)不转——不绕过防刷、不为无效号白烧。
|
||||
备也失败则抛备的 SmsError。返回 SendResult(cooldown + 实际渠道 + 是否 fallback)。
|
||||
"""
|
||||
primary = _primary()
|
||||
cooldown = primary.send_code(phone)
|
||||
return SendResult(cooldown_sec=cooldown, provider=_NAME[primary], fallback=False)
|
||||
fb = _fallback()
|
||||
try:
|
||||
cooldown = primary.send_code(phone)
|
||||
return SendResult(cooldown_sec=cooldown, provider=_NAME[primary], fallback=False)
|
||||
except SmsError as e:
|
||||
if fb is not None and e.status_code == 503:
|
||||
logger.warning("[SMS] primary=%s 不可用(%s),fallback→%s",
|
||||
_NAME[primary], e, _NAME[fb])
|
||||
cooldown = fb.send_code(phone) # 备的冷却/错误码原样透出
|
||||
return SendResult(cooldown_sec=cooldown, provider=_NAME[fb], fallback=True)
|
||||
raise
|
||||
|
||||
|
||||
def verify_code(phone: str, code: str) -> bool:
|
||||
|
||||
@@ -5,12 +5,12 @@ monkeypatch 各 provider 的 send_code/verify_code 为标记函数,不发真
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest # noqa: F401
|
||||
import pytest
|
||||
|
||||
from app.core.config import settings
|
||||
from app.integrations import sms
|
||||
from app.integrations.sms import aliyun, chuanglan, jiguang # noqa: F401
|
||||
from app.integrations.sms.base import SendResult, SmsError # noqa: F401 (SmsError 在 Task 4+ 使用)
|
||||
from app.integrations.sms import chuanglan, jiguang
|
||||
from app.integrations.sms.base import SendResult, SmsError
|
||||
|
||||
PHONE = "13800138000"
|
||||
|
||||
@@ -36,3 +36,59 @@ def test_send_code_primary_success_returns_sendresult(monkeypatch):
|
||||
assert r.cooldown_sec == 60
|
||||
assert r.provider == "jiguang"
|
||||
assert r.fallback is False
|
||||
|
||||
|
||||
def test_send_code_503_falls_back_to_configured_backup(monkeypatch):
|
||||
calls = []
|
||||
monkeypatch.setattr(settings, "SMS_PROVIDER", "jiguang")
|
||||
monkeypatch.setattr(settings, "SMS_FALLBACK_PROVIDER", "chuanglan")
|
||||
monkeypatch.setattr(jiguang, "send_code",
|
||||
_raiser(SmsError("极光不可用", status_code=503)))
|
||||
monkeypatch.setattr(chuanglan, "send_code",
|
||||
lambda phone: (calls.append("chuanglan"), 60)[1])
|
||||
|
||||
r = sms.send_code(PHONE)
|
||||
assert r.provider == "chuanglan"
|
||||
assert r.fallback is True
|
||||
assert r.cooldown_sec == 60
|
||||
assert calls == ["chuanglan"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("status", [429, 400])
|
||||
def test_send_code_non_503_does_not_fall_back(monkeypatch, status):
|
||||
calls = []
|
||||
monkeypatch.setattr(settings, "SMS_PROVIDER", "jiguang")
|
||||
monkeypatch.setattr(settings, "SMS_FALLBACK_PROVIDER", "chuanglan")
|
||||
monkeypatch.setattr(jiguang, "send_code",
|
||||
_raiser(SmsError("x", status_code=status)))
|
||||
monkeypatch.setattr(chuanglan, "send_code",
|
||||
lambda phone: (calls.append("chuanglan"), 60)[1])
|
||||
|
||||
with pytest.raises(SmsError) as ei:
|
||||
sms.send_code(PHONE)
|
||||
assert ei.value.status_code == status
|
||||
assert calls == []
|
||||
|
||||
|
||||
def test_send_code_503_without_backup_reraises(monkeypatch):
|
||||
monkeypatch.setattr(settings, "SMS_PROVIDER", "jiguang")
|
||||
monkeypatch.setattr(settings, "SMS_FALLBACK_PROVIDER", "")
|
||||
monkeypatch.setattr(jiguang, "send_code",
|
||||
_raiser(SmsError("极光不可用", status_code=503)))
|
||||
|
||||
with pytest.raises(SmsError) as ei:
|
||||
sms.send_code(PHONE)
|
||||
assert ei.value.status_code == 503
|
||||
|
||||
|
||||
def test_send_code_backup_also_fails_raises_backup_error(monkeypatch):
|
||||
monkeypatch.setattr(settings, "SMS_PROVIDER", "jiguang")
|
||||
monkeypatch.setattr(settings, "SMS_FALLBACK_PROVIDER", "chuanglan")
|
||||
monkeypatch.setattr(jiguang, "send_code",
|
||||
_raiser(SmsError("极光不可用", status_code=503)))
|
||||
monkeypatch.setattr(chuanglan, "send_code",
|
||||
_raiser(SmsError("创蓝也不可用", status_code=503)))
|
||||
|
||||
with pytest.raises(SmsError) as ei:
|
||||
sms.send_code(PHONE)
|
||||
assert "创蓝" in str(ei.value)
|
||||
|
||||
Reference in New Issue
Block a user