fix: widen None-deref guards to config-derived sibling sites + tests

Sibling sites of the salvaged #55997 fix, all reading user-editable
config values through .get(key, '').method(): MoA slot provider/model
labels, gateway quick-command alias targets (2 sites), gateway.proxy_url,
and gateway.relay_url. Regression tests for the contributor's two sites
plus the MoA labels.
This commit is contained in:
teknium1 2026-07-09 20:43:55 -07:00 committed by Teknium
parent 838aa742cb
commit a0972b9748
4 changed files with 45 additions and 5 deletions

View file

@ -120,7 +120,7 @@ _REFERENCE_SYSTEM_PROMPT = (
def _slot_label(slot: dict[str, str]) -> str:
return f"{slot.get('provider', '').strip()}:{slot.get('model', '').strip()}"
return f"{(slot.get('provider') or '').strip()}:{(slot.get('model') or '').strip()}"
def _slot_runtime(slot: dict[str, str]) -> dict[str, Any]:

View file

@ -36,7 +36,8 @@ def relay_url() -> Optional[str]:
from gateway.run import _load_gateway_config # late import to avoid cycle
cfg = _load_gateway_config()
url = (cfg.get("gateway") or {}).get("relay_url", "").strip()
url = (cfg.get("gateway") or {}).get("relay_url")
url = (url or "").strip()
if url:
return url.rstrip("/")
except Exception: # noqa: BLE001 - config absence/parse must never crash registration

View file

@ -9609,7 +9609,7 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
if isinstance(quick_commands, dict) and command in quick_commands:
qcmd = quick_commands[command]
if qcmd.get("type") == "alias":
target = qcmd.get("target", "").strip()
target = (qcmd.get("target") or "").strip()
if target:
target = target if target.startswith("/") else f"/{target}"
target_command = target.lstrip("/")
@ -10021,7 +10021,7 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
else:
return f"Quick command '/{command}' has no command defined."
elif qcmd.get("type") == "alias":
target = qcmd.get("target", "").strip()
target = (qcmd.get("target") or "").strip()
if target:
target = target if target.startswith("/") else f"/{target}"
target_command = target.lstrip("/")
@ -16523,7 +16523,8 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
if url:
return url.rstrip("/")
cfg = _load_gateway_config()
url = (cfg.get("gateway") or {}).get("proxy_url", "").strip()
url = (cfg.get("gateway") or {}).get("proxy_url")
url = (url or "").strip()
if url:
return url.rstrip("/")
return None

View file

@ -0,0 +1,38 @@
"""Regression tests for None-dereference guards on ``.get(key, "").method()``
patterns (#55997 salvage + config-derived sibling sites).
``dict.get(key, default)`` only returns the default when the key is ABSENT;
a present-but-null value sails through as None and crashes any chained
method call.
"""
from agent.anthropic_adapter import _convert_user_message
from agent.moa_loop import _slot_label
class TestAnthropicNullTextBlock:
def test_null_text_block_treated_as_empty(self):
"""A text block with ``text: null`` must not crash the empty-message
check in _convert_user_message (#55997)."""
result = _convert_user_message([{"type": "text", "text": None}])
# Must not raise; result is a valid user message dict
assert result["role"] == "user"
def test_mixed_null_and_real_text_blocks(self):
result = _convert_user_message(
[
{"type": "text", "text": None},
{"type": "text", "text": "hello"},
]
)
assert result["role"] == "user"
class TestMoaSlotLabelNullFields:
def test_null_provider_and_model(self):
"""MoA slot with ``provider: null`` in config.yaml must not crash
label construction."""
assert _slot_label({"provider": None, "model": None}) == ":" # type: ignore[arg-type]
def test_normal_slot(self):
assert _slot_label({"provider": "openrouter", "model": "m"}) == "openrouter:m"