Files
esphome/tests/unit_tests/components/api/test_api_to_code.py
T
Jesse Hills 041123b14c [api] Import noise-c and libsodium as ESP-IDF managed components
Both libraries now ship their own CMakeLists.txt, so on ESP-IDF they can be
pulled straight from the component registry (noise-c 0.1.15, libsodium
1.10021.2) instead of going through ESPHome's PlatformIO library converter.

A library must not be both converted and managed, or IDF refuses component
discovery, so the converter now takes a set of names the toolchain already
provides. Arduino keeps the converted path: arduino-esp32 brings its own
espressif/libsodium and IDF cannot pick between two managed components whose
names differ only by namespace.
2026-08-13 23:12:47 +12:00

145 lines
4.8 KiB
Python

"""Tests for the noise-c/libsodium library wiring in api's to_code.
On ESP-IDF (not Arduino) both libraries build themselves as native ESP-IDF
managed components, so they are declared via add_idf_component() instead of
going through ESPHome's PlatformIO-library converter. On every other target
(and on the Arduino framework, where arduino-esp32 brings its own bundled
espressif/libsodium) noise-c still goes through the PlatformIO-library
converter via cg.add_library(). This drives the real to_code() coroutine so
both branches of that decision are exercised end to end, not just mocked.
"""
from __future__ import annotations
import asyncio
import base64
from unittest.mock import MagicMock
import pytest
import esphome.codegen as cg
from esphome.components import api, esp32
import esphome.config_validation as cv
from esphome.const import (
KEY_CORE,
KEY_TARGET_FRAMEWORK,
KEY_TARGET_PLATFORM,
Framework,
Platform,
)
from esphome.core import CORE, ID
def _build_config(encryption_key: str) -> dict:
"""A minimal, already-validated api config with encryption enabled."""
return {
api.CONF_ID: ID("api_id", is_declaration=True, type=api.APIServer),
api.CONF_PORT: 6053,
api.CONF_REBOOT_TIMEOUT: cv.positive_time_period_milliseconds("15min"),
api.CONF_BATCH_DELAY: cv.positive_time_period_milliseconds("100ms"),
api.CONF_MAX_CONNECTIONS: 5,
api.CONF_MAX_SEND_QUEUE: 8,
api.CONF_CUSTOM_SERVICES: False,
api.CONF_HOMEASSISTANT_SERVICES: False,
api.CONF_HOMEASSISTANT_STATES: False,
api.CONF_ENCRYPTION: {api.CONF_KEY: encryption_key},
}
@pytest.fixture(name="encryption_key")
def fixture_encryption_key() -> str:
return base64.b64encode(b"0" * 32).decode()
def _setup_core(platform: Platform, framework: Framework) -> None:
CORE.reset()
CORE.data[KEY_CORE] = {
KEY_TARGET_PLATFORM: str(platform),
KEY_TARGET_FRAMEWORK: str(framework),
}
if platform == Platform.ESP32:
CORE.data[esp32.KEY_ESP32] = {esp32.KEY_VARIANT: "ESP32"}
def test_to_code_esp32_idf_encryption_uses_managed_idf_components(
encryption_key: str,
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""On ESP32 + ESP-IDF, noise-c and libsodium are declared as managed IDF
components (add_idf_component), not converted PlatformIO libraries."""
_setup_core(Platform.ESP32, Framework.ESP_IDF)
config = _build_config(encryption_key)
CORE.component_ids.add("api_id")
add_idf_component_calls: list[dict] = []
monkeypatch.setattr(
esp32,
"add_idf_component",
lambda **kwargs: add_idf_component_calls.append(kwargs),
)
add_library_mock = MagicMock()
monkeypatch.setattr(cg, "add_library", add_library_mock)
asyncio.run(api.to_code(config))
assert add_idf_component_calls == [
{"name": "esphome/noise-c", "ref": api.NOISE_C_VERSION},
{"name": "esphome/libsodium", "ref": api.LIBSODIUM_VERSION},
]
add_library_mock.assert_not_called()
def test_to_code_esp32_arduino_encryption_uses_add_library(
encryption_key: str,
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""On the Arduino framework, arduino-esp32 brings its own bundled
espressif/libsodium, so noise-c must still go through the PlatformIO-
library converter (cg.add_library) instead of add_idf_component()."""
_setup_core(Platform.ESP32, Framework.ARDUINO)
config = _build_config(encryption_key)
CORE.component_ids.add("api_id")
add_idf_component_mock = MagicMock()
monkeypatch.setattr(esp32, "add_idf_component", add_idf_component_mock)
add_library_calls: list[tuple] = []
monkeypatch.setattr(
cg,
"add_library",
lambda name, version, repository=None: add_library_calls.append(
(name, version)
),
)
asyncio.run(api.to_code(config))
assert add_library_calls == [("esphome/noise-c", api.NOISE_C_VERSION)]
add_idf_component_mock.assert_not_called()
def test_to_code_non_esp32_encryption_uses_add_library(
encryption_key: str,
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Off ESP32 entirely (e.g. host), noise-c always goes through the
PlatformIO-library converter -- add_idf_component is ESP-IDF-only."""
_setup_core(Platform.HOST, Framework.NATIVE)
config = _build_config(encryption_key)
CORE.component_ids.add("api_id")
add_idf_component_mock = MagicMock()
monkeypatch.setattr(esp32, "add_idf_component", add_idf_component_mock)
add_library_calls: list[tuple] = []
monkeypatch.setattr(
cg,
"add_library",
lambda name, version, repository=None: add_library_calls.append(
(name, version)
),
)
asyncio.run(api.to_code(config))
assert add_library_calls == [("esphome/noise-c", api.NOISE_C_VERSION)]
add_idf_component_mock.assert_not_called()