Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 2m38s
CD Pipeline / build-and-deploy (push) Failing after 24m19s
CD Pipeline / post-deploy-checks (push) Has been skipped
165 lines
4.9 KiB
Python
165 lines
4.9 KiB
Python
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from typing import Any
|
|
|
|
import httpx
|
|
import pytest
|
|
|
|
from src.services import openclaw as openclaw_module
|
|
from src.services.openclaw import OpenClawService
|
|
|
|
|
|
class _FakeRegistry:
|
|
def get_model(self, provider: str, use_case: str) -> str:
|
|
return "qwen2.5:7b-instruct"
|
|
|
|
def get_provider_options(self, provider: str) -> dict[str, Any]:
|
|
return {"num_predict": 32, "temperature": 0.1, "top_p": 0.9}
|
|
|
|
|
|
@dataclass
|
|
class _FakeEndpoint:
|
|
provider_name: str
|
|
url: str
|
|
|
|
|
|
class _FakeRoute:
|
|
def __init__(self, endpoints: list[_FakeEndpoint]) -> None:
|
|
self._endpoints = endpoints
|
|
|
|
def all_endpoints_in_order(self) -> list[_FakeEndpoint]:
|
|
return self._endpoints
|
|
|
|
|
|
class _FakeManager:
|
|
def __init__(self, endpoints: list[_FakeEndpoint]) -> None:
|
|
self._endpoints = endpoints
|
|
self.emit_side_effects: list[bool] = []
|
|
|
|
async def select_provider(self, *, emit_side_effects: bool = False) -> _FakeRoute:
|
|
self.emit_side_effects.append(emit_side_effects)
|
|
return _FakeRoute(self._endpoints)
|
|
|
|
|
|
class _FakeResponse:
|
|
status_code = 200
|
|
|
|
def raise_for_status(self) -> None:
|
|
return None
|
|
|
|
def json(self) -> dict[str, Any]:
|
|
return {"response": '{"action_title":"ok"}'}
|
|
|
|
|
|
class _FakeClient:
|
|
def __init__(self, fail_urls: set[str]) -> None:
|
|
self.fail_urls = fail_urls
|
|
self.posted_urls: list[str] = []
|
|
|
|
async def post(self, url: str, **kwargs: Any) -> _FakeResponse:
|
|
self.posted_urls.append(url)
|
|
if url in self.fail_urls:
|
|
raise httpx.ConnectError("offline")
|
|
return _FakeResponse()
|
|
|
|
|
|
def _prepared_alert_context(service: OpenClawService) -> dict[str, Any]:
|
|
context = service._prepare_paid_cloud_route_context(
|
|
{
|
|
"alert_type": "HostHighCpuLoad",
|
|
"source": "test-alert-producer",
|
|
"target_resource": "node-110",
|
|
}
|
|
)
|
|
assert context is not None
|
|
return context
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_legacy_ollama_uses_failover_order_before_gemini(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
monkeypatch.setattr(openclaw_module, "get_model_registry", lambda: _FakeRegistry())
|
|
monkeypatch.setattr(openclaw_module.settings, "OPENCLAW_TIMEOUT", 30)
|
|
monkeypatch.setattr(openclaw_module.settings, "OLLAMA_DIAGNOSE_TIMEOUT_SECONDS", 200)
|
|
manager = _FakeManager(
|
|
[
|
|
_FakeEndpoint("ollama_gcp_a", "http://gcp-a:11435"),
|
|
_FakeEndpoint("ollama_gcp_b", "http://gcp-b:11436"),
|
|
_FakeEndpoint("ollama_local", "http://local-111:11434"),
|
|
_FakeEndpoint("gemini", ""),
|
|
],
|
|
)
|
|
monkeypatch.setattr(
|
|
openclaw_module,
|
|
"get_ollama_failover_manager",
|
|
lambda: manager,
|
|
)
|
|
|
|
client = _FakeClient(fail_urls={"http://gcp-a:11435/api/generate"})
|
|
service = object.__new__(OpenClawService)
|
|
|
|
async def _get_client() -> _FakeClient:
|
|
return client
|
|
|
|
monkeypatch.setattr(service, "_get_client", _get_client)
|
|
|
|
result, ok = await service._call_ollama(
|
|
"diagnose",
|
|
alert_context=_prepared_alert_context(service),
|
|
)
|
|
|
|
assert ok is True
|
|
assert result == '{"action_title":"ok"}'
|
|
assert client.posted_urls == [
|
|
"http://gcp-a:11435/api/generate",
|
|
"http://gcp-b:11436/api/generate",
|
|
]
|
|
assert manager.emit_side_effects == [True]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_legacy_ollama_falls_back_to_configured_three_layer_urls(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
monkeypatch.setattr(openclaw_module, "get_model_registry", lambda: _FakeRegistry())
|
|
monkeypatch.setattr(openclaw_module.settings, "OPENCLAW_TIMEOUT", 30)
|
|
monkeypatch.setattr(openclaw_module.settings, "OLLAMA_DIAGNOSE_TIMEOUT_SECONDS", 200)
|
|
monkeypatch.setattr(openclaw_module.settings, "OLLAMA_URL", "http://gcp-a:11435")
|
|
monkeypatch.setattr(openclaw_module.settings, "OLLAMA_SECONDARY_URL", "http://gcp-b:11436")
|
|
monkeypatch.setattr(openclaw_module.settings, "OLLAMA_FALLBACK_URL", "http://local-111:11434")
|
|
manager = _FakeManager([_FakeEndpoint("gemini", "")])
|
|
monkeypatch.setattr(
|
|
openclaw_module,
|
|
"get_ollama_failover_manager",
|
|
lambda: manager,
|
|
)
|
|
|
|
client = _FakeClient(
|
|
fail_urls={
|
|
"http://gcp-a:11435/api/generate",
|
|
"http://gcp-b:11436/api/generate",
|
|
},
|
|
)
|
|
service = object.__new__(OpenClawService)
|
|
|
|
async def _get_client() -> _FakeClient:
|
|
return client
|
|
|
|
monkeypatch.setattr(service, "_get_client", _get_client)
|
|
|
|
result, ok = await service._call_ollama(
|
|
"diagnose",
|
|
alert_context=_prepared_alert_context(service),
|
|
)
|
|
|
|
assert ok is True
|
|
assert result == '{"action_title":"ok"}'
|
|
assert client.posted_urls == [
|
|
"http://gcp-a:11435/api/generate",
|
|
"http://gcp-b:11436/api/generate",
|
|
"http://local-111:11434/api/generate",
|
|
]
|
|
assert manager.emit_side_effects == [True]
|