Files
openworker/tests/test_connections.py
T
Rohit C PrasadandDevika 2b45018ffa OpenWorker: initial import
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>
2026-07-21 11:09:41 -07:00

247 lines
9.1 KiB
Python

"""Phase 3 — connection hierarchy (UI-REFRESH §4).
Three layers gate a connector for a session: account-connected → persona-default-enabled →
session-override. `effective(connector)` = connected AND (override if present, else persona default,
else inherit-on). These tests pin the stores, the resolver, and the two runtime gating points
(inbound delivery + the engine's connector tools).
"""
import asyncio
from pathlib import Path
import pytest
from coworker.connections import (
PersonaConnectionStore,
SessionConnectionStore,
effective,
)
from coworker.connectors.base import MessageEvent, SessionSource
from coworker.personas import registry as persona_registry
from coworker.personas.manifest import load_manifest_file
from coworker.providers import ModelCapabilities, ProviderClient
from coworker.server.manager import SessionManager
from coworker.sessions import SessionRecord
@pytest.fixture(autouse=True)
def _isolate_state_dir(tmp_path, monkeypatch):
"""Isolate the global state/secret dir for every test here.
The `SessionManager` tests build a real `SecretStore()`, which defaults to the developer's
global state dir (`~/.config/coworker`) unless `COWORKER_STATE_DIR` is set — so without this a
test's `secrets.put("github:default", …)` would write a fake token into the real secret store.
Pin it at a throwaway dir. (Harmless for the pure store/resolver tests that use explicit paths.)
"""
monkeypatch.setenv("COWORKER_STATE_DIR", str(tmp_path / "state"))
class ScriptedProvider(ProviderClient):
def __init__(self, turns=None):
self._turns = list(turns or [])
def complete(self, *, model, messages, tools=None, **settings):
return self._turns.pop(0)
def capabilities(self, model):
return ModelCapabilities()
def _ops_manifest():
md = Path(persona_registry.__file__).parent / "builtin" / "ops.md"
return load_manifest_file(md, builtin=True)
def _channel_event(text="deploy failed", chat_id="C1", platform="slack"):
return MessageEvent(
text=text,
source=SessionSource(
platform=platform, chat_id=chat_id, user_name="bob", chat_type="channel"
),
)
def _dm_event(text="ping", chat_id="D1", platform="slack"):
return MessageEvent(
text=text,
source=SessionSource(
platform=platform, chat_id=chat_id, user_name="sue", chat_type="dm"
),
)
# -- stores + resolver ---------------------------------------------------------
def test_persona_defaults_seeded_from_manifest(tmp_path):
p = tmp_path / "persona_connections.json"
store = PersonaConnectionStore(p)
manifest = (
_ops_manifest()
) # recommends: github/slack/datadog core, pagerduty optional, +mcp
# Every CORE connector seeds ON regardless of whether it's connected yet; the optional one
# (pagerduty) seeds OFF; the mcp recommend is not a connector and is ignored. datadog is core but
# NOT in the connected set here — it still seeds True (effective() gates connectedness, not the
# seed), so it self-lights when datadog is later connected instead of being frozen False.
seeded = store.defaults_for("ops", manifest, connected={"github", "slack"})
assert seeded == {
"github": True,
"slack": True,
"datadog": True,
"pagerduty": False,
}
# While datadog is disconnected, effective() excludes it (connected-gate) but keeps github/slack.
assert effective(
connected={"github", "slack"}, persona_defaults=seeded, session_overrides={}
) == {"github": True, "slack": True}
# Once datadog connects, its True seed now lights up — no re-seed, no manual toggle needed.
assert effective(
connected={"github", "slack", "datadog"},
persona_defaults=seeded,
session_overrides={},
) == {"github": True, "slack": True, "datadog": True}
# persisted on first read: a fresh store over the same path reads the seeded row back
assert PersonaConnectionStore(p).get("ops") == seeded
# ...and a second read does NOT re-seed even if the connected set changed
assert store.defaults_for("ops", manifest, connected=set()) == seeded
def test_effective_resolution():
eff = effective(
connected={"slack", "github"},
persona_defaults={"slack": True, "github": True, "datadog": False},
session_overrides={"slack": False},
)
# slack muted by the session override; datadog not connected; github inherits the persona on.
assert eff == {"github": True}
def test_session_override_clear_inherits(tmp_path):
sstore = SessionConnectionStore(tmp_path / "session_connections.json")
defaults = {"slack": True}
sstore.set("s1", "slack", False)
assert sstore.get("s1") == {"slack": False}
# the override mutes slack despite the persona default being on
assert (
effective(
connected={"slack"},
persona_defaults=defaults,
session_overrides=sstore.get("s1"),
)
== {}
)
# clearing the override → the session inherits the persona default again (on)
sstore.clear("s1", "slack")
assert sstore.get("s1") == {}
assert effective(
connected={"slack"},
persona_defaults=defaults,
session_overrides=sstore.get("s1"),
) == {"slack": True}
def test_remove_session_clears_overrides(tmp_path):
p = tmp_path / "session_connections.json"
sstore = SessionConnectionStore(p)
sstore.set("s1", "slack", False)
sstore.set("s2", "github", False)
sstore.remove_session("s1")
assert sstore.get("s1") == {}
assert sstore.get("s2") == {"github": False} # other sessions untouched
# persisted
assert SessionConnectionStore(p).get("s1") == {}
def test_delete_session_clears_overrides(tmp_path):
mgr = SessionManager(workspace=tmp_path, provider=ScriptedProvider())
mgr.session_store.save(
SessionRecord(
session_id="sX",
workspace=str(tmp_path),
model="gpt-5.5",
mode="interactive",
agent="cowork",
)
)
mgr.session_connections.set("sX", "slack", False)
mgr.delete_session("sX")
assert mgr.session_connections.get("sX") == {}
# -- runtime gating: inbound ---------------------------------------------------
def test_muted_connector_not_delivered(tmp_path, monkeypatch):
mgr = SessionManager(workspace=tmp_path, provider=ScriptedProvider())
delivered: list[str] = []
async def fake_deliver(session_id, message, *, source=None):
delivered.append(session_id)
monkeypatch.setattr(mgr, "deliver_to_session", fake_deliver)
# Slack is account-connected (an inbound message implies it is); the inbound gate resolves
# against the effective set, which is connected AND not session-muted.
mgr.secrets.put(
"slack:default",
{"bot_token": "xoxb-test", "app_token": "xapp-test", "enabled": True},
)
# two sessions subscribe to the same Slack channel; one has muted Slack for itself
mgr.subscriptions.subscribe("sListen", "slack:C1")
mgr.subscriptions.subscribe("sMute", "slack:C1")
mgr.session_connections.set("sMute", "slack", False)
asyncio.run(mgr._dispatch_inbound(_channel_event()))
assert "sListen" in delivered # not muted → delivered
assert "sMute" not in delivered # muted → skipped
# ...but the message is still buffered for catch-up, even for the muted session
assert mgr.channel_buffer.recent("slack:C1")[-1]["text"] == "deploy failed"
def test_dm_muted_session_not_delivered(tmp_path, monkeypatch):
mgr = SessionManager(workspace=tmp_path, provider=ScriptedProvider())
delivered: list[str] = []
async def fake_deliver(session_id, message, *, source=None):
delivered.append(session_id)
monkeypatch.setattr(mgr, "deliver_to_session", fake_deliver)
mgr.set_dm_session("sDM")
mgr.session_connections.set("sDM", "slack", False) # mute slack for the DM session
asyncio.run(mgr._dispatch_inbound(_dm_event()))
assert delivered == [] # parked, not delivered
parked = mgr.unrouted.list()
assert parked and parked[0]["reason"] == "connector muted for DM session"
# -- runtime gating: outbound / tools ------------------------------------------
def test_muted_connector_tools_absent(tmp_path):
mgr = SessionManager(workspace=tmp_path, provider=ScriptedProvider())
# github connected so its tools would otherwise be exposed to a connectors persona (cowork)
mgr.secrets.put("github:default", {"token": "ghp_test", "enabled": True})
for sid in ("sOn", "sOff"):
mgr.session_store.save(
SessionRecord(
session_id=sid,
workspace=str(tmp_path),
model="gpt-5.5",
mode="interactive",
agent="cowork",
)
)
mgr.session_connections.set("sOff", "github", False) # mute github for sOff only
on_engine = mgr.get_engine("sOn")
off_engine = mgr.get_engine("sOff")
# the un-muted session still has github tools; the muted session's engine omits them
assert "github_search" in on_engine.registry.names()
assert "github_search" not in off_engine.registry.names()