ha(H2): компонент fglair — manifest, config flow (облако/ручной/импорт), пробная сессия
- custom_components/fglair: manifest (config_flow, local_push, requirements),
const, trial (start -> ONLINE <=10 c -> GET базовых свойств), FglairClient
(сессия pyfglair в executor, колбэки в loop, --runtime_data),
__init__ (setup/unload, рефкаунт глобального лог-пампа)
- config flow: меню cloud/manual/import_json + import-source; discover через
pyfglair.provision; пробное подключение с диагностикой (cannot_connect,
no_slots, key_mismatch, timeout, invalid_auth, invalid_import); unique_id=dsn
- strings.json + translations/{en,ru}.json
- тесты HA (pytest-homeassistant-custom-component): меню/шаги/ошибки/импорт/
облако (discover замокан), setup/unload, занятый порт; trial против
mock_ac.py (online/key_mismatch/unreachable/no_slots)
- pytest.ini (общий), tests/conftest перенесён на уровень tests/,
scripts/py-ci.sh: --setup-ha и прогон tests/components
This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
"""Config flow fglair: меню, ручной ввод, импорт, облако (discover замокан)."""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
from helpers import DSN, KEY_ID, LANIP_KEY
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.core import HomeAssistant
|
||||
from pytest_homeassistant_custom_component.common import MockConfigEntry
|
||||
|
||||
import pyfglair
|
||||
from custom_components.fglair.const import DOMAIN
|
||||
from custom_components.fglair.trial import TrialResult
|
||||
from pyfglair.provision import Device
|
||||
|
||||
MANUAL_INPUT = {
|
||||
"host": "192.168.1.50",
|
||||
"device_port": 80,
|
||||
"dsn": DSN,
|
||||
"lanip_key": LANIP_KEY,
|
||||
"lanip_key_id": KEY_ID,
|
||||
"name": "Bedroom",
|
||||
"model": "AP-WC1E",
|
||||
}
|
||||
|
||||
TRIAL_OK = TrialResult(ok=True)
|
||||
TRIAL_NETWORK_FAIL = TrialResult(ok=False, reason="unreachable")
|
||||
|
||||
|
||||
async def _menu(hass: HomeAssistant):
|
||||
return await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
|
||||
async def _select(hass: HomeAssistant, flow_id: str, option: str):
|
||||
return await hass.config_entries.flow.async_configure(
|
||||
flow_id, {"next_step_id": option}
|
||||
)
|
||||
|
||||
|
||||
async def test_user_menu(hass: HomeAssistant):
|
||||
result = await _menu(hass)
|
||||
assert result["type"] == "menu"
|
||||
assert set(result["menu_options"]) == {"cloud", "manual", "import_json"}
|
||||
|
||||
|
||||
async def test_manual_success(hass: HomeAssistant):
|
||||
with patch(
|
||||
"custom_components.fglair.config_flow._run_trial",
|
||||
return_value=TRIAL_OK,
|
||||
) as trial:
|
||||
result = await _menu(hass)
|
||||
result = await _select(hass, result["flow_id"], "manual")
|
||||
assert result["type"] == "form"
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], MANUAL_INPUT
|
||||
)
|
||||
assert result["type"] == "create_entry"
|
||||
assert result["title"] == "Bedroom"
|
||||
assert result["result"].unique_id == DSN
|
||||
data = result["data"]
|
||||
assert data["lanip_key_id"] == KEY_ID
|
||||
assert data["template"] == "A"
|
||||
trial.assert_called_once()
|
||||
assert trial.call_args.args[0]["host"] == "192.168.1.50"
|
||||
|
||||
|
||||
async def test_manual_connect_error(hass: HomeAssistant):
|
||||
with patch(
|
||||
"custom_components.fglair.config_flow._run_trial",
|
||||
return_value=TRIAL_NETWORK_FAIL,
|
||||
):
|
||||
result = await _menu(hass)
|
||||
result = await _select(hass, result["flow_id"], "manual")
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], MANUAL_INPUT
|
||||
)
|
||||
assert result["type"] == "form"
|
||||
assert result["errors"] == {"base": "cannot_connect"}
|
||||
|
||||
|
||||
async def test_manual_key_mismatch_error(hass: HomeAssistant):
|
||||
with patch(
|
||||
"custom_components.fglair.config_flow._run_trial",
|
||||
return_value=TrialResult(ok=False, reason="key_mismatch"),
|
||||
):
|
||||
result = await _menu(hass)
|
||||
result = await _select(hass, result["flow_id"], "manual")
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], MANUAL_INPUT
|
||||
)
|
||||
assert result["type"] == "form"
|
||||
assert result["errors"] == {"base": "key_mismatch"}
|
||||
|
||||
|
||||
async def test_manual_already_configured(hass: HomeAssistant):
|
||||
existing = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DSN)
|
||||
existing.add_to_hass(hass)
|
||||
with patch(
|
||||
"custom_components.fglair.config_flow._run_trial",
|
||||
return_value=TRIAL_OK,
|
||||
):
|
||||
result = await _menu(hass)
|
||||
result = await _select(hass, result["flow_id"], "manual")
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], MANUAL_INPUT
|
||||
)
|
||||
assert result["type"] == "abort"
|
||||
assert result["reason"] == "already_configured"
|
||||
|
||||
|
||||
async def test_import_json_success(hass: HomeAssistant):
|
||||
payload = json.dumps(
|
||||
{
|
||||
"ip_address": "192.168.1.60",
|
||||
"dsn": DSN,
|
||||
"lanip_key": LANIP_KEY,
|
||||
"lanip_key_id": KEY_ID,
|
||||
"model": "AP-WB2E",
|
||||
"name": "Kitchen",
|
||||
}
|
||||
)
|
||||
with patch(
|
||||
"custom_components.fglair.config_flow._run_trial",
|
||||
return_value=TRIAL_OK,
|
||||
):
|
||||
result = await _menu(hass)
|
||||
result = await _select(hass, result["flow_id"], "import_json")
|
||||
assert result["type"] == "form"
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], {"config_json": payload}
|
||||
)
|
||||
assert result["type"] == "create_entry"
|
||||
assert result["data"]["host"] == "192.168.1.60"
|
||||
assert result["data"]["template"] == "B"
|
||||
|
||||
|
||||
async def test_import_json_invalid(hass: HomeAssistant):
|
||||
result = await _menu(hass)
|
||||
result = await _select(hass, result["flow_id"], "import_json")
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], {"config_json": "not json"}
|
||||
)
|
||||
assert result["type"] == "form"
|
||||
assert result["errors"] == {"base": "invalid_import"}
|
||||
|
||||
|
||||
async def test_cloud_flow_success(hass: HomeAssistant):
|
||||
device = Device(
|
||||
name="Bedroom",
|
||||
model="AP-WC1E",
|
||||
dsn=DSN,
|
||||
lan_ip="192.168.1.50",
|
||||
lanip_key=LANIP_KEY,
|
||||
lanip_key_id=KEY_ID,
|
||||
region="eu",
|
||||
)
|
||||
with patch(
|
||||
"pyfglair.discover", AsyncMock(return_value=[device])
|
||||
), patch(
|
||||
"custom_components.fglair.config_flow._run_trial",
|
||||
return_value=TRIAL_OK,
|
||||
):
|
||||
result = await _menu(hass)
|
||||
result = await _select(hass, result["flow_id"], "cloud")
|
||||
assert result["type"] == "form"
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{"email": "user@example.com", "password": "secret", "region": "eu"},
|
||||
)
|
||||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "cloud_device"
|
||||
with patch(
|
||||
"custom_components.fglair.config_flow._run_trial",
|
||||
return_value=TRIAL_OK,
|
||||
):
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], {"dsn": DSN}
|
||||
)
|
||||
assert result["type"] == "create_entry"
|
||||
assert result["data"]["host"] == "192.168.1.50"
|
||||
assert result["data"]["lanip_key_id"] == KEY_ID
|
||||
|
||||
|
||||
async def test_cloud_auth_error(hass: HomeAssistant):
|
||||
with patch(
|
||||
"pyfglair.discover",
|
||||
AsyncMock(side_effect=pyfglair.ProvisionError("Ошибка входа (401)")),
|
||||
):
|
||||
result = await _menu(hass)
|
||||
result = await _select(hass, result["flow_id"], "cloud")
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{"email": "user@example.com", "password": "bad", "region": "eu"},
|
||||
)
|
||||
assert result["type"] == "form"
|
||||
assert result["errors"] == {"base": "invalid_auth"}
|
||||
Reference in New Issue
Block a user