Trim the noise glue and drive the source filter from the define

This commit is contained in:
J. Nick Koston
2026-09-05 11:28:00 +02:00
parent 029f6d4bc3
commit 8d60f03ff3
9 changed files with 119 additions and 258 deletions
+29 -80
View File
@@ -11,6 +11,7 @@ import asyncio
from collections.abc import Generator
from contextlib import contextmanager
import functools
from pathlib import Path
import socket
import pytest
@@ -22,6 +23,7 @@ from .const import LOCALHOST, PORT_POLL_INTERVAL, PORT_WAIT_TIMEOUT
from .types import CompileFunction, ConfigWriter
DEVICE_NAME = "host-ota-test"
API_KEY = "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8="
@contextmanager
@@ -121,7 +123,6 @@ async def test_host_ota_encrypted(
) -> None:
"""Encrypted self-OTA succeeds; a plaintext upload to the same device fails."""
pytest.importorskip("aioesphomeapi.noise")
noise_psk = "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8="
api_port, api_socket = reserved_tcp_port
with _reserve_port() as (ota_port, ota_socket):
yaml_config = yaml_config.replace("__OTA_PORT__", str(ota_port))
@@ -158,7 +159,7 @@ async def test_host_ota_encrypted(
ota_port,
None,
binary_path,
noise_psk=noise_psk,
noise_psk=API_KEY,
),
)
assert rc == 0, "encrypted OTA reported failure"
@@ -187,61 +188,22 @@ class _RebootCounter:
await self._seen.wait()
@pytest.mark.asyncio
async def test_host_ota_api_key_offers_encryption(
yaml_config: str,
write_yaml_config: ConfigWriter,
compile_esphome: CompileFunction,
reserved_tcp_port: tuple[int, socket.socket],
) -> None:
"""With only an api key the device takes both a plaintext upload and an
encrypted one using that key, which is the enablement path for
`ota: encryption:`."""
pytest.importorskip("aioesphomeapi.noise")
api_key = "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8="
api_port, api_socket = reserved_tcp_port
with _reserve_port() as (ota_port, ota_socket):
yaml_config = yaml_config.replace("__OTA_PORT__", str(ota_port))
config_path = await write_yaml_config(yaml_config)
binary_path = await compile_esphome(config_path)
api_socket.close()
ota_socket.close()
loop = asyncio.get_running_loop()
reboots = _RebootCounter()
async with run_binary(binary_path, line_callback=reboots.on_log) as (
proc,
lines,
):
await _wait_for_port(LOCALHOST, api_port, PORT_WAIT_TIMEOUT)
pid_before = proc.pid
rc, _ = await loop.run_in_executor(
None, espota2.run_ota, LOCALHOST, ota_port, None, binary_path
)
assert rc == 0, "plaintext upload to an offering device must succeed"
await reboots.wait(1)
await _wait_for_port(LOCALHOST, api_port, PORT_WAIT_TIMEOUT)
assert proc.pid == pid_before
rc, _ = await loop.run_in_executor(
None,
functools.partial(
espota2.run_ota,
LOCALHOST,
ota_port,
None,
binary_path,
noise_psk=api_key,
),
)
assert rc == 0, "encrypted upload with the api key must succeed"
await reboots.wait(2)
await _wait_for_port(LOCALHOST, api_port, PORT_WAIT_TIMEOUT)
assert proc.returncode is None, "process exited instead of execing"
assert proc.pid == pid_before
assert any("Encryption: offered" in line for line in lines)
async def _run_ota(
ota_port: int, password: str | None, binary_path: Path, noise_psk: str | None
) -> int:
"""espota2 is blocking; run it in the executor and return its exit code."""
rc, _ = await asyncio.get_running_loop().run_in_executor(
None,
functools.partial(
espota2.run_ota,
LOCALHOST,
ota_port,
password,
binary_path,
noise_psk=noise_psk,
),
)
return rc
@pytest.mark.asyncio
@@ -251,10 +213,11 @@ async def test_host_ota_api_key_offer_with_password(
compile_esphome: CompileFunction,
reserved_tcp_port: tuple[int, socket.socket],
) -> None:
"""The OTA password still guards plaintext uploads on an offering device
while the api key alone authenticates an encrypted one."""
"""With only an api key the device offers encryption without requiring
it: the password still guards plaintext uploads, and the key alone
authenticates an encrypted one, which is the enablement path for
`ota: encryption:`."""
pytest.importorskip("aioesphomeapi.noise")
api_key = "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8="
api_port, api_socket = reserved_tcp_port
with _reserve_port() as (ota_port, ota_socket):
yaml_config = yaml_config.replace("__OTA_PORT__", str(ota_port))
@@ -263,46 +226,32 @@ async def test_host_ota_api_key_offer_with_password(
api_socket.close()
ota_socket.close()
loop = asyncio.get_running_loop()
reboots = _RebootCounter()
async with run_binary(binary_path, line_callback=reboots.on_log) as (
proc,
_lines,
lines,
):
await _wait_for_port(LOCALHOST, api_port, PORT_WAIT_TIMEOUT)
pid_before = proc.pid
rc, _ = await loop.run_in_executor(
None, espota2.run_ota, LOCALHOST, ota_port, None, binary_path
)
rc = await _run_ota(ota_port, None, binary_path, None)
assert rc == 1, "plaintext upload without the password must fail"
await asyncio.sleep(0.5)
assert proc.returncode is None, "process died on rejected upload"
rc, _ = await loop.run_in_executor(
None, espota2.run_ota, LOCALHOST, ota_port, "hunter2", binary_path
)
rc = await _run_ota(ota_port, "hunter2", binary_path, None)
assert rc == 0, "plaintext upload with the password must succeed"
await reboots.wait(1)
await _wait_for_port(LOCALHOST, api_port, PORT_WAIT_TIMEOUT)
assert proc.pid == pid_before
rc, _ = await loop.run_in_executor(
None,
functools.partial(
espota2.run_ota,
LOCALHOST,
ota_port,
None,
binary_path,
noise_psk=api_key,
),
)
rc = await _run_ota(ota_port, None, binary_path, API_KEY)
assert rc == 0, "encrypted upload with the api key must succeed"
await reboots.wait(2)
await _wait_for_port(LOCALHOST, api_port, PORT_WAIT_TIMEOUT)
assert proc.returncode is None, "process exited instead of execing"
assert proc.pid == pid_before
assert any("Encryption: offered" in line for line in lines)
@pytest.mark.asyncio