Files
ha-ninstrip/tests/conftest.py
T
Michael Sauer c40c68b537 feat(light): Farbe und Farbtemperatur ueber alle Segmente
In Home Assistant liessen sich nur Szenen und Helligkeit stellen, keine
Farben - device/state traegt naemlich keine. Die steht je Segment, und die
Segmente hat der Koordinator bisher gar nicht gelesen.

Jetzt holt er device/segments mit (fuenfte Anfrage je Zyklus), und die
Lichtentitaet bietet rgb_color und color_temp_kelvin an:

* Setzen schreibt in ALLE Segmente, aber nur die Farbfelder - Grenzen,
  Namen und Helligkeit bleiben stehen. Die Einteilung eines Strips ist von
  Hand gemacht, die reisst eine Farbwahl nicht ein. Geschrieben wird die
  ganze Liste auf einmal, danach zurueckgelesen statt angenommen.
* Angezeigt wird nur, worauf sich alle Segmente einigen. Zwei Farben und
  eine Entitaet - da waere jede Angabe gelogen, also gibt es keine.
* Farbtemperatur nur, wenn alle auf "white" stehen; sonst RGB, sonst
  blendete HA den Farbkreis aus, sobald ein Segment einen Effekt faehrt.
* Die Farbe geht VOR dem Einschalten raus, sonst leuchtet der Strip kurz
  in der alten auf.

Die Spanne 2700-6500 K ist die, die die Firmware auf die Weisskanaele
umrechnet (nl_ct.h); ohne Weisskanaele naehert sie sie in RGB an.

Dazu: das Markenbild ist jetzt dasselbe wie bei ninlight - Michaels
Wunsch, die beiden Integrationen gehoeren zusammen. Das Skript fuer ein
eigenes Motiv bleibt als Ausgangspunkt liegen.
2026-09-23 00:20:23 +02:00

147 lines
4.5 KiB
Python

"""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,
SEGMENTS_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 strip_segments() -> dict[str, Any]:
return deepcopy(SEGMENTS_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],
strip_segments: 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))
client.get_segments = AsyncMock(side_effect=lambda: deepcopy(strip_segments))
async def put_segments(segments: list[dict[str, Any]]) -> None:
# Wie das Geraet: es nimmt die Liste als Ganzes an und antwortet nur
# mit "ok"; der Aufrufer liest danach zurueck.
strip_segments["segments"] = deepcopy(segments)
client.put_segments = AsyncMock(side_effect=put_segments)
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