refactor(openclaw): remove REST facade (port 8004), unify on WebSocket
Remove the redundant OpenClaw REST service (port 8004) since frontend already uses WebSocket via Gateway (port 8765) → OpenClaw (port 18789). Deleted: - backend/apps/openclaw_service.py - backend/api/openclaw.py - backend/tests/test_openclaw_service_app.py - backend/tests/test_service_clients.py - shared/client/openclaw_client.py Updated: - backend/apps/__init__.py — remove openclaw_app exports - backend/api/__init__.py — remove openclaw_router - shared/client/__init__.py — remove OpenClawServiceClient - backend/services/gateway_openclaw_handlers.py — update docstring - start.sh — remove port 8004 service startup Architecture: - Before: Frontend → HTTP :8004 → subprocess openclaw CLI - After: Frontend → WS :8765 → Gateway → WS :18789 → OpenClaw Constraint: Frontend already uses WebSocket exclusively Confidence: high Scope-risk: low (frontend unchanged)
This commit is contained in:
@@ -1,148 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Tests for the extracted OpenClaw service app surface."""
|
||||
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from backend.apps.openclaw_service import create_app
|
||||
from backend.api import openclaw as openclaw_module
|
||||
|
||||
|
||||
class _FakeOpenClawCliService:
|
||||
def health(self):
|
||||
return {
|
||||
"status": "healthy",
|
||||
"service": "openclaw-service",
|
||||
"base_command": ["openclaw"],
|
||||
"cwd": "/tmp/openclaw",
|
||||
"binary_resolved": True,
|
||||
"reference_entry_available": True,
|
||||
"timeout_seconds": 15.0,
|
||||
}
|
||||
|
||||
def status(self):
|
||||
return {"runtimeVersion": "2026.3.24"}
|
||||
|
||||
def list_sessions(self):
|
||||
return {
|
||||
"sessions": [
|
||||
{"key": "main/session-1", "agentId": "main"},
|
||||
{"key": "analyst/session-2", "agentId": "analyst"},
|
||||
]
|
||||
}
|
||||
|
||||
def get_session(self, session_key: str):
|
||||
for session in self.list_sessions()["sessions"]:
|
||||
if session["key"] == session_key:
|
||||
return session
|
||||
raise KeyError(session_key)
|
||||
|
||||
def get_session_history(self, session_key: str, *, limit: int = 20):
|
||||
return {
|
||||
"sessionKey": session_key,
|
||||
"limit": limit,
|
||||
"items": [{"role": "assistant", "text": "hello"}],
|
||||
}
|
||||
|
||||
def status_model(self):
|
||||
from shared.models.openclaw import OpenClawStatus
|
||||
return OpenClawStatus(runtimeVersion="2026.3.24")
|
||||
|
||||
def get_session_model(self, session_key: str):
|
||||
from shared.models.openclaw import SessionEntry
|
||||
for session in self.list_sessions()["sessions"]:
|
||||
if session["key"] == session_key:
|
||||
return SessionEntry.model_validate(session, strict=False)
|
||||
raise KeyError(session_key)
|
||||
|
||||
def list_sessions_model(self):
|
||||
from shared.models.openclaw import SessionsList, SessionEntry
|
||||
sessions = [
|
||||
SessionEntry.model_validate(s, strict=False)
|
||||
for s in self.list_sessions()["sessions"]
|
||||
]
|
||||
return SessionsList(sessions=sessions)
|
||||
|
||||
def get_session_history_model(self, session_key: str, *, limit: int = 20):
|
||||
from shared.models.openclaw import SessionHistory
|
||||
raw = self.get_session_history(session_key, limit=limit)
|
||||
return SessionHistory(
|
||||
sessionKey=raw["sessionKey"],
|
||||
session_id=None,
|
||||
events=raw["items"],
|
||||
history=raw["items"],
|
||||
raw_text=None,
|
||||
)
|
||||
|
||||
def list_cron_jobs(self):
|
||||
return {"jobs": [{"id": "job-1", "name": "Daily sync"}]}
|
||||
|
||||
def list_cron_jobs_model(self):
|
||||
from shared.models.openclaw import CronList
|
||||
return CronList.from_raw(self.list_cron_jobs())
|
||||
|
||||
def list_approvals(self):
|
||||
return {"approvals": [{"approvalId": "ap-1", "toolName": "test_tool", "status": "pending"}]}
|
||||
|
||||
def list_approvals_model(self):
|
||||
from shared.models.openclaw import ApprovalsList
|
||||
return ApprovalsList.from_raw(self.list_approvals())
|
||||
|
||||
|
||||
def test_openclaw_service_routes_are_exposed():
|
||||
app = create_app()
|
||||
paths = {route.path for route in app.routes}
|
||||
|
||||
assert "/health" in paths
|
||||
assert "/api/status" in paths
|
||||
assert "/api/openclaw/status" in paths
|
||||
assert "/api/openclaw/sessions" in paths
|
||||
assert "/api/openclaw/sessions/{session_key:path}" in paths
|
||||
assert "/api/openclaw/sessions/{session_key:path}/history" in paths
|
||||
assert "/api/openclaw/cron" in paths
|
||||
assert "/api/openclaw/approvals" in paths
|
||||
|
||||
|
||||
def test_openclaw_service_read_routes():
|
||||
app = create_app()
|
||||
app.dependency_overrides[openclaw_module.get_openclaw_cli_service] = (
|
||||
lambda: _FakeOpenClawCliService()
|
||||
)
|
||||
|
||||
with TestClient(app) as client:
|
||||
health = client.get("/health")
|
||||
status = client.get("/api/status")
|
||||
openclaw_status = client.get("/api/openclaw/status")
|
||||
sessions = client.get("/api/openclaw/sessions")
|
||||
session = client.get("/api/openclaw/sessions/main/session-1")
|
||||
history = client.get("/api/openclaw/sessions/main/session-1/history", params={"limit": 5})
|
||||
cron = client.get("/api/openclaw/cron")
|
||||
approvals = client.get("/api/openclaw/approvals")
|
||||
|
||||
assert health.status_code == 200
|
||||
assert health.json()["service"] == "openclaw-service"
|
||||
assert status.status_code == 200
|
||||
assert status.json()["status"] == "operational"
|
||||
assert openclaw_status.status_code == 200
|
||||
assert openclaw_status.json()["runtime_version"] == "2026.3.24"
|
||||
assert sessions.status_code == 200
|
||||
assert len(sessions.json()["sessions"]) == 2
|
||||
assert session.status_code == 200
|
||||
assert session.json()["session"]["agent_id"] == "main"
|
||||
assert history.status_code == 200
|
||||
assert len(history.json()["events"]) == 1
|
||||
assert cron.status_code == 200
|
||||
assert cron.json()["jobs"][0]["id"] == "job-1"
|
||||
assert approvals.status_code == 200
|
||||
assert approvals.json()["approvals"][0]["approval_id"] == "ap-1"
|
||||
|
||||
|
||||
def test_openclaw_service_session_404():
|
||||
app = create_app()
|
||||
app.dependency_overrides[openclaw_module.get_openclaw_cli_service] = (
|
||||
lambda: _FakeOpenClawCliService()
|
||||
)
|
||||
|
||||
with TestClient(app) as client:
|
||||
response = client.get("/api/openclaw/sessions/missing")
|
||||
|
||||
assert response.status_code == 404
|
||||
@@ -1,132 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Tests for split-aware shared service clients."""
|
||||
|
||||
import pytest
|
||||
|
||||
from shared.client.control_client import ControlPlaneClient
|
||||
from shared.client.openclaw_client import OpenClawServiceClient
|
||||
from shared.client.runtime_client import RuntimeServiceClient
|
||||
|
||||
|
||||
class _DummyResponse:
|
||||
def __init__(self, payload):
|
||||
self._payload = payload
|
||||
|
||||
def raise_for_status(self):
|
||||
return None
|
||||
|
||||
def json(self):
|
||||
return self._payload
|
||||
|
||||
|
||||
class _DummyAsyncClient:
|
||||
def __init__(self):
|
||||
self.calls = []
|
||||
|
||||
async def get(self, path, params=None):
|
||||
self.calls.append(("get", path, params))
|
||||
if path == "/sessions/main/session-1":
|
||||
return _DummyResponse({"session": {"key": "main/session-1", "agentId": "main"}})
|
||||
return _DummyResponse({"path": path, "params": params})
|
||||
|
||||
async def post(self, path, json=None):
|
||||
self.calls.append(("post", path, json))
|
||||
return _DummyResponse({"path": path, "json": json})
|
||||
|
||||
async def put(self, path, json=None):
|
||||
self.calls.append(("put", path, json))
|
||||
return _DummyResponse({"path": path, "json": json})
|
||||
|
||||
async def aclose(self):
|
||||
return None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_control_plane_client_hits_current_workspace_and_guard_routes():
|
||||
client = ControlPlaneClient()
|
||||
client._client = _DummyAsyncClient()
|
||||
|
||||
await client.list_workspaces()
|
||||
await client.get_workspace("demo")
|
||||
await client.list_agents("demo")
|
||||
await client.get_agent("demo", "risk_manager")
|
||||
await client.fetch_pending_approvals()
|
||||
await client.approve_pending_approval("ap-1")
|
||||
await client.deny_pending_approval("ap-2", reason="nope")
|
||||
|
||||
assert client._client.calls == [
|
||||
("get", "/workspaces", None),
|
||||
("get", "/workspaces/demo", None),
|
||||
("get", "/workspaces/demo/agents", None),
|
||||
("get", "/workspaces/demo/agents/risk_manager", None),
|
||||
("get", "/guard/pending", None),
|
||||
(
|
||||
"post",
|
||||
"/guard/approve",
|
||||
{
|
||||
"approval_id": "ap-1",
|
||||
"one_time": True,
|
||||
"expires_in_minutes": 30,
|
||||
},
|
||||
),
|
||||
(
|
||||
"post",
|
||||
"/guard/deny",
|
||||
{
|
||||
"approval_id": "ap-2",
|
||||
"reason": "nope",
|
||||
},
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_runtime_service_client_hits_current_runtime_routes():
|
||||
client = RuntimeServiceClient()
|
||||
client._client = _DummyAsyncClient()
|
||||
|
||||
await client.fetch_context()
|
||||
await client.fetch_agents()
|
||||
await client.fetch_events()
|
||||
await client.fetch_gateway_port()
|
||||
await client.start_runtime({"tickers": ["AAPL"]})
|
||||
await client.stop_runtime(force=True)
|
||||
await client.restart_runtime({"tickers": ["MSFT"]})
|
||||
await client.fetch_current_runtime()
|
||||
await client.get_runtime_config()
|
||||
await client.update_runtime_config({"schedule_mode": "intraday"})
|
||||
|
||||
assert client._client.calls == [
|
||||
("get", "/context", None),
|
||||
("get", "/agents", None),
|
||||
("get", "/events", None),
|
||||
("get", "/gateway/port", None),
|
||||
("post", "/start", {"tickers": ["AAPL"]}),
|
||||
("post", "/stop?force=true", None),
|
||||
("post", "/restart", {"tickers": ["MSFT"]}),
|
||||
("get", "/current", None),
|
||||
("get", "/config", None),
|
||||
("put", "/config", {"schedule_mode": "intraday"}),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_openclaw_service_client_hits_current_openclaw_routes():
|
||||
client = OpenClawServiceClient()
|
||||
client._client = _DummyAsyncClient()
|
||||
|
||||
await client.fetch_status()
|
||||
await client.list_sessions()
|
||||
await client.get_session("main/session-1")
|
||||
await client.get_session_history("main/session-1", limit=5)
|
||||
await client.list_cron_jobs()
|
||||
await client.list_approvals()
|
||||
|
||||
assert client._client.calls == [
|
||||
("get", "/status", None),
|
||||
("get", "/sessions", None),
|
||||
("get", "/sessions/main/session-1", None),
|
||||
("get", "/sessions/main/session-1/history", {"limit": 5}),
|
||||
("get", "/cron", None),
|
||||
("get", "/approvals", None),
|
||||
]
|
||||
Reference in New Issue
Block a user