mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-09 03:30:48 +08:00
fix(whatsapp): deliver scheduled job attachments to groups
Scheduled outbound only sent text via pollOutboundQueue; collect deliverable attachments from the turn, encode them in source JSON, and send via sendReplyWithAttachments. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
40eed39110
commit
f5cb64a3a4
7 changed files with 248 additions and 16 deletions
116
tests/test_whatsapp_attachment_delivery.py
Normal file
116
tests/test_whatsapp_attachment_delivery.py
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from unittest import mock
|
||||
|
||||
from runtime.scheduler.channel_delivery import deliver_scheduled_reply
|
||||
from runtime.scheduler.whatsapp_mentions import encode_whatsapp_outbound_source
|
||||
from svc.persistence.sqlite_store import SqliteStore
|
||||
|
||||
|
||||
class WhatsappAttachmentDeliveryTests(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
self._tmp = tempfile.TemporaryDirectory(ignore_cleanup_errors=True)
|
||||
self.db = Path(self._tmp.name) / "wa_attach.sqlite"
|
||||
self.store = SqliteStore(str(self.db))
|
||||
tenant = self.store.create_tenant("Team")
|
||||
self.tenant_id = str(tenant["id"])
|
||||
sess = self.store.create_session("WA scheduled")
|
||||
self.session_id = str(sess.id)
|
||||
|
||||
def tearDown(self) -> None:
|
||||
self._tmp.cleanup()
|
||||
|
||||
def test_encode_whatsapp_outbound_source_with_attachments(self) -> None:
|
||||
atts = [{"name": "report.xlsx", "mime": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "data_base64": "abc"}]
|
||||
raw = encode_whatsapp_outbound_source(
|
||||
mention_jids=["111@lid"],
|
||||
attachments=atts,
|
||||
media_path="/tmp/report.xlsx",
|
||||
)
|
||||
data = json.loads(raw)
|
||||
self.assertEqual(data.get("mention_jids"), ["111@lid"])
|
||||
self.assertEqual(data.get("attachments"), atts)
|
||||
self.assertEqual(data.get("media_path"), "/tmp/report.xlsx")
|
||||
|
||||
def test_list_pending_whatsapp_outbound_includes_attachments_from_source(self) -> None:
|
||||
source = encode_whatsapp_outbound_source(
|
||||
attachments=[{"name": "a.txt", "data_base64": "dGVzdA=="}],
|
||||
media_path="D:/tmp/a.txt",
|
||||
)
|
||||
self.store.enqueue_channel_outbound_message(
|
||||
channel="whatsapp",
|
||||
chat_id="120363012345678@g.us",
|
||||
text="file attached",
|
||||
tenant_id=self.tenant_id,
|
||||
account_id="wa-default",
|
||||
source=source,
|
||||
)
|
||||
items = self.store.list_pending_channel_outbound_messages(channel="whatsapp", account_id="wa-default", limit=5)
|
||||
self.assertEqual(len(items), 1)
|
||||
self.assertEqual(len(items[0].get("attachments") or []), 1)
|
||||
self.assertEqual(items[0].get("media_path"), "D:/tmp/a.txt")
|
||||
|
||||
def test_deliver_scheduled_reply_encodes_tool_attachments_for_turn(self) -> None:
|
||||
turn_uuid = "turn-wa-attach-1"
|
||||
self.store.add_message(
|
||||
session_id=self.session_id,
|
||||
role="tool",
|
||||
content='{"ok": true}',
|
||||
turn_uuid=turn_uuid,
|
||||
event_type="tool_result",
|
||||
attachments=json.dumps(
|
||||
[
|
||||
{
|
||||
"type": "binary_ref",
|
||||
"attachment_id": "att-1",
|
||||
"name": "daily.xlsx",
|
||||
"mime": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
"deliverable": True,
|
||||
}
|
||||
],
|
||||
ensure_ascii=False,
|
||||
),
|
||||
)
|
||||
delivery = {
|
||||
"whatsapp": {
|
||||
"enabled": True,
|
||||
"target_type": "group",
|
||||
"chat_id": "120363012345678@g.us",
|
||||
"account_id": "wa-default",
|
||||
},
|
||||
"weixin": {"enabled": False},
|
||||
}
|
||||
with mock.patch(
|
||||
"runtime.scheduler.channel_delivery._prepare_channel_outbound_attachments",
|
||||
return_value=(
|
||||
[{"name": "daily.xlsx", "mime": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "data_base64": "ZGF0YQ=="}],
|
||||
"",
|
||||
),
|
||||
):
|
||||
result = deliver_scheduled_reply(
|
||||
self.store,
|
||||
tenant_id=self.tenant_id,
|
||||
reply_text="今日报表已生成",
|
||||
delivery_json=json.dumps(delivery, ensure_ascii=False),
|
||||
session_id=self.session_id,
|
||||
turn_uuid=turn_uuid,
|
||||
)
|
||||
self.assertTrue(result.get("ok"), result)
|
||||
pending = self.store.list_pending_channel_outbound_messages(
|
||||
channel="whatsapp",
|
||||
account_id="wa-default",
|
||||
limit=5,
|
||||
)
|
||||
self.assertEqual(len(pending), 1)
|
||||
source = json.loads(str(pending[0].get("source") or "{}"))
|
||||
self.assertEqual(len(source.get("attachments") or []), 1)
|
||||
self.assertEqual(source["attachments"][0].get("name"), "daily.xlsx")
|
||||
self.assertEqual((result.get("channels") or {}).get("whatsapp", {}).get("attachments"), 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
@ -39,6 +39,16 @@ def test_start_all_gracefully_skips_missing_channel_sidecars() -> None:
|
|||
assert 'Warn "whatsapp sidecar skipped:' in text
|
||||
|
||||
|
||||
def test_whatsapp_runner_outbound_poll_uses_reply_attachments() -> None:
|
||||
text = _read("runtime/operations/whatsapp_bridge/baileys_runner.ts")
|
||||
assert "sendReplyWithAttachments" in text
|
||||
assert "hasMedia" in text
|
||||
assert "pollOutboundQueue" in text
|
||||
poll_start = text.index("async function pollOutboundQueue")
|
||||
poll_chunk = text[poll_start : poll_start + 2500]
|
||||
assert "sendReplyWithAttachments" in poll_chunk
|
||||
|
||||
|
||||
def test_whatsapp_runner_supports_reply_attachments_base64() -> None:
|
||||
text = _read("runtime/operations/whatsapp_bridge/baileys_runner.ts")
|
||||
assert "sendReplyWithAttachments" in text
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue