mirror of
https://github.com/esphome/esphome.git
synced 2026-09-17 18:18:43 +00:00
[store_yaml] Gate YAML discovery on component presence, fail on unreadable files, warn on unresolved includes
This commit is contained in:
@@ -135,6 +135,42 @@ def test_gather_raises_when_no_sources(project: Path) -> None:
|
||||
_gather_files(include_secrets=False)
|
||||
|
||||
|
||||
def test_gather_raises_on_unreadable_file(
|
||||
project: Path, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
"""An unreadable tracked file fails the build instead of producing a
|
||||
silently partial recovery blob."""
|
||||
_set_sources(project, "entry.yaml", "wifi.yaml")
|
||||
orig_read_bytes = Path.read_bytes
|
||||
|
||||
def fake_read_bytes(self: Path) -> bytes:
|
||||
if self.name == "wifi.yaml":
|
||||
raise OSError("permission denied")
|
||||
return orig_read_bytes(self)
|
||||
|
||||
monkeypatch.setattr(Path, "read_bytes", fake_read_bytes)
|
||||
with pytest.raises(EsphomeError, match="wifi.yaml"):
|
||||
_gather_files(include_secrets=False)
|
||||
|
||||
|
||||
def test_gather_warns_on_unresolved_includes(
|
||||
project: Path, caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
"""Substitution-pathed includes that discovery could not capture produce a
|
||||
warning naming them, so the user knows the blob is incomplete."""
|
||||
CORE.config_path = project / "entry.yaml"
|
||||
CORE.data["yaml_sources"] = DiscoveredYamlFiles(
|
||||
[project / "entry.yaml"], set(), ["${board}.yaml"]
|
||||
)
|
||||
with caplog.at_level("WARNING", logger="esphome.components.store_yaml"):
|
||||
files = _gather_files(include_secrets=False)
|
||||
assert len(files) == 1
|
||||
assert any(
|
||||
"${board}.yaml" in r.message and "not contain" in r.message
|
||||
for r in caplog.records
|
||||
)
|
||||
|
||||
|
||||
def test_pack_envelope_roundtrip() -> None:
|
||||
files = [
|
||||
("entry.yaml", b"esphome:\n name: test\n"),
|
||||
|
||||
@@ -1084,6 +1084,18 @@ def test_force_load_include_files_unresolved_log_level(
|
||||
assert matching == [expect_level]
|
||||
|
||||
|
||||
def test_force_load_include_files_returns_unresolved_paths(
|
||||
patch_include_file: None,
|
||||
) -> None:
|
||||
"""Includes with substitution-templated paths are reported back to the
|
||||
caller; resolvable ones are not."""
|
||||
templated = _StubInclude("${var}.yaml", unresolved=True)
|
||||
plain = _StubInclude("ok.yaml")
|
||||
unresolved = force_load_include_files({"a": templated, "b": plain})
|
||||
assert unresolved == [str(templated.file)]
|
||||
assert plain.load_calls == 1
|
||||
|
||||
|
||||
def test_force_load_include_files_warns_on_load_failure(
|
||||
patch_include_file: None,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
@@ -1169,6 +1181,16 @@ def test_discover_user_yaml_files_swallows_parse_errors(tmp_path: Path) -> None:
|
||||
assert isinstance(discovered, DiscoveredYamlFiles)
|
||||
|
||||
|
||||
def test_discover_user_yaml_files_reports_unresolved_includes(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
"""A substitution-templated `!include` path is surfaced in `.unresolved`."""
|
||||
entry = _write_entry_including(tmp_path, "${board}.yaml")
|
||||
discovered = discover_user_yaml_files(entry)
|
||||
assert len(discovered.unresolved) == 1
|
||||
assert "${board}.yaml" in discovered.unresolved[0]
|
||||
|
||||
|
||||
def test_discover_user_yaml_files_deduplicates(tmp_path: Path) -> None:
|
||||
"""The same file referenced twice appears once in `.files`."""
|
||||
_write(tmp_path, "wifi.yaml", "ssid: a\n")
|
||||
|
||||
Reference in New Issue
Block a user