dfa3d4f07e
新增第三个可切换短信 provider chuanglan,与极光/阿里云并列(SMS_PROVIDER 切换,默认仍 jiguang,未知值回退 jiguang)。创蓝 tpl/send v2 是纯发送网关:本服务生成码、创蓝只下发、 本地内存校验(与极光同 Mode B)。存码/冷却/一次性/防爆破/GC 从极光隔离复制(极光文件不动, 零回归风险),唯一新逻辑是 HMAC-SHA256 签名(password 不上行)+ httpx POST + 错误码映射。 复用现有 httpx + 标准库 hashlib/hmac,无新依赖。 - app/integrations/sms/chuanglan.py: 新 provider - app/integrations/sms/__init__.py: 分派器改 dict + chuanglan 路由 - app/core/config.py: SMS_PROVIDER 加 chuanglan + CHUANGLAN_SMS_* + chuanglan_sms_configured - .env.example / docs/integrations/sms.md / docs/integrations/chuanglan/tpl-send.md: 配置与接口调研文档 - docs/superpowers/specs/2026-07-26-chuanglan-sms-verify-design.md: 设计 spec - tests/test_sms_chuanglan.py(28 例)+ test_sms_dispatch.py 扩 chuanglan 路由 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
54 lines
2.6 KiB
Python
54 lines
2.6 KiB
Python
"""SMS 分派器:按 settings.SMS_PROVIDER 路由到正确 provider。
|
|
|
|
契约:send_code / verify_code **每次调用**读 settings.SMS_PROVIDER 选 provider(支持运行时切换 /
|
|
灰度回退);默认 jiguang。此处 monkeypatch 两 provider 的实现为标记函数,断言路由命中 + 可秒切。
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from app.core.config import settings
|
|
from app.integrations import sms
|
|
from app.integrations.sms import aliyun, chuanglan, jiguang
|
|
|
|
|
|
def test_send_code_routes_by_provider_and_switches_per_call(monkeypatch) -> None:
|
|
calls: list[str] = []
|
|
monkeypatch.setattr(jiguang, "send_code", lambda phone: (calls.append("jiguang"), 60)[1])
|
|
monkeypatch.setattr(aliyun, "send_code", lambda phone: (calls.append("aliyun"), 60)[1])
|
|
monkeypatch.setattr(chuanglan, "send_code", lambda phone: (calls.append("chuanglan"), 60)[1])
|
|
|
|
monkeypatch.setattr(settings, "SMS_PROVIDER", "jiguang")
|
|
assert sms.send_code("13800138000") == 60
|
|
monkeypatch.setattr(settings, "SMS_PROVIDER", "aliyun")
|
|
assert sms.send_code("13800138000") == 60
|
|
monkeypatch.setattr(settings, "SMS_PROVIDER", "chuanglan")
|
|
assert sms.send_code("13800138000") == 60
|
|
|
|
assert calls == ["jiguang", "aliyun", "chuanglan"] # 每次按当前 provider 路由,运行时可切
|
|
|
|
|
|
def test_verify_code_routes_by_provider(monkeypatch) -> None:
|
|
calls: list[str] = []
|
|
monkeypatch.setattr(jiguang, "verify_code", lambda phone, code: (calls.append("jiguang"), True)[1])
|
|
monkeypatch.setattr(aliyun, "verify_code", lambda phone, code: (calls.append("aliyun"), True)[1])
|
|
monkeypatch.setattr(chuanglan, "verify_code", lambda phone, code: (calls.append("chuanglan"), True)[1])
|
|
|
|
monkeypatch.setattr(settings, "SMS_PROVIDER", "jiguang")
|
|
assert sms.verify_code("13800138000", "123456") is True
|
|
monkeypatch.setattr(settings, "SMS_PROVIDER", "aliyun")
|
|
assert sms.verify_code("13800138000", "123456") is True
|
|
monkeypatch.setattr(settings, "SMS_PROVIDER", "chuanglan")
|
|
assert sms.verify_code("13800138000", "123456") is True
|
|
|
|
assert calls == ["jiguang", "aliyun", "chuanglan"]
|
|
|
|
|
|
def test_unknown_provider_falls_back_to_jiguang(monkeypatch) -> None:
|
|
"""SMS_PROVIDER 非 aliyun 一律走 jiguang(默认兜底,防误配把登录打挂)。"""
|
|
calls: list[str] = []
|
|
monkeypatch.setattr(jiguang, "send_code", lambda phone: (calls.append("jiguang"), 60)[1])
|
|
monkeypatch.setattr(aliyun, "send_code", lambda phone: (calls.append("aliyun"), 60)[1])
|
|
monkeypatch.setattr(settings, "SMS_PROVIDER", "jiguang")
|
|
|
|
sms.send_code("13800138000")
|
|
assert calls == ["jiguang"]
|