统一聊天排序并支持公众号跳转小程序

This commit is contained in:
stu2not
2026-06-24 14:59:44 +08:00
parent 31804a78ff
commit e75a9015fa
16 changed files with 668 additions and 72 deletions

View File

@@ -1,8 +1,9 @@
from urllib.parse import parse_qs, urlparse
import pytest
from fastapi import HTTPException
from banban.service.wechat_mp_notification import WechatMpNotificationService
from banban.service.wechat_mp_notification import WechatMpNotificationService, WechatMpRecipient
def configure_enabled_wechat_mp(monkeypatch):
@@ -17,6 +18,8 @@ def configure_enabled_wechat_mp(monkeypatch):
monkeypatch.setattr("banban.service.wechat_mp_notification.settings.wechat_mp_miniprogram_link_enabled", True)
monkeypatch.setattr("banban.service.wechat_mp_notification.settings.wechat_mp_miniprogram_appid", "wx-mini")
monkeypatch.setattr("banban.service.wechat_mp_notification.settings.wechat_mp_chat_page", "pages/chat/detail/index")
monkeypatch.setattr("banban.service.wechat_mp_notification.settings.wechat_mp_notification_page", "pages/notification/index")
monkeypatch.setattr("banban.service.wechat_mp_notification.settings.wechat_mp_notification_token_expire_minutes", 10080)
monkeypatch.setattr("banban.service.wechat_mp_notification.settings.wechat_mp_device_page", "pages/device/index")
monkeypatch.setattr("banban.service.wechat_mp_notification.settings.wechat_mp_oauth_redirect_uri", "https://example.com/banban/wechat-mp/oauth/callback")
monkeypatch.setattr("banban.service.wechat_mp_notification.settings.wechat_mp_leave_message_dedup_seconds", 60)
@@ -34,13 +37,83 @@ async def test_notify_leave_message_sends_template_to_unique_subscribed_recipien
async def fake_list_recipients(*, device_id):
assert device_id == "TalkingQ_XQSN00001007"
return ["openid-1", "openid-2"]
return [
WechatMpRecipient(user_id=4, openid="openid-1"),
WechatMpRecipient(user_id=5, openid="openid-2"),
]
async def fake_send_template_message(**kwargs):
sent.append(kwargs)
return {"ok": True, "msgid": "msg-id"}
monkeypatch.setattr(service, "_list_device_family_mp_openids", fake_list_recipients)
monkeypatch.setattr(service, "_list_device_family_mp_recipients", fake_list_recipients)
monkeypatch.setattr(service, "_send_template_message", fake_send_template_message)
await service.notify_leave_message(
device_id="TalkingQ_XQSN00001007",
child_name="小明",
targets=[
{
"parent_user_id": 4,
"conversation_id": 6,
"message_id": 209,
"child_id": 8,
"child_name": "小明",
"device_id": "TalkingQ_XQSN00001007",
},
{
"parent_user_id": 5,
"conversation_id": 7,
"message_id": 210,
"child_id": 8,
"child_name": "小明",
"device_id": "TalkingQ_XQSN00001007",
},
],
)
assert len(sent) == 2
assert sent[0] == {
"openid": "openid-1",
"template_id": "template-id",
"title": "小明",
"condition": "收到新的设备留言",
"page": sent[0]["page"],
}
assert sent[1] == {
"openid": "openid-2",
"template_id": "template-id",
"title": "小明",
"condition": "收到新的设备留言",
"page": sent[1]["page"],
}
assert sent[0]["page"].startswith("pages/notification/index?token=")
assert sent[1]["page"].startswith("pages/notification/index?token=")
assert sent[0]["page"] != sent[1]["page"]
first_token = sent[0]["page"].split("token=", 1)[1]
second_token = sent[1]["page"].split("token=", 1)[1]
assert service._decode_notification_token(first_token)["conversation_id"] == 6
assert service._decode_notification_token(first_token)["user_id"] == 4
assert service._decode_notification_token(second_token)["conversation_id"] == 7
assert service._decode_notification_token(second_token)["user_id"] == 5
@pytest.mark.asyncio
async def test_notify_leave_message_falls_back_to_chat_page_without_targets(monkeypatch):
configure_enabled_wechat_mp(monkeypatch)
service = WechatMpNotificationService()
sent = []
async def fake_list_recipients(*, device_id):
del device_id
return [WechatMpRecipient(user_id=4, openid="openid-1")]
async def fake_send_template_message(**kwargs):
sent.append(kwargs)
return {"ok": True, "msgid": "msg-id"}
monkeypatch.setattr(service, "_list_device_family_mp_recipients", fake_list_recipients)
monkeypatch.setattr(service, "_send_template_message", fake_send_template_message)
await service.notify_leave_message(device_id="TalkingQ_XQSN00001007", child_name="小明")
@@ -52,14 +125,7 @@ async def test_notify_leave_message_sends_template_to_unique_subscribed_recipien
"title": "小明",
"condition": "收到新的设备留言",
"page": "pages/chat/detail/index",
},
{
"openid": "openid-2",
"template_id": "template-id",
"title": "小明",
"condition": "收到新的设备留言",
"page": "pages/chat/detail/index",
},
}
]
@@ -71,13 +137,13 @@ async def test_notify_leave_message_dedupes_same_device_and_openid(monkeypatch):
async def fake_list_recipients(*, device_id):
del device_id
return ["openid-1"]
return [WechatMpRecipient(user_id=4, openid="openid-1")]
async def fake_send_template_message(**kwargs):
sent.append(kwargs["openid"])
return {"ok": True, "msgid": "msg-id"}
monkeypatch.setattr(service, "_list_device_family_mp_openids", fake_list_recipients)
monkeypatch.setattr(service, "_list_device_family_mp_recipients", fake_list_recipients)
monkeypatch.setattr(service, "_send_template_message", fake_send_template_message)
await service.notify_leave_message(device_id="TalkingQ_XQSN00001007", child_name="小明")
@@ -86,6 +152,91 @@ async def test_notify_leave_message_dedupes_same_device_and_openid(monkeypatch):
assert sent == ["openid-1"]
@pytest.mark.asyncio
async def test_resolve_notification_link_returns_chat_detail_params(monkeypatch):
configure_enabled_wechat_mp(monkeypatch)
service = WechatMpNotificationService()
token = service._create_notification_token(
{
"type": "leave_message",
"user_id": 4,
"conversation_id": 6,
"message_id": 209,
"child_id": 8,
"child_name": "小明",
"device_id": "TalkingQ_XQSN00001007",
}
)
class FakeResult:
def mappings(self):
return self
def first(self):
return {
"conversation_id": 6,
"child_id": 8,
"child_name": "小明",
"device_id": "TalkingQ_XQSN00001007",
}
class FakeSession:
async def execute(self, sql, params):
assert params["message_id"] == 209
assert params["conversation_id"] == 6
assert params["child_id"] == 8
assert params["user_id"] == 4
return FakeResult()
async def close(self):
pass
async def fake_get_session():
return FakeSession()
monkeypatch.setattr(service, "get_session", fake_get_session)
result = await service.resolve_notification_link(token=token, user_id=4)
assert result["route"] == "pages/chat/detail/index"
assert result["params"] == {
"id": 6,
"source": "im",
"name": "家长沟通",
"peerKind": "parent",
"roleKey": "",
"conversationTypeName": "parent_child",
"peerId": "4",
"childId": 8,
"childName": "小明",
"parentUserId": 4,
"deviceId": "TalkingQ_XQSN00001007",
"channelLabel": "微信小程序",
"canSend": "1",
"messageId": 209,
}
@pytest.mark.asyncio
async def test_resolve_notification_link_rejects_other_user(monkeypatch):
configure_enabled_wechat_mp(monkeypatch)
service = WechatMpNotificationService()
token = service._create_notification_token(
{
"type": "leave_message",
"user_id": 4,
"conversation_id": 6,
"message_id": 209,
"child_id": 8,
}
)
with pytest.raises(HTTPException) as exc_info:
await service.resolve_notification_link(token=token, user_id=5)
assert exc_info.value.status_code == 403
@pytest.mark.asyncio
async def test_bind_url_persists_state_and_builds_oauth_url(monkeypatch):
configure_enabled_wechat_mp(monkeypatch)