feat: add config fallback for gateway version compatibility check
This commit is contained in:
@@ -6,8 +6,11 @@ import re
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
from app.core.config import settings
|
from app.core.config import settings
|
||||||
|
from app.core.logging import get_logger
|
||||||
from app.services.openclaw.gateway_rpc import (
|
from app.services.openclaw.gateway_rpc import (
|
||||||
GatewayConfig,
|
GatewayConfig,
|
||||||
|
OpenClawGatewayError,
|
||||||
|
openclaw_call,
|
||||||
openclaw_connect_metadata,
|
openclaw_connect_metadata,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,6 +19,8 @@ _CALVER_PATTERN = re.compile(
|
|||||||
re.IGNORECASE,
|
re.IGNORECASE,
|
||||||
)
|
)
|
||||||
_CONNECT_VERSION_PATH: tuple[str, ...] = ("server", "version")
|
_CONNECT_VERSION_PATH: tuple[str, ...] = ("server", "version")
|
||||||
|
_CONFIG_VERSION_PATH: tuple[str, ...] = ("config", "meta", "lastTouchedVersion")
|
||||||
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, slots=True)
|
@dataclass(frozen=True, slots=True)
|
||||||
@@ -84,6 +89,11 @@ def extract_connect_server_version(payload: object) -> str | None:
|
|||||||
return _coerce_version_string(_value_at_path(payload, _CONNECT_VERSION_PATH))
|
return _coerce_version_string(_value_at_path(payload, _CONNECT_VERSION_PATH))
|
||||||
|
|
||||||
|
|
||||||
|
def extract_config_last_touched_version(payload: object) -> str | None:
|
||||||
|
"""Extract a runtime version hint from config.get payload."""
|
||||||
|
return _coerce_version_string(_value_at_path(payload, _CONFIG_VERSION_PATH))
|
||||||
|
|
||||||
|
|
||||||
def evaluate_gateway_version(
|
def evaluate_gateway_version(
|
||||||
*,
|
*,
|
||||||
current_version: str | None,
|
current_version: str | None,
|
||||||
@@ -150,9 +160,21 @@ async def check_gateway_version_compatibility(
|
|||||||
*,
|
*,
|
||||||
minimum_version: str | None = None,
|
minimum_version: str | None = None,
|
||||||
) -> GatewayVersionCheckResult:
|
) -> GatewayVersionCheckResult:
|
||||||
"""Use connect metadata server.version and evaluate compatibility."""
|
"""Evaluate gateway compatibility using connect metadata with config fallback."""
|
||||||
connect_payload = await openclaw_connect_metadata(config=config)
|
connect_payload = await openclaw_connect_metadata(config=config)
|
||||||
current_version = extract_connect_server_version(connect_payload)
|
current_version = extract_connect_server_version(connect_payload)
|
||||||
|
if current_version is None or _parse_version_parts(current_version) is None:
|
||||||
|
try:
|
||||||
|
config_payload = await openclaw_call("config.get", config=config)
|
||||||
|
except OpenClawGatewayError as exc:
|
||||||
|
logger.debug(
|
||||||
|
"gateway.compat.config_get_fallback_unavailable reason=%s",
|
||||||
|
str(exc),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
fallback_version = extract_config_last_touched_version(config_payload)
|
||||||
|
if fallback_version is not None:
|
||||||
|
current_version = fallback_version
|
||||||
return evaluate_gateway_version(
|
return evaluate_gateway_version(
|
||||||
current_version=current_version,
|
current_version=current_version,
|
||||||
minimum_version=minimum_version,
|
minimum_version=minimum_version,
|
||||||
|
|||||||
@@ -35,6 +35,26 @@ def test_extract_connect_server_version_returns_none_when_server_version_missing
|
|||||||
assert gateway_compat.extract_connect_server_version(payload) is None
|
assert gateway_compat.extract_connect_server_version(payload) is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_extract_config_last_touched_version_reads_config_meta_last_touched_version() -> None:
|
||||||
|
payload = {
|
||||||
|
"config": {
|
||||||
|
"meta": {"lastTouchedVersion": "2026.2.9"},
|
||||||
|
"wizard": {"lastRunVersion": "2026.2.8"},
|
||||||
|
},
|
||||||
|
"parsed": {"meta": {"lastTouchedVersion": "2026.2.7"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
assert gateway_compat.extract_config_last_touched_version(payload) == "2026.2.9"
|
||||||
|
|
||||||
|
|
||||||
|
def test_extract_config_last_touched_version_returns_none_without_config_meta_last_touched_version() -> None:
|
||||||
|
payload = {
|
||||||
|
"config": {"wizard": {"lastRunVersion": "2026.2.9"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
assert gateway_compat.extract_config_last_touched_version(payload) is None
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("current_version", "minimum_version", "expected_compatible"),
|
("current_version", "minimum_version", "expected_compatible"),
|
||||||
[
|
[
|
||||||
@@ -97,7 +117,12 @@ async def test_check_gateway_version_compatibility_uses_connect_server_version_o
|
|||||||
"server": {"version": "2026.2.13"},
|
"server": {"version": "2026.2.13"},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async def _fake_openclaw_call(method: str, params: object = None, *, config: object) -> object:
|
||||||
|
_ = (method, params, config)
|
||||||
|
raise AssertionError("config.get fallback should not run for valid connect version")
|
||||||
|
|
||||||
monkeypatch.setattr(gateway_compat, "openclaw_connect_metadata", _fake_connect_metadata)
|
monkeypatch.setattr(gateway_compat, "openclaw_connect_metadata", _fake_connect_metadata)
|
||||||
|
monkeypatch.setattr(gateway_compat, "openclaw_call", _fake_openclaw_call)
|
||||||
|
|
||||||
result = await gateway_compat.check_gateway_version_compatibility(
|
result = await gateway_compat.check_gateway_version_compatibility(
|
||||||
GatewayConfig(url="ws://gateway.example/ws"),
|
GatewayConfig(url="ws://gateway.example/ws"),
|
||||||
@@ -116,7 +141,13 @@ async def test_check_gateway_version_compatibility_fails_without_server_version(
|
|||||||
_ = config
|
_ = config
|
||||||
return {"runtime": {"version": "2026.2.13"}}
|
return {"runtime": {"version": "2026.2.13"}}
|
||||||
|
|
||||||
|
async def _fake_openclaw_call(method: str, params: object = None, *, config: object) -> object:
|
||||||
|
_ = (params, config)
|
||||||
|
assert method == "config.get"
|
||||||
|
return {"config": {}}
|
||||||
|
|
||||||
monkeypatch.setattr(gateway_compat, "openclaw_connect_metadata", _fake_connect_metadata)
|
monkeypatch.setattr(gateway_compat, "openclaw_connect_metadata", _fake_connect_metadata)
|
||||||
|
monkeypatch.setattr(gateway_compat, "openclaw_call", _fake_openclaw_call)
|
||||||
|
|
||||||
result = await gateway_compat.check_gateway_version_compatibility(
|
result = await gateway_compat.check_gateway_version_compatibility(
|
||||||
GatewayConfig(url="ws://gateway.example/ws"),
|
GatewayConfig(url="ws://gateway.example/ws"),
|
||||||
@@ -129,14 +160,44 @@ async def test_check_gateway_version_compatibility_fails_without_server_version(
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_check_gateway_version_compatibility_rejects_non_calver_server_version(
|
async def test_check_gateway_version_compatibility_uses_config_get_fallback_when_connect_is_dev(
|
||||||
monkeypatch: pytest.MonkeyPatch,
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
) -> None:
|
) -> None:
|
||||||
async def _fake_connect_metadata(*, config: GatewayConfig) -> object | None:
|
async def _fake_connect_metadata(*, config: GatewayConfig) -> object | None:
|
||||||
_ = config
|
_ = config
|
||||||
return {"server": {"version": "dev"}}
|
return {"server": {"version": "dev"}}
|
||||||
|
|
||||||
|
async def _fake_openclaw_call(method: str, params: object = None, *, config: object) -> object:
|
||||||
|
_ = (params, config)
|
||||||
|
assert method == "config.get"
|
||||||
|
return {"config": {"meta": {"lastTouchedVersion": "2026.2.9"}}}
|
||||||
|
|
||||||
monkeypatch.setattr(gateway_compat, "openclaw_connect_metadata", _fake_connect_metadata)
|
monkeypatch.setattr(gateway_compat, "openclaw_connect_metadata", _fake_connect_metadata)
|
||||||
|
monkeypatch.setattr(gateway_compat, "openclaw_call", _fake_openclaw_call)
|
||||||
|
|
||||||
|
result = await gateway_compat.check_gateway_version_compatibility(
|
||||||
|
GatewayConfig(url="ws://gateway.example/ws"),
|
||||||
|
minimum_version="2026.1.30",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result.compatible is True
|
||||||
|
assert result.current_version == "2026.2.9"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_check_gateway_version_compatibility_rejects_non_calver_server_version_when_fallback_unavailable(
|
||||||
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
|
) -> None:
|
||||||
|
async def _fake_connect_metadata(*, config: GatewayConfig) -> object | None:
|
||||||
|
_ = config
|
||||||
|
return {"server": {"version": "dev"}}
|
||||||
|
|
||||||
|
async def _fake_openclaw_call(method: str, params: object = None, *, config: object) -> object:
|
||||||
|
_ = (method, params, config)
|
||||||
|
raise OpenClawGatewayError("method unavailable")
|
||||||
|
|
||||||
|
monkeypatch.setattr(gateway_compat, "openclaw_connect_metadata", _fake_connect_metadata)
|
||||||
|
monkeypatch.setattr(gateway_compat, "openclaw_call", _fake_openclaw_call)
|
||||||
|
|
||||||
result = await gateway_compat.check_gateway_version_compatibility(
|
result = await gateway_compat.check_gateway_version_compatibility(
|
||||||
GatewayConfig(url="ws://gateway.example/ws"),
|
GatewayConfig(url="ws://gateway.example/ws"),
|
||||||
|
|||||||
Reference in New Issue
Block a user