Files
localthings/tests/test_range_hood_fan.py
T
Marc Billow e9281aaead Bind microwave built-in vent fan's /hood/fanspeed/vs/0 (issues #137, #142)
Combi microwave units report their vent fan on the same resource shape a
standalone range hood uses, so reuse range_hood.HOOD_FAN directly in the
microwave registry. Unlike a standalone hood, this board has no sibling
/power/0 or /power/vs/0 resource, so LocalThingsRangeHoodFan now falls
back to treating fan speed 0 as the off state when no separate power
resource is present. Also gate HOOD_FAN's automatic_operation sensor on
field presence, since this board doesn't report it.
2026-07-28 00:18:38 +00:00

156 lines
5.2 KiB
Python

"""HA fan-entity mapping tests for the range hood."""
from custom_components.localthings.fan import LocalThingsRangeHoodFan
from custom_components.localthings.registry.by_type import microwave, range_hood
from custom_components.localthings.registry.discovery import discover
from custom_components.localthings.registry.entities import FanDesc
from tests.conftest import _load_device
class _FakeCoordinator:
device_serial = 'TEST-HOOD-SERIAL'
device_info = {}
data = {}
def __init__(self, resources):
self.last_resources = resources
self.commands = []
def resource(self, href):
return self.last_resources.get(href, {})
async def async_send_command(self, bound, payload):
self.commands.append((bound, payload))
def _entity(resources, coordinator=None, registry=range_hood.REGISTRY):
bound = discover(
resources,
registry.capabilities,
registry.pattern_capabilities,
)
fan_bound = next(item for item in bound if isinstance(item.desc, FanDesc))
return LocalThingsRangeHoodFan(
coordinator or _FakeCoordinator(resources), fan_bound,
)
def _microwave_entity(resources, coordinator=None):
return _entity(resources, coordinator, registry=microwave.REGISTRY)
def test_power_off_maps_to_zero_percent_and_four_retained_speeds():
entity = _entity(_load_device('range_hood'))
assert entity.is_on is False
assert entity.speed_count == 4
assert entity.percentage == 0
def test_active_codes_map_to_ordered_percentages():
resources = _load_device('range_hood')
resources['/power/0']['value'] = True
resources['/hood/fanspeed/vs/0'][
'x.com.samsung.da.hood.fanSpeed'
] = '14'
assert _entity(resources).percentage == 25
resources['/hood/fanspeed/vs/0'][
'x.com.samsung.da.hood.fanSpeed'
] = '15'
assert _entity(resources).percentage == 50
resources['/hood/fanspeed/vs/0'][
'x.com.samsung.da.hood.fanSpeed'
] = '16'
assert _entity(resources).percentage == 75
resources['/hood/fanspeed/vs/0'][
'x.com.samsung.da.hood.fanSpeed'
] = '17'
assert _entity(resources).percentage == 100
async def test_power_write_prefers_standard_resource_when_both_exist():
resources = _load_device('range_hood')
coordinator = _FakeCoordinator(resources)
entity = _entity(resources, coordinator)
await entity.async_turn_on()
assert coordinator.commands[-1][1] == ('power', True, '/power/0')
async def test_power_write_falls_back_to_vendor_resource():
resources = _load_device('range_hood')
resources.pop('/power/0')
coordinator = _FakeCoordinator(resources)
entity = _entity(resources, coordinator)
await entity.async_turn_off()
assert coordinator.commands[-1][1] == ('power', False, '/power/vs/0')
# ---------------------------------------------------------------------------
# Microwave built-in vent fan (issues #137/#142): reuses HOOD_FAN, but this
# board has no sibling /power/0 or /power/vs/0 resource -- fan speed 0 is
# itself the off state.
# ---------------------------------------------------------------------------
def test_microwave_vent_fan_has_no_separate_power_resource():
resources = _load_device('microwave_me7500d')
assert '/power/0' not in resources
assert '/power/vs/0' not in resources
entity = _microwave_entity(resources)
assert entity._has_separate_power() is False
def test_microwave_vent_fan_off_state_excludes_zero_from_speed_codes():
"""supportedFanSpeed reports ['0'..'4'] with 0 meaning off -- unlike the
standalone hood's codes, which never include an off entry."""
resources = _load_device('microwave_me7500d')
entity = _microwave_entity(resources)
assert entity.is_on is False
assert entity.percentage == 0
assert entity.speed_count == 4 # ['1', '2', '3', '4'], '0' excluded
def test_microwave_vent_fan_nonzero_speed_reads_as_on():
resources = _load_device('microwave_me7500d')
resources['/hood/fanspeed/vs/0']['x.com.samsung.da.hood.fanSpeed'] = '2'
entity = _microwave_entity(resources)
assert entity.is_on is True
assert entity.percentage == 50 # 2nd of ['1', '2', '3', '4']
async def test_microwave_vent_fan_turn_off_writes_zero_speed_not_power():
resources = _load_device('microwave_me7500d')
resources['/hood/fanspeed/vs/0']['x.com.samsung.da.hood.fanSpeed'] = '2'
coordinator = _FakeCoordinator(resources)
entity = _microwave_entity(resources, coordinator)
await entity.async_turn_off()
assert coordinator.commands[-1][1] == ('speed', '0')
async def test_microwave_vent_fan_turn_on_without_percentage_picks_lowest_speed():
resources = _load_device('microwave_me7500d')
coordinator = _FakeCoordinator(resources)
entity = _microwave_entity(resources, coordinator)
await entity.async_turn_on()
assert coordinator.commands[-1][1] == ('speed', '1')
async def test_microwave_vent_fan_set_percentage_writes_speed_only():
resources = _load_device('microwave_me7500d')
coordinator = _FakeCoordinator(resources)
entity = _microwave_entity(resources, coordinator)
await entity.async_set_percentage(100)
assert coordinator.commands == [(entity._bound, ('speed', '4'))]