mirror of
https://github.com/andrewyng/openworker.git
synced 2026-09-01 15:36:08 +00:00
135 lines
5.2 KiB
Python
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
|