forked from cleveragents/cleveragents-core
8e9aa7af48
- Promote _request() to public method request() in ServerHttpClient to fix encapsulation violation across sync_client and remote_project - Fix WebSocket connect() to raise NotImplementedError with clear TODO documenting that real websockets transport is not yet implemented - Fix thread safety: protect last_event_id write under self._lock in ws_client.process_event() - Broaden exception handling in sync() to catch ServerTimeoutError and A2aNotAvailableError in addition to ServerConnectionError - Add has_next to PageResult in benchmark and test helpers - Add comments explaining Retry-After header logging-only behavior and why blocking time.sleep is acceptable in sync client - Update all test steps, robot helpers, and benchmarks to use the renamed public request() method and updated connect() behavior Refs: #335, #336, #337, #338
141 lines
4.0 KiB
Python
141 lines
4.0 KiB
Python
"""Helper script for plan_sync.robot integration tests."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
from unittest.mock import MagicMock
|
|
|
|
import httpx
|
|
|
|
_SRC = str(Path(__file__).resolve().parents[1] / "src")
|
|
if _SRC not in sys.path:
|
|
sys.path.insert(0, _SRC)
|
|
|
|
from cleveragents.client.http_client import ServerHttpClient # noqa: E402
|
|
from cleveragents.client.sync_client import ( # noqa: E402
|
|
ConflictPolicy,
|
|
ExecutionResult,
|
|
PlanSyncClient,
|
|
SyncScope,
|
|
SyncSummary,
|
|
)
|
|
|
|
|
|
def _mock_response(
|
|
status: int = 200, body: dict | list | None = None
|
|
) -> httpx.Response:
|
|
return httpx.Response(
|
|
status_code=status,
|
|
json=body,
|
|
headers={},
|
|
request=httpx.Request("GET", "http://mock"),
|
|
)
|
|
|
|
|
|
def scope_active() -> None:
|
|
scope = SyncScope(actions=True, skills=False, tools=True, projects=False)
|
|
types = scope.active_types()
|
|
assert types == ["actions", "tools"], f"Got {types}"
|
|
print("plan-sync-scope-ok")
|
|
|
|
|
|
def sync_create() -> None:
|
|
mock_http = MagicMock(spec=ServerHttpClient)
|
|
mock_http.request.return_value = _mock_response(200, {"id": "srv-new"})
|
|
client = PlanSyncClient(mock_http)
|
|
items = [{"id": "a1"}, {"id": "a2"}]
|
|
summary = client.sync(items, "actions")
|
|
assert summary.created == 2
|
|
print("plan-sync-create-ok")
|
|
|
|
|
|
def sync_dry_run() -> None:
|
|
mock_http = MagicMock(spec=ServerHttpClient)
|
|
client = PlanSyncClient(mock_http)
|
|
items = [{"id": "a1"}]
|
|
summary = client.sync(items, "actions", dry_run=True)
|
|
assert summary.dry_run is True
|
|
assert summary.created == 1
|
|
print("plan-sync-dry-run-ok")
|
|
|
|
|
|
def execute_plan() -> None:
|
|
mock_http = MagicMock(spec=ServerHttpClient)
|
|
mock_http.request.return_value = _mock_response(
|
|
200, {"server_plan_id": "srv-001", "status": "submitted"}
|
|
)
|
|
client = PlanSyncClient(mock_http)
|
|
result = client.execute_plan("plan-001")
|
|
assert result.plan_id == "plan-001"
|
|
assert result.status == "submitted"
|
|
print("plan-sync-execute-ok")
|
|
|
|
|
|
def apply_plan() -> None:
|
|
mock_http = MagicMock(spec=ServerHttpClient)
|
|
mock_http.request.return_value = _mock_response(
|
|
200, {"server_plan_id": "srv-002", "status": "applying"}
|
|
)
|
|
client = PlanSyncClient(mock_http)
|
|
result = client.apply_plan("plan-002")
|
|
assert result.status == "applying"
|
|
print("plan-sync-apply-ok")
|
|
|
|
|
|
def plan_status() -> None:
|
|
mock_http = MagicMock(spec=ServerHttpClient)
|
|
mock_http.request.return_value = _mock_response(
|
|
200, {"phase": "running", "progress": 50}
|
|
)
|
|
client = PlanSyncClient(mock_http)
|
|
status = client.get_plan_status("srv-001")
|
|
assert status["phase"] == "running"
|
|
print("plan-sync-status-ok")
|
|
|
|
|
|
def conflict_policy() -> None:
|
|
mock_http = MagicMock(spec=ServerHttpClient)
|
|
client = PlanSyncClient(mock_http, conflict_policy=ConflictPolicy.LOCAL_WINS)
|
|
assert client.conflict_policy == ConflictPolicy.LOCAL_WINS
|
|
client.conflict_policy = ConflictPolicy.SERVER_WINS
|
|
assert client.conflict_policy == ConflictPolicy.SERVER_WINS
|
|
print("plan-sync-conflict-policy-ok")
|
|
|
|
|
|
def summary_total() -> None:
|
|
s = SyncSummary(created=2, updated=1, skipped=1, errors=1)
|
|
assert s.total_processed == 5
|
|
print("plan-sync-summary-total-ok")
|
|
|
|
|
|
def exec_result_attrs() -> None:
|
|
r = ExecutionResult(plan_id="p1", server_plan_id="sp1", status="done", message="ok")
|
|
assert r.message == "ok"
|
|
assert r.server_plan_id == "sp1"
|
|
print("plan-sync-exec-result-ok")
|
|
|
|
|
|
_COMMANDS = {
|
|
"scope-active": scope_active,
|
|
"sync-create": sync_create,
|
|
"sync-dry-run": sync_dry_run,
|
|
"execute-plan": execute_plan,
|
|
"apply-plan": apply_plan,
|
|
"plan-status": plan_status,
|
|
"conflict-policy": conflict_policy,
|
|
"summary-total": summary_total,
|
|
"exec-result-attrs": exec_result_attrs,
|
|
}
|
|
|
|
|
|
def main() -> None:
|
|
if len(sys.argv) < 2 or sys.argv[1] not in _COMMANDS:
|
|
print(f"Usage: {sys.argv[0]} <{'|'.join(_COMMANDS)}>", file=sys.stderr)
|
|
sys.exit(1)
|
|
_COMMANDS[sys.argv[1]]()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|