37619d4ac5
落地页对齐淘宝:微信内已拿 openid 但未授权头像时出单按钮插页,点按钮触发 snsapi_userinfo 授权(交互手势避免快照页),授权回来经 ?authed=1 自动跳券;取消授权由前端 pageshow/visibilitychange 转跳目标券页 + 回调空 code 兜底,绝不阻断领券。受 wx_oauth_active 开关控制。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
88 lines
3.6 KiB
Python
88 lines
3.6 KiB
Python
"""美团/京东 CPS 落地页「领券前授权头像」插页(/c/{code})。
|
|
|
|
验证:微信内未授权头像 → 出插页(snsapi_userinfo 按钮 + 「直接领券」出口);
|
|
已授权(wx_uinfo cookie) / 非微信 → 直接 302 跳 target。
|
|
插页只渲染 HTML、不真调微信,故无需 mock 微信网络。
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from app.core.config import settings
|
|
from app.db.session import SessionLocal
|
|
from app.repositories import cps_link as cps_link_repo
|
|
|
|
_WECHAT_UA = "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) MicroMessenger/8.0.40"
|
|
_OTHER_UA = "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) Safari/605.1"
|
|
|
|
|
|
@pytest.fixture()
|
|
def _wx_oauth_on(monkeypatch):
|
|
"""打开微信网页授权(插页只在 settings.wx_oauth_active 时出)。"""
|
|
monkeypatch.setattr(settings, "WX_MP_APPID", "wxtestappid")
|
|
monkeypatch.setattr(settings, "WX_MP_SECRET", "wxtestsecret")
|
|
monkeypatch.setattr(settings, "WX_MP_OAUTH_ENABLED", True)
|
|
monkeypatch.setattr(settings, "CPS_REDIRECT_BASE", "https://coupon.example.com")
|
|
|
|
|
|
def _make_link(platform: str, target: str) -> tuple[str, str]:
|
|
with SessionLocal() as db:
|
|
link = cps_link_repo.create_link(
|
|
db, group_id=1, activity_id=1,
|
|
sid="g1" if platform == "meituan" else None,
|
|
target_url=target, platform=platform,
|
|
)
|
|
return link.code, link.target_url
|
|
|
|
|
|
@pytest.mark.parametrize("platform,target", [
|
|
("meituan", "https://dpurl.cn/mt-abc"),
|
|
("jd", "https://u.jd.com/jd-xyz"),
|
|
])
|
|
def test_wechat_unauthorized_shows_interstitial(client, _wx_oauth_on, platform, target):
|
|
"""微信内 + 有 openid + 无 wx_uinfo → 200 插页:userinfo 授权按钮 + 直接领券出口。"""
|
|
code, tgt = _make_link(platform, target)
|
|
r = client.get(
|
|
f"/c/{code}",
|
|
headers={"user-agent": _WECHAT_UA, "cookie": "wx_openid=o_test"},
|
|
follow_redirects=False,
|
|
)
|
|
assert r.status_code == 200, r.text
|
|
body = r.text
|
|
assert "snsapi_userinfo" in body # 唯一按钮走 userinfo 授权(交互手势触发)
|
|
assert code in body # 授权 state=uinfo:{code}
|
|
assert tgt in body # 取消授权时前端转跳的目标券页(绝不阻断领券)
|
|
assert "暂不授权" not in body # 已去掉单独的"暂不授权"出口,改为取消即自动转跳
|
|
|
|
|
|
def test_authorized_redirects_to_target(client, _wx_oauth_on):
|
|
"""已授权头像(wx_uinfo=1)→ 不再出插页,直接 302 跳 target。"""
|
|
code, tgt = _make_link("meituan", "https://dpurl.cn/mt-ok")
|
|
r = client.get(
|
|
f"/c/{code}",
|
|
headers={"user-agent": _WECHAT_UA, "cookie": "wx_openid=o_test; wx_uinfo=1"},
|
|
follow_redirects=False,
|
|
)
|
|
assert r.status_code == 302
|
|
assert r.headers["location"] == tgt
|
|
|
|
|
|
def test_non_wechat_redirects_to_target(client, _wx_oauth_on):
|
|
"""非微信浏览器(拿不了授权)→ 直接 302 跳 target,不出插页。"""
|
|
code, tgt = _make_link("meituan", "https://dpurl.cn/mt-other")
|
|
r = client.get(
|
|
f"/c/{code}",
|
|
headers={"user-agent": _OTHER_UA, "cookie": "wx_openid=o_test"},
|
|
follow_redirects=False,
|
|
)
|
|
assert r.status_code == 302
|
|
assert r.headers["location"] == tgt
|
|
|
|
|
|
def test_oauth_cb_cancel_no_code_redirects_back(client):
|
|
"""授权弹窗点「取消」、微信带空 code 回调 → 兜底 302 回落地页(不报 422)。
|
|
落地页再由前端转跳目标券页,保证拒绝也能拿到券。"""
|
|
r = client.get("/wx/oauth/cb?state=uinfo:ABC123", follow_redirects=False)
|
|
assert r.status_code == 302
|
|
assert r.headers["location"] == "/c/ABC123"
|