mirror of
https://github.com/esphome/esphome.git
synced 2026-08-24 15:16:20 +00:00
155 lines
5.1 KiB
Python
155 lines
5.1 KiB
Python
"""Tests for the shelly_dimmer firmware download and prefetch extraction."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
from pathlib import Path
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
|
|
from esphome import external_files
|
|
from esphome.components.shelly_dimmer import light as shd
|
|
from esphome.config_validation import Invalid
|
|
from esphome.external_files import RemoteFile
|
|
|
|
|
|
def _sha(data: bytes) -> str:
|
|
return hashlib.sha256(data).hexdigest()
|
|
|
|
|
|
def test_prefetch_known_version(setup_core: Path) -> None:
|
|
entries = [{"firmware": {"version": "51.6", "update": True}}]
|
|
stages = list(shd.PREFETCH_FILES(entries))
|
|
url, sha = shd.KNOWN_FIRMWARE["51.6"]
|
|
assert stages == [[RemoteFile(url, shd._firmware_cache_path(sha))]]
|
|
|
|
|
|
def test_prefetch_normalizes_update_like_the_schema(setup_core: Path) -> None:
|
|
"""Quoted booleans behave as the schema will normalize them."""
|
|
url, sha = shd.KNOWN_FIRMWARE["51.6"]
|
|
off = [{"firmware": {"version": "51.6", "update": "false"}}]
|
|
assert list(shd.PREFETCH_FILES(off)) == [[]]
|
|
on = [{"firmware": {"version": "51.6", "update": "true"}}]
|
|
assert list(shd.PREFETCH_FILES(on)) == [
|
|
[RemoteFile(url, shd._firmware_cache_path(sha))]
|
|
]
|
|
|
|
|
|
def test_prefetch_rejects_malformed_sha256(setup_core: Path) -> None:
|
|
"""A raw sha256 that is not a hash never becomes a path component."""
|
|
entries = [
|
|
{
|
|
"firmware": {
|
|
"url": "https://example.com/fw.bin",
|
|
"sha256": "/tmp/payload",
|
|
"update": True,
|
|
}
|
|
}
|
|
]
|
|
assert list(shd.PREFETCH_FILES(entries)) == [[]]
|
|
|
|
|
|
def test_prefetch_skips_content_addressed_blob_on_disk(setup_core: Path) -> None:
|
|
"""A sha-keyed cache file needs no revalidation; get_firmware hashes it."""
|
|
url, sha = shd.KNOWN_FIRMWARE["51.6"]
|
|
shd._firmware_cache_path(sha).write_bytes(b"pinned firmware")
|
|
entries = [{"firmware": {"version": "51.6", "update": True}}]
|
|
assert list(shd.PREFETCH_FILES(entries)) == [[]]
|
|
|
|
|
|
def test_prefetch_explicit_url_without_sha(setup_core: Path) -> None:
|
|
url = "https://example.com/fw.bin"
|
|
entries = [{"firmware": {"url": url, "update": True}}]
|
|
stages = list(shd.PREFETCH_FILES(entries))
|
|
key = external_files.url_cache_key(url)
|
|
# No sha means the bytes cannot be verified, so the prefetch itself
|
|
# must carry the validator's strict no-stale policy.
|
|
assert stages == [
|
|
[RemoteFile(url, shd._firmware_cache_path(key), allow_stale=False)]
|
|
]
|
|
|
|
|
|
def test_prefetch_skips_no_update(setup_core: Path) -> None:
|
|
entries = [
|
|
{"firmware": {"version": "51.6"}},
|
|
{"firmware": "51.6"},
|
|
{"firmware": {"version": "0.0", "update": True}},
|
|
{},
|
|
]
|
|
assert list(shd.PREFETCH_FILES(entries)) == [[]]
|
|
|
|
|
|
def test_get_firmware_rejects_corrupted_cache(setup_core: Path) -> None:
|
|
"""A cached blob failing its hash check is discarded and re-downloaded."""
|
|
good = b"good firmware"
|
|
expected = _sha(good)
|
|
path = shd._firmware_cache_path(expected)
|
|
path.write_bytes(b"corrupted blob")
|
|
|
|
with patch(
|
|
"esphome.components.shelly_dimmer.light.external_files.download_content",
|
|
return_value=good,
|
|
) as mock_download:
|
|
result = shd.get_firmware(
|
|
{
|
|
"update": True,
|
|
"url": "https://example.com/fw.bin",
|
|
"sha256": expected,
|
|
}
|
|
)
|
|
|
|
mock_download.assert_called_once()
|
|
assert result == [int(b) for b in good]
|
|
|
|
|
|
def test_get_firmware_trusts_valid_cache(setup_core: Path) -> None:
|
|
"""A cached blob passing its hash check is used with zero network."""
|
|
good = b"good firmware"
|
|
expected = _sha(good)
|
|
shd._firmware_cache_path(expected).write_bytes(good)
|
|
|
|
with patch(
|
|
"esphome.components.shelly_dimmer.light.external_files.download_content"
|
|
) as mock_download:
|
|
result = shd.get_firmware(
|
|
{
|
|
"update": True,
|
|
"url": "https://example.com/fw.bin",
|
|
"sha256": expected,
|
|
}
|
|
)
|
|
|
|
mock_download.assert_not_called()
|
|
assert result == [int(b) for b in good]
|
|
|
|
|
|
def test_get_firmware_hash_mismatch_raises_and_uncaches(setup_core: Path) -> None:
|
|
"""A fresh download failing its hash check raises and is not cached."""
|
|
expected = _sha(b"expected firmware")
|
|
path = shd._firmware_cache_path(expected)
|
|
|
|
with (
|
|
patch(
|
|
"esphome.components.shelly_dimmer.light.external_files.download_content",
|
|
return_value=b"wrong firmware",
|
|
),
|
|
pytest.raises(Invalid, match="Hash mismatch"),
|
|
):
|
|
shd.get_firmware(
|
|
{"update": True, "url": "https://example.com/fw.bin", "sha256": expected}
|
|
)
|
|
|
|
assert not path.exists()
|
|
|
|
|
|
def test_get_firmware_without_sha_rejects_stale(setup_core: Path) -> None:
|
|
"""The unverifiable no-hash branch must not accept a stale copy."""
|
|
with patch(
|
|
"esphome.components.shelly_dimmer.light.external_files.download_content",
|
|
return_value=b"fw",
|
|
) as mock_download:
|
|
shd.get_firmware({"update": True, "url": "https://example.com/fw.bin"})
|
|
|
|
assert mock_download.call_args.kwargs["allow_stale"] is False
|