Merge remote-tracking branch 'upstream/dev' into neutral-ble-client

This commit is contained in:
J. Nick Koston
2026-08-20 00:45:33 -05:00
274 changed files with 6589 additions and 2242 deletions
@@ -0,0 +1,7 @@
esphome:
name: bk-family-gate-n
bk72xx:
board: cb2s
bk72xx_ble:
@@ -0,0 +1,7 @@
esphome:
name: bk-family-gate-q
bk72xx:
board: wa2
bk72xx_ble:
@@ -0,0 +1,7 @@
esphome:
name: bk-family-gate-t
bk72xx:
board: generic-bk7231t-qfn32-tuya
bk72xx_ble:
@@ -0,0 +1,7 @@
esphome:
name: bk-family-gate-7252
bk72xx:
board: generic-bk7252
bk72xx_ble:
@@ -0,0 +1,40 @@
"""The non-5.x family rejection lives in to_code (config validation must stay
family-agnostic for the validate-only CI fixtures), so codegen is the only
place it can be pinned."""
from collections.abc import Callable
from pathlib import Path
import pytest
from esphome.core import EsphomeError
@pytest.mark.parametrize(
("config_file", "match"),
[
("test_bk7231t.yaml", "BK7231T.*BLE 4.2"),
("test_bk7252.yaml", "BK7251.*BLE 4.2"),
("test_bk7231q.yaml", "BK7231Q.*no BLE"),
],
)
def test_unsupported_family_rejected(
generate_main: Callable[[str | Path], str],
component_config_path: Callable[[str], Path],
config_file: str,
match: str,
caplog: pytest.LogCaptureFixture,
) -> None:
with pytest.raises(EsphomeError, match=match):
generate_main(component_config_path(config_file))
# Validation itself must not fail (CI validate fixtures run on a BLE 4.2
# board), but it warns before codegen raises.
assert "cannot compile" in caplog.text
def test_ble5_family_generates(
generate_main: Callable[[str | Path], str],
component_config_path: Callable[[str], Path],
) -> None:
main_cpp = generate_main(component_config_path("test_bk7231n.yaml"))
assert "bk72xx_ble::BK72xxBLE" in main_cpp
@@ -2,6 +2,6 @@ esphome:
name: slotcount-controller
bk72xx:
board: generic-bk7252
board: cb2s
bk72xx_ble:
@@ -2,6 +2,6 @@ esphome:
name: slotcount-tracker
bk72xx:
board: generic-bk7252
board: cb2s
bk72xx_ble_tracker:
@@ -57,7 +57,12 @@ def test_bk72xx_defaults_are_valid() -> None:
def test_esp32_defaults_are_valid() -> None:
"""esp32 pins the ESP-IDF reference rate and exposes active (default on)."""
"""esp32 pins the ESP-IDF reference rate and exposes active (default on).
Without wifi loaded, the conditional window default falls back to the
historical 30 ms; the wifi-aware resolution is covered by the
esp32_ble_tracker component tests.
"""
config = ESP32_SCHEMA({})
assert to_ble_units(config["interval"]) == 512
assert to_ble_units(config["window"]) == 48
@@ -0,0 +1,122 @@
"""Tests for the esp32_ble_tracker conditional scan window default.
The scan window default depends on wifi coexistence and the IDF version:
IDF 5.5.5 fixed a coexistence bug where BLE scans ran far longer than the
configured window (espressif/esp-idf#18931), so on fixed versions the
historical 30 ms default would only listen 9.4 % of the time and miss most
advertisements. With the coexistence arbiter compiled in on a fixed IDF, the
window instead defaults to the interval, as Espressif recommends; without the
arbiter a full-duty scan would starve wifi, so the 30 ms default is kept.
"""
from __future__ import annotations
from collections.abc import Callable
import pytest
from esphome import config_validation as cv
from esphome.components.ble_device_base import to_ble_units
from esphome.components.const import CONF_SCAN_PARAMETERS, CONF_WINDOW
from esphome.components.esp32 import KEY_IDF_VERSION
from esphome.components.esp32_ble_tracker import (
CONF_SOFTWARE_COEXISTENCE,
CONFIG_SCHEMA,
)
from esphome.const import CONF_INTERVAL, PlatformFramework
from esphome.core import CORE
from esphome.types import ConfigType
from ..types import SetCoreConfigCallable
@pytest.fixture
def stage_esp32(
set_core_config: SetCoreConfigCallable,
) -> Callable[..., None]:
"""Stage an esp32 build with a given IDF version and wifi presence."""
def stage(idf: str, *, wifi: bool) -> None:
set_core_config(
PlatformFramework.ESP32_IDF,
platform_data={KEY_IDF_VERSION: cv.Version.parse(idf)},
)
if wifi:
# Makes cv.OnlyWith default software_coexistence to True, exactly
# as a real config with wifi: does.
CORE.loaded_integrations.add("wifi")
return stage
def _scan_params(config: ConfigType) -> ConfigType:
return CONFIG_SCHEMA(config)[CONF_SCAN_PARAMETERS]
@pytest.mark.parametrize(
("idf", "config", "expected_units"),
[
("5.5.5", {}, 512), # first fixed version, default 320 ms interval
("6.0.1", {}, 512), # any newer version behaves the same
# Follows a user-set interval.
("5.5.5", {"scan_parameters": {"interval": "1s"}}, 1600),
],
)
def test_wifi_on_fixed_idf_defaults_window_to_interval(
stage_esp32: Callable[..., None],
idf: str,
config: ConfigType,
expected_units: int,
) -> None:
"""With wifi coexistence on a fixed IDF, the window defaults to the interval."""
stage_esp32(idf, wifi=True)
params = _scan_params(config)
assert params[CONF_WINDOW] == params[CONF_INTERVAL]
assert to_ble_units(params[CONF_WINDOW]) == expected_units
@pytest.mark.parametrize(
("idf", "wifi", "config"),
[
# Buggy IDF over-scans anyway; keep the 30 ms default.
("5.5.4", True, {}),
# No wifi (e.g. ethernet) means no radio contention.
("5.5.5", False, {}),
# Coexistence disabled: no arbiter, so a full-duty scan would starve
# wifi outright.
("5.5.5", True, {CONF_SOFTWARE_COEXISTENCE: False}),
],
)
def test_30ms_default_kept(
stage_esp32: Callable[..., None],
idf: str,
wifi: bool,
config: ConfigType,
) -> None:
stage_esp32(idf, wifi=wifi)
assert to_ble_units(_scan_params(config)[CONF_WINDOW]) == 48
@pytest.mark.parametrize("window", ["60ms", "30ms"])
def test_explicit_window_is_never_touched(
stage_esp32: Callable[..., None], window: str
) -> None:
"""A user-set window wins over the conditional default.
The explicit 30 ms case matters: it is indistinguishable from the
defaulted value by inspection, so the defaulted flag must separate them.
"""
stage_esp32("5.5.5", wifi=True)
params = _scan_params({"scan_parameters": {"window": window}})
assert to_ble_units(params[CONF_WINDOW]) == to_ble_units(
cv.positive_time_period(window)
)
def test_short_interval_without_window_still_rejected(
stage_esp32: Callable[..., None],
) -> None:
"""The provisional 30 ms default validates against the interval as before."""
stage_esp32("5.5.5", wifi=True)
with pytest.raises(cv.Invalid, match="needs to be smaller than scan interval"):
_scan_params({"scan_parameters": {"interval": "20ms"}})
@@ -0,0 +1,35 @@
"""Tests for the esp32_hosted ESP-IDF version gate."""
import pytest
from esphome import config_validation as cv
from esphome.components.esp32 import KEY_IDF_VERSION
from esphome.components.esp32_hosted import _final_validate
from esphome.const import PlatformFramework
from ..types import SetCoreConfigCallable
@pytest.mark.parametrize("idf", ["5.3.0", "5.4.2", "5.5.5"])
def test_final_validate_accepts_supported_idf(
set_core_config: SetCoreConfigCallable, idf: str
) -> None:
"""ESP-IDF 5.3 and newer passes validation unchanged."""
set_core_config(
PlatformFramework.ESP32_IDF,
platform_data={KEY_IDF_VERSION: cv.Version.parse(idf)},
)
_final_validate({})
@pytest.mark.parametrize("idf", ["5.0.0", "5.2.2"])
def test_final_validate_rejects_old_idf(
set_core_config: SetCoreConfigCallable, idf: str
) -> None:
"""ESP-IDF older than 5.3 is rejected with a clear error."""
set_core_config(
PlatformFramework.ESP32_IDF,
platform_data={KEY_IDF_VERSION: cv.Version.parse(idf)},
)
with pytest.raises(cv.Invalid, match="requires ESP-IDF 5.3 or newer"):
_final_validate({})
@@ -0,0 +1,61 @@
"""Tests for the shared io expander interrupt_pin validator."""
from __future__ import annotations
import importlib
import pytest
from esphome import config_validation as cv
from esphome.components.esp32 import KEY_BOARD, KEY_VARIANT, VARIANT_ESP32
from esphome.components.gpio_expander import validate_interrupt_pin
from esphome.const import PlatformFramework
from tests.component_tests.types import SetCoreConfigCallable
@pytest.fixture
def stage_esp32(set_core_config: SetCoreConfigCallable) -> None:
set_core_config(
PlatformFramework.ESP32_IDF,
platform_data={KEY_BOARD: "esp32dev", KEY_VARIANT: VARIANT_ESP32},
)
def test_plain_pin_accepted(stage_esp32: None) -> None:
value = validate_interrupt_pin(
{"number": 16, "mode": {"input": True, "pullup": True}}
)
assert value["number"] == 16
def test_inverted_rejected(stage_esp32: None) -> None:
with pytest.raises(cv.Invalid, match="'inverted: true' is not supported"):
validate_interrupt_pin({"number": 16, "inverted": True})
def test_allow_other_uses_rejected(stage_esp32: None) -> None:
with pytest.raises(cv.Invalid, match="'allow_other_uses: true' is not supported"):
validate_interrupt_pin({"number": 16, "allow_other_uses": True})
# mcp23017 covers the shared mcp23xxx_base schema
@pytest.mark.parametrize(
"component",
[
"pcf8574",
"pca9554",
"tca9555",
"pca6416a",
"pi4ioe5v6408",
"mcp23016",
"mcp23017",
],
)
def test_component_schemas_route_through_validator(
stage_esp32: None, component: str
) -> None:
module = importlib.import_module(f"esphome.components.{component}")
with pytest.raises(cv.Invalid, match="'inverted: true' is not supported"):
module.CONFIG_SCHEMA(
{"id": "expander_hub", "interrupt_pin": {"number": 16, "inverted": True}}
)
+334 -11
View File
@@ -21,16 +21,20 @@ from esphome.components.image import (
CONF_OPAQUE,
CONF_TRANSPARENCY,
PLATFORM_FILE,
_expand_platform_entry,
_flatten_legacy_image_config,
_is_legacy_image_format,
_is_new_image_format,
_migrate_legacy_image_config,
expand_platform_config,
get_all_image_metadata,
get_image_metadata,
)
from esphome.const import (
CONF_DEFAULTS,
CONF_DITHER,
CONF_FILE,
CONF_FILES,
CONF_ID,
CONF_PLATFORM,
CONF_RAW_DATA_ID,
@@ -259,6 +263,15 @@ def test_flatten_keeps_byte_order_for_endian_type() -> None:
assert out[0][CONF_BYTE_ORDER] == "little_endian"
def test_flatten_drops_byte_order_written_directly_on_legacy_entry() -> None:
"""The legacy flattener drops an incompatible byte_order even when written directly on the entry."""
out = _flatten_legacy_image_config(
{"binary": [{"id": "a", "file": "x.png", "byte_order": "little_endian"}]}
)
assert out == [{"id": "a", "file": "x.png", "type": "binary"}]
assert CONF_BYTE_ORDER not in out[0]
def test_flatten_skips_meta_and_unknown_keys() -> None:
out = _flatten_legacy_image_config(
{
@@ -342,6 +355,42 @@ def test_migrate_legacy_warns_and_prepends_platform(
),
pytest.param({"foo": 1}, False, id="dict_unknown_keys"),
pytest.param("a string", False, id="scalar"),
# A `platform:`-tagged dict is the new format written without list brackets.
pytest.param(
{CONF_PLATFORM: "file", "id": "a", "file": "x.png"},
False,
id="platform_tagged_flat_dict",
),
pytest.param(
{
CONF_PLATFORM: "file",
"defaults": {"type": "rgb565"},
"files": [{"id": "a", "file": "x.png"}],
},
False,
id="platform_tagged_defaults_files_dict",
),
# `files:` without `platform:` is not legacy either -- the flattener has no branch for it.
pytest.param(
{
"defaults": {"type": "rgb565"},
"files": [{"id": "a", "file": "x.png"}],
},
False,
id="defaults_files_dict_without_platform",
),
# Same as above in a list -- without this exclusion it would be silently
# migrated to a hard-coded `platform: file` instead of raising the error.
pytest.param(
[
{
"defaults": {"type": "rgb565"},
"files": [{"id": "a", "file": "x.png"}],
}
],
False,
id="defaults_files_list_entry_without_platform",
),
],
)
def test_is_legacy_image_format(config: object, expected: bool) -> None:
@@ -359,39 +408,313 @@ def test_is_legacy_image_format(config: object, expected: bool) -> None:
def test_migrate_returns_none_for_invalid_legacy_shapes(
config: object, caplog: pytest.LogCaptureFixture
) -> None:
"""Unrecognised shapes are not migrated (and emit no warning) so normal
platform validation surfaces a proper error instead of silently dropping
the offending input."""
"""Unrecognised shapes are not migrated (and emit no warning), so normal platform validation reports them."""
with caplog.at_level(logging.WARNING):
assert _migrate_legacy_image_config(config) is None
assert "deprecated" not in caplog.text
def test_migrate_returns_none_for_mapping_form_defaults_files() -> None:
"""A `platform:`-tagged `defaults:`/`files:` mapping must not be swallowed by the legacy migrator."""
config = {
CONF_PLATFORM: "file",
"defaults": {"type": "rgb565"},
"files": [{"id": "a", "file": "a.png"}],
}
assert _migrate_legacy_image_config(config) is None
def test_migrate_returns_none_for_defaults_files_dict_without_platform() -> None:
"""`defaults:`/`files:` without `platform:` must not be swallowed either -- the flattener has
no `files:` branch and would silently return `[]`."""
config = {
"defaults": {"type": "rgb565"},
"files": [{"id": "a", "file": "a.png"}],
}
assert _migrate_legacy_image_config(config) is None
def test_migrate_returns_none_for_defaults_files_list_entry_without_platform() -> None:
"""Same, in a list -- previously the list branch migrated it to a hard-coded
`platform: file` instead of raising a missing-platform error."""
config = [
{
"defaults": {"type": "rgb565"},
"files": [{"id": "a", "file": "a.png"}],
}
]
assert _migrate_legacy_image_config(config) is None
# --------------------------- end legacy migration --------------------------
def test_expand_platform_entry_passes_through_plain_entry() -> None:
entry = {CONF_PLATFORM: "file", "id": "a", "file": "x.png"}
assert _expand_platform_entry(0, entry) == [entry]
def test_expand_platform_entry_expands_files_with_defaults() -> None:
entry = {
CONF_PLATFORM: "file",
CONF_DEFAULTS: {"type": "RGB565", "transparency": "opaque"},
CONF_FILES: [
{"id": "img1", "file": "foo.png"},
{"id": "img2", "file": "bar.png", "type": "GRAYSCALE"},
],
}
assert _expand_platform_entry(0, entry) == [
{
CONF_PLATFORM: "file",
"id": "img1",
"file": "foo.png",
"type": "RGB565",
"transparency": "opaque",
},
{
CONF_PLATFORM: "file",
"id": "img2",
"file": "bar.png",
"type": "GRAYSCALE",
"transparency": "opaque",
},
]
def test_expand_platform_entry_files_without_defaults() -> None:
entry = {
CONF_PLATFORM: "file",
CONF_FILES: [{"id": "img1", "file": "foo.png"}],
}
assert _expand_platform_entry(0, entry) == [
{CONF_PLATFORM: "file", "id": "img1", "file": "foo.png"}
]
def test_expand_platform_entry_preserves_source_range() -> None:
"""A merged entry keeps the source range of its `files:` item so whole-entry errors anchor there."""
from esphome import yaml_util
file_entry = yaml_util.make_data_base({"id": "img1", "file": "foo.png"})
file_entry._esp_range = "sentinel-range"
entry = {
CONF_PLATFORM: "file",
CONF_DEFAULTS: {"type": "RGB565"},
CONF_FILES: [file_entry],
}
[out] = _expand_platform_entry(0, entry)
assert isinstance(out, yaml_util.ESPHomeDataBase)
assert out.esp_range == "sentinel-range"
def test_expand_platform_entry_plain_dict_file_entry_has_no_source_range() -> None:
"""Plain-dict `files:` items must not crash -- `from_database` reads `.esp_range` unconditionally."""
entry = {
CONF_PLATFORM: "file",
CONF_FILES: [{"id": "img1", "file": "foo.png"}],
}
[out] = _expand_platform_entry(0, entry)
assert out == {CONF_PLATFORM: "file", "id": "img1", "file": "foo.png"}
def test_expand_platform_entry_per_file_overrides_win() -> None:
entry = {
CONF_PLATFORM: "file",
CONF_DEFAULTS: {"type": "RGB565"},
CONF_FILES: [{"id": "img1", "file": "foo.png", "type": "BINARY"}],
}
[out] = _expand_platform_entry(0, entry)
assert out["type"] == "BINARY"
def test_expand_platform_entry_drops_byte_order_for_non_endian_override() -> None:
"""A `byte_order` default merged into a non-endian override is dropped, as the legacy flattener did."""
entry = {
CONF_PLATFORM: "file",
CONF_DEFAULTS: {"type": "rgb565", "byte_order": "little_endian"},
CONF_FILES: [
{"id": "a", "file": "x.png"},
{"id": "b", "file": "y.png", "type": "binary"},
],
}
out = _expand_platform_entry(0, entry)
assert out[0]["byte_order"] == "little_endian"
assert "byte_order" not in out[1]
def test_expand_platform_entry_invalid_byte_order_in_defaults_raises() -> None:
"""A dropped `byte_order` inherited from `defaults:` is still validated, so a typo raises."""
entry = {
CONF_PLATFORM: "file",
CONF_DEFAULTS: {"type": "rgb565", "byte_order": "little_andian"},
CONF_FILES: [{"id": "a", "file": "x.png", "type": "binary"}],
}
with pytest.raises(cv.Invalid, match="did you mean") as excinfo:
_expand_platform_entry(0, entry)
assert excinfo.value.path == [0]
def test_expand_platform_entry_keeps_byte_order_for_endian_override() -> None:
entry = {
CONF_PLATFORM: "file",
CONF_DEFAULTS: {"type": "rgb565", "byte_order": "big_endian"},
CONF_FILES: [{"id": "a", "file": "x.png", "type": "rgb565"}],
}
[out] = _expand_platform_entry(0, entry)
assert out["byte_order"] == "big_endian"
def test_expand_platform_entry_keeps_explicit_byte_order_conflict() -> None:
"""A `byte_order` written directly on the entry is kept so validate_settings raises the normal error."""
entry = {
CONF_PLATFORM: "file",
CONF_DEFAULTS: {"type": "rgb565"},
CONF_FILES: [
{
"id": "a",
"file": "x.png",
"type": "binary",
"byte_order": "little_endian",
}
],
}
[out] = _expand_platform_entry(0, entry)
assert out["byte_order"] == "little_endian"
def test_expand_platform_entry_defaults_without_files_raises() -> None:
entry = {CONF_PLATFORM: "file", CONF_DEFAULTS: {"type": "RGB565"}}
with pytest.raises(cv.Invalid, match="may only be used together with") as excinfo:
_expand_platform_entry(0, entry)
assert excinfo.value.path == [0]
def test_expand_platform_entry_null_files_raises_not_empty() -> None:
"""A `files:` key with no value parses to `None` and must be reported clearly."""
entry = {CONF_PLATFORM: "file", CONF_DEFAULTS: {"type": "RGB565"}, CONF_FILES: None}
with pytest.raises(cv.Invalid, match="must not be empty"):
_expand_platform_entry(0, entry)
def test_expand_platform_entry_empty_files_list_raises_not_empty() -> None:
"""An explicit `files: []` must not silently drop the whole platform entry."""
entry = {CONF_PLATFORM: "file", CONF_FILES: []}
with pytest.raises(cv.Invalid, match="must not be empty"):
_expand_platform_entry(0, entry)
def test_expand_platform_entry_files_with_stray_key_raises() -> None:
entry = {
CONF_PLATFORM: "file",
CONF_FILES: [{"id": "a", "file": "x.png"}],
"extra": 1,
}
with pytest.raises(cv.Invalid, match="cannot be combined with"):
_expand_platform_entry(0, entry)
def test_expand_platform_entry_id_in_defaults_raises() -> None:
entry = {
CONF_PLATFORM: "file",
CONF_DEFAULTS: {CONF_ID: "a"},
CONF_FILES: [{"file": "x.png"}],
}
with pytest.raises(cv.Invalid, match="not allowed inside"):
_expand_platform_entry(0, entry)
def test_expand_platform_entry_platform_in_defaults_raises() -> None:
"""`platform:` inside `defaults:` would silently reassign every file's platform."""
entry = {
CONF_PLATFORM: "file",
CONF_DEFAULTS: {CONF_PLATFORM: "animation"},
CONF_FILES: [{"id": "a", "file": "x.png"}],
}
with pytest.raises(cv.Invalid, match="not allowed inside"):
_expand_platform_entry(0, entry)
def test_expand_platform_entry_platform_in_file_entry_raises() -> None:
"""`platform:` on a `files:` item must not silently override the entry's platform."""
entry = {
CONF_PLATFORM: "file",
CONF_FILES: [{"id": "a", "file": "x.png", CONF_PLATFORM: "animation"}],
}
with pytest.raises(cv.Invalid, match="not allowed inside"):
_expand_platform_entry(0, entry)
def test_expand_platform_entry_files_not_list_raises() -> None:
entry = {CONF_PLATFORM: "file", CONF_FILES: "not-a-list"}
with pytest.raises(cv.Invalid, match="must be a list"):
_expand_platform_entry(0, entry)
def test_expand_platform_entry_defaults_not_mapping_raises() -> None:
entry = {
CONF_PLATFORM: "file",
CONF_DEFAULTS: "not-a-mapping",
CONF_FILES: [{"id": "a", "file": "x.png"}],
}
with pytest.raises(cv.Invalid, match="must be a mapping"):
_expand_platform_entry(0, entry)
def test_expand_platform_entry_file_item_not_mapping_raises() -> None:
entry = {CONF_PLATFORM: "file", CONF_FILES: [1, 2]}
with pytest.raises(cv.Invalid, match="must be a mapping"):
_expand_platform_entry(0, entry)
def test_expand_platform_config_mixes_plain_and_expanded_entries() -> None:
config = [
{
CONF_PLATFORM: "file",
CONF_DEFAULTS: {"type": "RGB565"},
CONF_FILES: [
{"id": "img1", "file": "foo.png"},
{"id": "img2", "file": "bar.png"},
],
},
{CONF_PLATFORM: "file", "id": "plain", "file": "baz.png", "type": "BINARY"},
]
out = expand_platform_config(config)
assert [entry["id"] for entry in out] == ["img1", "img2", "plain"]
def test_expand_platform_config_ignores_non_platform_entries() -> None:
# Not expanded here -- legacy_config_migrate runs before this hook and is
# responsible for tagging/flattening pre-platform shapes.
config = ["not-a-platform-entry"]
assert expand_platform_config(config) == config
# --------------------- end defaults/files expansion -------------------------
def test_validate_image_final_defaults_to_little_endian() -> None:
out = validate_image_final({CONF_FILE: "x.png"})
assert out[CONF_BYTE_ORDER] == "LITTLE_ENDIAN"
config = {CONF_FILE: "x.png"}
validate_image_final(config)
assert config[CONF_BYTE_ORDER] == "LITTLE_ENDIAN"
def test_validate_image_final_keeps_little_endian(
caplog: pytest.LogCaptureFixture,
) -> None:
config = {CONF_FILE: "x.png", CONF_BYTE_ORDER: "LITTLE_ENDIAN"}
with caplog.at_level(logging.WARNING):
out = validate_image_final(
{CONF_FILE: "x.png", CONF_BYTE_ORDER: "LITTLE_ENDIAN"}
)
assert out[CONF_BYTE_ORDER] == "LITTLE_ENDIAN"
validate_image_final(config)
assert config[CONF_BYTE_ORDER] == "LITTLE_ENDIAN"
assert "big-endian" not in caplog.text
def test_validate_image_final_warns_on_big_endian(
caplog: pytest.LogCaptureFixture,
) -> None:
config = {CONF_FILE: "x.png", CONF_BYTE_ORDER: "BIG_ENDIAN"}
with caplog.at_level(logging.WARNING):
out = validate_image_final({CONF_FILE: "x.png", CONF_BYTE_ORDER: "BIG_ENDIAN"})
assert out[CONF_BYTE_ORDER] == "BIG_ENDIAN"
validate_image_final(config)
assert config[CONF_BYTE_ORDER] == "BIG_ENDIAN"
assert "big-endian" in caplog.text
@@ -37,7 +37,7 @@ def test_provisioning_accepts_a_registered_source(
set_core_config(PlatformFramework.ESP32_IDF)
register_source("network")
# Should not raise.
assert FINAL_VALIDATE_SCHEMA({}) == {}
FINAL_VALIDATE_SCHEMA({})
def test_provisioning_warns_on_hardcoded_credentials(
@@ -49,7 +49,7 @@ def test_provisioning_warns_on_hardcoded_credentials(
register_source("network")
report_hardcoded_credentials("wifi")
with caplog.at_level(logging.WARNING):
assert FINAL_VALIDATE_SCHEMA({}) == {}
FINAL_VALIDATE_SCHEMA({})
assert "wifi" in caplog.text
assert "credentials" in caplog.text
@@ -62,7 +62,7 @@ def test_provisioning_no_warning_without_hardcoded_credentials(
set_core_config(PlatformFramework.ESP32_IDF)
register_source("network")
with caplog.at_level(logging.WARNING):
assert FINAL_VALIDATE_SCHEMA({}) == {}
FINAL_VALIDATE_SCHEMA({})
assert "credentials" not in caplog.text