mirror of
https://github.com/esphome/esphome.git
synced 2025-09-14 09:12:19 +01:00
Add additional dashboard and main tests (#10688)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -556,6 +556,66 @@ def test_start_web_server_with_address_port(
|
||||
assert (archive_dir / "old.yaml").exists()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_edit_request_handler_get(dashboard: DashboardTestHelper) -> None:
|
||||
"""Test EditRequestHandler.get method."""
|
||||
# Test getting a valid yaml file
|
||||
response = await dashboard.fetch("/edit?configuration=pico.yaml")
|
||||
assert response.code == 200
|
||||
assert response.headers["content-type"] == "application/yaml"
|
||||
content = response.body.decode()
|
||||
assert "esphome:" in content # Verify it's a valid ESPHome config
|
||||
|
||||
# Test getting a non-existent file
|
||||
with pytest.raises(HTTPClientError) as exc_info:
|
||||
await dashboard.fetch("/edit?configuration=nonexistent.yaml")
|
||||
assert exc_info.value.code == 404
|
||||
|
||||
# Test getting a non-yaml file
|
||||
with pytest.raises(HTTPClientError) as exc_info:
|
||||
await dashboard.fetch("/edit?configuration=test.txt")
|
||||
assert exc_info.value.code == 404
|
||||
|
||||
# Test path traversal attempt
|
||||
with pytest.raises(HTTPClientError) as exc_info:
|
||||
await dashboard.fetch("/edit?configuration=../../../etc/passwd")
|
||||
assert exc_info.value.code == 404
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_archive_request_handler_post(
|
||||
dashboard: DashboardTestHelper,
|
||||
mock_archive_storage_path: MagicMock,
|
||||
mock_ext_storage_path: MagicMock,
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
"""Test ArchiveRequestHandler.post method."""
|
||||
|
||||
# Set up temp directories
|
||||
config_dir = Path(get_fixture_path("conf"))
|
||||
archive_dir = tmp_path / "archive"
|
||||
|
||||
# Create a test configuration file
|
||||
test_config = config_dir / "test_archive.yaml"
|
||||
test_config.write_text("esphome:\n name: test_archive\n")
|
||||
|
||||
# Archive the configuration
|
||||
response = await dashboard.fetch(
|
||||
"/archive",
|
||||
method="POST",
|
||||
body="configuration=test_archive.yaml",
|
||||
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
||||
)
|
||||
assert response.code == 200
|
||||
|
||||
# Verify file was moved to archive
|
||||
assert not test_config.exists()
|
||||
assert (archive_dir / "test_archive.yaml").exists()
|
||||
assert (
|
||||
archive_dir / "test_archive.yaml"
|
||||
).read_text() == "esphome:\n name: test_archive\n"
|
||||
|
||||
|
||||
@pytest.mark.skipif(os.name == "nt", reason="Unix sockets are not supported on Windows")
|
||||
@pytest.mark.usefixtures("mock_trash_storage_path", "mock_archive_storage_path")
|
||||
def test_start_web_server_with_unix_socket(tmp_path: Path) -> None:
|
||||
|
@@ -9,18 +9,27 @@ from typing import Any
|
||||
from unittest.mock import MagicMock, Mock, patch
|
||||
|
||||
import pytest
|
||||
from pytest import CaptureFixture
|
||||
|
||||
from esphome.__main__ import choose_upload_log_host, show_logs, upload_program
|
||||
from esphome.__main__ import (
|
||||
choose_upload_log_host,
|
||||
command_rename,
|
||||
command_wizard,
|
||||
show_logs,
|
||||
upload_program,
|
||||
)
|
||||
from esphome.const import (
|
||||
CONF_BROKER,
|
||||
CONF_DISABLED,
|
||||
CONF_ESPHOME,
|
||||
CONF_MDNS,
|
||||
CONF_MQTT,
|
||||
CONF_NAME,
|
||||
CONF_OTA,
|
||||
CONF_PASSWORD,
|
||||
CONF_PLATFORM,
|
||||
CONF_PORT,
|
||||
CONF_SUBSTITUTIONS,
|
||||
CONF_USE_ADDRESS,
|
||||
CONF_WIFI,
|
||||
KEY_CORE,
|
||||
@@ -170,6 +179,14 @@ def mock_has_mqtt_logging() -> Generator[Mock]:
|
||||
yield mock
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_run_external_process() -> Generator[Mock]:
|
||||
"""Mock run_external_process for testing."""
|
||||
with patch("esphome.__main__.run_external_process") as mock:
|
||||
mock.return_value = 0 # Default to success
|
||||
yield mock
|
||||
|
||||
|
||||
def test_choose_upload_log_host_with_string_default() -> None:
|
||||
"""Test with a single string default device."""
|
||||
result = choose_upload_log_host(
|
||||
@@ -606,6 +623,9 @@ class MockArgs:
|
||||
password: str | None = None
|
||||
client_id: str | None = None
|
||||
topic: str | None = None
|
||||
configuration: str | None = None
|
||||
name: str | None = None
|
||||
dashboard: bool = False
|
||||
|
||||
|
||||
def test_upload_program_serial_esp32(
|
||||
@@ -1053,3 +1073,178 @@ def test_show_logs_platform_specific_handler(
|
||||
assert result == 0
|
||||
mock_import.assert_called_once_with("esphome.components.custom_platform")
|
||||
mock_module.show_logs.assert_called_once_with(config, args, devices)
|
||||
|
||||
|
||||
def test_command_wizard(tmp_path: Path) -> None:
|
||||
"""Test command_wizard function."""
|
||||
config_file = tmp_path / "test.yaml"
|
||||
|
||||
# Mock wizard.wizard to avoid interactive prompts
|
||||
with patch("esphome.wizard.wizard") as mock_wizard:
|
||||
mock_wizard.return_value = 0
|
||||
|
||||
args = MockArgs(configuration=str(config_file))
|
||||
result = command_wizard(args)
|
||||
|
||||
assert result == 0
|
||||
mock_wizard.assert_called_once_with(str(config_file))
|
||||
|
||||
|
||||
def test_command_rename_invalid_characters(
|
||||
tmp_path: Path, capfd: CaptureFixture[str]
|
||||
) -> None:
|
||||
"""Test command_rename with invalid characters in name."""
|
||||
setup_core(tmp_path=tmp_path)
|
||||
|
||||
# Test with invalid character (space)
|
||||
args = MockArgs(name="invalid name")
|
||||
result = command_rename(args, {})
|
||||
|
||||
assert result == 1
|
||||
captured = capfd.readouterr()
|
||||
assert "invalid character" in captured.out.lower()
|
||||
|
||||
|
||||
def test_command_rename_complex_yaml(
|
||||
tmp_path: Path, capfd: CaptureFixture[str]
|
||||
) -> None:
|
||||
"""Test command_rename with complex YAML that cannot be renamed."""
|
||||
config_file = tmp_path / "test.yaml"
|
||||
config_file.write_text("# Complex YAML without esphome section\nsome_key: value\n")
|
||||
setup_core(tmp_path=tmp_path)
|
||||
CORE.config_path = str(config_file)
|
||||
|
||||
args = MockArgs(name="newname")
|
||||
result = command_rename(args, {})
|
||||
|
||||
assert result == 1
|
||||
captured = capfd.readouterr()
|
||||
assert "complex yaml" in captured.out.lower()
|
||||
|
||||
|
||||
def test_command_rename_success(
|
||||
tmp_path: Path,
|
||||
capfd: CaptureFixture[str],
|
||||
mock_run_external_process: Mock,
|
||||
) -> None:
|
||||
"""Test successful rename of a simple configuration."""
|
||||
config_file = tmp_path / "oldname.yaml"
|
||||
config_file.write_text("""
|
||||
esphome:
|
||||
name: oldname
|
||||
|
||||
esp32:
|
||||
board: nodemcu-32s
|
||||
|
||||
wifi:
|
||||
ssid: "test"
|
||||
password: "test1234"
|
||||
""")
|
||||
setup_core(tmp_path=tmp_path)
|
||||
CORE.config_path = str(config_file)
|
||||
|
||||
# Set up CORE.config to avoid ValueError when accessing CORE.address
|
||||
CORE.config = {CONF_ESPHOME: {CONF_NAME: "oldname"}}
|
||||
|
||||
args = MockArgs(name="newname", dashboard=False)
|
||||
|
||||
# Simulate successful validation and upload
|
||||
mock_run_external_process.return_value = 0
|
||||
|
||||
result = command_rename(args, {})
|
||||
|
||||
assert result == 0
|
||||
|
||||
# Verify new file was created
|
||||
new_file = tmp_path / "newname.yaml"
|
||||
assert new_file.exists()
|
||||
|
||||
# Verify old file was removed
|
||||
assert not config_file.exists()
|
||||
|
||||
# Verify content was updated
|
||||
content = new_file.read_text()
|
||||
assert (
|
||||
'name: "newname"' in content
|
||||
or "name: 'newname'" in content
|
||||
or "name: newname" in content
|
||||
)
|
||||
|
||||
captured = capfd.readouterr()
|
||||
assert "SUCCESS" in captured.out
|
||||
|
||||
|
||||
def test_command_rename_with_substitutions(
|
||||
tmp_path: Path,
|
||||
mock_run_external_process: Mock,
|
||||
) -> None:
|
||||
"""Test rename with substitutions in YAML."""
|
||||
config_file = tmp_path / "oldname.yaml"
|
||||
config_file.write_text("""
|
||||
substitutions:
|
||||
device_name: oldname
|
||||
|
||||
esphome:
|
||||
name: ${device_name}
|
||||
|
||||
esp32:
|
||||
board: nodemcu-32s
|
||||
""")
|
||||
setup_core(tmp_path=tmp_path)
|
||||
CORE.config_path = str(config_file)
|
||||
|
||||
# Set up CORE.config to avoid ValueError when accessing CORE.address
|
||||
CORE.config = {
|
||||
CONF_ESPHOME: {CONF_NAME: "oldname"},
|
||||
CONF_SUBSTITUTIONS: {"device_name": "oldname"},
|
||||
}
|
||||
|
||||
args = MockArgs(name="newname", dashboard=False)
|
||||
|
||||
mock_run_external_process.return_value = 0
|
||||
|
||||
result = command_rename(args, {})
|
||||
|
||||
assert result == 0
|
||||
|
||||
# Verify substitution was updated
|
||||
new_file = tmp_path / "newname.yaml"
|
||||
content = new_file.read_text()
|
||||
assert 'device_name: "newname"' in content
|
||||
|
||||
|
||||
def test_command_rename_validation_failure(
|
||||
tmp_path: Path,
|
||||
capfd: CaptureFixture[str],
|
||||
mock_run_external_process: Mock,
|
||||
) -> None:
|
||||
"""Test rename when validation fails."""
|
||||
config_file = tmp_path / "oldname.yaml"
|
||||
config_file.write_text("""
|
||||
esphome:
|
||||
name: oldname
|
||||
|
||||
esp32:
|
||||
board: nodemcu-32s
|
||||
""")
|
||||
setup_core(tmp_path=tmp_path)
|
||||
CORE.config_path = str(config_file)
|
||||
|
||||
args = MockArgs(name="newname", dashboard=False)
|
||||
|
||||
# First call for validation fails
|
||||
mock_run_external_process.return_value = 1
|
||||
|
||||
result = command_rename(args, {})
|
||||
|
||||
assert result == 1
|
||||
|
||||
# Verify new file was created but then removed due to failure
|
||||
new_file = tmp_path / "newname.yaml"
|
||||
assert not new_file.exists()
|
||||
|
||||
# Verify old file still exists (not removed on failure)
|
||||
assert config_file.exists()
|
||||
|
||||
captured = capfd.readouterr()
|
||||
assert "Rename failed" in captured.out
|
||||
|
Reference in New Issue
Block a user