mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-09 05:50:44 +08:00
- Rename platform/ to svc/ to avoid shadowing stdlib platform. - Replace from oclaw.* with from svc/runtime/interfaces; update -m CLI paths. - tests/conftest: prepend repo root to sys.path (no parent-folder package name). - CI: paths and offline_eval script under repo root. - Ops scripts: PYTHONPATH must be repo root for python -m runtime.* (fixes gateway/WhatsApp sidecar startup). - Fix default oclaw.json path in tabular/file attachment limits; stabilize attachment test config. Co-authored-by: Cursor <cursoragent@cursor.com>
88 lines
2.8 KiB
Python
88 lines
2.8 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
import queue
|
|
import sys
|
|
from types import SimpleNamespace
|
|
from typing import Any
|
|
|
|
import pytest
|
|
|
|
|
|
def test_wecom_longconn_drains_outbound_on_idle_timeout(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
# Import module under test.
|
|
import interfaces.channels.wecom.longconn_runner as m
|
|
|
|
# Build a fake websocket that raises timeout once, then exits by raising KeyboardInterrupt.
|
|
class _TimeoutOnceWs:
|
|
def __init__(self) -> None:
|
|
self.sent: list[str] = []
|
|
self._recv_calls = 0
|
|
|
|
def settimeout(self, _t: float) -> None:
|
|
return None
|
|
|
|
def recv(self) -> str:
|
|
self._recv_calls += 1
|
|
if self._recv_calls == 1:
|
|
# Mimic websocket-client exception type used in module.
|
|
raise WebSocketTimeoutException()
|
|
raise KeyboardInterrupt()
|
|
|
|
def send(self, data: str) -> None:
|
|
self.sent.append(data)
|
|
|
|
def ping(self) -> None:
|
|
return None
|
|
|
|
def close(self) -> None:
|
|
return None
|
|
|
|
ws = _TimeoutOnceWs()
|
|
|
|
class WebSocketTimeoutException(Exception):
|
|
pass
|
|
|
|
fake_ws_mod = SimpleNamespace(
|
|
WebSocketTimeoutException=WebSocketTimeoutException,
|
|
create_connection=lambda *_a, **_k: ws,
|
|
)
|
|
monkeypatch.setitem(sys.modules, "websocket", fake_ws_mod)
|
|
|
|
# Patch WeComClient to avoid real credentials.
|
|
class _Sender:
|
|
def __init__(self) -> None:
|
|
self.store = SimpleNamespace(
|
|
get_setting=lambda _k: "",
|
|
set_setting=lambda _k, _v: None,
|
|
)
|
|
|
|
def get_bot_credentials(self) -> tuple[str, str]:
|
|
return ("bot", "secret")
|
|
|
|
sender = _Sender()
|
|
|
|
# Patch worker threads/queues by replacing queue.Queue with one we can prefill for outbound only.
|
|
# We do this by monkeypatching the queue constructor used in _run_ws_forever.
|
|
real_queue = queue.Queue
|
|
ctor_count = {"n": 0}
|
|
|
|
def _queue_ctor(*args: Any, **kwargs: Any) -> Any:
|
|
ctor_count["n"] += 1
|
|
q = real_queue(*args, **kwargs)
|
|
# Second queue constructed is outbound_q in _run_ws_forever.
|
|
if ctor_count["n"] == 2:
|
|
q.put({"callback_req_id": "r1", "response_url": "", "text": "hello", "raw_rep": {}})
|
|
return q
|
|
|
|
monkeypatch.setattr(m.queue, "Queue", _queue_ctor)
|
|
|
|
# Run; it should exit cleanly via KeyboardInterrupt from fake ws.recv.
|
|
assert m._run_ws_forever(sender=sender, deliver_outbound=True, use_response_url=False) == 0 # type: ignore[arg-type]
|
|
|
|
# Assert that a send happened with expected cmd.
|
|
assert ws.sent, "expected outbound to be sent during idle timeout drain"
|
|
obj = json.loads(ws.sent[-1])
|
|
assert obj.get("cmd") == "aibot_respond_msg"
|
|
assert (obj.get("body") or {}).get("markdown", {}).get("content") == "hello"
|
|
|