TP2X_REF_20K-class 3-compartment kimchi refrigerators report each compartment's storage mode and ripening status/timer on /status/kimchi/<slot>/vs/0, plus a top-compartment door sensor on /kimchidoors/top/vs/0 -- all previously unbound. Bind them as pattern capabilities (fridge.KIMCHI_ZONE, fridge.KIMCHI_DOOR_GENERIC), deriving the per-compartment entity key and display name from the href's top/middle/bottom segment, the same way DOOR_GENERIC/TEMP_CURRENT_GENERIC already do. Storage-mode option labels were translated directly from the reporter's own SmartThings app screenshots rather than guessed from the raw device codes or their English paraphrase, confirming the on-screen option order matches supportMode's array order (including the freezer triplet's -19/-21/-17°C -> Standard/Strong/Weak mapping). Also tighten FLEX_ZONE's exists_fn: this device's /mode/vs/0 also populates modes/supportedOptions, but with a token shape that never overlaps (a "_[n]:[n]" suffix supportedOptions carries that modes never repeats), so the existing "supportedOptions is nonempty" check let the entity bind anyway and get stuck permanently on "unknown". Requiring an actual resolvable value keeps it working for the RF9000/Bespoke-class fridges it was built for while leaving it absent here.
467 lines
21 KiB
Python
467 lines
21 KiB
Python
"""Tests for fridge-specific capabilities."""
|
|
from custom_components.localthings.registry.capabilities import fridge
|
|
|
|
|
|
class TestTempCurrentGeneric:
|
|
"""Issue #7: unit must be read live from the device, not assumed to be
|
|
Fahrenheit -- a TP1X_REF_21K fridge (RL38C6B0CWW/EG) reports the same
|
|
resource in Celsius."""
|
|
|
|
def test_unit_reads_celsius(self):
|
|
desc = fridge.TEMP_CURRENT_GENERIC.entities[0]
|
|
assert desc.unit_fn({'temperature': 3.0, 'units': 'C'}) == '°C'
|
|
|
|
def test_unit_reads_fahrenheit(self):
|
|
desc = fridge.TEMP_CURRENT_GENERIC.entities[0]
|
|
assert desc.unit_fn({'temperature': 5.0, 'units': 'F'}) == '°F'
|
|
|
|
def test_unit_defaults_to_fahrenheit_when_missing(self):
|
|
desc = fridge.TEMP_CURRENT_GENERIC.entities[0]
|
|
assert desc.unit_fn({'temperature': 5.0}) == '°F'
|
|
|
|
|
|
class TestDoorGeneric:
|
|
"""Issues #77/#83: DOOR_GENERIC used to read only the bare `openState`
|
|
field. Most /door/* resources use that, but the ARTIK051_DONGLE_REF
|
|
family's /door/onedoorfreezer/vs/0 reports the vendor-prefixed
|
|
`x.com.samsung.da.openState` instead -- the pattern capability still
|
|
bound the href either way, but the entity's value was always None."""
|
|
|
|
def test_reads_bare_open_state(self):
|
|
desc = fridge.DOOR_GENERIC.entities[0]
|
|
assert desc.rep_fn({'openState': 'Open'}) is True
|
|
assert desc.rep_fn({'openState': 'Close'}) is False
|
|
|
|
def test_reads_vendor_prefixed_open_state(self):
|
|
desc = fridge.DOOR_GENERIC.entities[0]
|
|
assert desc.rep_fn({'x.com.samsung.da.openState': 'Open'}) is True
|
|
assert desc.rep_fn({'x.com.samsung.da.openState': 'Close'}) is False
|
|
|
|
def test_prefers_bare_field_when_both_present(self):
|
|
desc = fridge.DOOR_GENERIC.entities[0]
|
|
rep = {'openState': 'Open', 'x.com.samsung.da.openState': 'Close'}
|
|
assert desc.rep_fn(rep) is True
|
|
|
|
|
|
class TestTempSetpointGeneric:
|
|
def test_unit_reads_celsius(self):
|
|
desc = fridge.TEMP_SETPOINT.entities[0]
|
|
assert desc.unit_fn({'temperature': -19.0, 'units': 'C'}) == '°C'
|
|
|
|
|
|
class TestTemperaturesFallback:
|
|
"""Aggregate /temperatures/vs/0 -- per-item unit from
|
|
x.com.samsung.da.unit ('Celsius'/'Fahrenheit')."""
|
|
|
|
def test_freezer_unit_celsius(self):
|
|
desc = next(e for e in fridge.TEMPERATURES_FALLBACK.entities
|
|
if e.key == 'freezer_temperature')
|
|
rep = {'x.com.samsung.da.items': [
|
|
{'x.com.samsung.da.description': 'Freezer', 'x.com.samsung.da.current': '-19',
|
|
'x.com.samsung.da.unit': 'Celsius'},
|
|
{'x.com.samsung.da.description': 'Fridge', 'x.com.samsung.da.current': '3',
|
|
'x.com.samsung.da.unit': 'Celsius'},
|
|
]}
|
|
assert desc.unit_fn(rep) == '°C'
|
|
assert desc.value_fn(rep['x.com.samsung.da.items']) == -19
|
|
|
|
def test_fridge_unit_fahrenheit(self):
|
|
desc = next(e for e in fridge.TEMPERATURES_FALLBACK.entities
|
|
if e.key == 'fridge_temperature')
|
|
rep = {'x.com.samsung.da.items': [
|
|
{'x.com.samsung.da.description': 'Fridge', 'x.com.samsung.da.current': '37',
|
|
'x.com.samsung.da.unit': 'Fahrenheit'},
|
|
]}
|
|
assert desc.unit_fn(rep) == '°F'
|
|
|
|
def test_unit_defaults_to_fahrenheit_when_item_missing(self):
|
|
desc = next(e for e in fridge.TEMPERATURES_FALLBACK.entities
|
|
if e.key == 'freezer_temperature')
|
|
assert desc.unit_fn({'x.com.samsung.da.items': []}) == '°F'
|
|
|
|
|
|
class TestDefrostBlockStatus:
|
|
"""DEFROST_BLOCK_ON means the defrost cycle is actively running, not
|
|
that defrost is being withheld -- confirmed against live dumps showing
|
|
it ON while defrost_delay is off."""
|
|
|
|
def test_key_and_name_reflect_active_defrosting(self):
|
|
desc = fridge.DEFROST_BLOCK_STATUS.entities[0]
|
|
assert desc.key == 'defrost_active'
|
|
|
|
def test_value_fn(self):
|
|
desc = fridge.DEFROST_BLOCK_STATUS.entities[0]
|
|
assert desc.value_fn(['DEFROST_BLOCK_ON']) is True
|
|
assert desc.value_fn(['DEFROST_BLOCK_OFF']) is False
|
|
|
|
|
|
class TestRefrigerationFallback:
|
|
"""/refrigeration/0 duplicates two of REFRIGERATION's (/refrigeration/vs/0)
|
|
fields under different names (rapidFreeze/rapidCool vs
|
|
rapidFreezing/rapidFridge) but also carries one genuinely new field
|
|
(defrost) that has no vs-href equivalent."""
|
|
|
|
def test_href(self):
|
|
assert fridge.REFRIGERATION_FALLBACK.href == '/refrigeration/0'
|
|
|
|
def test_defrost_active_is_fallback_of_defrost_block_status(self):
|
|
# Duplicates DEFROST_BLOCK_STATUS's defrost_active
|
|
# (/defrost/block/vs/0) -- only a true fallback when that richer
|
|
# href is absent (issue #7's device: /refrigeration/0 only).
|
|
desc = next(e for e in fridge.REFRIGERATION_FALLBACK.entities
|
|
if e.key == 'defrost_active')
|
|
assert desc.value_fn(True) is True
|
|
assert desc.value_fn(False) is False
|
|
assert desc.exists_fn({}, {'/refrigeration/0': {}}) is True
|
|
assert desc.exists_fn(
|
|
{}, {'/refrigeration/0': {}, '/defrost/block/vs/0': {}}) is False
|
|
|
|
def test_rapid_switches_hidden_when_vs_href_present(self):
|
|
for key in ('rapid_fridge', 'rapid_freezing'):
|
|
desc = next(e for e in fridge.REFRIGERATION_FALLBACK.entities if e.key == key)
|
|
assert desc.exists_fn({}, {'/refrigeration/vs/0': {}, '/refrigeration/0': {}}) is False
|
|
|
|
def test_rapid_switches_shown_when_vs_href_absent(self):
|
|
for key in ('rapid_fridge', 'rapid_freezing'):
|
|
desc = next(e for e in fridge.REFRIGERATION_FALLBACK.entities if e.key == key)
|
|
assert desc.exists_fn({}, {'/refrigeration/0': {}}) is True
|
|
|
|
|
|
class TestFlexZone:
|
|
"""Issue #32 (surfaced by #26/#27's TP2X/Bespoke dumps): the flex-zone
|
|
entry's prefix varies by fridge family (CV_TTYPE_RF9000A_ on RF9000-class,
|
|
CV_FDR_ on TP1X/Bespoke-class). The old hardcoded-prefix match left
|
|
CV_FDR_-family fridges with an always-unknown select, and a write would
|
|
have appended a duplicate flag instead of replacing the existing one."""
|
|
|
|
def test_reads_rf9000_prefix(self):
|
|
rep = {
|
|
'x.com.samsung.da.modes': ['CV_TTYPE_RF9000A_BEVERAGE', 'WATERFILTER_DISABLE'],
|
|
'x.com.samsung.da.supportedOptions': [
|
|
'CV_TTYPE_RF9000A_FREEZE', 'CV_TTYPE_RF9000A_BEVERAGE'],
|
|
}
|
|
assert fridge._flex_zone_current(rep) == 'CV_TTYPE_RF9000A_BEVERAGE'
|
|
|
|
def test_reads_cv_fdr_prefix(self):
|
|
rep = {
|
|
'x.com.samsung.da.modes': ['CVN_CONVERTIBLE_ZONE', 'CV_FDR_MEAT', 'WATERFILTER_ENABLE'],
|
|
'x.com.samsung.da.supportedOptions': [
|
|
'CV_FDR_WINE', 'CV_FDR_DELI', 'CV_FDR_BEVERAGE', 'CV_FDR_MEAT'],
|
|
}
|
|
assert fridge._flex_zone_current(rep) == 'CV_FDR_MEAT'
|
|
|
|
def test_write_replaces_rather_than_duplicates(self):
|
|
rep = {
|
|
'x.com.samsung.da.modes': ['CVN_CONVERTIBLE_ZONE', 'CV_FDR_MEAT', 'WATERFILTER_ENABLE'],
|
|
'x.com.samsung.da.supportedOptions': [
|
|
'CV_FDR_WINE', 'CV_FDR_DELI', 'CV_FDR_BEVERAGE', 'CV_FDR_MEAT'],
|
|
}
|
|
path, payload = fridge._flex_zone_write('CV_FDR_WINE', rep)
|
|
assert path == ['mode', 'vs', '0']
|
|
modes = payload['x.com.samsung.da.modes']
|
|
assert modes.count('CV_FDR_WINE') == 1
|
|
assert 'CV_FDR_MEAT' not in modes
|
|
assert 'CVN_CONVERTIBLE_ZONE' in modes and 'WATERFILTER_ENABLE' in modes
|
|
|
|
def test_exists_only_when_a_current_value_resolves(self):
|
|
"""Issue #26's kimchi-refrigerator family also populates /mode/vs/0's
|
|
modes/supportedOptions, but its supportedOptions tokens carry a
|
|
"_[n]:[n]" suffix modes never repeats, so no item ever overlaps --
|
|
the entity used to bind anyway (supportedOptions is nonempty) and
|
|
get stuck on "unknown" forever."""
|
|
no_overlap_rep = {
|
|
'x.com.samsung.da.modes': ['KIMCHIT_STORAGE_FREEZER_NORMAL'],
|
|
'x.com.samsung.da.supportedOptions': [
|
|
'KIMCHIT_STORAGE_FREEZER_NORMAL_[0]:[0]'],
|
|
}
|
|
desc = fridge.FLEX_ZONE.entities[0]
|
|
assert desc.exists_fn(no_overlap_rep, {}) is False
|
|
|
|
overlap_rep = {
|
|
'x.com.samsung.da.modes': ['CV_FDR_MEAT'],
|
|
'x.com.samsung.da.supportedOptions': ['CV_FDR_WINE', 'CV_FDR_MEAT'],
|
|
}
|
|
assert desc.exists_fn(overlap_rep, {}) is True
|
|
|
|
|
|
class TestTp1xNativeDuplicateResources:
|
|
"""The US TP1X_REF_21K publishes two native mirrors in addition to the
|
|
richer vendor resources. They must count as covered without producing
|
|
duplicate entities or guessing unverified write contracts."""
|
|
|
|
def test_duplicate_capabilities_have_no_entities(self):
|
|
assert fridge.DEFROST_DELAY_NATIVE_DUPLICATE.entities == ()
|
|
assert fridge.ICEMAKER_STATUS_NATIVE_DUPLICATE.entities == ()
|
|
|
|
def test_us_fixture_has_complete_coverage(self):
|
|
from custom_components.localthings.registry.adapter import flatten
|
|
from custom_components.localthings.registry.by_type import refrigerator
|
|
from custom_components.localthings.registry.discovery import discover
|
|
from tests.conftest import _load_device
|
|
|
|
resources = _load_device('refrigerator_tp1x_ref_21k_us')
|
|
unbound = []
|
|
bound = discover(
|
|
resources,
|
|
refrigerator.REGISTRY.capabilities,
|
|
refrigerator.REGISTRY.pattern_capabilities,
|
|
log=unbound.append,
|
|
)
|
|
|
|
assert unbound == []
|
|
state = flatten(bound, resources)
|
|
assert state['defrost_delay'] is False
|
|
assert 'ice_maker_enabled' not in state
|
|
assert state['icemaker_one_enabled'] is True
|
|
assert state['icemaker_two_enabled'] is True
|
|
assert state['selfcheck_error'] == 'DA_ERROR_NONE'
|
|
|
|
|
|
class TestPantryZone:
|
|
"""Cool Select Zone pantry compartment on ARTIK051_REF_17K -- issue #20."""
|
|
|
|
def test_href(self):
|
|
assert fridge.PANTRY_ZONE.href == '/status/pantry/one/vs/0'
|
|
|
|
def test_write(self):
|
|
desc = fridge.PANTRY_ZONE.entities[0]
|
|
path, body = desc.write_fn('FDR_WINE', {})
|
|
assert path == ['status', 'pantry', 'one', 'vs', '0']
|
|
assert body == {'x.com.samsung.da.mode': 'FDR_WINE'}
|
|
|
|
|
|
class TestKimchiZone:
|
|
"""Kimchi-refrigerator compartments (TP2X_REF_20K-class, issue #26) --
|
|
top/middle/bottom each report an identically-shaped currentMode/
|
|
supportMode resource under /status/kimchi/<slot>/vs/0."""
|
|
|
|
def test_href_prefix(self):
|
|
assert fridge.KIMCHI_ZONE.href_prefix == '/status/kimchi/'
|
|
assert fridge.KIMCHI_DOOR_GENERIC.href_prefix == '/kimchidoors/'
|
|
|
|
def test_write_derives_path_from_href(self):
|
|
desc = fridge.KIMCHI_ZONE.entities[0]
|
|
path, body = desc.write_fn(
|
|
'KIMCHI_STORAGE_COLD', {}, href='/status/kimchi/middle/vs/0')
|
|
assert path == ['status', 'kimchi', 'middle', 'vs', '0']
|
|
assert body == {'x.com.samsung.da.currentMode': 'KIMCHI_STORAGE_COLD'}
|
|
|
|
def test_write_without_href_is_rejected(self):
|
|
desc = fridge.KIMCHI_ZONE.entities[0]
|
|
assert desc.write_fn('KIMCHI_STORAGE_COLD', {}) is None
|
|
|
|
def test_ripening_status_is_lowercased(self):
|
|
desc = next(e for e in fridge.KIMCHI_ZONE.entities if e.key == 'ripening_status')
|
|
assert desc.value_fn('Off') == 'off'
|
|
assert desc.value_fn(None) is None
|
|
|
|
def test_door_reuses_open_state_helper(self):
|
|
desc = fridge.KIMCHI_DOOR_GENERIC.entities[0]
|
|
assert desc.rep_fn({'x.com.samsung.da.openState': 'Open'}) is True
|
|
assert desc.rep_fn({'x.com.samsung.da.openState': 'Close'}) is False
|
|
|
|
|
|
class TestArtik051AndTp2xFixturesHaveCompleteCoverage:
|
|
"""issue #20 (ARTIK051_REF_17K) and #26 (TP2X_REF_20K) both triggered
|
|
the incomplete-capability-coverage repair; both must resolve to zero
|
|
unbound hrefs now that /diagnosis/vs/0 (dishwasher.DIAGNOSIS, reused for
|
|
fridges), /status/pantry/one/vs/0 (PANTRY_ZONE), and the OCF-native
|
|
defrost/icemaker mirrors are covered."""
|
|
|
|
def test_artik051_ref_17k(self):
|
|
from custom_components.localthings.registry.adapter import flatten
|
|
from custom_components.localthings.registry.by_type import refrigerator
|
|
from custom_components.localthings.registry.discovery import discover
|
|
from tests.conftest import _load_device
|
|
|
|
resources = _load_device('refrigerator_artik051_ref_17k')
|
|
unbound = []
|
|
bound = discover(
|
|
resources,
|
|
refrigerator.REGISTRY.capabilities,
|
|
refrigerator.REGISTRY.pattern_capabilities,
|
|
log=unbound.append,
|
|
)
|
|
assert unbound == []
|
|
state = flatten(bound, resources)
|
|
assert state['pantry_zone_mode'] == 'FDR_DRINKS'
|
|
|
|
def test_tp2x_ref_20k(self):
|
|
from custom_components.localthings.registry.adapter import flatten
|
|
from custom_components.localthings.registry.by_type import refrigerator
|
|
from custom_components.localthings.registry.discovery import discover
|
|
from tests.conftest import _load_device
|
|
|
|
resources = _load_device('refrigerator_tp2x_ref_20k')
|
|
unbound = []
|
|
bound = discover(
|
|
resources,
|
|
refrigerator.REGISTRY.capabilities,
|
|
refrigerator.REGISTRY.pattern_capabilities,
|
|
log=unbound.append,
|
|
)
|
|
assert unbound == []
|
|
state = flatten(bound, resources)
|
|
assert state['flex_zone_mode'] == 'CV_FDR_BEVERAGE'
|
|
|
|
def test_tp2x_ref_20k_kimchi(self):
|
|
"""A different physical unit sharing the same modelNum string (issue
|
|
#26, second reporter) -- a 3-compartment kimchi refrigerator instead
|
|
of the flex-zone fridge above. /kimchidoors/top/vs/0 and
|
|
/status/kimchi/{top,middle,bottom}/vs/0 were previously unbound."""
|
|
from custom_components.localthings.registry.adapter import flatten
|
|
from custom_components.localthings.registry.by_type import refrigerator
|
|
from custom_components.localthings.registry.discovery import discover
|
|
from tests.conftest import _load_device
|
|
|
|
resources = _load_device('refrigerator_tp2x_ref_20k_kimchi')
|
|
unbound = []
|
|
bound = discover(
|
|
resources,
|
|
refrigerator.REGISTRY.capabilities,
|
|
refrigerator.REGISTRY.pattern_capabilities,
|
|
log=unbound.append,
|
|
)
|
|
assert unbound == []
|
|
state = flatten(bound, resources)
|
|
assert state['top_mode'] == 'STORAGE_FREEZER_NORMAL'
|
|
assert state['middle_mode'] == 'KIMCHI_STORAGE_NORMAL'
|
|
assert state['bottom_mode'] == 'KIMCHI_STORAGE_NORMAL'
|
|
assert state['top_open'] is False
|
|
assert 'middle_open' not in state # no /kimchidoors/middle/vs/0 reported
|
|
assert 'flex_zone_mode' not in state # no resolvable overlap on this family
|
|
|
|
|
|
class TestArtik051DongleRefFixtureCoverage:
|
|
"""Issues #77/#83: the ARTIK051_DONGLE_REF standalone freezer resolves
|
|
to the refrigerator registry (segment-based modelNum detection) with
|
|
zero unbound hrefs, and its door/temperature entities -- which only
|
|
bind through pattern capabilities the 'unknown' fallback never tried --
|
|
now carry real values."""
|
|
|
|
def test_no_unbound_hrefs_and_expected_entities(self):
|
|
from custom_components.localthings.registry.adapter import flatten
|
|
from custom_components.localthings.registry.by_type import (
|
|
for_device_by_model, refrigerator,
|
|
)
|
|
from custom_components.localthings.registry.discovery import discover
|
|
from tests.conftest import _load_device
|
|
|
|
resources = _load_device('refrigerator_artik051_dongle_ref')
|
|
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 == 'refrigerator'
|
|
|
|
unbound = []
|
|
bound = discover(
|
|
resources, refrigerator.REGISTRY.capabilities,
|
|
refrigerator.REGISTRY.pattern_capabilities, log=unbound.append,
|
|
)
|
|
assert unbound == []
|
|
state = flatten(bound, resources)
|
|
assert state['door_onedoorfreezer_open'] is False # rep reports 'Close'
|
|
assert state['freezer_temperature'] == -20.0
|
|
assert state['freezer_setpoint'] == -20.0
|
|
|
|
|
|
class TestArtik051DongleRefCoolerFixtureCoverage:
|
|
"""Issue #78: RR40M7165WW, the fridge half of the same household
|
|
ARTIK051_DONGLE_REF dongle setup as issue #77's freezer. Notably reports
|
|
*two* door hrefs (/door/cooler/0 and /door/onedoorfreezer/vs/0) despite
|
|
being a single-door fridge, not a fridge/freezer combo -- apparently
|
|
shared firmware naming across the product line, not a real second
|
|
compartment. Both must still resolve to zero unbound hrefs and real
|
|
values with the same fix as #77."""
|
|
|
|
def test_no_unbound_hrefs_and_expected_entities(self):
|
|
from custom_components.localthings.registry.adapter import flatten
|
|
from custom_components.localthings.registry.by_type import (
|
|
for_device_by_model, refrigerator,
|
|
)
|
|
from custom_components.localthings.registry.discovery import discover
|
|
from tests.conftest import _load_device
|
|
|
|
resources = _load_device('refrigerator_artik051_dongle_ref_cooler')
|
|
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 == 'refrigerator'
|
|
|
|
unbound = []
|
|
bound = discover(
|
|
resources, refrigerator.REGISTRY.capabilities,
|
|
refrigerator.REGISTRY.pattern_capabilities, log=unbound.append,
|
|
)
|
|
assert unbound == []
|
|
state = flatten(bound, resources)
|
|
assert state['door_cooler_open'] is False
|
|
assert state['door_onedoorfreezer_open'] is False
|
|
assert state['cooler_temperature'] == 3.0
|
|
assert state['cooler_setpoint'] == 3.0
|
|
|
|
|
|
class TestRefrigeratorAiEnergyLevelFixtureCoverage:
|
|
"""AI energy-saving level (common.AI_ENERGY_LEVEL, see
|
|
test_common_capabilities.py) is exercised end-to-end here against the
|
|
refrigerator fixture/registry.
|
|
|
|
refrigerator_device.json's supportedAiLevel was extended to two entries
|
|
(['1', '2']) specifically to exercise the select branch -- the real
|
|
captured TP1X_REF_21K_US dump only ever reports one entry (switch
|
|
branch), so the select is synthetic-fixture-only for now."""
|
|
|
|
def test_synthetic_fixture_has_complete_coverage(self):
|
|
from custom_components.localthings.registry.adapter import flatten
|
|
from custom_components.localthings.registry.by_type import refrigerator
|
|
from custom_components.localthings.registry.discovery import discover
|
|
from tests.conftest import _load_device
|
|
|
|
resources = _load_device('refrigerator')
|
|
unbound = []
|
|
bound = discover(
|
|
resources,
|
|
refrigerator.REGISTRY.capabilities,
|
|
refrigerator.REGISTRY.pattern_capabilities,
|
|
log=unbound.append,
|
|
)
|
|
assert unbound == []
|
|
state = flatten(bound, resources)
|
|
assert state['ai_energy_level'] == '1'
|
|
|
|
|
|
class TestSwitchOffIsNotInverted:
|
|
"""Regression: several fridge SwitchDesc write_fns used ``'On' if p else 'Off'``.
|
|
|
|
The switch platform passes the literal string ``'Off'`` on turn-off (and
|
|
``'On'`` on turn-on). ``'Off'`` is a non-empty, truthy string, so
|
|
``'On' if p else 'Off'`` always evaluated to ``'On'`` — turning the switch
|
|
OFF silently re-sent ``On`` and the switch could never be turned off. The
|
|
guard must compare ``p == 'On'`` (as the other capability files already do).
|
|
"""
|
|
|
|
# (switch descriptor, payload key it writes)
|
|
CASES = [
|
|
(fridge.ICEMAKER_NIGHTTIME.entities[0], 'ice.night.status'),
|
|
(fridge.STATUS_LOCK.entities[0], 'x.com.samsung.da.ado.devicecontrol'),
|
|
(fridge.STATUS_LOCK.entities[1], 'x.com.samsung.da.device.sound'),
|
|
(fridge.DEFROST_DELAY.entities[0], 'x.com.samsung.da.delayDefrost'),
|
|
(fridge.WELCOME_LIGHTING.entities[0], 'status'),
|
|
(fridge.CABINET_LIGHT.entities[1], 'light.dimming.status'),
|
|
(fridge.ICEMAKER_STATUS_FALLBACK.entities[0], 'x.com.samsung.da.iceMaker'),
|
|
]
|
|
|
|
def test_turning_off_sends_off(self):
|
|
for desc, key in self.CASES:
|
|
_segs, payload = desc.write_fn('Off', {})
|
|
assert payload[key] == 'Off', (
|
|
f"{desc.key}: OFF must send 'Off', got {payload[key]!r} (inverted)"
|
|
)
|
|
|
|
def test_turning_on_sends_on(self):
|
|
for desc, key in self.CASES:
|
|
_segs, payload = desc.write_fn('On', {})
|
|
assert payload[key] == 'On', (
|
|
f"{desc.key}: ON must send 'On', got {payload[key]!r}"
|
|
)
|