Files
ha-ninlight/tests/test_http.py
T
Michael Sauer b8f3c94278 fix(update): Lampe bekommt genau die geprüften Bytes
Die Registry für Einmal-URLs speicherte bisher nur einen Pfad; View und
verify() lasen die Datei getrennt und zu unterschiedlichen Zeitpunkten
erneut ein. Wurde die Datei zwischen Prüfung und Abruf ersetzt (z. B.
gleicher Dateiname in build/ und build-nocreds/), erreichte ein nie
geprüftes Image die Lampe und hebelte damit die WLAN-Sicherung aus.

Jetzt werden die Bytes einmal im Install-Pfad gelesen, Gerätetyp/Version
und WLAN-SSID auf genau diesen Bytes geprüft (FirmwareCatalog.verify_and_read,
firmware.contains_ssid) und exakt diese Bytes in der Registry hinterlegt;
die View serviert nur noch das, was tatsächlich geprüft wurde.

Außerdem: Bewertungen (Katalog-Rescan-Signal) werden nicht mehr angestoßen,
während ein Update läuft - das würde eine gerade flashende Lampe zusätzlich
anfragen und Angebot/Issue unter dem laufenden Update verändern; nach jedem
Update wird stattdessen genau einmal neu bewertet. Kommentar zum Install-Lock
korrigiert (der eigentliche Lücke liegt vor dem Setzen der Flags, nicht in
HAs eigener "bereits läuft"-Prüfung). Testlücke geschlossen: die View liefert
jetzt nachweislich nur die zu ihrem Schlüssel gehörenden Bytes. Timeout in
einem WLAN-Regressionstest verkürzt, damit er bei einem Rückfall nicht
12 Minuten hängt.
2026-09-16 17:40:10 +02:00

79 lines
3.0 KiB
Python

"""One-time firmware URLs and the address lamps use to reach Home Assistant."""
from __future__ import annotations
from pathlib import Path
from homeassistant.core import HomeAssistant
from homeassistant.core_config import async_process_ha_core_config
from homeassistant.exceptions import HomeAssistantError
import pytest
from pytest_homeassistant_custom_component.typing import ClientSessionGenerator
from custom_components.ninlight.const import CONF_LAMP_BASE_URL
from custom_components.ninlight.http import OtaRegistry, lamp_base_url
from custom_components.ninlight.shared import DATA_SHARED
from tests_unit.images import build_image
def test_registry_keys_are_unique_and_43_characters() -> None:
registry = OtaRegistry()
keys = {registry.create(b"payload") for _ in range(50)}
assert len(keys) == 50
assert all(len(key) == 43 for key in keys)
assert len(registry) == 50
async def test_view_serves_only_its_own_registered_bytes(
hass: HomeAssistant,
hass_client_no_auth: ClientSessionGenerator,
init_integration,
firmware_dir: Path,
) -> None:
registry = hass.data[DATA_SHARED].registry
mine = build_image()
other = build_image(version="0.3.9-rc1")
key = registry.create(mine)
other_key = registry.create(other)
client = await hass_client_no_auth()
response = await client.get(f"/api/ninlight/firmware/{key}")
assert response.status == 200
assert await response.read() == mine
other_response = await client.get(f"/api/ninlight/firmware/{other_key}")
assert other_response.status == 200
assert await other_response.read() == other
assert (await client.get("/api/ninlight/firmware/" + "x" * 43)).status == 404
registry.revoke(key)
assert (await client.get(f"/api/ninlight/firmware/{key}")).status == 404
# revoking one key never touches another registered under a different key
assert (await client.get(f"/api/ninlight/firmware/{other_key}")).status == 200
async def test_base_url_prefers_option(hass: HomeAssistant) -> None:
await async_process_ha_core_config(hass, {"internal_url": "http://192.168.1.60:8123"})
assert (
lamp_base_url(hass, {CONF_LAMP_BASE_URL: "http://192.168.1.50:8123"})
== "http://192.168.1.50:8123"
)
async def test_base_url_falls_back_to_internal_http_url(hass: HomeAssistant) -> None:
await async_process_ha_core_config(hass, {"internal_url": "http://192.168.1.60:8123"})
assert lamp_base_url(hass, {}) == "http://192.168.1.60:8123"
async def test_base_url_rejects_https(hass: HomeAssistant) -> None:
await async_process_ha_core_config(hass, {"internal_url": "https://ha.local:8123"})
with pytest.raises(HomeAssistantError) as raised:
lamp_base_url(hass, {})
assert raised.value.translation_key == "no_http_url"
async def test_base_url_without_any_url(hass: HomeAssistant) -> None:
with pytest.raises(HomeAssistantError) as raised:
lamp_base_url(hass, {})
assert raised.value.translation_key == "no_http_url"