mirror of
https://github.com/esphome/esphome.git
synced 2025-09-13 16:52:18 +01:00
Improve coverage for various core modules (#10663)
This commit is contained in:
@@ -36,3 +36,10 @@ def fixture_path() -> Path:
|
||||
Location of all fixture files.
|
||||
"""
|
||||
return here / "fixtures"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def setup_core(tmp_path: Path) -> Path:
|
||||
"""Set up CORE with test paths."""
|
||||
CORE.config_path = str(tmp_path / "test.yaml")
|
||||
return tmp_path
|
||||
|
187
tests/unit_tests/test_config_validation_paths.py
Normal file
187
tests/unit_tests/test_config_validation_paths.py
Normal file
@@ -0,0 +1,187 @@
|
||||
"""Tests for config_validation.py path-related functions."""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome import config_validation as cv
|
||||
|
||||
|
||||
def test_directory_valid_path(setup_core: Path) -> None:
|
||||
"""Test directory validator with valid directory."""
|
||||
test_dir = setup_core / "test_directory"
|
||||
test_dir.mkdir()
|
||||
|
||||
result = cv.directory("test_directory")
|
||||
|
||||
assert result == "test_directory"
|
||||
|
||||
|
||||
def test_directory_absolute_path(setup_core: Path) -> None:
|
||||
"""Test directory validator with absolute path."""
|
||||
test_dir = setup_core / "test_directory"
|
||||
test_dir.mkdir()
|
||||
|
||||
result = cv.directory(str(test_dir))
|
||||
|
||||
assert result == str(test_dir)
|
||||
|
||||
|
||||
def test_directory_nonexistent_path(setup_core: Path) -> None:
|
||||
"""Test directory validator raises error for non-existent directory."""
|
||||
with pytest.raises(
|
||||
vol.Invalid, match="Could not find directory.*nonexistent_directory"
|
||||
):
|
||||
cv.directory("nonexistent_directory")
|
||||
|
||||
|
||||
def test_directory_file_instead_of_directory(setup_core: Path) -> None:
|
||||
"""Test directory validator raises error when path is a file."""
|
||||
test_file = setup_core / "test_file.txt"
|
||||
test_file.write_text("content")
|
||||
|
||||
with pytest.raises(vol.Invalid, match="is not a directory"):
|
||||
cv.directory("test_file.txt")
|
||||
|
||||
|
||||
def test_directory_with_parent_directory(setup_core: Path) -> None:
|
||||
"""Test directory validator with nested directory structure."""
|
||||
nested_dir = setup_core / "parent" / "child" / "grandchild"
|
||||
nested_dir.mkdir(parents=True)
|
||||
|
||||
result = cv.directory("parent/child/grandchild")
|
||||
|
||||
assert result == "parent/child/grandchild"
|
||||
|
||||
|
||||
def test_file_valid_path(setup_core: Path) -> None:
|
||||
"""Test file_ validator with valid file."""
|
||||
test_file = setup_core / "test_file.yaml"
|
||||
test_file.write_text("test content")
|
||||
|
||||
result = cv.file_("test_file.yaml")
|
||||
|
||||
assert result == "test_file.yaml"
|
||||
|
||||
|
||||
def test_file_absolute_path(setup_core: Path) -> None:
|
||||
"""Test file_ validator with absolute path."""
|
||||
test_file = setup_core / "test_file.yaml"
|
||||
test_file.write_text("test content")
|
||||
|
||||
result = cv.file_(str(test_file))
|
||||
|
||||
assert result == str(test_file)
|
||||
|
||||
|
||||
def test_file_nonexistent_path(setup_core: Path) -> None:
|
||||
"""Test file_ validator raises error for non-existent file."""
|
||||
with pytest.raises(vol.Invalid, match="Could not find file.*nonexistent_file.yaml"):
|
||||
cv.file_("nonexistent_file.yaml")
|
||||
|
||||
|
||||
def test_file_directory_instead_of_file(setup_core: Path) -> None:
|
||||
"""Test file_ validator raises error when path is a directory."""
|
||||
test_dir = setup_core / "test_directory"
|
||||
test_dir.mkdir()
|
||||
|
||||
with pytest.raises(vol.Invalid, match="is not a file"):
|
||||
cv.file_("test_directory")
|
||||
|
||||
|
||||
def test_file_with_parent_directory(setup_core: Path) -> None:
|
||||
"""Test file_ validator with file in nested directory."""
|
||||
nested_dir = setup_core / "configs" / "sensors"
|
||||
nested_dir.mkdir(parents=True)
|
||||
test_file = nested_dir / "temperature.yaml"
|
||||
test_file.write_text("sensor config")
|
||||
|
||||
result = cv.file_("configs/sensors/temperature.yaml")
|
||||
|
||||
assert result == "configs/sensors/temperature.yaml"
|
||||
|
||||
|
||||
def test_directory_handles_trailing_slash(setup_core: Path) -> None:
|
||||
"""Test directory validator handles trailing slashes correctly."""
|
||||
test_dir = setup_core / "test_dir"
|
||||
test_dir.mkdir()
|
||||
|
||||
result = cv.directory("test_dir/")
|
||||
assert result == "test_dir/"
|
||||
|
||||
result = cv.directory("test_dir")
|
||||
assert result == "test_dir"
|
||||
|
||||
|
||||
def test_file_handles_various_extensions(setup_core: Path) -> None:
|
||||
"""Test file_ validator works with different file extensions."""
|
||||
yaml_file = setup_core / "config.yaml"
|
||||
yaml_file.write_text("yaml content")
|
||||
assert cv.file_("config.yaml") == "config.yaml"
|
||||
|
||||
yml_file = setup_core / "config.yml"
|
||||
yml_file.write_text("yml content")
|
||||
assert cv.file_("config.yml") == "config.yml"
|
||||
|
||||
txt_file = setup_core / "readme.txt"
|
||||
txt_file.write_text("text content")
|
||||
assert cv.file_("readme.txt") == "readme.txt"
|
||||
|
||||
no_ext_file = setup_core / "LICENSE"
|
||||
no_ext_file.write_text("license content")
|
||||
assert cv.file_("LICENSE") == "LICENSE"
|
||||
|
||||
|
||||
def test_directory_with_symlink(setup_core: Path) -> None:
|
||||
"""Test directory validator follows symlinks."""
|
||||
actual_dir = setup_core / "actual_directory"
|
||||
actual_dir.mkdir()
|
||||
|
||||
symlink_dir = setup_core / "symlink_directory"
|
||||
symlink_dir.symlink_to(actual_dir)
|
||||
|
||||
result = cv.directory("symlink_directory")
|
||||
assert result == "symlink_directory"
|
||||
|
||||
|
||||
def test_file_with_symlink(setup_core: Path) -> None:
|
||||
"""Test file_ validator follows symlinks."""
|
||||
actual_file = setup_core / "actual_file.txt"
|
||||
actual_file.write_text("content")
|
||||
|
||||
symlink_file = setup_core / "symlink_file.txt"
|
||||
symlink_file.symlink_to(actual_file)
|
||||
|
||||
result = cv.file_("symlink_file.txt")
|
||||
assert result == "symlink_file.txt"
|
||||
|
||||
|
||||
def test_directory_error_shows_full_path(setup_core: Path) -> None:
|
||||
"""Test directory validator error message includes full path."""
|
||||
with pytest.raises(vol.Invalid, match=".*missing_dir.*full path:.*"):
|
||||
cv.directory("missing_dir")
|
||||
|
||||
|
||||
def test_file_error_shows_full_path(setup_core: Path) -> None:
|
||||
"""Test file_ validator error message includes full path."""
|
||||
with pytest.raises(vol.Invalid, match=".*missing_file.yaml.*full path:.*"):
|
||||
cv.file_("missing_file.yaml")
|
||||
|
||||
|
||||
def test_directory_with_spaces_in_name(setup_core: Path) -> None:
|
||||
"""Test directory validator handles spaces in directory names."""
|
||||
dir_with_spaces = setup_core / "my test directory"
|
||||
dir_with_spaces.mkdir()
|
||||
|
||||
result = cv.directory("my test directory")
|
||||
assert result == "my test directory"
|
||||
|
||||
|
||||
def test_file_with_spaces_in_name(setup_core: Path) -> None:
|
||||
"""Test file_ validator handles spaces in file names."""
|
||||
file_with_spaces = setup_core / "my test file.yaml"
|
||||
file_with_spaces.write_text("content")
|
||||
|
||||
result = cv.file_("my test file.yaml")
|
||||
assert result == "my test file.yaml"
|
196
tests/unit_tests/test_external_files.py
Normal file
196
tests/unit_tests/test_external_files.py
Normal file
@@ -0,0 +1,196 @@
|
||||
"""Tests for external_files.py functions."""
|
||||
|
||||
from pathlib import Path
|
||||
import time
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
import requests
|
||||
|
||||
from esphome import external_files
|
||||
from esphome.config_validation import Invalid
|
||||
from esphome.core import CORE, TimePeriod
|
||||
|
||||
|
||||
def test_compute_local_file_dir(setup_core: Path) -> None:
|
||||
"""Test compute_local_file_dir creates and returns correct path."""
|
||||
domain = "font"
|
||||
|
||||
result = external_files.compute_local_file_dir(domain)
|
||||
|
||||
assert isinstance(result, Path)
|
||||
assert result == Path(CORE.data_dir) / domain
|
||||
assert result.exists()
|
||||
assert result.is_dir()
|
||||
|
||||
|
||||
def test_compute_local_file_dir_nested(setup_core: Path) -> None:
|
||||
"""Test compute_local_file_dir works with nested domains."""
|
||||
domain = "images/icons"
|
||||
|
||||
result = external_files.compute_local_file_dir(domain)
|
||||
|
||||
assert result == Path(CORE.data_dir) / "images" / "icons"
|
||||
assert result.exists()
|
||||
assert result.is_dir()
|
||||
|
||||
|
||||
def test_is_file_recent_with_recent_file(setup_core: Path) -> None:
|
||||
"""Test is_file_recent returns True for recently created file."""
|
||||
test_file = setup_core / "recent.txt"
|
||||
test_file.write_text("content")
|
||||
|
||||
refresh = TimePeriod(seconds=3600)
|
||||
|
||||
result = external_files.is_file_recent(str(test_file), refresh)
|
||||
|
||||
assert result is True
|
||||
|
||||
|
||||
def test_is_file_recent_with_old_file(setup_core: Path) -> None:
|
||||
"""Test is_file_recent returns False for old file."""
|
||||
test_file = setup_core / "old.txt"
|
||||
test_file.write_text("content")
|
||||
|
||||
old_time = time.time() - 7200
|
||||
|
||||
with patch("os.path.getctime", return_value=old_time):
|
||||
refresh = TimePeriod(seconds=3600)
|
||||
|
||||
result = external_files.is_file_recent(str(test_file), refresh)
|
||||
|
||||
assert result is False
|
||||
|
||||
|
||||
def test_is_file_recent_nonexistent_file(setup_core: Path) -> None:
|
||||
"""Test is_file_recent returns False for non-existent file."""
|
||||
test_file = setup_core / "nonexistent.txt"
|
||||
refresh = TimePeriod(seconds=3600)
|
||||
|
||||
result = external_files.is_file_recent(str(test_file), refresh)
|
||||
|
||||
assert result is False
|
||||
|
||||
|
||||
def test_is_file_recent_with_zero_refresh(setup_core: Path) -> None:
|
||||
"""Test is_file_recent with zero refresh period returns False."""
|
||||
test_file = setup_core / "test.txt"
|
||||
test_file.write_text("content")
|
||||
|
||||
# Mock getctime to return a time 10 seconds ago
|
||||
with patch("os.path.getctime", return_value=time.time() - 10):
|
||||
refresh = TimePeriod(seconds=0)
|
||||
result = external_files.is_file_recent(str(test_file), refresh)
|
||||
assert result is False
|
||||
|
||||
|
||||
@patch("esphome.external_files.requests.head")
|
||||
def test_has_remote_file_changed_not_modified(
|
||||
mock_head: MagicMock, setup_core: Path
|
||||
) -> None:
|
||||
"""Test has_remote_file_changed returns False when file not modified."""
|
||||
test_file = setup_core / "cached.txt"
|
||||
test_file.write_text("cached content")
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 304
|
||||
mock_head.return_value = mock_response
|
||||
|
||||
url = "https://example.com/file.txt"
|
||||
result = external_files.has_remote_file_changed(url, str(test_file))
|
||||
|
||||
assert result is False
|
||||
mock_head.assert_called_once()
|
||||
|
||||
call_args = mock_head.call_args
|
||||
headers = call_args[1]["headers"]
|
||||
assert external_files.IF_MODIFIED_SINCE in headers
|
||||
assert external_files.CACHE_CONTROL in headers
|
||||
|
||||
|
||||
@patch("esphome.external_files.requests.head")
|
||||
def test_has_remote_file_changed_modified(
|
||||
mock_head: MagicMock, setup_core: Path
|
||||
) -> None:
|
||||
"""Test has_remote_file_changed returns True when file modified."""
|
||||
test_file = setup_core / "cached.txt"
|
||||
test_file.write_text("cached content")
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
mock_head.return_value = mock_response
|
||||
|
||||
url = "https://example.com/file.txt"
|
||||
result = external_files.has_remote_file_changed(url, str(test_file))
|
||||
|
||||
assert result is True
|
||||
|
||||
|
||||
def test_has_remote_file_changed_no_local_file(setup_core: Path) -> None:
|
||||
"""Test has_remote_file_changed returns True when local file doesn't exist."""
|
||||
test_file = setup_core / "nonexistent.txt"
|
||||
|
||||
url = "https://example.com/file.txt"
|
||||
result = external_files.has_remote_file_changed(url, str(test_file))
|
||||
|
||||
assert result is True
|
||||
|
||||
|
||||
@patch("esphome.external_files.requests.head")
|
||||
def test_has_remote_file_changed_network_error(
|
||||
mock_head: MagicMock, setup_core: Path
|
||||
) -> None:
|
||||
"""Test has_remote_file_changed handles network errors gracefully."""
|
||||
test_file = setup_core / "cached.txt"
|
||||
test_file.write_text("cached content")
|
||||
|
||||
mock_head.side_effect = requests.exceptions.RequestException("Network error")
|
||||
|
||||
url = "https://example.com/file.txt"
|
||||
|
||||
with pytest.raises(Invalid, match="Could not check if.*Network error"):
|
||||
external_files.has_remote_file_changed(url, str(test_file))
|
||||
|
||||
|
||||
@patch("esphome.external_files.requests.head")
|
||||
def test_has_remote_file_changed_timeout(
|
||||
mock_head: MagicMock, setup_core: Path
|
||||
) -> None:
|
||||
"""Test has_remote_file_changed respects timeout."""
|
||||
test_file = setup_core / "cached.txt"
|
||||
test_file.write_text("cached content")
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 304
|
||||
mock_head.return_value = mock_response
|
||||
|
||||
url = "https://example.com/file.txt"
|
||||
external_files.has_remote_file_changed(url, str(test_file))
|
||||
|
||||
call_args = mock_head.call_args
|
||||
assert call_args[1]["timeout"] == external_files.NETWORK_TIMEOUT
|
||||
|
||||
|
||||
def test_compute_local_file_dir_creates_parent_dirs(setup_core: Path) -> None:
|
||||
"""Test compute_local_file_dir creates parent directories."""
|
||||
domain = "level1/level2/level3/level4"
|
||||
|
||||
result = external_files.compute_local_file_dir(domain)
|
||||
|
||||
assert result.exists()
|
||||
assert result.is_dir()
|
||||
assert result.parent.name == "level3"
|
||||
assert result.parent.parent.name == "level2"
|
||||
assert result.parent.parent.parent.name == "level1"
|
||||
|
||||
|
||||
def test_is_file_recent_handles_float_seconds(setup_core: Path) -> None:
|
||||
"""Test is_file_recent works with float seconds in TimePeriod."""
|
||||
test_file = setup_core / "test.txt"
|
||||
test_file.write_text("content")
|
||||
|
||||
refresh = TimePeriod(seconds=3600.5)
|
||||
|
||||
result = external_files.is_file_recent(str(test_file), refresh)
|
||||
|
||||
assert result is True
|
129
tests/unit_tests/test_platformio_api.py
Normal file
129
tests/unit_tests/test_platformio_api.py
Normal file
@@ -0,0 +1,129 @@
|
||||
"""Tests for platformio_api.py path functions."""
|
||||
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
from esphome import platformio_api
|
||||
from esphome.core import CORE
|
||||
|
||||
|
||||
def test_idedata_firmware_elf_path(setup_core: Path) -> None:
|
||||
"""Test IDEData.firmware_elf_path returns correct path."""
|
||||
CORE.build_path = str(setup_core / "build" / "test")
|
||||
CORE.name = "test"
|
||||
raw_data = {"prog_path": "/path/to/firmware.elf"}
|
||||
idedata = platformio_api.IDEData(raw_data)
|
||||
|
||||
assert idedata.firmware_elf_path == "/path/to/firmware.elf"
|
||||
|
||||
|
||||
def test_idedata_firmware_bin_path(setup_core: Path) -> None:
|
||||
"""Test IDEData.firmware_bin_path returns Path with .bin extension."""
|
||||
CORE.build_path = str(setup_core / "build" / "test")
|
||||
CORE.name = "test"
|
||||
prog_path = str(Path("/path/to/firmware.elf"))
|
||||
raw_data = {"prog_path": prog_path}
|
||||
idedata = platformio_api.IDEData(raw_data)
|
||||
|
||||
result = idedata.firmware_bin_path
|
||||
assert isinstance(result, str)
|
||||
expected = str(Path("/path/to/firmware.bin"))
|
||||
assert result == expected
|
||||
assert result.endswith(".bin")
|
||||
|
||||
|
||||
def test_idedata_firmware_bin_path_preserves_directory(setup_core: Path) -> None:
|
||||
"""Test firmware_bin_path preserves the directory structure."""
|
||||
CORE.build_path = str(setup_core / "build" / "test")
|
||||
CORE.name = "test"
|
||||
prog_path = str(Path("/complex/path/to/build/firmware.elf"))
|
||||
raw_data = {"prog_path": prog_path}
|
||||
idedata = platformio_api.IDEData(raw_data)
|
||||
|
||||
result = idedata.firmware_bin_path
|
||||
expected = str(Path("/complex/path/to/build/firmware.bin"))
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_idedata_extra_flash_images(setup_core: Path) -> None:
|
||||
"""Test IDEData.extra_flash_images returns list of FlashImage objects."""
|
||||
CORE.build_path = str(setup_core / "build" / "test")
|
||||
CORE.name = "test"
|
||||
raw_data = {
|
||||
"prog_path": "/path/to/firmware.elf",
|
||||
"extra": {
|
||||
"flash_images": [
|
||||
{"path": "/path/to/bootloader.bin", "offset": "0x1000"},
|
||||
{"path": "/path/to/partition.bin", "offset": "0x8000"},
|
||||
]
|
||||
},
|
||||
}
|
||||
idedata = platformio_api.IDEData(raw_data)
|
||||
|
||||
images = idedata.extra_flash_images
|
||||
assert len(images) == 2
|
||||
assert all(isinstance(img, platformio_api.FlashImage) for img in images)
|
||||
assert images[0].path == "/path/to/bootloader.bin"
|
||||
assert images[0].offset == "0x1000"
|
||||
assert images[1].path == "/path/to/partition.bin"
|
||||
assert images[1].offset == "0x8000"
|
||||
|
||||
|
||||
def test_idedata_extra_flash_images_empty(setup_core: Path) -> None:
|
||||
"""Test extra_flash_images returns empty list when no extra images."""
|
||||
CORE.build_path = str(setup_core / "build" / "test")
|
||||
CORE.name = "test"
|
||||
raw_data = {"prog_path": "/path/to/firmware.elf", "extra": {"flash_images": []}}
|
||||
idedata = platformio_api.IDEData(raw_data)
|
||||
|
||||
images = idedata.extra_flash_images
|
||||
assert images == []
|
||||
|
||||
|
||||
def test_idedata_cc_path(setup_core: Path) -> None:
|
||||
"""Test IDEData.cc_path returns compiler path."""
|
||||
CORE.build_path = str(setup_core / "build" / "test")
|
||||
CORE.name = "test"
|
||||
raw_data = {
|
||||
"prog_path": "/path/to/firmware.elf",
|
||||
"cc_path": "/Users/test/.platformio/packages/toolchain-xtensa32/bin/xtensa-esp32-elf-gcc",
|
||||
}
|
||||
idedata = platformio_api.IDEData(raw_data)
|
||||
|
||||
assert (
|
||||
idedata.cc_path
|
||||
== "/Users/test/.platformio/packages/toolchain-xtensa32/bin/xtensa-esp32-elf-gcc"
|
||||
)
|
||||
|
||||
|
||||
def test_flash_image_dataclass() -> None:
|
||||
"""Test FlashImage dataclass stores path and offset correctly."""
|
||||
image = platformio_api.FlashImage(path="/path/to/image.bin", offset="0x10000")
|
||||
|
||||
assert image.path == "/path/to/image.bin"
|
||||
assert image.offset == "0x10000"
|
||||
|
||||
|
||||
def test_load_idedata_returns_dict(setup_core: Path) -> None:
|
||||
"""Test _load_idedata returns parsed idedata dict when successful."""
|
||||
CORE.build_path = str(setup_core / "build" / "test")
|
||||
CORE.name = "test"
|
||||
|
||||
# Create required files
|
||||
platformio_ini = setup_core / "build" / "test" / "platformio.ini"
|
||||
platformio_ini.parent.mkdir(parents=True, exist_ok=True)
|
||||
platformio_ini.touch()
|
||||
|
||||
idedata_path = setup_core / ".esphome" / "idedata" / "test.json"
|
||||
idedata_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
idedata_path.write_text('{"prog_path": "/test/firmware.elf"}')
|
||||
|
||||
with patch("esphome.platformio_api.run_platformio_cli_run") as mock_run:
|
||||
mock_run.return_value = '{"prog_path": "/test/firmware.elf"}'
|
||||
|
||||
config = {"name": "test"}
|
||||
result = platformio_api._load_idedata(config)
|
||||
|
||||
assert result is not None
|
||||
assert isinstance(result, dict)
|
||||
assert result["prog_path"] == "/test/firmware.elf"
|
182
tests/unit_tests/test_storage_json.py
Normal file
182
tests/unit_tests/test_storage_json.py
Normal file
@@ -0,0 +1,182 @@
|
||||
"""Tests for storage_json.py path functions."""
|
||||
|
||||
from pathlib import Path
|
||||
import sys
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome import storage_json
|
||||
from esphome.core import CORE
|
||||
|
||||
|
||||
def test_storage_path(setup_core: Path) -> None:
|
||||
"""Test storage_path returns correct path for current config."""
|
||||
CORE.config_path = str(setup_core / "my_device.yaml")
|
||||
|
||||
result = storage_json.storage_path()
|
||||
|
||||
data_dir = Path(CORE.data_dir)
|
||||
expected = str(data_dir / "storage" / "my_device.yaml.json")
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_ext_storage_path(setup_core: Path) -> None:
|
||||
"""Test ext_storage_path returns correct path for given filename."""
|
||||
result = storage_json.ext_storage_path("other_device.yaml")
|
||||
|
||||
data_dir = Path(CORE.data_dir)
|
||||
expected = str(data_dir / "storage" / "other_device.yaml.json")
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_ext_storage_path_handles_various_extensions(setup_core: Path) -> None:
|
||||
"""Test ext_storage_path works with different file extensions."""
|
||||
result_yml = storage_json.ext_storage_path("device.yml")
|
||||
assert result_yml.endswith("device.yml.json")
|
||||
|
||||
result_no_ext = storage_json.ext_storage_path("device")
|
||||
assert result_no_ext.endswith("device.json")
|
||||
|
||||
result_path = storage_json.ext_storage_path("my/device.yaml")
|
||||
assert result_path.endswith("device.yaml.json")
|
||||
|
||||
|
||||
def test_esphome_storage_path(setup_core: Path) -> None:
|
||||
"""Test esphome_storage_path returns correct path."""
|
||||
result = storage_json.esphome_storage_path()
|
||||
|
||||
data_dir = Path(CORE.data_dir)
|
||||
expected = str(data_dir / "esphome.json")
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_ignored_devices_storage_path(setup_core: Path) -> None:
|
||||
"""Test ignored_devices_storage_path returns correct path."""
|
||||
result = storage_json.ignored_devices_storage_path()
|
||||
|
||||
data_dir = Path(CORE.data_dir)
|
||||
expected = str(data_dir / "ignored-devices.json")
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_trash_storage_path(setup_core: Path) -> None:
|
||||
"""Test trash_storage_path returns correct path."""
|
||||
CORE.config_path = str(setup_core / "configs" / "device.yaml")
|
||||
|
||||
result = storage_json.trash_storage_path()
|
||||
|
||||
expected = str(setup_core / "configs" / "trash")
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_archive_storage_path(setup_core: Path) -> None:
|
||||
"""Test archive_storage_path returns correct path."""
|
||||
CORE.config_path = str(setup_core / "configs" / "device.yaml")
|
||||
|
||||
result = storage_json.archive_storage_path()
|
||||
|
||||
expected = str(setup_core / "configs" / "archive")
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_storage_path_with_subdirectory(setup_core: Path) -> None:
|
||||
"""Test storage paths work correctly when config is in subdirectory."""
|
||||
subdir = setup_core / "configs" / "basement"
|
||||
subdir.mkdir(parents=True, exist_ok=True)
|
||||
CORE.config_path = str(subdir / "sensor.yaml")
|
||||
|
||||
result = storage_json.storage_path()
|
||||
|
||||
data_dir = Path(CORE.data_dir)
|
||||
expected = str(data_dir / "storage" / "sensor.yaml.json")
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_storage_json_firmware_bin_path_property(setup_core: Path) -> None:
|
||||
"""Test StorageJSON firmware_bin_path property."""
|
||||
storage = storage_json.StorageJSON(
|
||||
storage_version=1,
|
||||
name="test_device",
|
||||
friendly_name="Test Device",
|
||||
comment=None,
|
||||
esphome_version="2024.1.0",
|
||||
src_version=None,
|
||||
address="192.168.1.100",
|
||||
web_port=80,
|
||||
target_platform="ESP32",
|
||||
build_path="build/test_device",
|
||||
firmware_bin_path="/path/to/firmware.bin",
|
||||
loaded_integrations={"wifi", "api"},
|
||||
loaded_platforms=set(),
|
||||
no_mdns=False,
|
||||
)
|
||||
|
||||
assert storage.firmware_bin_path == "/path/to/firmware.bin"
|
||||
|
||||
|
||||
def test_storage_json_save_creates_directory(setup_core: Path, tmp_path: Path) -> None:
|
||||
"""Test StorageJSON.save creates storage directory if it doesn't exist."""
|
||||
storage_dir = tmp_path / "new_data" / "storage"
|
||||
storage_file = storage_dir / "test.json"
|
||||
|
||||
assert not storage_dir.exists()
|
||||
|
||||
storage = storage_json.StorageJSON(
|
||||
storage_version=1,
|
||||
name="test",
|
||||
friendly_name="Test",
|
||||
comment=None,
|
||||
esphome_version="2024.1.0",
|
||||
src_version=None,
|
||||
address="test.local",
|
||||
web_port=None,
|
||||
target_platform="ESP8266",
|
||||
build_path=None,
|
||||
firmware_bin_path=None,
|
||||
loaded_integrations=set(),
|
||||
loaded_platforms=set(),
|
||||
no_mdns=False,
|
||||
)
|
||||
|
||||
with patch("esphome.storage_json.write_file_if_changed") as mock_write:
|
||||
storage.save(str(storage_file))
|
||||
mock_write.assert_called_once()
|
||||
call_args = mock_write.call_args[0]
|
||||
assert call_args[0] == str(storage_file)
|
||||
|
||||
|
||||
def test_storage_json_from_wizard(setup_core: Path) -> None:
|
||||
"""Test StorageJSON.from_wizard creates correct storage object."""
|
||||
storage = storage_json.StorageJSON.from_wizard(
|
||||
name="my_device",
|
||||
friendly_name="My Device",
|
||||
address="my_device.local",
|
||||
platform="ESP32",
|
||||
)
|
||||
|
||||
assert storage.name == "my_device"
|
||||
assert storage.friendly_name == "My Device"
|
||||
assert storage.address == "my_device.local"
|
||||
assert storage.target_platform == "ESP32"
|
||||
assert storage.build_path is None
|
||||
assert storage.firmware_bin_path is None
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.platform == "win32", reason="HA addons don't run on Windows")
|
||||
@patch("esphome.core.is_ha_addon")
|
||||
def test_storage_paths_with_ha_addon(mock_is_ha_addon: bool, tmp_path: Path) -> None:
|
||||
"""Test storage paths when running as Home Assistant addon."""
|
||||
mock_is_ha_addon.return_value = True
|
||||
|
||||
CORE.config_path = str(tmp_path / "test.yaml")
|
||||
|
||||
result = storage_json.storage_path()
|
||||
# When is_ha_addon is True, CORE.data_dir returns "/data"
|
||||
# This is the standard mount point for HA addon containers
|
||||
expected = str(Path("/data") / "storage" / "test.yaml.json")
|
||||
assert result == expected
|
||||
|
||||
result = storage_json.esphome_storage_path()
|
||||
expected = str(Path("/data") / "esphome.json")
|
||||
assert result == expected
|
Reference in New Issue
Block a user