ha(H2): исправления по ревью — несколько устройств, suggested values, YAML-import, типы ошибок

- critical: listen_port по умолчанию 0 (эфемерный) — несколько ConfigEntry
  не конфликтуют; тест двух записей на дефолтном порту
- manifest: добавлен обязательный ключ codeowners
- unload: set_log_handler(None) вынесен в executor (не блокирует loop);
  рефкаунт лог-пампа без гонки (старт после успешного setup)
- config flow: возврат введённого в форму при ошибке
  (add_suggested_values_to_schema; секреты исключены), YAML-import подключён
  через async_setup + SOURCE_IMPORT, ProvisionError.kind вместо разбора
  русского текста, валидация device_port/key_id из JSON
- trial: требует получения всех базовых свойств шаблона (не пустой ok),
  безопасное создание Session
- py-ci.sh: отсутствие .venv-ha — ошибка (PY_CI_SKIP_HA=1 для пропуска);
  pytest.ini testpaths
This commit is contained in:
2026-09-29 12:20:37 +03:00
parent 26d1d6d64f
commit da01c607a6
16 changed files with 275 additions and 100 deletions
+31 -3
View File
@@ -3,6 +3,7 @@ from __future__ import annotations
import socket
from helpers import free_port
from homeassistant.core import HomeAssistant
from pytest_homeassistant_custom_component.common import MockConfigEntry
@@ -24,14 +25,41 @@ async def test_setup_unload(hass: HomeAssistant, config_entry, socket_enabled):
assert client.cached(Prop.OPERATION_MODE) is None
async def test_two_entries_default_port(hass: HomeAssistant, entry_data, socket_enabled):
"""Две записи без явного listen_port не конфликтуют (порт 0 → эфемерный)."""
first = MockConfigEntry(domain=DOMAIN, data=entry_data, unique_id="DSN-1")
second = MockConfigEntry(
domain=DOMAIN,
data={**entry_data, "dsn": "DSN-2"},
unique_id="DSN-2",
)
first.add_to_hass(hass)
assert await hass.config_entries.async_setup(first.entry_id)
await hass.async_block_till_done()
second.add_to_hass(hass)
assert await hass.config_entries.async_setup(second.entry_id)
await hass.async_block_till_done()
assert first.state.name == "LOADED"
assert second.state.name == "LOADED"
first_client = first.runtime_data
assert await hass.config_entries.async_unload(first.entry_id)
assert first_client.cached(Prop.OPERATION_MODE) is None
assert second.state.name == "LOADED"
assert await hass.config_entries.async_unload(second.entry_id)
async def test_setup_port_busy(hass: HomeAssistant, entry_data, socket_enabled):
port = free_port()
sock = socket.socket()
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 0)
sock.bind(("127.0.0.1", entry_data[CONF_LISTEN_PORT]))
sock.bind(("127.0.0.1", port))
sock.listen(1)
try:
entry = MockConfigEntry(
domain=DOMAIN, data=entry_data, unique_id=entry_data["dsn"]
domain=DOMAIN,
data={**entry_data, CONF_LISTEN_PORT: port},
unique_id=entry_data["dsn"],
)
entry.add_to_hass(hass)
assert not await hass.config_entries.async_setup(entry.entry_id)