diff --git a/core/wren/src/wren/policy.py b/core/wren/src/wren/policy.py index 9ded7e3a5e..54fff54bc0 100644 --- a/core/wren/src/wren/policy.py +++ b/core/wren/src/wren/policy.py @@ -156,9 +156,10 @@ def resolve_model_name( falls back to a case-insensitive scan. Returns ``None`` if no model matches. """ - model_set = ( - model_names if isinstance(model_names, (set, frozenset)) else set(model_names) - ) + # Drop non-str names so a corrupt models collection cannot crash .lower(). + if not isinstance(name, str) or not name: + return None + model_set = {n for n in model_names if isinstance(n, str) and n} if name in model_set: return name if quoted: diff --git a/core/wren/tests/unit/test_resolve_model_name_nonstr.py b/core/wren/tests/unit/test_resolve_model_name_nonstr.py new file mode 100644 index 0000000000..68daab26be --- /dev/null +++ b/core/wren/tests/unit/test_resolve_model_name_nonstr.py @@ -0,0 +1,25 @@ +"""resolve_model_name must ignore non-str names without AttributeError.""" + +from __future__ import annotations + +import pytest + +from wren.policy import resolve_model_name + +pytestmark = pytest.mark.unit + + +def test_resolve_filters_non_str_candidates() -> None: + names = {"Orders", None, 12, "", "customers"} # type: ignore[list-item] + assert resolve_model_name("orders", quoted=False, model_names=names) == "Orders" + assert resolve_model_name("missing", quoted=False, model_names=names) is None + + +def test_resolve_rejects_non_str_lookup() -> None: + assert resolve_model_name(None, quoted=False, model_names={"a"}) is None # type: ignore[arg-type] + assert resolve_model_name(1, quoted=True, model_names={"1"}) is None # type: ignore[arg-type] + + +def test_resolve_quoted_exact() -> None: + assert resolve_model_name("Orders", quoted=True, model_names={"Orders"}) == "Orders" + assert resolve_model_name("orders", quoted=True, model_names={"Orders"}) is None