fix(genbi): normalise null apps/schema_version in app index (#2406)

This commit is contained in:
Bartok
2026-06-29 17:56:49 +08:00
committed by GitHub
parent a2a37b3955
commit 0c031d60b0
2 changed files with 65 additions and 2 deletions
+18 -2
View File
@@ -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
+47
View File
@@ -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