- major: probe_template прекращает перебор на unreachable/no_slots/key_error (недоступный модуль: ~10 c вместо ~49 c), продолжает только при properties_missing (доступен, но чужой шаблон); +тест - m1: extra_state_attributes не падает до добавления в hass - m2: reconfigure берёт сохранённый шаблон как дефолт (не пересчитывает по модели); +тест - m3: device_capabilities/display_temperature переведены в необязательные зонды (trial не падает на молчащем caps; feature-модель трактует None как «не режем») - m4: async_sync_now(online_timeout) — честное имя/докстринг - ниты: удалены PRESET_CAP/as_dict/лишние плейсхолдеры, DIRECTION_KEYS упрощён, тавтология в тесте исправлена, en-тире - 59 components + 47 pyfglair/acceptance зелёные
122 lines
4.5 KiB
Python
122 lines
4.5 KiB
Python
"""Дефолты 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
|
|
from custom_components.fglair.const import FEATURES
|
|
assert len(resolved) == len(FEATURES)
|
|
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
|
|
|
|
|
|
def test_probe_stops_on_unreachable(monkeypatch):
|
|
"""Недоступный модуль не перебираем трижды — выходим после первой сессии."""
|
|
from custom_components.fglair import trial as tr
|
|
|
|
calls: list = []
|
|
|
|
def fake_trial(*args, **kwargs):
|
|
calls.append(kwargs.get("template"))
|
|
return tr.TrialResult(ok=False, reason="unreachable")
|
|
|
|
monkeypatch.setattr(tr, "trial_connect", fake_trial)
|
|
template, props = tr.probe_template("host", "dsn", "key", 1)
|
|
assert template is None
|
|
assert props == {}
|
|
assert calls == [Template.A]
|