1
0
mirror of https://github.com/esphome/esphome.git synced 2025-11-17 23:35:47 +00:00
Files
esphome/tests/component_tests/ota/test_web_server_ota.py

256 lines
8.4 KiB
Python

"""Tests for the web_server OTA platform."""
from __future__ import annotations
from collections.abc import Callable
import logging
from typing import Any
import pytest
from esphome import config_validation as cv
from esphome.components.web_server.ota import _web_server_ota_final_validate
from esphome.const import CONF_ID, CONF_OTA, CONF_PLATFORM, CONF_WEB_SERVER
from esphome.core import ID
import esphome.final_validate as fv
def test_web_server_ota_generated(generate_main: Callable[[str], str]) -> None:
"""Test that web_server OTA platform generates correct code."""
main_cpp = generate_main("tests/component_tests/ota/test_web_server_ota.yaml")
# Check that the web server OTA component is included
assert "WebServerOTAComponent" in main_cpp
assert "web_server::WebServerOTAComponent" in main_cpp
# Check that global web server base is referenced
assert "global_web_server_base" in main_cpp
# Check component is registered
assert "App.register_component(web_server_webserverotacomponent_id)" in main_cpp
def test_web_server_ota_with_callbacks(generate_main: Callable[[str], str]) -> None:
"""Test web_server OTA with state callbacks."""
main_cpp = generate_main(
"tests/component_tests/ota/test_web_server_ota_callbacks.yaml"
)
# Check that web server OTA component is present
assert "WebServerOTAComponent" in main_cpp
# Check that callbacks are configured
# The actual callback code is in the component implementation, not main.cpp
# But we can check that logger.log statements are present from the callbacks
assert "logger.log" in main_cpp
assert "OTA started" in main_cpp
assert "OTA completed" in main_cpp
assert "OTA error" in main_cpp
def test_web_server_ota_idf_multipart(generate_main: Callable[[str], str]) -> None:
"""Test that ESP-IDF builds include multipart parser dependency."""
main_cpp = generate_main("tests/component_tests/ota/test_web_server_ota_idf.yaml")
# Check that web server OTA component is present
assert "WebServerOTAComponent" in main_cpp
# For ESP-IDF builds, the framework type is esp-idf
# The multipart parser dependency is added by web_server_idf
assert "web_server::WebServerOTAComponent" in main_cpp
def test_web_server_ota_without_web_server_fails(
generate_main: Callable[[str], str],
) -> None:
"""Test that web_server OTA requires web_server component."""
# This should fail during validation since web_server_base is required
# but we can't test validation failures with generate_main
# Instead, verify that both components are needed in valid config
main_cpp = generate_main("tests/component_tests/ota/test_web_server_ota.yaml")
# Both web server and OTA components should be present
assert "WebServer" in main_cpp
assert "WebServerOTAComponent" in main_cpp
def test_multiple_ota_platforms(generate_main: Callable[[str], str]) -> None:
"""Test multiple OTA platforms can coexist."""
main_cpp = generate_main("tests/component_tests/ota/test_web_server_ota_multi.yaml")
# Check all OTA platforms are included
assert "WebServerOTAComponent" in main_cpp
assert "ESPHomeOTAComponent" in main_cpp
assert "OtaHttpRequestComponent" in main_cpp
# Check components are from correct namespaces
assert "web_server::WebServerOTAComponent" in main_cpp
assert "esphome::ESPHomeOTAComponent" in main_cpp
assert "http_request::OtaHttpRequestComponent" in main_cpp
def test_web_server_ota_arduino_with_auth(generate_main: Callable[[str], str]) -> None:
"""Test web_server OTA with Arduino framework and authentication."""
main_cpp = generate_main(
"tests/component_tests/ota/test_web_server_ota_arduino.yaml"
)
# Check web server OTA component is present
assert "WebServerOTAComponent" in main_cpp
# Check authentication is set up for web server
assert "set_auth_username" in main_cpp
assert "set_auth_password" in main_cpp
def test_web_server_ota_esp8266(generate_main: Callable[[str], str]) -> None:
"""Test web_server OTA on ESP8266 platform."""
main_cpp = generate_main(
"tests/component_tests/ota/test_web_server_ota_esp8266.yaml"
)
# Check web server OTA component is present
assert "WebServerOTAComponent" in main_cpp
assert "web_server::WebServerOTAComponent" in main_cpp
@pytest.mark.parametrize(
("ota_configs", "expected_count", "warning_expected"),
[
pytest.param(
[
{
CONF_PLATFORM: CONF_WEB_SERVER,
CONF_ID: ID("ota_web", is_manual=False),
}
],
1,
False,
id="single_instance_no_merge",
),
pytest.param(
[
{
CONF_PLATFORM: CONF_WEB_SERVER,
CONF_ID: ID("ota_web_1", is_manual=False),
},
{
CONF_PLATFORM: CONF_WEB_SERVER,
CONF_ID: ID("ota_web_2", is_manual=False),
},
],
1,
True,
id="two_instances_merged",
),
pytest.param(
[
{
CONF_PLATFORM: CONF_WEB_SERVER,
CONF_ID: ID("ota_web_1", is_manual=False),
},
{
CONF_PLATFORM: "esphome",
CONF_ID: ID("ota_esphome", is_manual=False),
},
{
CONF_PLATFORM: CONF_WEB_SERVER,
CONF_ID: ID("ota_web_2", is_manual=False),
},
],
2,
True,
id="mixed_platforms_web_server_merged",
),
],
)
def test_web_server_ota_instance_merging(
ota_configs: list[dict[str, Any]],
expected_count: int,
warning_expected: bool,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test web_server OTA instance merging behavior."""
full_conf = {CONF_OTA: ota_configs.copy()}
token = fv.full_config.set(full_conf)
try:
with caplog.at_level(logging.WARNING):
_web_server_ota_final_validate({})
updated_conf = fv.full_config.get()
# Verify total number of OTA platforms
assert len(updated_conf[CONF_OTA]) == expected_count
# Verify warning
if warning_expected:
assert any(
"Found and merged" in record.message
and "web_server OTA" in record.message
for record in caplog.records
), "Expected merge warning not found in log"
else:
assert len(caplog.records) == 0, "Unexpected warnings logged"
finally:
fv.full_config.reset(token)
def test_web_server_ota_consistent_manual_ids(
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test that consistent manual IDs can be merged successfully."""
ota_configs = [
{
CONF_PLATFORM: CONF_WEB_SERVER,
CONF_ID: ID("ota_web", is_manual=True),
},
{
CONF_PLATFORM: CONF_WEB_SERVER,
CONF_ID: ID("ota_web", is_manual=True),
},
]
full_conf = {CONF_OTA: ota_configs}
token = fv.full_config.set(full_conf)
try:
with caplog.at_level(logging.WARNING):
_web_server_ota_final_validate({})
updated_conf = fv.full_config.get()
assert len(updated_conf[CONF_OTA]) == 1
assert updated_conf[CONF_OTA][0][CONF_ID].id == "ota_web"
assert any(
"Found and merged" in record.message and "web_server OTA" in record.message
for record in caplog.records
)
finally:
fv.full_config.reset(token)
def test_web_server_ota_inconsistent_manual_ids() -> None:
"""Test that inconsistent manual IDs raise an error."""
ota_configs = [
{
CONF_PLATFORM: CONF_WEB_SERVER,
CONF_ID: ID("ota_web_1", is_manual=True),
},
{
CONF_PLATFORM: CONF_WEB_SERVER,
CONF_ID: ID("ota_web_2", is_manual=True),
},
]
full_conf = {CONF_OTA: ota_configs}
token = fv.full_config.set(full_conf)
try:
with pytest.raises(
cv.Invalid,
match="Found multiple web_server OTA configurations but id is inconsistent",
):
_web_server_ota_final_validate({})
finally:
fv.full_config.reset(token)