mirror of
https://github.com/Canner/WrenAI.git
synced 2026-09-24 23:29:49 +08:00
fix(genbi): normalise null apps/schema_version in app index (#2406)
This commit is contained in:
@@ -50,8 +50,24 @@ def load_index(project_path: Path) -> dict:
|
||||
f"{path} is malformed — expected a mapping at the top level, "
|
||||
f"got {type(data).__name__}."
|
||||
)
|
||||
data.setdefault("schema_version", INDEX_SCHEMA_VERSION)
|
||||
data.setdefault("apps", {})
|
||||
# Normalise explicit null values (``apps:`` / ``schema_version:`` with no
|
||||
# value, e.g. from a hand-edit or a truncated write). ``setdefault`` is a
|
||||
# no-op when the key already exists, which left ``apps: None`` in place and
|
||||
# made every accessor crash with an opaque ``AttributeError`` deep in a
|
||||
# command. We check ``is None`` specifically rather than truthiness so a
|
||||
# valid falsy value is preserved and surfaced for validation instead of
|
||||
# being silently coerced to a default.
|
||||
if data.get("schema_version") is None:
|
||||
data["schema_version"] = INDEX_SCHEMA_VERSION
|
||||
apps = data.get("apps")
|
||||
if apps is None:
|
||||
apps = {}
|
||||
if not isinstance(apps, dict):
|
||||
raise MalformedIndexError(
|
||||
f"{path} is malformed — 'apps' must be a mapping, "
|
||||
f"got {type(apps).__name__}."
|
||||
)
|
||||
data["apps"] = apps
|
||||
return data
|
||||
|
||||
|
||||
|
||||
@@ -145,3 +145,50 @@ def test_register_rejects_path_traversal_name(tmp_path: Path) -> None:
|
||||
result = runner.invoke(app, ["genbi", "register", "../evil", "-p", str(project)])
|
||||
assert result.exit_code != 0
|
||||
assert "invalid app name" in result.output
|
||||
|
||||
|
||||
def test_load_index_normalises_null_apps(tmp_path: Path) -> None:
|
||||
# Regression: an explicit ``apps:`` with no value (hand-edit / truncated
|
||||
# write) is valid YAML and parses to None. setdefault() was a no-op since
|
||||
# the key existed, leaving ``apps: None`` so every accessor crashed with an
|
||||
# opaque AttributeError. load_index must normalise it to an empty dict.
|
||||
path = index_path(tmp_path)
|
||||
path.parent.mkdir(parents=True)
|
||||
path.write_text("schema_version: 1\napps:\n")
|
||||
|
||||
data = load_index(tmp_path)
|
||||
assert data["apps"] == {}
|
||||
assert data["schema_version"] == 1
|
||||
# And the normal accessor must not crash on it.
|
||||
from wren.genbi.index import get_app
|
||||
|
||||
assert get_app(tmp_path, "missing") is None
|
||||
|
||||
|
||||
def test_load_index_normalises_null_schema_version(tmp_path: Path) -> None:
|
||||
path = index_path(tmp_path)
|
||||
path.parent.mkdir(parents=True)
|
||||
path.write_text("schema_version:\napps: {}\n")
|
||||
|
||||
data = load_index(tmp_path)
|
||||
assert data["schema_version"] == 1
|
||||
|
||||
|
||||
def test_load_index_raises_on_non_mapping_apps(tmp_path: Path) -> None:
|
||||
path = index_path(tmp_path)
|
||||
path.parent.mkdir(parents=True)
|
||||
path.write_text("apps:\n - one\n - two\n") # apps is a list, not a mapping
|
||||
|
||||
with pytest.raises(MalformedIndexError):
|
||||
load_index(tmp_path)
|
||||
|
||||
|
||||
def test_load_index_preserves_falsy_schema_version(tmp_path: Path) -> None:
|
||||
# A literal 0 is a real value, not a missing one — it must be preserved
|
||||
# rather than coerced to the default, so only explicit null normalises.
|
||||
path = index_path(tmp_path)
|
||||
path.parent.mkdir(parents=True)
|
||||
path.write_text("schema_version: 0\napps: {}\n")
|
||||
|
||||
data = load_index(tmp_path)
|
||||
assert data["schema_version"] == 0
|
||||
|
||||
Reference in New Issue
Block a user