"""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 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 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' 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}" )