mirror of
https://github.com/esphome/esphome.git
synced 2025-09-15 01:32:19 +01:00
[tests] Add upload_program and show_logs test coverage to prevent regressions (#10684)
This commit is contained in:
@@ -4,14 +4,33 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from collections.abc import Generator
|
from collections.abc import Generator
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from unittest.mock import Mock, patch
|
from unittest.mock import MagicMock, Mock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from esphome.__main__ import choose_upload_log_host
|
from esphome.__main__ import choose_upload_log_host, show_logs, upload_program
|
||||||
from esphome.const import CONF_BROKER, CONF_MQTT, CONF_USE_ADDRESS, CONF_WIFI
|
from esphome.const import (
|
||||||
from esphome.core import CORE
|
CONF_BROKER,
|
||||||
|
CONF_DISABLED,
|
||||||
|
CONF_ESPHOME,
|
||||||
|
CONF_MDNS,
|
||||||
|
CONF_MQTT,
|
||||||
|
CONF_OTA,
|
||||||
|
CONF_PASSWORD,
|
||||||
|
CONF_PLATFORM,
|
||||||
|
CONF_PORT,
|
||||||
|
CONF_USE_ADDRESS,
|
||||||
|
CONF_WIFI,
|
||||||
|
KEY_CORE,
|
||||||
|
KEY_TARGET_PLATFORM,
|
||||||
|
PLATFORM_BK72XX,
|
||||||
|
PLATFORM_ESP32,
|
||||||
|
PLATFORM_ESP8266,
|
||||||
|
PLATFORM_RP2040,
|
||||||
|
)
|
||||||
|
from esphome.core import CORE, EsphomeError
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -28,7 +47,11 @@ class MockSerialPort:
|
|||||||
|
|
||||||
|
|
||||||
def setup_core(
|
def setup_core(
|
||||||
config: dict[str, Any] | None = None, address: str | None = None
|
config: dict[str, Any] | None = None,
|
||||||
|
address: str | None = None,
|
||||||
|
platform: str | None = None,
|
||||||
|
tmp_path: Path | None = None,
|
||||||
|
name: str = "test",
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Helper to set up CORE configuration with optional address.
|
Helper to set up CORE configuration with optional address.
|
||||||
@@ -36,6 +59,9 @@ def setup_core(
|
|||||||
Args:
|
Args:
|
||||||
config (dict[str, Any] | None): The configuration dictionary to set for CORE. If None, an empty dict is used.
|
config (dict[str, Any] | None): The configuration dictionary to set for CORE. If None, an empty dict is used.
|
||||||
address (str | None): Optional network address to set in the configuration. If provided, it is set under the wifi config.
|
address (str | None): Optional network address to set in the configuration. If provided, it is set under the wifi config.
|
||||||
|
platform (str | None): Optional target platform to set in CORE.data.
|
||||||
|
tmp_path (Path | None): Optional temp path for setting up build paths.
|
||||||
|
name (str): The name of the device (defaults to "test").
|
||||||
"""
|
"""
|
||||||
if config is None:
|
if config is None:
|
||||||
config = {}
|
config = {}
|
||||||
@@ -46,6 +72,15 @@ def setup_core(
|
|||||||
|
|
||||||
CORE.config = config
|
CORE.config = config
|
||||||
|
|
||||||
|
if platform is not None:
|
||||||
|
CORE.data[KEY_CORE] = {}
|
||||||
|
CORE.data[KEY_CORE][KEY_TARGET_PLATFORM] = platform
|
||||||
|
|
||||||
|
if tmp_path is not None:
|
||||||
|
CORE.config_path = str(tmp_path / f"{name}.yaml")
|
||||||
|
CORE.name = name
|
||||||
|
CORE.build_path = str(tmp_path / ".esphome" / "build" / name)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_no_serial_ports() -> Generator[Mock]:
|
def mock_no_serial_ports() -> Generator[Mock]:
|
||||||
@@ -54,6 +89,55 @@ def mock_no_serial_ports() -> Generator[Mock]:
|
|||||||
yield mock
|
yield mock
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_get_port_type() -> Generator[Mock]:
|
||||||
|
"""Mock get_port_type for testing."""
|
||||||
|
with patch("esphome.__main__.get_port_type") as mock:
|
||||||
|
yield mock
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_check_permissions() -> Generator[Mock]:
|
||||||
|
"""Mock check_permissions for testing."""
|
||||||
|
with patch("esphome.__main__.check_permissions") as mock:
|
||||||
|
yield mock
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_run_miniterm() -> Generator[Mock]:
|
||||||
|
"""Mock run_miniterm for testing."""
|
||||||
|
with patch("esphome.__main__.run_miniterm") as mock:
|
||||||
|
yield mock
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_upload_using_esptool() -> Generator[Mock]:
|
||||||
|
"""Mock upload_using_esptool for testing."""
|
||||||
|
with patch("esphome.__main__.upload_using_esptool") as mock:
|
||||||
|
yield mock
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_upload_using_platformio() -> Generator[Mock]:
|
||||||
|
"""Mock upload_using_platformio for testing."""
|
||||||
|
with patch("esphome.__main__.upload_using_platformio") as mock:
|
||||||
|
yield mock
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_run_ota() -> Generator[Mock]:
|
||||||
|
"""Mock espota2.run_ota for testing."""
|
||||||
|
with patch("esphome.espota2.run_ota") as mock:
|
||||||
|
yield mock
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_is_ip_address() -> Generator[Mock]:
|
||||||
|
"""Mock is_ip_address for testing."""
|
||||||
|
with patch("esphome.__main__.is_ip_address") as mock:
|
||||||
|
yield mock
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_serial_ports() -> Generator[Mock]:
|
def mock_serial_ports() -> Generator[Mock]:
|
||||||
"""Mock get_serial_ports to return test ports."""
|
"""Mock get_serial_ports to return test ports."""
|
||||||
@@ -510,3 +594,462 @@ def test_choose_upload_log_host_no_address_with_ota_config() -> None:
|
|||||||
show_api=False,
|
show_api=False,
|
||||||
)
|
)
|
||||||
assert result == []
|
assert result == []
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class MockArgs:
|
||||||
|
"""Mock args for testing."""
|
||||||
|
|
||||||
|
file: str | None = None
|
||||||
|
upload_speed: int = 460800
|
||||||
|
username: str | None = None
|
||||||
|
password: str | None = None
|
||||||
|
client_id: str | None = None
|
||||||
|
topic: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
def test_upload_program_serial_esp32(
|
||||||
|
mock_upload_using_esptool: Mock,
|
||||||
|
mock_get_port_type: Mock,
|
||||||
|
mock_check_permissions: Mock,
|
||||||
|
) -> None:
|
||||||
|
"""Test upload_program with serial port for ESP32."""
|
||||||
|
setup_core(platform=PLATFORM_ESP32)
|
||||||
|
mock_get_port_type.return_value = "SERIAL"
|
||||||
|
mock_upload_using_esptool.return_value = 0
|
||||||
|
|
||||||
|
config = {}
|
||||||
|
args = MockArgs()
|
||||||
|
devices = ["/dev/ttyUSB0"]
|
||||||
|
|
||||||
|
exit_code, host = upload_program(config, args, devices)
|
||||||
|
|
||||||
|
assert exit_code == 0
|
||||||
|
assert host == "/dev/ttyUSB0"
|
||||||
|
mock_check_permissions.assert_called_once_with("/dev/ttyUSB0")
|
||||||
|
mock_upload_using_esptool.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
|
def test_upload_program_serial_esp8266_with_file(
|
||||||
|
mock_upload_using_esptool: Mock,
|
||||||
|
mock_get_port_type: Mock,
|
||||||
|
mock_check_permissions: Mock,
|
||||||
|
) -> None:
|
||||||
|
"""Test upload_program with serial port for ESP8266 with custom file."""
|
||||||
|
setup_core(platform=PLATFORM_ESP8266)
|
||||||
|
mock_get_port_type.return_value = "SERIAL"
|
||||||
|
mock_upload_using_esptool.return_value = 0
|
||||||
|
|
||||||
|
config = {}
|
||||||
|
args = MockArgs(file="firmware.bin")
|
||||||
|
devices = ["/dev/ttyUSB0"]
|
||||||
|
|
||||||
|
exit_code, host = upload_program(config, args, devices)
|
||||||
|
|
||||||
|
assert exit_code == 0
|
||||||
|
assert host == "/dev/ttyUSB0"
|
||||||
|
mock_check_permissions.assert_called_once_with("/dev/ttyUSB0")
|
||||||
|
mock_upload_using_esptool.assert_called_once_with(
|
||||||
|
config, "/dev/ttyUSB0", "firmware.bin", 460800
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"platform,device",
|
||||||
|
[
|
||||||
|
(PLATFORM_RP2040, "/dev/ttyACM0"),
|
||||||
|
(PLATFORM_BK72XX, "/dev/ttyUSB0"), # LibreTiny platform
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_upload_program_serial_platformio_platforms(
|
||||||
|
mock_upload_using_platformio: Mock,
|
||||||
|
mock_get_port_type: Mock,
|
||||||
|
mock_check_permissions: Mock,
|
||||||
|
platform: str,
|
||||||
|
device: str,
|
||||||
|
) -> None:
|
||||||
|
"""Test upload_program with serial port for platformio platforms (RP2040/LibreTiny)."""
|
||||||
|
setup_core(platform=platform)
|
||||||
|
mock_get_port_type.return_value = "SERIAL"
|
||||||
|
mock_upload_using_platformio.return_value = 0
|
||||||
|
|
||||||
|
config = {}
|
||||||
|
args = MockArgs()
|
||||||
|
devices = [device]
|
||||||
|
|
||||||
|
exit_code, host = upload_program(config, args, devices)
|
||||||
|
|
||||||
|
assert exit_code == 0
|
||||||
|
assert host == device
|
||||||
|
mock_check_permissions.assert_called_once_with(device)
|
||||||
|
mock_upload_using_platformio.assert_called_once_with(config, device)
|
||||||
|
|
||||||
|
|
||||||
|
def test_upload_program_serial_upload_failed(
|
||||||
|
mock_upload_using_esptool: Mock,
|
||||||
|
mock_get_port_type: Mock,
|
||||||
|
mock_check_permissions: Mock,
|
||||||
|
) -> None:
|
||||||
|
"""Test upload_program when serial upload fails."""
|
||||||
|
setup_core(platform=PLATFORM_ESP32)
|
||||||
|
mock_get_port_type.return_value = "SERIAL"
|
||||||
|
mock_upload_using_esptool.return_value = 1 # Failed
|
||||||
|
|
||||||
|
config = {}
|
||||||
|
args = MockArgs()
|
||||||
|
devices = ["/dev/ttyUSB0"]
|
||||||
|
|
||||||
|
exit_code, host = upload_program(config, args, devices)
|
||||||
|
|
||||||
|
assert exit_code == 1
|
||||||
|
assert host is None
|
||||||
|
mock_check_permissions.assert_called_once_with("/dev/ttyUSB0")
|
||||||
|
mock_upload_using_esptool.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
|
def test_upload_program_ota_success(
|
||||||
|
mock_run_ota: Mock,
|
||||||
|
mock_get_port_type: Mock,
|
||||||
|
tmp_path: Path,
|
||||||
|
) -> None:
|
||||||
|
"""Test upload_program with OTA."""
|
||||||
|
setup_core(platform=PLATFORM_ESP32, tmp_path=tmp_path)
|
||||||
|
|
||||||
|
mock_get_port_type.return_value = "NETWORK"
|
||||||
|
mock_run_ota.return_value = (0, "192.168.1.100")
|
||||||
|
|
||||||
|
config = {
|
||||||
|
CONF_OTA: [
|
||||||
|
{
|
||||||
|
CONF_PLATFORM: CONF_ESPHOME,
|
||||||
|
CONF_PORT: 3232,
|
||||||
|
CONF_PASSWORD: "secret",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
args = MockArgs()
|
||||||
|
devices = ["192.168.1.100"]
|
||||||
|
|
||||||
|
exit_code, host = upload_program(config, args, devices)
|
||||||
|
|
||||||
|
assert exit_code == 0
|
||||||
|
assert host == "192.168.1.100"
|
||||||
|
expected_firmware = str(
|
||||||
|
tmp_path / ".esphome" / "build" / "test" / ".pioenvs" / "test" / "firmware.bin"
|
||||||
|
)
|
||||||
|
mock_run_ota.assert_called_once_with(
|
||||||
|
["192.168.1.100"], 3232, "secret", expected_firmware
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_upload_program_ota_with_file_arg(
|
||||||
|
mock_run_ota: Mock,
|
||||||
|
mock_get_port_type: Mock,
|
||||||
|
tmp_path: Path,
|
||||||
|
) -> None:
|
||||||
|
"""Test upload_program with OTA and custom file."""
|
||||||
|
setup_core(platform=PLATFORM_ESP32, tmp_path=tmp_path)
|
||||||
|
|
||||||
|
mock_get_port_type.return_value = "NETWORK"
|
||||||
|
mock_run_ota.return_value = (0, "192.168.1.100")
|
||||||
|
|
||||||
|
config = {
|
||||||
|
CONF_OTA: [
|
||||||
|
{
|
||||||
|
CONF_PLATFORM: CONF_ESPHOME,
|
||||||
|
CONF_PORT: 3232,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
args = MockArgs(file="custom.bin")
|
||||||
|
devices = ["192.168.1.100"]
|
||||||
|
|
||||||
|
exit_code, host = upload_program(config, args, devices)
|
||||||
|
|
||||||
|
assert exit_code == 0
|
||||||
|
assert host == "192.168.1.100"
|
||||||
|
mock_run_ota.assert_called_once_with(["192.168.1.100"], 3232, "", "custom.bin")
|
||||||
|
|
||||||
|
|
||||||
|
def test_upload_program_ota_no_config(
|
||||||
|
mock_get_port_type: Mock,
|
||||||
|
) -> None:
|
||||||
|
"""Test upload_program with OTA but no OTA config."""
|
||||||
|
setup_core(platform=PLATFORM_ESP32)
|
||||||
|
mock_get_port_type.return_value = "NETWORK"
|
||||||
|
|
||||||
|
config = {} # No OTA config
|
||||||
|
args = MockArgs()
|
||||||
|
devices = ["192.168.1.100"]
|
||||||
|
|
||||||
|
with pytest.raises(EsphomeError, match="Cannot upload Over the Air"):
|
||||||
|
upload_program(config, args, devices)
|
||||||
|
|
||||||
|
|
||||||
|
@patch("esphome.mqtt.get_esphome_device_ip")
|
||||||
|
def test_upload_program_ota_with_mqtt_resolution(
|
||||||
|
mock_mqtt_get_ip: Mock,
|
||||||
|
mock_is_ip_address: Mock,
|
||||||
|
mock_run_ota: Mock,
|
||||||
|
mock_get_port_type: Mock,
|
||||||
|
tmp_path: Path,
|
||||||
|
) -> None:
|
||||||
|
"""Test upload_program with OTA using MQTT for address resolution."""
|
||||||
|
setup_core(address="device.local", platform=PLATFORM_ESP32, tmp_path=tmp_path)
|
||||||
|
|
||||||
|
mock_get_port_type.side_effect = ["MQTT", "NETWORK"]
|
||||||
|
mock_is_ip_address.return_value = False
|
||||||
|
mock_mqtt_get_ip.return_value = ["192.168.1.100"]
|
||||||
|
mock_run_ota.return_value = (0, "192.168.1.100")
|
||||||
|
|
||||||
|
config = {
|
||||||
|
CONF_OTA: [
|
||||||
|
{
|
||||||
|
CONF_PLATFORM: CONF_ESPHOME,
|
||||||
|
CONF_PORT: 3232,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
CONF_MQTT: {
|
||||||
|
CONF_BROKER: "mqtt.local",
|
||||||
|
},
|
||||||
|
CONF_MDNS: {
|
||||||
|
CONF_DISABLED: True,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
args = MockArgs(username="user", password="pass", client_id="client")
|
||||||
|
devices = ["MQTT"]
|
||||||
|
|
||||||
|
exit_code, host = upload_program(config, args, devices)
|
||||||
|
|
||||||
|
assert exit_code == 0
|
||||||
|
assert host == "192.168.1.100"
|
||||||
|
mock_mqtt_get_ip.assert_called_once_with(config, "user", "pass", "client")
|
||||||
|
expected_firmware = str(
|
||||||
|
tmp_path / ".esphome" / "build" / "test" / ".pioenvs" / "test" / "firmware.bin"
|
||||||
|
)
|
||||||
|
mock_run_ota.assert_called_once_with(
|
||||||
|
[["192.168.1.100"]], 3232, "", expected_firmware
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@patch("esphome.__main__.importlib.import_module")
|
||||||
|
def test_upload_program_platform_specific_handler(
|
||||||
|
mock_import: Mock,
|
||||||
|
mock_get_port_type: Mock,
|
||||||
|
) -> None:
|
||||||
|
"""Test upload_program with platform-specific upload handler."""
|
||||||
|
setup_core(platform="custom_platform")
|
||||||
|
mock_get_port_type.return_value = "CUSTOM"
|
||||||
|
|
||||||
|
mock_module = MagicMock()
|
||||||
|
mock_module.upload_program.return_value = True
|
||||||
|
mock_import.return_value = mock_module
|
||||||
|
|
||||||
|
config = {}
|
||||||
|
args = MockArgs()
|
||||||
|
devices = ["custom_device"]
|
||||||
|
|
||||||
|
exit_code, host = upload_program(config, args, devices)
|
||||||
|
|
||||||
|
assert exit_code == 0
|
||||||
|
assert host == "custom_device"
|
||||||
|
mock_import.assert_called_once_with("esphome.components.custom_platform")
|
||||||
|
mock_module.upload_program.assert_called_once_with(config, args, "custom_device")
|
||||||
|
|
||||||
|
|
||||||
|
def test_show_logs_serial(
|
||||||
|
mock_get_port_type: Mock,
|
||||||
|
mock_check_permissions: Mock,
|
||||||
|
mock_run_miniterm: Mock,
|
||||||
|
) -> None:
|
||||||
|
"""Test show_logs with serial port."""
|
||||||
|
setup_core(config={"logger": {}}, platform=PLATFORM_ESP32)
|
||||||
|
mock_get_port_type.return_value = "SERIAL"
|
||||||
|
mock_run_miniterm.return_value = 0
|
||||||
|
|
||||||
|
args = MockArgs()
|
||||||
|
devices = ["/dev/ttyUSB0"]
|
||||||
|
|
||||||
|
result = show_logs(CORE.config, args, devices)
|
||||||
|
|
||||||
|
assert result == 0
|
||||||
|
mock_check_permissions.assert_called_once_with("/dev/ttyUSB0")
|
||||||
|
mock_run_miniterm.assert_called_once_with(CORE.config, "/dev/ttyUSB0", args)
|
||||||
|
|
||||||
|
|
||||||
|
def test_show_logs_no_logger() -> None:
|
||||||
|
"""Test show_logs when logger is not configured."""
|
||||||
|
setup_core(config={}, platform=PLATFORM_ESP32) # No logger config
|
||||||
|
args = MockArgs()
|
||||||
|
devices = ["/dev/ttyUSB0"]
|
||||||
|
|
||||||
|
with pytest.raises(EsphomeError, match="Logger is not configured"):
|
||||||
|
show_logs(CORE.config, args, devices)
|
||||||
|
|
||||||
|
|
||||||
|
@patch("esphome.components.api.client.run_logs")
|
||||||
|
def test_show_logs_api(
|
||||||
|
mock_run_logs: Mock,
|
||||||
|
mock_get_port_type: Mock,
|
||||||
|
) -> None:
|
||||||
|
"""Test show_logs with API."""
|
||||||
|
setup_core(
|
||||||
|
config={
|
||||||
|
"logger": {},
|
||||||
|
"api": {},
|
||||||
|
CONF_MDNS: {CONF_DISABLED: False},
|
||||||
|
},
|
||||||
|
platform=PLATFORM_ESP32,
|
||||||
|
)
|
||||||
|
mock_get_port_type.return_value = "NETWORK"
|
||||||
|
mock_run_logs.return_value = 0
|
||||||
|
|
||||||
|
args = MockArgs()
|
||||||
|
devices = ["192.168.1.100", "192.168.1.101"]
|
||||||
|
|
||||||
|
result = show_logs(CORE.config, args, devices)
|
||||||
|
|
||||||
|
assert result == 0
|
||||||
|
mock_run_logs.assert_called_once_with(
|
||||||
|
CORE.config, ["192.168.1.100", "192.168.1.101"]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@patch("esphome.mqtt.get_esphome_device_ip")
|
||||||
|
@patch("esphome.components.api.client.run_logs")
|
||||||
|
def test_show_logs_api_with_mqtt_fallback(
|
||||||
|
mock_run_logs: Mock,
|
||||||
|
mock_mqtt_get_ip: Mock,
|
||||||
|
mock_get_port_type: Mock,
|
||||||
|
) -> None:
|
||||||
|
"""Test show_logs with API using MQTT for address resolution."""
|
||||||
|
setup_core(
|
||||||
|
config={
|
||||||
|
"logger": {},
|
||||||
|
"api": {},
|
||||||
|
CONF_MDNS: {CONF_DISABLED: True},
|
||||||
|
CONF_MQTT: {CONF_BROKER: "mqtt.local"},
|
||||||
|
},
|
||||||
|
platform=PLATFORM_ESP32,
|
||||||
|
)
|
||||||
|
mock_get_port_type.return_value = "NETWORK"
|
||||||
|
mock_run_logs.return_value = 0
|
||||||
|
mock_mqtt_get_ip.return_value = ["192.168.1.200"]
|
||||||
|
|
||||||
|
args = MockArgs(username="user", password="pass", client_id="client")
|
||||||
|
devices = ["device.local"]
|
||||||
|
|
||||||
|
result = show_logs(CORE.config, args, devices)
|
||||||
|
|
||||||
|
assert result == 0
|
||||||
|
mock_mqtt_get_ip.assert_called_once_with(CORE.config, "user", "pass", "client")
|
||||||
|
mock_run_logs.assert_called_once_with(CORE.config, ["192.168.1.200"])
|
||||||
|
|
||||||
|
|
||||||
|
@patch("esphome.mqtt.show_logs")
|
||||||
|
def test_show_logs_mqtt(
|
||||||
|
mock_mqtt_show_logs: Mock,
|
||||||
|
mock_get_port_type: Mock,
|
||||||
|
) -> None:
|
||||||
|
"""Test show_logs with MQTT."""
|
||||||
|
setup_core(
|
||||||
|
config={
|
||||||
|
"logger": {},
|
||||||
|
"mqtt": {CONF_BROKER: "mqtt.local"},
|
||||||
|
},
|
||||||
|
platform=PLATFORM_ESP32,
|
||||||
|
)
|
||||||
|
mock_get_port_type.return_value = "MQTT"
|
||||||
|
mock_mqtt_show_logs.return_value = 0
|
||||||
|
|
||||||
|
args = MockArgs(
|
||||||
|
topic="esphome/logs",
|
||||||
|
username="user",
|
||||||
|
password="pass",
|
||||||
|
client_id="client",
|
||||||
|
)
|
||||||
|
devices = ["MQTT"]
|
||||||
|
|
||||||
|
result = show_logs(CORE.config, args, devices)
|
||||||
|
|
||||||
|
assert result == 0
|
||||||
|
mock_mqtt_show_logs.assert_called_once_with(
|
||||||
|
CORE.config, "esphome/logs", "user", "pass", "client"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@patch("esphome.mqtt.show_logs")
|
||||||
|
def test_show_logs_network_with_mqtt_only(
|
||||||
|
mock_mqtt_show_logs: Mock,
|
||||||
|
mock_get_port_type: Mock,
|
||||||
|
) -> None:
|
||||||
|
"""Test show_logs with network port but only MQTT configured."""
|
||||||
|
setup_core(
|
||||||
|
config={
|
||||||
|
"logger": {},
|
||||||
|
"mqtt": {CONF_BROKER: "mqtt.local"},
|
||||||
|
# No API configured
|
||||||
|
},
|
||||||
|
platform=PLATFORM_ESP32,
|
||||||
|
)
|
||||||
|
mock_get_port_type.return_value = "NETWORK"
|
||||||
|
mock_mqtt_show_logs.return_value = 0
|
||||||
|
|
||||||
|
args = MockArgs(
|
||||||
|
topic="esphome/logs",
|
||||||
|
username="user",
|
||||||
|
password="pass",
|
||||||
|
client_id="client",
|
||||||
|
)
|
||||||
|
devices = ["192.168.1.100"]
|
||||||
|
|
||||||
|
result = show_logs(CORE.config, args, devices)
|
||||||
|
|
||||||
|
assert result == 0
|
||||||
|
mock_mqtt_show_logs.assert_called_once_with(
|
||||||
|
CORE.config, "esphome/logs", "user", "pass", "client"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_show_logs_no_method_configured(
|
||||||
|
mock_get_port_type: Mock,
|
||||||
|
) -> None:
|
||||||
|
"""Test show_logs when no remote logging method is configured."""
|
||||||
|
setup_core(
|
||||||
|
config={
|
||||||
|
"logger": {},
|
||||||
|
# No API or MQTT configured
|
||||||
|
},
|
||||||
|
platform=PLATFORM_ESP32,
|
||||||
|
)
|
||||||
|
mock_get_port_type.return_value = "NETWORK"
|
||||||
|
|
||||||
|
args = MockArgs()
|
||||||
|
devices = ["192.168.1.100"]
|
||||||
|
|
||||||
|
with pytest.raises(
|
||||||
|
EsphomeError, match="No remote or local logging method configured"
|
||||||
|
):
|
||||||
|
show_logs(CORE.config, args, devices)
|
||||||
|
|
||||||
|
|
||||||
|
@patch("esphome.__main__.importlib.import_module")
|
||||||
|
def test_show_logs_platform_specific_handler(
|
||||||
|
mock_import: Mock,
|
||||||
|
) -> None:
|
||||||
|
"""Test show_logs with platform-specific logs handler."""
|
||||||
|
setup_core(platform="custom_platform", config={"logger": {}})
|
||||||
|
|
||||||
|
mock_module = MagicMock()
|
||||||
|
mock_module.show_logs.return_value = True
|
||||||
|
mock_import.return_value = mock_module
|
||||||
|
|
||||||
|
config = {"logger": {}}
|
||||||
|
args = MockArgs()
|
||||||
|
devices = ["custom_device"]
|
||||||
|
|
||||||
|
result = show_logs(config, args, devices)
|
||||||
|
|
||||||
|
assert result == 0
|
||||||
|
mock_import.assert_called_once_with("esphome.components.custom_platform")
|
||||||
|
mock_module.show_logs.assert_called_once_with(config, args, devices)
|
||||||
|
Reference in New Issue
Block a user