feat: update-Entitaet fuer die Firmware und das Testgeruest
Aufgebaut wie ha-ninlight: die Abbilder liegen unter ninstrip_firmware im Konfigurationsverzeichnis, Home Assistant liefert sie selbst unter einer einmaligen Adresse aus, und zwar genau die Bytes, die vorher geprueft wurden. Dazu firmware.py (Anhaenger, Versionsreihenfolge, WLAN-Pruefung, Katalog), http.py, urls.py und shared.py. Drei Unterschiede zu ninlight, alle aus der Firmware heraus: * Verglichen wird device_type - die Platine -, nicht product. Letzteres ist bei allen Platinen 'ninstrip'; die Abbilder unterscheiden sich aber hart, die K1Y invertiert das Datensignal und die KL-RGBC3 nicht. Firmware ohne dieses Feld bekommt gar kein Angebot. * Die WLAN-Pruefung sucht die SSID unverpackt: ein ESP32-Abbild ist nicht gzip-verpackt wie das des W600. An einem mit sdkconfig.local gebauten Abbild gegengeprueft - die SSID steht genau einmal NUL-gerahmt drin. * Der Fehlerschluessel heisst device_url_too_long statt lamp_url_too_long. Dazu das Testgeruest, das es hier bisher gar nicht gab: 204 Tests in tests_unit/ (ohne HA-Instanz) und tests/ (mit), Abdeckung 97 %, dazu scripts/check.sh als Ersatz fuer die fehlende CI. Die Nutzdaten in tests_unit/samples.py sind an der Stehlampe abgefragt. Beim Gegenpruefen per Mutation fiel auf, dass die Platinenpruefung unmittelbar vor dem Senden ungeprueft war - der Test dazu ist jetzt da und faellt, wenn man die Pruefung entfernt.
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
"""Fixtures fuer die Tests, die gegen eine Home-Assistant-Instanz laufen."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Generator
|
||||
from copy import deepcopy
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
from homeassistant.const import CONF_HOST, CONF_TOKEN
|
||||
from homeassistant.core import HomeAssistant
|
||||
import pytest
|
||||
from pytest_homeassistant_custom_component.common import MockConfigEntry
|
||||
|
||||
from custom_components.ninstrip.api import NinstripClient
|
||||
from custom_components.ninstrip.const import DOMAIN
|
||||
from tests_unit.samples import (
|
||||
HOST_K1Y,
|
||||
INFO_K1Y,
|
||||
MAC_K1Y,
|
||||
PRESETS_K1Y,
|
||||
SETTINGS_COMPILED,
|
||||
STATE_K1Y,
|
||||
TOKEN,
|
||||
)
|
||||
|
||||
CLIENT_PATCH_TARGETS = [
|
||||
"custom_components.ninstrip.NinstripClient",
|
||||
"custom_components.ninstrip.config_flow.NinstripClient",
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def auto_enable_custom_integrations(enable_custom_integrations: None) -> Generator[None]:
|
||||
"""custom_components/ in jedem Home-Assistant-Test laden."""
|
||||
yield
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def strip_state() -> dict[str, Any]:
|
||||
return deepcopy(STATE_K1Y)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def strip_info() -> dict[str, Any]:
|
||||
return deepcopy(INFO_K1Y)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def strip_settings() -> dict[str, Any]:
|
||||
return deepcopy(SETTINGS_COMPILED)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def strip_presets() -> dict[str, Any]:
|
||||
return deepcopy(PRESETS_K1Y)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_client(
|
||||
strip_state: dict[str, Any],
|
||||
strip_info: dict[str, Any],
|
||||
strip_settings: dict[str, Any],
|
||||
strip_presets: dict[str, Any],
|
||||
) -> Generator[MagicMock]:
|
||||
"""Ein Geraet, dessen Antworten den veraenderbaren strip_*-Fixtures folgen."""
|
||||
client = MagicMock(spec=NinstripClient)
|
||||
client.host = HOST_K1Y
|
||||
client.get_state = AsyncMock(side_effect=lambda: deepcopy(strip_state))
|
||||
client.get_info = AsyncMock(side_effect=lambda: deepcopy(strip_info))
|
||||
client.get_settings = AsyncMock(side_effect=lambda: deepcopy(strip_settings))
|
||||
client.get_presets = AsyncMock(side_effect=lambda: deepcopy(strip_presets))
|
||||
|
||||
async def set_state(**fields: Any) -> dict[str, Any]:
|
||||
strip_state.update(fields)
|
||||
return deepcopy(strip_state)
|
||||
|
||||
client.set_state = AsyncMock(side_effect=set_state)
|
||||
|
||||
async def set_sacn(**fields: Any) -> None:
|
||||
# Wie das Geraet: es merkt sich, was es annimmt, und der Aufrufer
|
||||
# liest den Block danach zurueck.
|
||||
strip_settings.setdefault("sacn", {}).update(fields)
|
||||
|
||||
client.set_sacn = AsyncMock(side_effect=set_sacn)
|
||||
client.apply_preset = AsyncMock(return_value=None)
|
||||
client.save_preset = AsyncMock(return_value=None)
|
||||
client.reboot = AsyncMock(return_value=None)
|
||||
client.fw_update = AsyncMock(return_value=None)
|
||||
patches = [patch(target, return_value=client) for target in CLIENT_PATCH_TARGETS]
|
||||
for p in patches:
|
||||
p.start()
|
||||
yield client
|
||||
for p in patches:
|
||||
p.stop()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_config_entry() -> MockConfigEntry:
|
||||
return MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
title="ninstrip ad4c",
|
||||
unique_id=MAC_K1Y,
|
||||
data={CONF_HOST: HOST_K1Y, CONF_TOKEN: TOKEN},
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def init_integration(
|
||||
hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_client: MagicMock
|
||||
) -> MockConfigEntry:
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
return mock_config_entry
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_setup_entry() -> Generator[AsyncMock]:
|
||||
"""Tests des Einrichtungsablaufs duerfen den Eintrag nicht wirklich aufsetzen."""
|
||||
with patch("custom_components.ninstrip.async_setup_entry", return_value=True) as mock:
|
||||
yield mock
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def firmware_dir(tmp_path: Path) -> Generator[Path]:
|
||||
"""Jeder Test bekommt seinen eigenen, anfangs leeren Firmware-Ordner."""
|
||||
verzeichnis = tmp_path / "ninstrip_firmware"
|
||||
with patch("custom_components.ninstrip.shared.firmware_directory", return_value=verzeichnis):
|
||||
yield verzeichnis
|
||||
Reference in New Issue
Block a user