mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-09 03:30:48 +08:00
Fix duplicate WhatsApp outbound delivery.
Claim pending rows atomically and serialize the sidecar poller so overlapping polls cannot send the same reply twice; also stop inbound queue delivery from falling through to a second enqueue. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
10ae4abc1d
commit
b6e023cd91
6 changed files with 193 additions and 14 deletions
|
|
@ -54,3 +54,26 @@ def test_channel_outbound_retry_success_clears_schedule(tmp_path) -> None:
|
|||
assert row["status"] == "sent"
|
||||
assert int(row["send_attempts"] or 0) == 2
|
||||
assert not str(row["next_attempt_at"] or "").strip()
|
||||
|
||||
|
||||
def test_claim_pending_channel_outbound_messages_is_exclusive(tmp_path) -> None:
|
||||
store = SqliteStore(str(tmp_path / "ops.sqlite"))
|
||||
msg_id = store.enqueue_channel_outbound_message(
|
||||
channel="whatsapp",
|
||||
account_id="wa-default",
|
||||
chat_id="120363012345678@g.us",
|
||||
text="once only",
|
||||
)
|
||||
first = store.claim_pending_channel_outbound_messages(
|
||||
channel="whatsapp", account_id="wa-default", limit=5
|
||||
)
|
||||
second = store.claim_pending_channel_outbound_messages(
|
||||
channel="whatsapp", account_id="wa-default", limit=5
|
||||
)
|
||||
assert len(first) == 1
|
||||
assert first[0]["id"] == msg_id
|
||||
assert second == []
|
||||
row = _row(store, msg_id)
|
||||
assert row["status"] == "sending"
|
||||
assert store.ack_channel_outbound_message(message_id=msg_id, ok=True, stanza_id="S1") is True
|
||||
assert _row(store, msg_id)["status"] == "sent"
|
||||
|
|
|
|||
|
|
@ -101,6 +101,26 @@ class WhatsappInboundSerialQueueTests(unittest.TestCase):
|
|||
self.assertEqual(source.get("kind"), "inbound_reply")
|
||||
self.assertEqual(source.get("quote_stanza_id"), "stanza1")
|
||||
|
||||
def test_whatsapp_queue_delivery_does_not_double_enqueue(self) -> None:
|
||||
"""Regression: agent-path enqueue must not fall through to bottom enqueue."""
|
||||
with self._patch_common(), mock.patch("runtime.gateway.OclawGateway") as gw_cls, mock.patch(
|
||||
"runtime.orchestration.group_ingest.should_process_group_inbound", return_value=True
|
||||
), mock.patch(
|
||||
"runtime.application.gateway.whatsapp_inbound_access.handle_whatsapp_access",
|
||||
return_value=None,
|
||||
):
|
||||
gw = gw_cls.return_value
|
||||
gw.handle_turn.return_value = _FakeTurn("only once")
|
||||
out = inbound_mod.process_inbound_payload(self._payload("ping"))
|
||||
|
||||
self.assertEqual(out.get("delivery"), "queued")
|
||||
self.assertEqual(out.get("replies"), [])
|
||||
pending = self.store.list_pending_channel_outbound_messages(
|
||||
channel="whatsapp", account_id="wa-default", limit=10
|
||||
)
|
||||
self.assertEqual(len(pending), 1)
|
||||
self.assertEqual(pending[0].get("text"), "only once")
|
||||
|
||||
def test_busy_inbound_is_accepted_queued_then_merged(self) -> None:
|
||||
release = threading.Event()
|
||||
seen_texts: list[str] = []
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue