mirror of
https://github.com/TencentCloud/Octop.git
synced 2026-10-20 06:19:17 +08:00
* fix(bridge): keep team member streams alive through refresh Cloud rooms dropped member tokens after the host turn ended, and a refresh mid-answer had nothing in history to restore. Hold the room subscriber and replay the in-progress member text when a client subscribes again. Co-authored-by: Cursor <cursoragent@cursor.com> * docs: note cloud team stream refresh in the changelog Co-authored-by: Cursor <cursoragent@cursor.com> * fix(bridge): keep local login when peer credentials are rejected Peer auth failure used HTTP 401, so probing cloud collab cleared the local session and showed Unauthorized. Fixes #2166 Co-authored-by: Cursor <cursoragent@cursor.com> * feat(dashboard): split chat nav into experts, teams, and cloud Empty team and cloud lists can jump to create the first one, the host wrap-up lines up with the host avatar, and the cloud collab page explains how to connect. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(dashboard): add knowledge-base folder tree and unify list-panel rail toggles - Knowledge bases: add a resizable folder navigation tree beside the document list; move the breadcrumb into the docs toolbar and surface base id plus creator on list rows. - Knowledge bases / skill packages: move the collapse control onto the split divider as a hover pill and drop the separate detail header. - RailEdgeControl: pill button with center/before/after placement so two rails can share one divider. --------- Co-authored-by: jubaoliang <jubaoliang@tencent.com> Co-authored-by: Cursor <cursoragent@cursor.com>
107 lines
3.5 KiB
Python
107 lines
3.5 KiB
Python
"""Integration smoke for Bridge connection management API."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
from unittest.mock import AsyncMock, MagicMock, patch
|
|
|
|
from octop.infra.errors import ErrorCode, OctopError
|
|
|
|
|
|
async def test_bridge_list_empty(env: Any) -> None:
|
|
client, _srv, auth = env
|
|
r = await client.get("/api/bridge/connections", headers=auth)
|
|
assert r.status_code == 200
|
|
assert r.json() == []
|
|
|
|
|
|
async def test_bridge_probe_returns_expert_list(env: Any) -> None:
|
|
client, _srv, auth = env
|
|
peer_agents = [
|
|
{
|
|
"agent_id": "01REMOTE",
|
|
"id": 3,
|
|
"name": "Remote Expert",
|
|
"description": "Cloud helper",
|
|
"icon_url": "/experts/avatars/scene-healthcare.svg",
|
|
"color": "#112233",
|
|
"state": "idle",
|
|
"kind": "expert",
|
|
}
|
|
]
|
|
fake_resp = MagicMock()
|
|
fake_resp.status_code = 200
|
|
fake_resp.json.return_value = peer_agents
|
|
fake_resp.text = "[]"
|
|
|
|
mock_client = MagicMock()
|
|
mock_client.get = AsyncMock(return_value=fake_resp)
|
|
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
|
mock_client.__aexit__ = AsyncMock(return_value=None)
|
|
|
|
with (
|
|
patch(
|
|
"octop.infra.bridge.manager.login_peer",
|
|
new=AsyncMock(
|
|
return_value={
|
|
"access_token": "peer-token",
|
|
"user": {"username": "peer", "display_name": "Peer User"},
|
|
}
|
|
),
|
|
),
|
|
patch("octop.infra.bridge.manager.httpx.AsyncClient", return_value=mock_client),
|
|
):
|
|
r = await client.post(
|
|
"/api/bridge/probe",
|
|
headers=auth,
|
|
json={
|
|
"peer_base_url": "https://demo.octop.chat",
|
|
"peer_username": "peer",
|
|
"password": "secret",
|
|
},
|
|
)
|
|
assert r.status_code == 200, r.text
|
|
body = r.json()
|
|
assert body["peer_display_name"] == "Peer User"
|
|
assert body["agent_count"] == 1
|
|
assert body["agents"][0]["agent_id"] == "01REMOTE"
|
|
assert body["agents"][0]["name"] == "Remote Expert"
|
|
assert body["agents"][0]["description"] == "Cloud helper"
|
|
assert body["agents"][0]["icon_url"] == "/experts/avatars/scene-healthcare.svg"
|
|
|
|
# Probe must not create a persisted connection.
|
|
listed = await client.get("/api/bridge/connections", headers=auth)
|
|
assert listed.json() == []
|
|
|
|
|
|
async def test_bridge_probe_peer_rejection_stays_logged_in(env: Any) -> None:
|
|
"""Peer credential failure is 400, so the dashboard does not clear the local session."""
|
|
client, _srv, auth = env
|
|
with patch(
|
|
"octop.infra.bridge.manager.login_peer",
|
|
new=AsyncMock(
|
|
side_effect=OctopError(ErrorCode.BRIDGE_AUTH_FAILED, "peer rejected credentials")
|
|
),
|
|
):
|
|
r = await client.post(
|
|
"/api/bridge/probe",
|
|
headers=auth,
|
|
json={
|
|
"peer_base_url": "https://demo.octop.chat",
|
|
"peer_username": "peer",
|
|
"password": "secret",
|
|
},
|
|
)
|
|
assert r.status_code == 400, r.text
|
|
assert r.json()["error"]["code"] == "BRIDGE_AUTH_FAILED"
|
|
|
|
|
|
async def test_bridge_probe_validation(env: Any) -> None:
|
|
client, _srv, auth = env
|
|
r = await client.post(
|
|
"/api/bridge/probe",
|
|
headers=auth,
|
|
json={"peer_base_url": "https://demo.octop.chat", "peer_username": "", "password": "x"},
|
|
)
|
|
assert r.status_code == 422
|