Address Opus review findings on the device-support commits above
- Fix a real bug: airconditioner.SOUND_MODE had no exists_fn, so on boards (issue #319's FAC) that never report a live 'mode' value, entity.py's default field-presence gate silently kept the select from ever registering in HA -- while adapter.flatten() (what the golden/tests read) has no such gate, so the tests passed while documenting behavior the opposite of what shipped. Gate on supportedModes' presence instead. - Add airconditioner.MDS_ABSENCE_CLEAN for the CAC-class board's /mds/absenceclean/vs/0 -- byte-identical shape to issue #319's /csi/absenceclean/vs/0, confirmed rather than guessed, closing one more of that board's documented coverage-gap hrefs. - Add missing translation state labels (all 6 languages) for edge_lighting_mode/edge_lighting_color/indicator_light_mode's raw device codes, so they render as real words instead of a raw '3000K' -> '3000 K' fallback. - Fix an orphaned comment above SOUND_MODE that actually described the unrelated DISPLAY reuse, and correct two inaccurate rationale comments: the sound/voice ignore reason claimed a distinction from SOUND_MODE that this same dump contradicts, and the /csi/* ignore block's 'same reasoning as air_purifier.COVERAGE' precedent only actually covers 1 of its 5 hrefs. - Correct the false 'no board-token match' claim in the FAC test file and golden-regression docstring -- 'FAC' is a real _BOARD_TOKEN_TO_KEY entry (for_device_by_model alone already resolves this board); add a test that actually exercises that path, which nothing previously did despite the docstring's claim. - Drop a tautological burner-slot test that only re-asserted what the golden regression test already covers via the same code path.
This commit is contained in:
13 files changed
+250
-68
No files matched your search
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"state_keys": [
|
||||
"absence_clean",
|
||||
"absence_power_saving_active",
|
||||
"absence_power_saving_mode",
|
||||
"air_filter_pm1_status",
|
||||
|
||||
@@ -1,21 +1,42 @@
|
||||
"""Tests for the AILP_DA-AC-FAC-02011_0000 air conditioner (issue #319).
|
||||
|
||||
This board has no `_BOARD_TOKEN_TO_KEY` entry (routes via `/oic/d`'s
|
||||
`oic.d.airconditioner` type only) and reports several resources the sibling
|
||||
TP1X_DA-AC-CAC-01001 board (issue #191) left as a documented gap:
|
||||
/display/vs/0 and the /settings/sound/* trio (now shared with
|
||||
air_purifier.py's identical shapes), plus two genuinely new hrefs
|
||||
(/csi/absenceclean/vs/0, /csi/energysaving/vs/0).
|
||||
This board resolves to the airconditioner registry two independent ways --
|
||||
modelNum's 'FAC' board token (for_device_by_model) and /oic/d's
|
||||
oic.d.airconditioner type (for_device_by_oic_type) agree -- and reports
|
||||
several resources the sibling TP1X_DA-AC-CAC-01001 board (issue #191) left
|
||||
as a documented gap: /display/vs/0 and the /settings/sound/* trio (now
|
||||
shared with air_purifier.py's identical shapes), plus two genuinely new
|
||||
hrefs (/csi/absenceclean/vs/0, /csi/energysaving/vs/0).
|
||||
"""
|
||||
|
||||
from typing import cast
|
||||
|
||||
from custom_components.localthings.coordinator import LocalThingsCoordinator
|
||||
from custom_components.localthings.entity import _is_included
|
||||
from custom_components.localthings.registry.adapter import flatten
|
||||
from custom_components.localthings.registry.by_type import for_device_by_oic_type, resolve
|
||||
from custom_components.localthings.registry.by_type import (
|
||||
for_device_by_model,
|
||||
for_device_by_oic_type,
|
||||
resolve,
|
||||
)
|
||||
from custom_components.localthings.registry.discovery import discover
|
||||
from tests.conftest import _load_device
|
||||
|
||||
_DEVICE_TYPES = ("oic.wk.d", "oic.d.airconditioner")
|
||||
|
||||
|
||||
class _FakeCoordinator:
|
||||
"""Minimal stand-in for entity._is_included's coordinator dependency --
|
||||
same shape as test_entity.py's own fake, kept local rather than shared
|
||||
across test modules."""
|
||||
|
||||
def __init__(self, last_resources):
|
||||
self.last_resources = last_resources
|
||||
|
||||
def canonical_resources(self, subdevice):
|
||||
return self.last_resources
|
||||
|
||||
|
||||
def _resources():
|
||||
return _load_device("airconditioner_ailp_fac")
|
||||
|
||||
@@ -31,6 +52,19 @@ def test_oic_device_type_resolves_to_airconditioner_registry():
|
||||
assert reg is not None and reg.name == "airconditioner"
|
||||
|
||||
|
||||
def test_board_token_resolves_to_airconditioner_registry():
|
||||
"""'FAC' is a real _BOARD_TOKEN_TO_KEY entry -- for_device_by_model
|
||||
alone (no device_types) already resolves this board correctly, same as
|
||||
for_device_by_oic_type above; resolve()'s device_types-agreement isn't
|
||||
covering an otherwise-unreachable path."""
|
||||
resources = _resources()
|
||||
info = resources["/information/vs/0"]
|
||||
reg = for_device_by_model(
|
||||
info["x.com.samsung.da.modelNum"], info["x.com.samsung.da.description"]
|
||||
)
|
||||
assert reg is not None and reg.name == "airconditioner"
|
||||
|
||||
|
||||
def test_no_unbound_hrefs():
|
||||
resources = _resources()
|
||||
reg = resolve(resources, device_types=_DEVICE_TYPES)
|
||||
@@ -39,15 +73,18 @@ def test_no_unbound_hrefs():
|
||||
assert unbound == []
|
||||
|
||||
|
||||
def test_sound_mode_bound_with_live_supported_modes():
|
||||
def test_sound_mode_registers_despite_no_live_mode_value():
|
||||
"""This dump has no current `mode` value yet, only supportedModes
|
||||
(mute/tone/voice) -- confirms the select is bound (present in state,
|
||||
even if unknown) and its options come from the live field rather than
|
||||
a static tuple (see laundry.SOUND_MODE's docstring for why that's
|
||||
preferred whenever the resource carries one)."""
|
||||
(mute/tone/voice). entity.py's default field-presence gate would
|
||||
otherwise keep the select from ever being created in HA even though
|
||||
adapter.flatten() (checked below) has no such gate and would look
|
||||
bound regardless -- exercise the real _is_included gate, not just
|
||||
flatten(), so a regression here can't hide behind that gap again."""
|
||||
bound, resources = _bound()
|
||||
desc = next(b.desc for b in bound if b.desc.key == "sound_mode")
|
||||
assert desc.options_field == "supportedModes"
|
||||
entity = next(b for b in bound if b.desc.key == "sound_mode")
|
||||
assert entity.desc.options_field == "supportedModes"
|
||||
coord = cast(LocalThingsCoordinator, _FakeCoordinator(resources))
|
||||
assert _is_included(entity, coord) is True
|
||||
state = flatten(bound, resources)
|
||||
assert "sound_mode" in state
|
||||
assert state["sound_mode"] is None
|
||||
|
||||
@@ -6,11 +6,11 @@ token -- this board was the one exception (its oneUiVersion self-reports
|
||||
"7.0 Air conditioner", but 'CAC' had never been added to the board-token
|
||||
table), so it silently fell back to common caps and lost its climate entity.
|
||||
|
||||
This dump is NOT fully covered yet -- three hrefs remain unbound
|
||||
(absence-clean, `/settings/sound/optimization/vs/0`, smart-sensing-cooling),
|
||||
all genuinely new to this board generation. That's a real device-support
|
||||
gap, left documented here rather than guessed at, per the 'don't guess'
|
||||
rule -- fixing the routing regression was the scope of #191.
|
||||
This dump is NOT fully covered yet -- two hrefs remain unbound
|
||||
(`/settings/sound/optimization/vs/0`, smart-sensing-cooling), both
|
||||
genuinely new to this board generation. That's a real device-support gap,
|
||||
left documented here rather than guessed at, per the 'don't guess' rule --
|
||||
fixing the routing regression was the scope of #191.
|
||||
|
||||
/settings/sound/mode/vs/0, /settings/sound/output/vs/0 and
|
||||
/settings/sound/volume/vs/0 used to be on this list too, until issue #319
|
||||
@@ -23,6 +23,11 @@ until issue #288 (six System A/C cassette units on this same board) gave
|
||||
real dump evidence for both -- airconditioner.EDGE_LIGHTING and
|
||||
LIGHT_STATEFUL now cover them.
|
||||
|
||||
/mds/absenceclean/vs/0 used to be on this list too -- its {mode,
|
||||
supportedModes: [On, Off]} shape is byte-identical to issue #319's
|
||||
/csi/absenceclean/vs/0, confirmed on that sibling board rather than
|
||||
guessed, so airconditioner.MDS_ABSENCE_CLEAN now covers it too.
|
||||
|
||||
/uvled/vs/0 and /filter/airdustPM1filter/vs/0 used to be on this list too,
|
||||
until issue #270 (TP1X_FAC_TIME_23K) added real capabilities for both --
|
||||
this board's own live filterUsage/filterStatus data on the PM1 filter binds
|
||||
@@ -37,7 +42,6 @@ from tests.conftest import _load_device
|
||||
|
||||
_STILL_UNBOUND = frozenset(
|
||||
{
|
||||
"/mds/absenceclean/vs/0",
|
||||
"/settings/sound/optimization/vs/0",
|
||||
"/smartsensingcooling/vs/0",
|
||||
}
|
||||
@@ -70,6 +74,17 @@ def test_documented_coverage_gap_is_exactly_this_set():
|
||||
assert set(unbound) == _STILL_UNBOUND
|
||||
|
||||
|
||||
def test_mds_absenceclean_shares_csi_absenceclean_key():
|
||||
"""/mds/absenceclean/vs/0's mode=='Off' on this dump -- confirms
|
||||
MDS_ABSENCE_CLEAN actually binds (not just that the href stops
|
||||
reporting as unbound)."""
|
||||
resources = _resources()
|
||||
reg = _reg(resources)
|
||||
bound = discover(resources, reg.capabilities, reg.pattern_capabilities)
|
||||
state = flatten(bound, resources)
|
||||
assert state["absence_clean"] is False
|
||||
|
||||
|
||||
def test_non_legacy_board_uses_the_generic_energy_scale():
|
||||
"""This board reports /wind/strength/vs/0 (not /airflow/vs/0), so
|
||||
is_legacy_board() is False and it must use the plain wh_to_kwh scale,
|
||||
|
||||
@@ -45,11 +45,12 @@ def test_alarm_code_and_child_lock_bound():
|
||||
assert state["child_lock"] is True
|
||||
|
||||
|
||||
def test_advertises_six_burner_slots_only_three_of_which_are_real():
|
||||
"""Not a bug to fix -- the board's own /mode/vs/0 options genuinely
|
||||
list six OperationState slots (issue #314); the reporter's physical
|
||||
cooktop only has three. Locks in the current, documented behavior."""
|
||||
bound, resources = _bound()
|
||||
state = flatten(bound, resources)
|
||||
for i in range(6):
|
||||
assert f"burner_{i}_state" in state
|
||||
# The reporter's original complaint -- six advertised burner slots when
|
||||
# only three are physically present -- isn't tested here beyond what the
|
||||
# golden regression test already locks in (burner_0_state..burner_5_state
|
||||
# all present). It's expected, not a bug: the registry declares a generous
|
||||
# static superset of slots per cooktop.py's own comment, and this board's
|
||||
# /mode/vs/0 rep carries no field distinguishing a real slot from an
|
||||
# advertised-but-nonexistent one for a test to assert against -- the user
|
||||
# disabling the three extra entities is the correct fix, not something the
|
||||
# integration can filter automatically.
|
||||
@@ -135,9 +135,11 @@ def test_registry_reproduces_golden_state_keys_for_airconditioner():
|
||||
|
||||
|
||||
def test_registry_reproduces_golden_state_keys_for_airconditioner_ailp_fac():
|
||||
"""AILP_DA-AC-FAC-02011_0000 (issue #319) has no board-token match --
|
||||
resolves purely via /oic/d's oic.d.airconditioner type, exercising the
|
||||
device_types-only path through resolve()."""
|
||||
"""AILP_DA-AC-FAC-02011_0000 (issue #319) resolves both ways --
|
||||
modelNum's 'FAC' board token and /oic/d's oic.d.airconditioner type
|
||||
independently agree on the airconditioner registry. Passes
|
||||
device_types through resolve() to exercise that agreement, not because
|
||||
the board token is absent."""
|
||||
from tests.conftest import _load_device
|
||||
|
||||
resources = _load_device("airconditioner_ailp_fac")
|
||||
@@ -1173,10 +1175,9 @@ def test_registry_reproduces_golden_state_keys_for_airconditioner_cac():
|
||||
0.16.0 when oneUiVersion detection was dropped, since 'CAC' had never
|
||||
been added to the modelNum board-token table. Resolved via the new 'CAC'
|
||||
token onto the existing airconditioner registry. Not fully covered yet --
|
||||
three hrefs remain unbound (absence-clean, sound-optimization,
|
||||
smart-sensing-cooling), all genuinely new to this board generation and
|
||||
out of scope for the routing fix; see test_airconditioner_cac.py for
|
||||
the documented gap."""
|
||||
two hrefs remain unbound (sound-optimization, smart-sensing-cooling),
|
||||
both genuinely new to this board generation and out of scope for the
|
||||
routing fix; see test_airconditioner_cac.py for the documented gap."""
|
||||
from tests.conftest import _load_device
|
||||
|
||||
resources = _load_device("airconditioner_cac")
|
||||
|
||||
Reference in new issue
Block a user