feat: implement channel runtime connectors

This commit is contained in:
2026-06-03 16:22:44 +08:00
parent ee972441f5
commit c3d84b904a
105 changed files with 15621 additions and 322 deletions

View File

@ -0,0 +1,135 @@
from __future__ import annotations
import base64
from fastapi.testclient import TestClient
from external_connector.app import create_app
from external_connector.providers.fake import FakeProvider
from external_connector.state import SidecarStateStore
def test_fake_provider_lists_weixin_and_feishu(tmp_path) -> None:
provider = FakeProvider(SidecarStateStore(tmp_path / "state.json"))
connectors = provider.connectors()
assert [item["kind"] for item in connectors] == ["weixin", "feishu"]
assert connectors[0]["authType"] == "qr"
def test_fake_provider_session_flow(tmp_path) -> None:
provider = FakeProvider(SidecarStateStore(tmp_path / "state.json"))
session = provider.start_session(
{
"kind": "weixin",
"connectionId": "conn_1",
"channelId": "weixin-main",
"displayName": "Weixin Main",
"callbackBaseUrl": "http://beaver:8080",
"options": {},
}
)
loaded = provider.get_session(session["sessionId"])
assert session["status"] == "qr_ready"
prefix = "data:image/svg+xml;base64,"
assert session["qrImage"].startswith(prefix)
svg = base64.b64decode(session["qrImage"][len(prefix) :]).decode("utf-8")
assert svg.startswith("<svg")
assert "FAKE QR" in svg
assert loaded["sessionId"] == session["sessionId"]
def test_fake_provider_send_returns_idempotent_result(tmp_path) -> None:
provider = FakeProvider(SidecarStateStore(tmp_path / "state.json"))
payload = {
"requestId": "out_1",
"connectionId": "conn_1",
"channelId": "weixin-main",
"kind": "weixin",
"target": {"peerId": "peer-1", "peerType": "dm", "threadId": None},
"content": "hello",
"metadata": {},
}
first = provider.send(payload)
second = provider.send(payload)
assert first == second
assert first["ok"] is True
def test_sidecar_http_api_requires_bearer_token(tmp_path) -> None:
app = create_app(provider=FakeProvider(SidecarStateStore(tmp_path / "state.json")), api_token="sidecar-token")
with TestClient(app) as client:
response = client.get("/connectors")
assert response.status_code == 401
def test_sidecar_http_api_session_and_send(tmp_path) -> None:
app = create_app(provider=FakeProvider(SidecarStateStore(tmp_path / "state.json")), api_token="sidecar-token")
headers = {"Authorization": "Bearer sidecar-token"}
with TestClient(app) as client:
connectors = client.get("/connectors", headers=headers)
session = client.post(
"/connector-sessions",
headers=headers,
json={
"kind": "weixin",
"connectionId": "conn_1",
"channelId": "weixin-main",
"displayName": "Weixin Main",
"callbackBaseUrl": "http://beaver:8080",
"options": {},
},
)
session_id = session.json()["sessionId"]
loaded = client.get(f"/connector-sessions/{session_id}", headers=headers)
sent = client.post(
"/send",
headers=headers,
json={
"requestId": "out_1",
"connectionId": "conn_1",
"channelId": "weixin-main",
"kind": "weixin",
"target": {"peerId": "peer-1", "peerType": "dm", "threadId": None},
"content": "hello",
"metadata": {},
},
)
assert connectors.status_code == 200
assert session.status_code == 200
assert loaded.json()["sessionId"] == session_id
assert sent.json()["ok"] is True
def test_sidecar_http_api_returns_conflict_for_processing_send(tmp_path) -> None:
store = SidecarStateStore(tmp_path / "state.json", send_processing_ttl_seconds=60)
store.begin_send(connection_id="conn_1", request_id="out_1")
app = create_app(provider=FakeProvider(store), api_token="sidecar-token")
headers = {"Authorization": "Bearer sidecar-token"}
with TestClient(app) as client:
response = client.post(
"/send",
headers=headers,
json={
"requestId": "out_1",
"connectionId": "conn_1",
"channelId": "weixin-main",
"kind": "weixin",
"target": {"peerId": "peer-1", "peerType": "dm", "threadId": None},
"content": "hello",
"metadata": {},
},
)
assert response.status_code == 409
assert response.json()["retryAfterSeconds"] == 5