mirror of
https://github.com/andrewyng/openworker.git
synced 2026-08-30 22:53:41 +00:00
221 lines
7.5 KiB
Python
221 lines
7.5 KiB
Python
"""Sidecar loopback routes for OpenWorker Cloud: /oauth/callback,
|
|
/auth/callback, /v1/cloud/*, connect-managed gating."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
from fastapi.testclient import TestClient
|
|
|
|
from coworker.server import SessionManager, create_app
|
|
|
|
|
|
def _allow_managed_state(state: str = "s") -> None:
|
|
from coworker import cloud
|
|
|
|
cloud._pending_managed_states[state] = cloud._now()
|
|
|
|
|
|
@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 test_cloud_status_signed_out(client):
|
|
body = client.get("/v1/cloud/status").json()
|
|
assert body == {
|
|
"signed_in": False,
|
|
"account": "",
|
|
"user_id": "",
|
|
"telemetry_enabled": True, # local default; nothing is sent while signed out
|
|
}
|
|
|
|
|
|
def test_connect_managed_requires_sign_in(client):
|
|
# notion, not gmail: the Google trio is managed_paused (CASA pending) and its
|
|
# guard fires before the sign-in check — see test_google_one_click_paused….
|
|
body = client.post("/v1/connectors/notion/connect-managed").json()
|
|
assert not body["ok"]
|
|
assert "not signed in" in body["error"]
|
|
|
|
|
|
def test_oauth_callback_writes_profile_and_returns_page(client):
|
|
_allow_managed_state()
|
|
resp = client.post(
|
|
"/oauth/callback",
|
|
data={
|
|
"provider": "google",
|
|
"connector": "gmail",
|
|
"connection_id": "conn_9",
|
|
"access_token": "ya29.tok",
|
|
"refresh_token": "1//r",
|
|
"expires_in": "3599",
|
|
"scope": "gmail.readonly",
|
|
"account": "a@b.c",
|
|
"app_state": "s",
|
|
},
|
|
)
|
|
assert resp.status_code == 200
|
|
# §30: the loopback page is a branded card, Title-cased connector name.
|
|
assert "Gmail connected" in resp.text
|
|
assert "Served locally by OpenWorker" in resp.text
|
|
|
|
# Multi-account: the callback lands in gmail:account:<email>; gmail:default
|
|
# is just the default pointer.
|
|
profile = client.manager.secrets.get("gmail:account:a@b.c")
|
|
assert profile["access_token"] == "ya29.tok"
|
|
assert profile["managed"] is True
|
|
assert profile["connection_id"] == "conn_9"
|
|
assert client.manager.secrets.get("gmail:default")["default_account"] == "a@b.c"
|
|
|
|
listed = {c["name"]: c for c in client.manager.list_connectors()}
|
|
assert listed["gmail"]["connected"]
|
|
assert listed["gmail"]["account"] == "a@b.c"
|
|
assert [a["email"] for a in listed["gmail"]["accounts"]] == ["a@b.c"]
|
|
|
|
|
|
def test_oauth_callback_error_shows_failure_page(client):
|
|
_allow_managed_state()
|
|
resp = client.post(
|
|
"/oauth/callback",
|
|
data={"connector": "gmail", "error": "access_denied", "app_state": "s"},
|
|
)
|
|
assert resp.status_code == 400
|
|
assert "access_denied" in resp.text
|
|
assert client.manager.secrets.get("gmail:default") is None
|
|
|
|
|
|
def test_oauth_callback_rejects_unmanaged_connector(client):
|
|
# telegram is manual-only (github gained a managed path with the App relay)
|
|
_allow_managed_state()
|
|
resp = client.post(
|
|
"/oauth/callback",
|
|
data={"connector": "telegram", "access_token": "x", "app_state": "s"},
|
|
)
|
|
assert resp.status_code == 400
|
|
assert client.manager.secrets.get("telegram:default") is None
|
|
|
|
|
|
def test_oauth_callback_rejects_unknown_and_replayed_state(client):
|
|
form = {
|
|
"provider": "google",
|
|
"connector": "gmail",
|
|
"access_token": "token",
|
|
"account": "a@b.c",
|
|
"app_state": "once",
|
|
}
|
|
assert client.post("/oauth/callback", data=form).status_code == 400
|
|
assert client.manager.secrets.get("gmail:default") is None
|
|
|
|
_allow_managed_state("once")
|
|
assert client.post("/oauth/callback", data=form).status_code == 200
|
|
assert client.post("/oauth/callback", data=form).status_code == 400
|
|
|
|
|
|
def test_auth_callback_rejects_unknown_state(client):
|
|
resp = client.get("/auth/callback", params={"code": "c", "state": "forged"})
|
|
assert resp.status_code == 400
|
|
assert "Sign-in failed" in resp.text
|
|
|
|
|
|
def test_disconnect_works_signed_out(client):
|
|
# manual profile, no cloud session: disconnect must not require the cloud
|
|
client.manager.secrets.put("gmail:default", {"type": "oauth", "access_token": "t"})
|
|
body = client.post("/v1/connectors/gmail/disconnect").json()
|
|
assert body["ok"]
|
|
assert client.manager.secrets.get("gmail:default") is None
|
|
|
|
|
|
SALES_MANIFEST = """---
|
|
id: sales
|
|
name: Sales Coworker
|
|
icon: chart
|
|
tagline: t
|
|
family: knowledge
|
|
workspace: deliverable
|
|
tools: [files, search, todo]
|
|
description: d
|
|
---
|
|
You are the Sales Coworker."""
|
|
|
|
|
|
def _stub_gallery(monkeypatch, markdown=SALES_MANIFEST, *, hash_ok=True):
|
|
import hashlib
|
|
|
|
from coworker import cloud
|
|
|
|
digest = "sha256:" + hashlib.sha256(markdown.encode()).hexdigest()
|
|
manifest = {
|
|
"slug": "sales",
|
|
"version": 1,
|
|
"manifest_markdown": markdown,
|
|
"manifest_hash": digest if hash_ok else "sha256:tampered",
|
|
}
|
|
events = []
|
|
monkeypatch.setattr(cloud, "gallery_manifest", lambda s, c, slug: manifest)
|
|
monkeypatch.setattr(
|
|
cloud, "gallery_install_event", lambda s, c, slug: events.append(slug)
|
|
)
|
|
return events
|
|
|
|
|
|
def test_gallery_install_runs_consent_flow(client, monkeypatch):
|
|
events = _stub_gallery(monkeypatch)
|
|
body = client.post("/v1/personas/install", json={"gallery_slug": "sales"}).json()
|
|
assert body["ok"], body
|
|
assert body["consent"][0]["id"] == "sales"
|
|
installed = {p["id"]: p for p in body["personas"]}
|
|
# lands disabled + unsurfaced pending explicit user approval (trust model)
|
|
assert installed["sales"]["enabled"] is False
|
|
assert events == ["sales"] # install event fired
|
|
|
|
|
|
def test_gallery_install_rejects_hash_mismatch(client, monkeypatch):
|
|
_stub_gallery(monkeypatch, hash_ok=False)
|
|
body = client.post("/v1/personas/install", json={"gallery_slug": "sales"}).json()
|
|
assert not body["ok"]
|
|
assert "hash" in body["error"]
|
|
|
|
|
|
def test_gallery_install_requires_sign_in(client, monkeypatch):
|
|
from coworker import cloud
|
|
|
|
monkeypatch.setattr(cloud, "gallery_manifest", lambda s, c, slug: None)
|
|
body = client.post("/v1/personas/install", json={"gallery_slug": "sales"}).json()
|
|
assert not body["ok"]
|
|
assert "sign-in" in body["error"]
|
|
|
|
|
|
def test_cloud_gallery_endpoint_signed_out(client):
|
|
body = client.get("/v1/cloud/gallery").json()
|
|
assert not body["ok"]
|
|
assert body["personas"] == []
|
|
|
|
|
|
def test_delete_persona_after_gallery_install(client, monkeypatch):
|
|
_stub_gallery(monkeypatch)
|
|
assert client.post("/v1/personas/install", json={"gallery_slug": "sales"}).json()[
|
|
"ok"
|
|
]
|
|
body = client.delete("/v1/personas/sales").json()
|
|
assert body["ok"]
|
|
assert "sales" not in {p["id"] for p in body["personas"]}
|
|
|
|
|
|
def test_cloud_status_carries_telemetry_pref_and_toggle_flips_it(client):
|
|
assert client.get("/v1/cloud/status").json()["telemetry_enabled"] is True
|
|
body = client.post("/v1/cloud/telemetry", json={"enabled": False}).json()
|
|
assert body["ok"] and body["telemetry_enabled"] is False
|
|
assert client.get("/v1/cloud/status").json()["telemetry_enabled"] is False
|
|
|
|
|
|
def test_delete_persona_refuses_builtin_and_unknown(client):
|
|
body = client.delete("/v1/personas/cowork").json()
|
|
assert not body["ok"] and "built-in" in body["error"]
|
|
body = client.delete("/v1/personas/ghost").json()
|
|
assert not body["ok"] and "unknown" in body["error"]
|