mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-09 03:23:23 +08:00
- List/delete chat sessions for administrator by username (cross-tenant UUID) - Return 404 when delete does not remove a session; add tests - Weixin: dispatch lang, inbound attachments, reply persist, native reply timeout - PG compat scrub and administrator session delete repo path - RUNBOOK: WhatsApp re-bind with Remove-Item auth; weixin poll diag scripts Co-authored-by: Cursor <cursoragent@cursor.com>
220 lines
9 KiB
Python
220 lines
9 KiB
Python
from __future__ import annotations
|
|
|
|
import os
|
|
import time
|
|
import unittest
|
|
|
|
from fastapi.testclient import TestClient
|
|
|
|
from interfaces.http.fastapi_app import create_app
|
|
from interfaces.http import weixin_ilink_api
|
|
|
|
|
|
class WeixinIlinkApiTests(unittest.TestCase):
|
|
def setUp(self) -> None:
|
|
self.client = TestClient(create_app())
|
|
self.headers = {
|
|
"AuthorizationType": "ilink_bot_token",
|
|
"Authorization": "Bearer test-ilink-token",
|
|
}
|
|
|
|
def test_sendmessage_requires_account_id(self) -> None:
|
|
r = self.client.post(
|
|
"/ilink/bot/sendmessage",
|
|
headers=self.headers,
|
|
json={
|
|
"channel": "wechat",
|
|
"user_id": "wxid_u1",
|
|
"text": "hello",
|
|
},
|
|
)
|
|
self.assertEqual(r.status_code, 200, r.text)
|
|
self.assertEqual((r.json() or {}).get("ret"), 400)
|
|
|
|
def test_sendmessage_enqueues_reply_for_getupdates(self) -> None:
|
|
old_usecase = weixin_ilink_api._process_inbound_payload_usecase
|
|
|
|
def _fake_usecase(payload: dict[str, object]) -> dict[str, object]:
|
|
text = str(payload.get("text") or "")
|
|
return {
|
|
"ok": True,
|
|
"replies": [
|
|
{
|
|
"chat_id": str(payload.get("chat_id") or ""),
|
|
"text": f"echo:{text}",
|
|
}
|
|
],
|
|
}
|
|
|
|
try:
|
|
weixin_ilink_api._process_inbound_payload_usecase = _fake_usecase # type: ignore[assignment]
|
|
s = self.client.post(
|
|
"/ilink/bot/sendmessage",
|
|
headers=self.headers,
|
|
json={
|
|
"channel": "wechat",
|
|
"account_id": "bot-1",
|
|
"user_id": "wxid_u2",
|
|
"chat_id": "room_1",
|
|
"text": "ping",
|
|
},
|
|
)
|
|
self.assertEqual(s.status_code, 200, s.text)
|
|
self.assertEqual((s.json() or {}).get("ret"), 0)
|
|
|
|
g = self.client.post(
|
|
"/ilink/bot/getupdates",
|
|
headers=self.headers,
|
|
json={
|
|
"channel": "wechat",
|
|
"account_id": "bot-1",
|
|
"get_updates_buf": "0",
|
|
"longpolling_timeout_ms": 1000,
|
|
},
|
|
)
|
|
self.assertEqual(g.status_code, 200, g.text)
|
|
data = g.json() or {}
|
|
self.assertEqual(data.get("ret"), 0)
|
|
msgs = data.get("msgs") if isinstance(data.get("msgs"), list) else []
|
|
self.assertTrue(msgs, data)
|
|
self.assertEqual(str((msgs[0] or {}).get("text") or ""), "echo:ping")
|
|
finally:
|
|
weixin_ilink_api._process_inbound_payload_usecase = old_usecase # type: ignore[assignment]
|
|
|
|
def test_native_reply_returns_sync_replies(self) -> None:
|
|
old_usecase = weixin_ilink_api._process_inbound_payload_usecase
|
|
|
|
def _fake_usecase(payload: dict[str, object]) -> dict[str, object]:
|
|
text = str(payload.get("text") or "")
|
|
self.assertEqual(str(payload.get("channel") or ""), "wechat")
|
|
self.assertEqual(str(payload.get("user_id") or ""), "wxid_u3")
|
|
return {
|
|
"ok": True,
|
|
"replies": [
|
|
{
|
|
"chat_id": str(payload.get("chat_id") or ""),
|
|
"text": f"native:{text}",
|
|
"attachments": [
|
|
{
|
|
"type": "image_ref",
|
|
"name": "demo.png",
|
|
"mime_type": "image/png",
|
|
"data_base64": "aGVsbG8=",
|
|
}
|
|
],
|
|
}
|
|
],
|
|
}
|
|
|
|
try:
|
|
weixin_ilink_api._process_inbound_payload_usecase = _fake_usecase # type: ignore[assignment]
|
|
r = self.client.post(
|
|
"/weixin/native/reply",
|
|
headers=self.headers,
|
|
json={
|
|
"channel": "wechat",
|
|
"account_id": "bot-1",
|
|
"ctx": {
|
|
"From": "wxid_u3",
|
|
"To": "wxid_u3",
|
|
"Body": "hello native",
|
|
"context_token": "ctx-1",
|
|
},
|
|
},
|
|
)
|
|
self.assertEqual(r.status_code, 200, r.text)
|
|
data = r.json() or {}
|
|
self.assertTrue(data.get("ok"), data)
|
|
replies = data.get("replies") if isinstance(data.get("replies"), list) else []
|
|
self.assertEqual(len(replies), 1, data)
|
|
self.assertEqual(str((replies[0] or {}).get("text") or ""), "native:hello native")
|
|
atts = (replies[0] or {}).get("attachments") if isinstance((replies[0] or {}).get("attachments"), list) else []
|
|
self.assertEqual(len(atts), 1, data)
|
|
self.assertEqual(str((atts[0] or {}).get("name") or ""), "demo.png")
|
|
finally:
|
|
weixin_ilink_api._process_inbound_payload_usecase = old_usecase # type: ignore[assignment]
|
|
|
|
def test_native_reply_timeout_returns_user_message(self) -> None:
|
|
old_usecase = weixin_ilink_api._process_inbound_payload_usecase
|
|
old_timeout = weixin_ilink_api._native_reply_timeout_sec
|
|
|
|
def _slow_usecase(payload: dict[str, object]) -> dict[str, object]:
|
|
_ = payload
|
|
time.sleep(1.2)
|
|
return {"ok": True, "replies": [{"text": "late"}]}
|
|
|
|
try:
|
|
weixin_ilink_api._process_inbound_payload_usecase = _slow_usecase # type: ignore[assignment]
|
|
weixin_ilink_api._native_reply_timeout_sec = lambda: 0.2 # type: ignore[assignment]
|
|
r = self.client.post(
|
|
"/weixin/native/reply",
|
|
headers=self.headers,
|
|
json={
|
|
"channel": "wechat",
|
|
"account_id": "bot-1",
|
|
"ctx": {"From": "wxid_u4", "To": "wxid_u4", "Body": "hi"},
|
|
},
|
|
)
|
|
self.assertEqual(r.status_code, 200, r.text)
|
|
data = r.json() or {}
|
|
replies = data.get("replies") if isinstance(data.get("replies"), list) else []
|
|
self.assertEqual(len(replies), 1, data)
|
|
self.assertIn("超时", str((replies[0] or {}).get("text") or ""))
|
|
finally:
|
|
weixin_ilink_api._process_inbound_payload_usecase = old_usecase # type: ignore[assignment]
|
|
weixin_ilink_api._native_reply_timeout_sec = old_timeout # type: ignore[assignment]
|
|
|
|
def test_whatsapp_native_reply_returns_sync_replies(self) -> None:
|
|
old_usecase = weixin_ilink_api._process_inbound_payload_usecase
|
|
|
|
def _fake_usecase(payload: dict[str, object]) -> dict[str, object]:
|
|
text = str(payload.get("text") or "")
|
|
self.assertEqual(str(payload.get("channel") or ""), "whatsapp")
|
|
self.assertEqual(str(payload.get("user_id") or ""), "8613800138000@s.whatsapp.net")
|
|
return {
|
|
"ok": True,
|
|
"replies": [
|
|
{
|
|
"chat_id": str(payload.get("chat_id") or ""),
|
|
"text": f"wa:{text}",
|
|
"attachments": [
|
|
{
|
|
"type": "binary_ref",
|
|
"name": "demo.txt",
|
|
"mime_type": "text/plain",
|
|
"data_base64": "d2E=",
|
|
}
|
|
],
|
|
}
|
|
],
|
|
}
|
|
|
|
try:
|
|
weixin_ilink_api._process_inbound_payload_usecase = _fake_usecase # type: ignore[assignment]
|
|
r = self.client.post(
|
|
"/whatsapp/native/reply",
|
|
headers=self.headers,
|
|
json={
|
|
"account_id": "wa-bot-1",
|
|
"ctx": {
|
|
"From": "8613800138000@s.whatsapp.net",
|
|
"To": "8613800138000@s.whatsapp.net",
|
|
"Body": "hello whatsapp",
|
|
},
|
|
},
|
|
)
|
|
self.assertEqual(r.status_code, 200, r.text)
|
|
data = r.json() or {}
|
|
self.assertTrue(data.get("ok"), data)
|
|
replies = data.get("replies") if isinstance(data.get("replies"), list) else []
|
|
self.assertEqual(len(replies), 1, data)
|
|
self.assertEqual(str((replies[0] or {}).get("text") or ""), "wa:hello whatsapp")
|
|
atts = (replies[0] or {}).get("attachments") if isinstance((replies[0] or {}).get("attachments"), list) else []
|
|
self.assertEqual(len(atts), 1, data)
|
|
self.assertEqual(str((atts[0] or {}).get("name") or ""), "demo.txt")
|
|
finally:
|
|
weixin_ilink_api._process_inbound_payload_usecase = old_usecase # type: ignore[assignment]
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|