diff --git a/tests/fixtures/golden/refrigerator_artik051_dongle_ref_cooler.json b/tests/fixtures/golden/refrigerator_artik051_dongle_ref_cooler.json new file mode 100644 index 0000000..984569c --- /dev/null +++ b/tests/fixtures/golden/refrigerator_artik051_dongle_ref_cooler.json @@ -0,0 +1,23 @@ +{ + "state_keys": [ + "alarm_code", + "cooler_setpoint", + "cooler_temperature", + "defrost_active", + "defrost_delay", + "diagnosis_status", + "door_cooler_open", + "door_onedoorfreezer_open", + "energy_kwh", + "energy_last_month_kwh", + "energy_saved_kwh", + "energy_this_month_kwh", + "firmware_update", + "ice_maker_enabled", + "power_energy_kwh", + "power_watts", + "rapid_freezing", + "rapid_fridge", + "sabbath_mode" + ] +} diff --git a/tests/fixtures/refrigerator_artik051_dongle_ref_cooler_device.json b/tests/fixtures/refrigerator_artik051_dongle_ref_cooler_device.json new file mode 100644 index 0000000..de8e022 --- /dev/null +++ b/tests/fixtures/refrigerator_artik051_dongle_ref_cooler_device.json @@ -0,0 +1,201 @@ +{ + "device0": [ + { + "rt": [ + "x.com.samsung.devcol", + "oic.wk.col" + ], + "if": [ + "oic.if.baseline", + "oic.if.ll", + "oic.if.b" + ] + }, + { + "href": "/alarms/vs/0", + "rep": {} + }, + { + "href": "/bespoke/vs/0", + "rep": { + "x.com.samsung.da.BespokeProduct": "Off" + } + }, + { + "href": "/defrost/delay/0", + "rep": {} + }, + { + "href": "/defrost/delay/vs/0", + "rep": {} + }, + { + "href": "/diagnosis/vs/0", + "rep": { + "x.com.samsung.da.diagnosisStart": "Ready" + } + }, + { + "href": "/door/cooler/0", + "rep": { + "openState": "Close" + } + }, + { + "href": "/door/onedoorfreezer/vs/0", + "rep": { + "x.com.samsung.da.openState": "Close" + } + }, + { + "href": "/doors/vs/0", + "rep": { + "x.com.samsung.da.items": [ + { + "x.com.samsung.da.id": "1", + "x.com.samsung.da.description": "Door", + "x.com.samsung.da.openState": "Close" + }, + { + "x.com.samsung.da.id": "3", + "x.com.samsung.da.description": "Door", + "x.com.samsung.da.openState": "Close" + } + ] + } + }, + { + "href": "/energy/consumption/vs/0", + "rep": {} + }, + { + "href": "/file/information/vs/0", + "rep": { + "x.com.samsung.timeoffset": "+01:00", + "x.com.samsung.supprtedtype": 1 + } + }, + { + "href": "/icemaker/status/0", + "rep": {} + }, + { + "href": "/icemaker/status/vs/0", + "rep": {} + }, + { + "href": "/information/vs/0", + "rep": { + "x.com.samsung.da.modelNum": "ARTIK051_DONGLE_REF|00090741|00090028001130300100000000000000", + "x.com.samsung.da.description": "ARTIK_REF_17K", + "x.com.samsung.da.serialNum": "**REDACTED**", + "x.com.samsung.da.otnDUID": "**REDACTED**", + "x.com.samsung.da.items": [ + { + "x.com.samsung.da.id": "0", + "x.com.samsung.da.description": "WiFi Module", + "x.com.samsung.da.type": "Software", + "x.com.samsung.da.number": "011120190711", + "x.com.samsung.da.newVersionAvailable": "0" + }, + { + "x.com.samsung.da.id": "1", + "x.com.samsung.da.description": "Micom", + "x.com.samsung.da.type": "Firmware", + "x.com.samsung.da.number": "14040703,ffffffff", + "x.com.samsung.da.newVersionAvailable": "0" + } + ] + } + }, + { + "href": "/mode/0", + "rep": { + "supportedModes": [ + "HOMECARE_WIZARD_V2", + "RVACATION_ON", + "RVACATION_OFF" + ], + "modes": [ + "RVACATION_OFF" + ] + } + }, + { + "href": "/mode/vs/0", + "rep": { + "x.com.samsung.da.supportedModes": [ + "HOMECARE_WIZARD_V2", + "RVACATION_ON", + "RVACATION_OFF" + ], + "x.com.samsung.da.modes": [ + "RVACATION_OFF" + ] + } + }, + { + "href": "/otninformation/vs/0", + "rep": { + "x.com.samsung.da.target": "Micom", + "x.com.samsung.da.newVersionAvailable": "false" + } + }, + { + "href": "/refrigeration/0", + "rep": { + "defrost": false, + "rapidFreeze": false, + "rapidCool": false + } + }, + { + "href": "/refrigeration/vs/0", + "rep": { + "x.com.samsung.da.rapidFridge": "Off" + } + }, + { + "href": "/sabbath/vs/0", + "rep": {} + }, + { + "href": "/temperature/current/cooler/0", + "rep": { + "range": [ + 1.0, + 7.0 + ], + "units": "C", + "temperature": 3.0 + } + }, + { + "href": "/temperature/desired/cooler/0", + "rep": { + "range": [ + 1.0, + 7.0 + ], + "units": "C", + "temperature": 3.0 + } + }, + { + "href": "/temperatures/vs/0", + "rep": { + "x.com.samsung.da.items": [ + { + "x.com.samsung.da.id": "1", + "x.com.samsung.da.description": "Fridge", + "x.com.samsung.da.desired": "3", + "x.com.samsung.da.current": "3", + "x.com.samsung.da.maximum": "7", + "x.com.samsung.da.minimum": "1", + "x.com.samsung.da.unit": "Celsius" + } + ] + } + } + ] +} \ No newline at end of file diff --git a/tests/test_fridge_capabilities.py b/tests/test_fridge_capabilities.py index bbd7d54..1d3634b 100644 --- a/tests/test_fridge_capabilities.py +++ b/tests/test_fridge_capabilities.py @@ -287,6 +287,42 @@ class TestArtik051DongleRefFixtureCoverage: 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 diff --git a/tests/test_golden_regression.py b/tests/test_golden_regression.py index d3b2b8f..8769adf 100644 --- a/tests/test_golden_regression.py +++ b/tests/test_golden_regression.py @@ -166,6 +166,24 @@ def test_registry_reproduces_golden_state_keys_for_artik051_ref_17k(): ) +def test_registry_reproduces_golden_state_keys_for_artik051_dongle_ref_cooler(): + """RR40M7165WW (issue #78) -- the same ARTIK051_DONGLE_REF household + dongle family as issue #77's freezer, but the fridge half: reports + /door/cooler/0 *and* /door/onedoorfreezer/vs/0 (the latter apparently + shared firmware naming, not an actual second freezer compartment) plus + /temperature/{current,desired}/cooler/0. Same pipe-delimited modelNum + detection gap and DOOR_GENERIC field-name gap as #77, same fix.""" + from tests.conftest import _load_device + resources = _load_device('refrigerator_artik051_dongle_ref_cooler') + golden = json.loads((GOLDEN / 'refrigerator_artik051_dongle_ref_cooler.json').read_text()) + state_keys = _new_state_keys('refrigerator_artik051_dongle_ref_cooler', resources) + assert set(state_keys) == set(golden['state_keys']), ( + f"state_keys mismatch:\n" + f" extra: {sorted(set(state_keys) - set(golden['state_keys']))}\n" + f" missing: {sorted(set(golden['state_keys']) - set(state_keys))}" + ) + + def test_registry_reproduces_golden_state_keys_for_artik051_dongle_ref(): """ARTIK051_DONGLE_REF standalone freezer (issues #77/#83) -- reports no oneUiVersion and a pipe-delimited modelNum ('..._DONGLE_REF|')