Files
openworker/tests/test_slack_workspaces.py

135 lines
5.2 KiB
Python

"""Add/remove workspace for the managed Slack relay (M3.5 Step 2).
Add = the managed OAuth callback writes `slack:team:<id>` and hot-reloads the
gateway (no app restart). Remove = per-workspace disconnect: cloud routing row
deleted best-effort, local token dropped, gateway reloaded; the LAST removal
flips the connector off without resurrecting any stored manual creds.
"""
from __future__ import annotations
import pytest
from fastapi.testclient import TestClient
from coworker import cloud
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
def _install_form(team_id: str) -> dict:
state = f"slack-{team_id}"
cloud._pending_managed_states[state] = cloud._now()
return {
"connector": "slack",
"team_id": team_id,
"access_token": f"xoxb-{team_id}",
"bot_user_id": "B1",
"slack_user_id": f"U_{team_id}",
"account": f"Workspace {team_id}",
"team_domain": f"dom-{team_id.lower()}",
"connection_id": f"conn_{team_id}",
"app_state": state,
}
def _no_cloud(monkeypatch):
"""The cloud row delete is best-effort HTTP; record instead of calling out."""
import coworker.cloud as cloud
calls: list[str] = []
monkeypatch.setattr(
cloud, "slack_disconnect_workspace", lambda s, c, team_id: calls.append(team_id)
)
return calls
def test_managed_callback_installs_and_hot_reloads(client, monkeypatch):
refreshes = []
async def _refresh():
refreshes.append(True)
return []
monkeypatch.setattr(client.manager, "refresh_gateway", _refresh)
resp = client.post("/oauth/callback", data=_install_form("T1"))
assert resp.status_code == 200 and "Slack connected" in resp.text
assert client.manager.secrets.get("slack:team:T1")["bot_token"] == "xoxb-T1"
# The broker-resolved workspace domain persists (names collide; domains don't)
# and rides the workspaces list for the GUI's group headers.
assert client.manager.secrets.get("slack:team:T1")["domain"] == "dom-t1"
slack = [
c
for c in client.get("/v1/connectors").json()["connectors"]
if c["name"] == "slack"
][0]
assert [w["domain"] for w in slack["workspaces"]] == ["dom-t1"]
assert slack["workspaces"][0]["approval_owner_ids"] == ["U_T1"]
assert client.manager.secrets.get("slack:default")["mode"] == "relay"
assert refreshes # new workspace's token loads without an app restart
# a second workspace instals alongside, not instead
client.post("/oauth/callback", data=_install_form("T2"))
assert client.manager.secrets.get("slack:team:T1") is not None
assert client.manager.secrets.get("slack:team:T2") is not None
def test_disconnect_one_workspace_keeps_the_other(client, monkeypatch):
cloud_calls = _no_cloud(monkeypatch)
for t in ("T1", "T2"):
client.post("/oauth/callback", data=_install_form(t))
body = client.post("/v1/connectors/slack/workspaces/T1/disconnect").json()
assert body["ok"] is True and body["remaining_workspaces"] == 1
assert cloud_calls == ["T1"]
assert client.manager.secrets.get("slack:team:T1") is None
assert client.manager.secrets.get("slack:team:T2") is not None
# connector still connected in relay mode for the surviving workspace
slack = next(c for c in client.manager.list_connectors() if c["name"] == "slack")
assert slack["connected"] is True
assert [w["team_id"] for w in slack["workspaces"]] == ["T2"]
def test_disconnect_last_workspace_flips_connector_off(client, monkeypatch):
_no_cloud(monkeypatch)
client.post("/oauth/callback", data=_install_form("T1"))
body = client.post("/v1/connectors/slack/workspaces/T1/disconnect").json()
assert body["ok"] is True and body["remaining_workspaces"] == 0
assert client.manager.secrets.get("slack:default") is None
slack = next(c for c in client.manager.list_connectors() if c["name"] == "slack")
assert slack["connected"] is False
def test_last_disconnect_never_resurrects_manual_creds(client, monkeypatch):
# Manual Socket Mode tokens stored BEFORE the relay switch must stay stored
# but disabled — removing the last workspace must not start listening on them.
_no_cloud(monkeypatch)
client.manager.secrets.put(
"slack:default",
{"type": "token", "bot_token": "xoxb-manual", "app_token": "xapp-manual"},
)
client.post("/oauth/callback", data=_install_form("T1"))
client.post("/v1/connectors/slack/workspaces/T1/disconnect")
default = client.manager.secrets.get("slack:default")
assert default["bot_token"] == "xoxb-manual" # creds kept for a manual re-enable
assert default["enabled"] is False and "mode" not in default
from coworker.connectors import load_settings
assert load_settings(client.manager.secrets)["slack"].enabled is False
def test_disconnect_unknown_workspace_errors(client, monkeypatch):
_no_cloud(monkeypatch)
body = client.post("/v1/connectors/slack/workspaces/T_NOPE/disconnect").json()
assert body["ok"] is False