ha(ux): мастер из трёх страниц, feature-модель и capabilities-фильтрация
- config flow: template (A/B/F с пояснениями, значения с устройства, определение по oem_model; для неизвестных моделей — пробинг A/B/F тремя короткими сессиями) -> capabilities (23 тумблера с описаниями, дефолты из device_capabilities/num_dir/ответов) -> limits (диапазон/шаг, ручная конверсия); каждая страница с пометкой «значения определены автоматически, можно пропустить» - features.py: FeatureSet/LiveValues/device_default/resolve_features; entry.data["features"] хранит выбор пользователя; reconfigure предзаполняет; YAML-import получает дефолты - сущности создаются только для включённых фич: climate (режимы/скорости/ swing/пресеты), switch, select заслонок (caps+num_dir), датчик комнаты; исправлено появление select без ламелей - trial: caps/num_dir и presence-зонды (необязательные свойства), answered; координатор ждёт первые свойства перед созданием сущностей (feature- дефолты без гонки) - атрибут description у сущностей (HA не поддерживает тултипы) + описания фич в мастере (en/ru) - тесты: features, probing, обновлённые flow/entities/repairs; 57 components
This commit is contained in:
@@ -14,7 +14,7 @@ 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 import Prop, Value, ValueKind
|
||||
from pyfglair import Prop, Template, Value, ValueKind
|
||||
from pyfglair.provision import Device
|
||||
|
||||
MANUAL_INPUT = {
|
||||
@@ -27,8 +27,7 @@ MANUAL_INPUT = {
|
||||
"model": "AP-WC1E",
|
||||
}
|
||||
|
||||
PREVIEW_INPUT = {
|
||||
"template": "A",
|
||||
LIMITS_INPUT = {
|
||||
"temp_min": 16.0,
|
||||
"temp_max": 30.0,
|
||||
"temp_step": 0.5,
|
||||
@@ -37,8 +36,20 @@ PREVIEW_INPUT = {
|
||||
"temp_offset": 0,
|
||||
}
|
||||
|
||||
TRIAL_OK = TrialResult(ok=True)
|
||||
TRIAL_NETWORK_FAIL = TrialResult(ok=False, reason="unreachable")
|
||||
TRIAL_OK = (TrialResult(ok=True), Template.A, False)
|
||||
TRIAL_NETWORK_FAIL = (
|
||||
TrialResult(ok=False, reason="unreachable"),
|
||||
Template.A,
|
||||
False,
|
||||
)
|
||||
|
||||
|
||||
def _trial_ok(properties=None):
|
||||
return (
|
||||
TrialResult(ok=True, properties=properties or {}),
|
||||
Template.A,
|
||||
False,
|
||||
)
|
||||
|
||||
|
||||
async def _menu(hass: HomeAssistant):
|
||||
@@ -57,6 +68,22 @@ async def _configure(hass: HomeAssistant, flow_id: str, user_input: dict):
|
||||
return await hass.config_entries.flow.async_configure(flow_id, user_input)
|
||||
|
||||
|
||||
async def _pages(
|
||||
hass: HomeAssistant,
|
||||
flow_id: str,
|
||||
*,
|
||||
template: str = "A",
|
||||
caps: dict | None = None,
|
||||
limits: dict | None = None,
|
||||
):
|
||||
"""Проходит страницы template → capabilities → limits."""
|
||||
result = await _configure(hass, flow_id, {"template": template})
|
||||
assert result["step_id"] == "capabilities", result
|
||||
result = await _configure(hass, flow_id, caps or {})
|
||||
assert result["step_id"] == "limits", result
|
||||
return await _configure(hass, flow_id, limits or LIMITS_INPUT)
|
||||
|
||||
|
||||
def _suggested(schema, field: str):
|
||||
for key in schema.schema:
|
||||
if getattr(key, "schema", None) == field:
|
||||
@@ -81,9 +108,8 @@ async def test_manual_success(hass: HomeAssistant):
|
||||
result = await _select(hass, result["flow_id"], "manual")
|
||||
assert result["type"] == "form"
|
||||
result = await _configure(hass, result["flow_id"], MANUAL_INPUT)
|
||||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "preview"
|
||||
result = await _configure(hass, result["flow_id"], PREVIEW_INPUT)
|
||||
assert result["step_id"] == "template"
|
||||
result = await _pages(hass, result["flow_id"])
|
||||
assert result["type"] == "create_entry"
|
||||
assert result["title"] == "Bedroom"
|
||||
assert result["result"].unique_id == DSN
|
||||
@@ -98,11 +124,13 @@ async def test_manual_success(hass: HomeAssistant):
|
||||
"min": 160,
|
||||
"max": 300,
|
||||
}
|
||||
assert isinstance(data["features"], dict)
|
||||
assert data["features"]["mode_heat"] is True
|
||||
trial.assert_called_once()
|
||||
assert trial.call_args.args[0]["host"] == "192.168.1.50"
|
||||
|
||||
|
||||
async def test_preview_template_change_and_conversion(hass: HomeAssistant):
|
||||
async def test_template_change_and_conversion(hass: HomeAssistant):
|
||||
with patch(
|
||||
"custom_components.fglair.config_flow._run_trial",
|
||||
return_value=TRIAL_OK,
|
||||
@@ -110,17 +138,20 @@ async def test_preview_template_change_and_conversion(hass: HomeAssistant):
|
||||
result = await _menu(hass)
|
||||
result = await _select(hass, result["flow_id"], "manual")
|
||||
result = await _configure(hass, result["flow_id"], MANUAL_INPUT)
|
||||
preview_input = {
|
||||
**PREVIEW_INPUT,
|
||||
"template": "B",
|
||||
"temp_min": 17.0,
|
||||
"temp_max": 28.0,
|
||||
"temp_step": 1.0,
|
||||
"temp_num": 2,
|
||||
"temp_den": 4,
|
||||
"temp_offset": 5,
|
||||
}
|
||||
result = await _configure(hass, result["flow_id"], preview_input)
|
||||
result = await _pages(
|
||||
hass,
|
||||
result["flow_id"],
|
||||
template="B",
|
||||
limits={
|
||||
**LIMITS_INPUT,
|
||||
"temp_min": 17.0,
|
||||
"temp_max": 28.0,
|
||||
"temp_step": 1.0,
|
||||
"temp_num": 2,
|
||||
"temp_den": 4,
|
||||
"temp_offset": 5,
|
||||
},
|
||||
)
|
||||
assert result["type"] == "create_entry"
|
||||
assert result["data"]["template"] == "B"
|
||||
assert result["data"]["temp_step"] == 10
|
||||
@@ -133,7 +164,7 @@ async def test_preview_template_change_and_conversion(hass: HomeAssistant):
|
||||
}
|
||||
|
||||
|
||||
async def test_preview_invalid_range(hass: HomeAssistant):
|
||||
async def test_limits_invalid_range(hass: HomeAssistant):
|
||||
with patch(
|
||||
"custom_components.fglair.config_flow._run_trial",
|
||||
return_value=TRIAL_OK,
|
||||
@@ -141,24 +172,23 @@ async def test_preview_invalid_range(hass: HomeAssistant):
|
||||
result = await _menu(hass)
|
||||
result = await _select(hass, result["flow_id"], "manual")
|
||||
result = await _configure(hass, result["flow_id"], MANUAL_INPUT)
|
||||
result = await _configure(
|
||||
hass, result["flow_id"], {"template": "B"}
|
||||
)
|
||||
result = await _configure(hass, result["flow_id"], {})
|
||||
assert result["step_id"] == "limits"
|
||||
result = await _configure(
|
||||
hass,
|
||||
result["flow_id"],
|
||||
{
|
||||
**PREVIEW_INPUT,
|
||||
"template": "B",
|
||||
"temp_min": 30.0,
|
||||
"temp_max": 16.0,
|
||||
},
|
||||
{**LIMITS_INPUT, "temp_min": 30.0, "temp_max": 16.0},
|
||||
)
|
||||
assert result["type"] == "form"
|
||||
assert result["errors"] == {"base": "invalid_range"}
|
||||
assert "Template: B" in result["description_placeholders"]["preview"]
|
||||
result = await _configure(hass, result["flow_id"], PREVIEW_INPUT)
|
||||
result = await _configure(hass, result["flow_id"], LIMITS_INPUT)
|
||||
assert result["type"] == "create_entry"
|
||||
|
||||
|
||||
async def test_preview_unreachable_range(hass: HomeAssistant):
|
||||
async def test_limits_unreachable_range(hass: HomeAssistant):
|
||||
"""Конверсия выводит таблицу за min/max — пустой достижимый диапазон."""
|
||||
with patch(
|
||||
"custom_components.fglair.config_flow._run_trial",
|
||||
@@ -167,11 +197,15 @@ async def test_preview_unreachable_range(hass: HomeAssistant):
|
||||
result = await _menu(hass)
|
||||
result = await _select(hass, result["flow_id"], "manual")
|
||||
result = await _configure(hass, result["flow_id"], MANUAL_INPUT)
|
||||
result = await _configure(
|
||||
hass, result["flow_id"], {"template": "A"}
|
||||
)
|
||||
result = await _configure(hass, result["flow_id"], {})
|
||||
result = await _configure(
|
||||
hass,
|
||||
result["flow_id"],
|
||||
{
|
||||
**PREVIEW_INPUT,
|
||||
**LIMITS_INPUT,
|
||||
"temp_min": 30.0,
|
||||
"temp_max": 40.0,
|
||||
"temp_num": 1,
|
||||
@@ -182,22 +216,37 @@ async def test_preview_unreachable_range(hass: HomeAssistant):
|
||||
assert result["errors"] == {"base": "invalid_range"}
|
||||
|
||||
|
||||
async def test_preview_contains_trial_values(hass: HomeAssistant):
|
||||
async def test_template_device_values(hass: HomeAssistant):
|
||||
props = {
|
||||
Prop.DISPLAY_TEMPERATURE: Value(ValueKind.INT, 200),
|
||||
Prop.OPERATION_MODE: Value(ValueKind.INT, 3),
|
||||
Prop.FAN_SPEED: Value(ValueKind.INT, 4),
|
||||
Prop.DEVICE_CAPABILITIES: Value(ValueKind.INT, 0b11011),
|
||||
Prop.AF_VERTICAL_NUM_DIR: Value(ValueKind.INT, 5),
|
||||
}
|
||||
with patch(
|
||||
"custom_components.fglair.config_flow._run_trial",
|
||||
return_value=TrialResult(ok=True, properties=props),
|
||||
return_value=(
|
||||
TrialResult(ok=True, properties=props),
|
||||
Template.A,
|
||||
False,
|
||||
),
|
||||
):
|
||||
result = await _menu(hass)
|
||||
result = await _select(hass, result["flow_id"], "manual")
|
||||
result = await _configure(hass, result["flow_id"], MANUAL_INPUT)
|
||||
preview = result["description_placeholders"]["preview"]
|
||||
assert "20.0" in preview
|
||||
assert "0x0000001B" in preview
|
||||
assert "cool" in preview and "heat" in preview
|
||||
assert result["step_id"] == "template"
|
||||
values = result["description_placeholders"]["device_values"]
|
||||
assert "20.0" in values
|
||||
assert "0x0000001B" in values
|
||||
assert "cool" in values
|
||||
assert "направлений заслонок: верт. 5" in values
|
||||
# capabilities: дефолты из маски, swing выключен
|
||||
result = await _configure(hass, result["flow_id"], {"template": "A"})
|
||||
assert result["step_id"] == "capabilities"
|
||||
assert _suggested(result["data_schema"], "swing_vertical") is False
|
||||
assert _suggested(result["data_schema"], "mode_cool") is True
|
||||
assert _suggested(result["data_schema"], "af_vertical_direction") is True
|
||||
|
||||
|
||||
async def test_manual_connect_error(hass: HomeAssistant):
|
||||
@@ -215,7 +264,11 @@ async def test_manual_connect_error(hass: HomeAssistant):
|
||||
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"),
|
||||
return_value=(
|
||||
TrialResult(ok=False, reason="key_mismatch"),
|
||||
Template.A,
|
||||
False,
|
||||
),
|
||||
):
|
||||
result = await _menu(hass)
|
||||
result = await _select(hass, result["flow_id"], "manual")
|
||||
@@ -265,7 +318,7 @@ async def test_import_json_success(hass: HomeAssistant):
|
||||
)
|
||||
with patch(
|
||||
"custom_components.fglair.config_flow._run_trial",
|
||||
return_value=TRIAL_OK,
|
||||
return_value=(TrialResult(ok=True), Template.B, False),
|
||||
):
|
||||
result = await _menu(hass)
|
||||
result = await _select(hass, result["flow_id"], "import_json")
|
||||
@@ -273,11 +326,9 @@ async def test_import_json_success(hass: HomeAssistant):
|
||||
result = await _configure(
|
||||
hass, result["flow_id"], {"config_json": payload}
|
||||
)
|
||||
assert result["step_id"] == "preview"
|
||||
assert result["step_id"] == "template"
|
||||
assert _suggested(result["data_schema"], "template") == "B"
|
||||
result = await _configure(
|
||||
hass, result["flow_id"], {**PREVIEW_INPUT, "template": "B"}
|
||||
)
|
||||
result = await _pages(hass, result["flow_id"], template="B")
|
||||
assert result["type"] == "create_entry"
|
||||
assert result["data"]["host"] == "192.168.1.60"
|
||||
assert result["data"]["template"] == "B"
|
||||
@@ -319,8 +370,8 @@ async def test_cloud_flow_success(hass: HomeAssistant):
|
||||
)
|
||||
assert result["step_id"] == "cloud_device"
|
||||
result = await _configure(hass, result["flow_id"], {"dsn": DSN})
|
||||
assert result["step_id"] == "preview"
|
||||
result = await _configure(hass, result["flow_id"], PREVIEW_INPUT)
|
||||
assert result["step_id"] == "template"
|
||||
result = await _pages(hass, result["flow_id"])
|
||||
assert result["type"] == "create_entry"
|
||||
assert result["data"]["host"] == "192.168.1.50"
|
||||
assert result["data"]["lanip_key_id"] == KEY_ID
|
||||
@@ -374,7 +425,7 @@ async def test_yaml_import(hass: HomeAssistant):
|
||||
}
|
||||
with patch(
|
||||
"custom_components.fglair.config_flow._run_trial",
|
||||
return_value=TRIAL_OK,
|
||||
return_value=(TrialResult(ok=True), Template.F, False),
|
||||
):
|
||||
assert await async_setup_component(hass, DOMAIN, {DOMAIN: payload})
|
||||
await hass.async_block_till_done()
|
||||
@@ -382,6 +433,7 @@ async def test_yaml_import(hass: HomeAssistant):
|
||||
assert len(entries) == 1
|
||||
assert entries[0].data["template"] == "F"
|
||||
assert entries[0].data["host"] == "192.168.1.70"
|
||||
assert isinstance(entries[0].data["features"], dict)
|
||||
|
||||
|
||||
async def test_yaml_import_invalid(hass: HomeAssistant):
|
||||
@@ -410,7 +462,8 @@ async def test_import_json_null_device_port(hass: HomeAssistant):
|
||||
result = await _configure(
|
||||
hass, result["flow_id"], {"config_json": payload}
|
||||
)
|
||||
result = await _configure(hass, result["flow_id"], PREVIEW_INPUT)
|
||||
assert result["step_id"] == "template"
|
||||
result = await _pages(hass, result["flow_id"])
|
||||
assert result["type"] == "create_entry"
|
||||
assert result["data"]["device_port"] == 80
|
||||
assert trial.call_args.args[0]["device_port"] == 80
|
||||
@@ -448,8 +501,8 @@ async def test_reconfigure_success(hass: HomeAssistant, mock_ac, socket_enabled)
|
||||
result = await _configure(
|
||||
hass, result["flow_id"], {**MANUAL_INPUT, "name": "Renamed"}
|
||||
)
|
||||
assert result["step_id"] == "preview"
|
||||
result = await _configure(hass, result["flow_id"], PREVIEW_INPUT)
|
||||
assert result["step_id"] == "template"
|
||||
result = await _pages(hass, result["flow_id"])
|
||||
assert result["type"] == "abort"
|
||||
assert result["reason"] == "reconfigure_successful"
|
||||
await hass.async_block_till_done()
|
||||
@@ -520,3 +573,30 @@ async def test_options_flow_new_key_reloads(
|
||||
assert entry.state.name in ("LOADED", "SETUP_RETRY")
|
||||
if entry.state.name == "LOADED":
|
||||
assert await hass.config_entries.async_unload(entry.entry_id)
|
||||
|
||||
|
||||
async def test_run_trial_probes_unknown_model(hass: HomeAssistant):
|
||||
"""Без oem_model шаблон определяется пробингом A/B/F."""
|
||||
from custom_components.fglair import config_flow as cf
|
||||
|
||||
with patch.object(
|
||||
cf, "probe_template", return_value=(Template.B, {})
|
||||
) as probe, patch.object(
|
||||
cf, "trial_connect", return_value=TrialResult(ok=True)
|
||||
):
|
||||
result = await hass.async_add_executor_job(
|
||||
cf._run_trial,
|
||||
{
|
||||
"host": "127.0.0.1",
|
||||
"dsn": DSN,
|
||||
"lanip_key": LANIP_KEY,
|
||||
"lanip_key_id": KEY_ID,
|
||||
"model": "",
|
||||
"template": "A",
|
||||
},
|
||||
)
|
||||
trial_result, template, probed = result
|
||||
assert trial_result.ok
|
||||
assert template is Template.B
|
||||
assert probed is True
|
||||
probe.assert_called_once()
|
||||
|
||||
@@ -77,6 +77,7 @@ async def test_entities_snapshot(hass: HomeAssistant, mock_ac, socket_enabled):
|
||||
)
|
||||
|
||||
climate = hass.states.get(climate_id)
|
||||
assert climate.attributes["description"]
|
||||
assert climate.attributes["current_temperature"] == 20.0
|
||||
assert climate.attributes["temperature"] == 22.0
|
||||
assert climate.attributes["fan_mode"] == "auto"
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
"""Дефолты feature-модели: device_capabilities, num_dir, presence."""
|
||||
from __future__ import annotations
|
||||
|
||||
from custom_components.fglair.features import (
|
||||
FeatureSet,
|
||||
device_default,
|
||||
resolve_features,
|
||||
)
|
||||
from pyfglair import Prop, Template, Value, ValueKind
|
||||
|
||||
|
||||
def _value(value: int) -> Value:
|
||||
return Value(ValueKind.INT, value)
|
||||
|
||||
|
||||
def test_caps_defaults_ap_wc1e():
|
||||
values = {Prop.DEVICE_CAPABILITIES: _value(5119)} # 0x13FF
|
||||
assert device_default("mode_cool", values) is True
|
||||
assert device_default("mode_heat", values) is True
|
||||
assert device_default("fan_auto", values) is True
|
||||
assert device_default("swing_vertical", values) is False
|
||||
assert device_default("swing_horizontal", values) is False
|
||||
assert device_default("economy_mode", values) is True
|
||||
assert device_default("powerful_mode", values) is False
|
||||
assert device_default("min_heat", values) is False
|
||||
assert device_default("coil_dry_mode", values) is False
|
||||
|
||||
|
||||
def test_caps_unknown_keeps_everything():
|
||||
assert device_default("swing_vertical", {}) is True
|
||||
assert device_default(
|
||||
"min_heat", {Prop.DEVICE_CAPABILITIES: _value(0)}
|
||||
) is True
|
||||
|
||||
|
||||
def test_directions_need_num_dir():
|
||||
zero = {Prop.AF_VERTICAL_NUM_DIR: _value(0)}
|
||||
five = {Prop.AF_VERTICAL_NUM_DIR: _value(5)}
|
||||
assert device_default("af_vertical_direction", zero) is False
|
||||
assert device_default("af_vertical_direction", five) is True
|
||||
assert device_default("af_vertical_direction", {}) is False
|
||||
assert device_default("af_horizontal_direction", zero) is False
|
||||
|
||||
|
||||
def test_presence_defaults():
|
||||
values = {Prop.DISPLAY_TEMPERATURE: _value(200)}
|
||||
assert device_default("display_temperature", values) is True
|
||||
assert device_default("wifi_led_enable", values) is False
|
||||
assert (
|
||||
device_default(
|
||||
"display_temperature", {}, {Prop.DISPLAY_TEMPERATURE}
|
||||
)
|
||||
is True
|
||||
)
|
||||
assert device_default("wifi_led_enable", {}, set()) is False
|
||||
|
||||
|
||||
def test_resolve_and_featureset_override():
|
||||
values = {Prop.DEVICE_CAPABILITIES: _value(5119)}
|
||||
resolved = resolve_features(values, set(), {"swing_vertical": True})
|
||||
assert resolved["swing_vertical"] is True
|
||||
assert resolved["powerful_mode"] is False
|
||||
assert len(resolved) == len(
|
||||
{key for key in resolved}
|
||||
)
|
||||
feature_set = FeatureSet({"swing_vertical": True}, values)
|
||||
assert feature_set.enabled("swing_vertical") is True
|
||||
assert feature_set.enabled("powerful_mode") is False
|
||||
default_set = FeatureSet(None, values)
|
||||
assert default_set.enabled("economy_mode") is True
|
||||
assert default_set.enabled("swing_vertical") is False
|
||||
|
||||
|
||||
def test_classify_template():
|
||||
from custom_components.fglair.trial import classify_template
|
||||
|
||||
assert classify_template(
|
||||
{Prop.AF_VERTICAL_MOVE_STEP1}
|
||||
) is Template.B
|
||||
assert classify_template(
|
||||
{Prop.MONITOR1, Prop.AF_VERTICAL_DIRECTION}
|
||||
) is Template.F
|
||||
assert classify_template(
|
||||
{Prop.AF_VERTICAL_DIRECTION}
|
||||
) is Template.A
|
||||
assert classify_template({Prop.DISPLAY_TEMPERATURE}) is Template.A
|
||||
assert classify_template(set()) is None
|
||||
|
||||
|
||||
def test_resolved_features_for_ap_wc1e_without_sensor():
|
||||
"""Канальный блок: датчик комнаты есть, ламелей/swing нет."""
|
||||
values = {
|
||||
Prop.DEVICE_CAPABILITIES: _value(5119),
|
||||
Prop.AF_VERTICAL_NUM_DIR: _value(0),
|
||||
Prop.AF_HORIZONTAL_NUM_DIR: _value(0),
|
||||
Prop.DISPLAY_TEMPERATURE: _value(205),
|
||||
}
|
||||
resolved = resolve_features(values, set(values))
|
||||
assert resolved["display_temperature"] is True
|
||||
assert resolved["swing_vertical"] is False
|
||||
assert resolved["af_vertical_direction"] is False
|
||||
assert resolved["af_horizontal_direction"] is False
|
||||
assert resolved["mode_cool"] is True
|
||||
assert resolved["economy_mode"] is True
|
||||
assert resolved["powerful_mode"] is False
|
||||
@@ -58,7 +58,7 @@ async def test_reconfigure_fixes_key_error(
|
||||
):
|
||||
"""Repair-путь целиком: bad key_id → issue → reconfigure → online → issue снят."""
|
||||
from homeassistant.config_entries import SOURCE_RECONFIGURE
|
||||
from test_config_flow import MANUAL_INPUT, PREVIEW_INPUT
|
||||
from test_config_flow import LIMITS_INPUT, MANUAL_INPUT
|
||||
|
||||
proc = mock_ac(
|
||||
["--set", "operation_mode=6"], key_id=KEY_ID + 1
|
||||
@@ -91,9 +91,16 @@ async def test_reconfigure_fixes_key_error(
|
||||
"lanip_key_id": KEY_ID + 1,
|
||||
},
|
||||
)
|
||||
assert result["step_id"] == "preview", result.get("errors")
|
||||
assert result["step_id"] == "template", result.get("errors")
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], PREVIEW_INPUT
|
||||
result["flow_id"], {"template": "A"}
|
||||
)
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], {}
|
||||
)
|
||||
assert result["step_id"] == "limits"
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], LIMITS_INPUT
|
||||
)
|
||||
assert result["type"] == "abort"
|
||||
assert result["reason"] == "reconfigure_successful"
|
||||
|
||||
Reference in New Issue
Block a user