mirror of
https://github.com/andrewyng/openworker.git
synced 2026-09-03 04:49:26 +00:00
Imported from andrewyng/aisuite@1b4bbf303e (contents of its platform/ directory, hoisted to the repo root). Development history prior to this commit lives in that repository. Co-authored-by: Devika <devikaverma11@gmail.com>
228 lines
7.1 KiB
Python
228 lines
7.1 KiB
Python
"""Slack connection health (M3.6 Step 2) — three honest layers: the
|
|
desktop↔relay socket, the cloud sign-in, and per-workspace bot tokens.
|
|
The endpoint aggregates what the adapter observed; it never invents a
|
|
Slack↔cloud claim (that leg is invisible from the desktop).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
from types import SimpleNamespace
|
|
|
|
import pytest
|
|
from fastapi.testclient import TestClient
|
|
|
|
from coworker.connectors.base import SendResult
|
|
from coworker.connectors.relay_client import SlackRelayAdapter
|
|
from coworker.server import SessionManager, create_app
|
|
|
|
|
|
@pytest.fixture
|
|
def client(tmp_path, monkeypatch):
|
|
monkeypatch.setenv("COWORKER_STATE_DIR", str(tmp_path / "state"))
|
|
manager = SessionManager(workspace=tmp_path)
|
|
app = create_app(manager)
|
|
with TestClient(app) as c:
|
|
c.manager = manager
|
|
yield c
|
|
|
|
|
|
class _StubAdapter:
|
|
"""Quacks like SlackRelayAdapter.status() without a socket."""
|
|
|
|
def __init__(self, state="live", teams=None):
|
|
self._state = state
|
|
self._teams = teams or {}
|
|
|
|
def status(self):
|
|
return {
|
|
"state": self._state,
|
|
"reconnects": 2,
|
|
"last_event_at": 1751970000.0,
|
|
"last_error": "" if self._state == "live" else "boom",
|
|
"teams": self._teams,
|
|
}
|
|
|
|
|
|
async def _noop_stop():
|
|
return None
|
|
|
|
|
|
def _gateway_with(adapter) -> SimpleNamespace:
|
|
# `stop` because app shutdown tears the gateway down.
|
|
return SimpleNamespace(
|
|
_adapters={"slack": adapter} if adapter else {}, stop=_noop_stop
|
|
)
|
|
|
|
|
|
# --- endpoint aggregation ----------------------------------------------------
|
|
|
|
|
|
def test_status_live_relay_with_teams(client):
|
|
client.manager.secrets.put("slack:default", {"mode": "relay", "enabled": True})
|
|
client.manager.secrets.put(
|
|
"cloud:auth", {"access_token": "jwt", "account": "rohit@x.com"}
|
|
)
|
|
client.manager.gateway = _gateway_with(
|
|
_StubAdapter("live", {"T1": {"token_ok": True}, "T2": {"token_ok": False}})
|
|
)
|
|
data = client.get("/v1/connectors/slack/status").json()
|
|
assert data["mode"] == "relay"
|
|
assert data["signed_in"] is True
|
|
assert data["relay"]["state"] == "live"
|
|
assert data["relay"]["last_event_at"] == 1751970000.0
|
|
assert data["teams"]["T1"]["token_ok"] is True
|
|
assert data["teams"]["T2"]["token_ok"] is False
|
|
assert "teams" not in data["relay"] # folded up, not duplicated
|
|
|
|
|
|
def test_status_reconnecting_carries_last_error(client):
|
|
client.manager.secrets.put("slack:default", {"mode": "relay", "enabled": True})
|
|
client.manager.gateway = _gateway_with(_StubAdapter("reconnecting"))
|
|
data = client.get("/v1/connectors/slack/status").json()
|
|
assert data["relay"]["state"] == "reconnecting"
|
|
assert data["relay"]["last_error"] == "boom"
|
|
assert data["signed_in"] is False
|
|
|
|
|
|
def test_status_offline_when_no_adapter(client):
|
|
# Relay mode configured but the gateway has no slack adapter (e.g. signed
|
|
# out at startup so it never built) → offline, not a crash.
|
|
client.manager.secrets.put("slack:default", {"mode": "relay", "enabled": True})
|
|
client.manager.gateway = _gateway_with(None)
|
|
data = client.get("/v1/connectors/slack/status").json()
|
|
assert data["relay"]["state"] == "offline"
|
|
assert data["teams"] == {}
|
|
|
|
|
|
def test_status_manual_socket_mode_has_no_relay_layer(client):
|
|
# Socket-Mode adapters expose no status(); the endpoint still answers.
|
|
client.manager.secrets.put("slack:default", {"bot_token": "xoxb", "enabled": True})
|
|
client.manager.gateway = None
|
|
data = client.get("/v1/connectors/slack/status").json()
|
|
assert data["mode"] == "" # not relay
|
|
assert data["relay"]["state"] == "offline"
|
|
|
|
|
|
# --- adapter state tracking --------------------------------------------------
|
|
|
|
|
|
def _adapter(**kw) -> SlackRelayAdapter:
|
|
return SlackRelayAdapter(
|
|
"wss://relay.test/ws",
|
|
token_provider=lambda: "jwt",
|
|
teams={"T1": {"bot_token": "xoxb-1", "bot_user_id": "UBOT"}},
|
|
reconnect_delay=0.0,
|
|
**kw,
|
|
)
|
|
|
|
|
|
def test_adapter_status_offline_before_connect():
|
|
st = _adapter().status()
|
|
assert st["state"] == "offline"
|
|
assert st["last_event_at"] is None
|
|
assert st["teams"] == {"T1": {"token_ok": True}} # unknown ⇒ assumed good
|
|
|
|
|
|
async def test_adapter_stamps_last_event_and_reports_live():
|
|
class _Blocking:
|
|
async def open(self):
|
|
pass
|
|
|
|
async def recv(self):
|
|
await asyncio.Event().wait()
|
|
|
|
async def close(self):
|
|
pass
|
|
|
|
adapter = _adapter(transport_factory=lambda: _Blocking())
|
|
assert await adapter.connect() is True
|
|
try:
|
|
assert adapter.status()["state"] == "live"
|
|
assert adapter.status()["last_event_at"] is None
|
|
await adapter._dispatch_slack_event(
|
|
"T1",
|
|
{
|
|
"type": "app_mention",
|
|
"user": "U1",
|
|
"channel": "C1",
|
|
"text": "x",
|
|
"ts": "1",
|
|
},
|
|
)
|
|
assert adapter.status()["last_event_at"] is not None
|
|
finally:
|
|
await adapter.disconnect()
|
|
assert adapter.status()["state"] == "offline"
|
|
|
|
|
|
async def test_adapter_connect_failure_records_last_error():
|
|
class _Broken:
|
|
async def open(self):
|
|
raise OSError("relay unreachable")
|
|
|
|
async def recv(self):
|
|
return None
|
|
|
|
async def close(self):
|
|
pass
|
|
|
|
adapter = _adapter(transport_factory=lambda: _Broken())
|
|
assert await adapter.connect() is False
|
|
assert "relay unreachable" in adapter.status()["last_error"]
|
|
|
|
|
|
async def test_send_error_marks_token_dead_and_recovers(monkeypatch):
|
|
from coworker.connectors import relay_client
|
|
|
|
results = iter(
|
|
[SendResult(False, error="invalid_auth"), SendResult(True, message_id="ts")]
|
|
)
|
|
monkeypatch.setattr(relay_client, "_send_slack", lambda *a, **k: next(results))
|
|
adapter = _adapter()
|
|
|
|
await adapter.send("T1/C1", "hi")
|
|
assert adapter.status()["teams"]["T1"]["token_ok"] is False
|
|
await adapter.send("T1/C1", "hi again")
|
|
assert adapter.status()["teams"]["T1"]["token_ok"] is True
|
|
|
|
|
|
async def test_non_token_send_error_does_not_flag_token(monkeypatch):
|
|
from coworker.connectors import relay_client
|
|
|
|
monkeypatch.setattr(
|
|
relay_client,
|
|
"_send_slack",
|
|
lambda *a, **k: SendResult(False, error="channel_not_found"),
|
|
)
|
|
adapter = _adapter()
|
|
await adapter.send("T1/C1", "hi")
|
|
assert adapter.status()["teams"]["T1"]["token_ok"] is True
|
|
|
|
|
|
async def test_slack_get_token_error_marks_team(monkeypatch):
|
|
adapter = _adapter()
|
|
|
|
class _Resp:
|
|
def json(self):
|
|
return {"ok": False, "error": "account_inactive"}
|
|
|
|
class _Http:
|
|
def __init__(self, *a, **k):
|
|
pass
|
|
|
|
async def __aenter__(self):
|
|
return self
|
|
|
|
async def __aexit__(self, *a):
|
|
return False
|
|
|
|
async def get(self, *a, **k):
|
|
return _Resp()
|
|
|
|
import httpx
|
|
|
|
monkeypatch.setattr(httpx, "AsyncClient", _Http)
|
|
assert await adapter._slack_get("T1", "users.info", {"user": "U1"}) is None
|
|
assert adapter.status()["teams"]["T1"]["token_ok"] is False
|