|
|
@@ -4,8 +4,16 @@ import pytest
|
|
|
from bleak.backends.device import BLEDevice
|
|
|
|
|
|
from switchbot import SwitchBotAdvertisement, SwitchbotModel
|
|
|
-from switchbot.const.fan import FanMode
|
|
|
+from switchbot.const.fan import (
|
|
|
+ FanMode,
|
|
|
+ HorizontalOscillationAngle,
|
|
|
+ NightLightState,
|
|
|
+ StandingFanMode,
|
|
|
+ VerticalOscillationAngle,
|
|
|
+)
|
|
|
from switchbot.devices import fan
|
|
|
+from switchbot.devices.device import SwitchbotOperationError
|
|
|
+from switchbot.devices.fan import SwitchbotStandingFan
|
|
|
|
|
|
from .test_adv_parser import generate_ble_device
|
|
|
|
|
|
@@ -159,6 +167,147 @@ async def test_set_oscillation():
|
|
|
assert fan_device.get_oscillating_state() is True
|
|
|
|
|
|
|
|
|
+@pytest.mark.asyncio
|
|
|
+@pytest.mark.parametrize(
|
|
|
+ ("oscillating", "expected_cmd"),
|
|
|
+ [
|
|
|
+ (True, fan.COMMAND_START_OSCILLATION),
|
|
|
+ (False, fan.COMMAND_STOP_OSCILLATION),
|
|
|
+ ],
|
|
|
+)
|
|
|
+async def test_circulator_fan_set_oscillation_command(oscillating, expected_cmd):
|
|
|
+ """Circulator Fan keeps the original single-axis (V kept) payload."""
|
|
|
+ fan_device = create_device_for_command_testing({"oscillating": oscillating})
|
|
|
+ await fan_device.set_oscillation(oscillating)
|
|
|
+ fan_device._send_command.assert_called_once()
|
|
|
+ cmd = fan_device._send_command.call_args[0][0]
|
|
|
+ assert cmd == expected_cmd
|
|
|
+
|
|
|
+
|
|
|
+def test_circulator_fan_oscillation_command_constants():
|
|
|
+ """Lock the bytes for the Circulator Fan oscillation commands."""
|
|
|
+ # These are master-version bytes preserved for backward compatibility.
|
|
|
+ assert fan.COMMAND_START_OSCILLATION == "570f41020101ff"
|
|
|
+ assert fan.COMMAND_STOP_OSCILLATION == "570f41020102ff"
|
|
|
+
|
|
|
+
|
|
|
+@pytest.mark.asyncio
|
|
|
+@pytest.mark.parametrize(
|
|
|
+ ("oscillating", "expected_cmd"),
|
|
|
+ [
|
|
|
+ (True, fan.COMMAND_START_OSCILLATION_ALL_AXES),
|
|
|
+ (False, fan.COMMAND_STOP_OSCILLATION_ALL_AXES),
|
|
|
+ ],
|
|
|
+)
|
|
|
+async def test_standing_fan_set_oscillation_command(oscillating, expected_cmd):
|
|
|
+ """Standing Fan oscillation toggles both axes at once."""
|
|
|
+ standing_fan = create_standing_fan_for_testing({"oscillating": oscillating})
|
|
|
+ await standing_fan.set_oscillation(oscillating)
|
|
|
+ standing_fan._send_command.assert_called_once()
|
|
|
+ cmd = standing_fan._send_command.call_args[0][0]
|
|
|
+ assert cmd == expected_cmd
|
|
|
+
|
|
|
+
|
|
|
+def test_standing_fan_oscillation_command_constants():
|
|
|
+ """Lock the bytes for the Standing Fan dual-axis oscillation commands."""
|
|
|
+ assert fan.COMMAND_START_OSCILLATION_ALL_AXES == "570f4102010101"
|
|
|
+ assert fan.COMMAND_STOP_OSCILLATION_ALL_AXES == "570f4102010202"
|
|
|
+
|
|
|
+
|
|
|
+def _fan_with_real_result_check(init_data: dict | None = None):
|
|
|
+ """
|
|
|
+ Command-test fixture that uses the real _check_command_result.
|
|
|
+
|
|
|
+ Unlike `create_device_for_command_testing`, this keeps the real
|
|
|
+ `_check_command_result` so setter methods exercise the success-byte
|
|
|
+ validation path.
|
|
|
+ """
|
|
|
+ ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
|
|
|
+ fan_device = fan.SwitchbotFan(ble_device, model=SwitchbotModel.CIRCULATOR_FAN)
|
|
|
+ fan_device.update_from_advertisement(make_advertisement_data(ble_device, init_data))
|
|
|
+ fan_device._send_command = AsyncMock()
|
|
|
+ fan_device.update = AsyncMock()
|
|
|
+ return fan_device
|
|
|
+
|
|
|
+
|
|
|
+def _standing_fan_with_real_result_check(init_data: dict | None = None):
|
|
|
+ ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
|
|
|
+ standing_fan = SwitchbotStandingFan(ble_device, model=SwitchbotModel.STANDING_FAN)
|
|
|
+ standing_fan.update_from_advertisement(
|
|
|
+ make_advertisement_data(ble_device, init_data)
|
|
|
+ )
|
|
|
+ standing_fan._send_command = AsyncMock()
|
|
|
+ standing_fan.update = AsyncMock()
|
|
|
+ return standing_fan
|
|
|
+
|
|
|
+
|
|
|
+@pytest.mark.asyncio
|
|
|
+@pytest.mark.parametrize(
|
|
|
+ ("response", "expected"),
|
|
|
+ [
|
|
|
+ # Success byte is 1.
|
|
|
+ (b"\x01", True),
|
|
|
+ (b"\x01\xff", True),
|
|
|
+ # Known fan error payloads.
|
|
|
+ (b"\x00", False),
|
|
|
+ (b"\x07", False),
|
|
|
+ ],
|
|
|
+)
|
|
|
+@pytest.mark.parametrize(
|
|
|
+ "invoke",
|
|
|
+ [
|
|
|
+ lambda d: d.set_preset_mode("baby"),
|
|
|
+ lambda d: d.set_percentage(80),
|
|
|
+ lambda d: d.set_oscillation(True),
|
|
|
+ lambda d: d.set_oscillation(False),
|
|
|
+ lambda d: d.set_horizontal_oscillation(True),
|
|
|
+ lambda d: d.set_vertical_oscillation(True),
|
|
|
+ ],
|
|
|
+)
|
|
|
+async def test_circulator_fan_setters_validate_success_byte(response, expected, invoke):
|
|
|
+ """Every Circulator Fan setter returns True only on success-byte 1."""
|
|
|
+ device = _fan_with_real_result_check()
|
|
|
+ device._send_command.return_value = response
|
|
|
+ assert await invoke(device) is expected
|
|
|
+
|
|
|
+
|
|
|
+@pytest.mark.asyncio
|
|
|
+@pytest.mark.parametrize(
|
|
|
+ ("response", "expected"),
|
|
|
+ [
|
|
|
+ (b"\x01", True),
|
|
|
+ (b"\x01\xff", True),
|
|
|
+ (b"\x00", False),
|
|
|
+ (b"\x07", False),
|
|
|
+ ],
|
|
|
+)
|
|
|
+@pytest.mark.parametrize(
|
|
|
+ "invoke",
|
|
|
+ [
|
|
|
+ lambda d: d.set_horizontal_oscillation_angle(
|
|
|
+ HorizontalOscillationAngle.ANGLE_60
|
|
|
+ ),
|
|
|
+ lambda d: d.set_vertical_oscillation_angle(VerticalOscillationAngle.ANGLE_90),
|
|
|
+ lambda d: d.set_night_light(NightLightState.LEVEL_1),
|
|
|
+ lambda d: d.set_night_light(NightLightState.OFF),
|
|
|
+ ],
|
|
|
+)
|
|
|
+async def test_standing_fan_setters_validate_success_byte(response, expected, invoke):
|
|
|
+ """Every Standing Fan setter returns True only on success-byte 1."""
|
|
|
+ device = _standing_fan_with_real_result_check()
|
|
|
+ device._send_command.return_value = response
|
|
|
+ assert await invoke(device) is expected
|
|
|
+
|
|
|
+
|
|
|
+@pytest.mark.asyncio
|
|
|
+async def test_fan_setter_raises_on_none_response():
|
|
|
+ """None responses raise SwitchbotOperationError via _check_command_result."""
|
|
|
+ device = _fan_with_real_result_check()
|
|
|
+ device._send_command.return_value = None
|
|
|
+ with pytest.raises(SwitchbotOperationError):
|
|
|
+ await device.set_oscillation(True)
|
|
|
+
|
|
|
+
|
|
|
@pytest.mark.asyncio
|
|
|
async def test_turn_on():
|
|
|
fan_device = create_device_for_command_testing({"isOn": True})
|
|
|
@@ -175,3 +324,298 @@ async def test_turn_off():
|
|
|
|
|
|
def test_get_modes():
|
|
|
assert FanMode.get_modes() == ["normal", "natural", "sleep", "baby"]
|
|
|
+
|
|
|
+
|
|
|
+def create_standing_fan_for_testing(init_data: dict | None = None):
|
|
|
+ """Create a SwitchbotStandingFan instance for command testing."""
|
|
|
+ ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
|
|
|
+ standing_fan = SwitchbotStandingFan(ble_device, model=SwitchbotModel.STANDING_FAN)
|
|
|
+ standing_fan.update_from_advertisement(
|
|
|
+ make_advertisement_data(ble_device, init_data)
|
|
|
+ )
|
|
|
+ standing_fan._send_command = AsyncMock()
|
|
|
+ standing_fan._check_command_result = MagicMock()
|
|
|
+ standing_fan.update = AsyncMock()
|
|
|
+ return standing_fan
|
|
|
+
|
|
|
+
|
|
|
+def test_standing_fan_inherits_from_switchbot_fan():
|
|
|
+ assert issubclass(SwitchbotStandingFan, fan.SwitchbotFan)
|
|
|
+
|
|
|
+
|
|
|
+def test_standing_fan_instantiation():
|
|
|
+ ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
|
|
|
+ standing_fan = SwitchbotStandingFan(ble_device, model=SwitchbotModel.STANDING_FAN)
|
|
|
+ assert standing_fan is not None
|
|
|
+
|
|
|
+
|
|
|
+def test_standing_fan_get_modes():
|
|
|
+ assert StandingFanMode.get_modes() == [
|
|
|
+ "normal",
|
|
|
+ "natural",
|
|
|
+ "sleep",
|
|
|
+ "baby",
|
|
|
+ "custom_natural",
|
|
|
+ ]
|
|
|
+
|
|
|
+
|
|
|
+@pytest.mark.asyncio
|
|
|
+async def test_standing_fan_turn_on():
|
|
|
+ standing_fan = create_standing_fan_for_testing({"isOn": True})
|
|
|
+ await standing_fan.turn_on()
|
|
|
+ assert standing_fan.is_on() is True
|
|
|
+
|
|
|
+
|
|
|
+@pytest.mark.asyncio
|
|
|
+async def test_standing_fan_turn_off():
|
|
|
+ standing_fan = create_standing_fan_for_testing({"isOn": False})
|
|
|
+ await standing_fan.turn_off()
|
|
|
+ assert standing_fan.is_on() is False
|
|
|
+
|
|
|
+
|
|
|
+@pytest.mark.asyncio
|
|
|
+@pytest.mark.parametrize(
|
|
|
+ "mode",
|
|
|
+ ["normal", "natural", "sleep", "baby", "custom_natural"],
|
|
|
+)
|
|
|
+async def test_standing_fan_set_preset_mode(mode):
|
|
|
+ standing_fan = create_standing_fan_for_testing({"mode": mode})
|
|
|
+ await standing_fan.set_preset_mode(mode)
|
|
|
+ assert standing_fan.get_current_mode() == mode
|
|
|
+
|
|
|
+
|
|
|
+@pytest.mark.asyncio
|
|
|
+@pytest.mark.parametrize(
|
|
|
+ ("basic_info", "firmware_info", "result"),
|
|
|
+ [
|
|
|
+ (
|
|
|
+ bytearray(b"\x01\x02W\x82g\xf5\xde4\x01=dPP\x03\x14P\x00\x00\x00\x00"),
|
|
|
+ bytearray(b"\x01W\x0b\x17\x01"),
|
|
|
+ {
|
|
|
+ "battery": 87,
|
|
|
+ "isOn": True,
|
|
|
+ "oscillating": False,
|
|
|
+ "oscillating_horizontal": False,
|
|
|
+ "oscillating_vertical": False,
|
|
|
+ "mode": "normal",
|
|
|
+ "speed": 61,
|
|
|
+ "firmware": 1.1,
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ (
|
|
|
+ bytearray(b"\x01\x02U\xc2g\xf5\xde4\x04+dPP\x03\x14P\x00\x00\x00\x00"),
|
|
|
+ bytearray(b"\x01U\x0b\x17\x01"),
|
|
|
+ {
|
|
|
+ "battery": 85,
|
|
|
+ "isOn": True,
|
|
|
+ "oscillating": True,
|
|
|
+ "oscillating_horizontal": True,
|
|
|
+ "oscillating_vertical": False,
|
|
|
+ "mode": "baby",
|
|
|
+ "speed": 43,
|
|
|
+ "firmware": 1.1,
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ (
|
|
|
+ bytearray(b"\x01\x02U\xe2g\xf5\xde4\x05+dPP\x03\x14P\x00\x00\x00\x00"),
|
|
|
+ bytearray(b"\x01U\x0b\x17\x01"),
|
|
|
+ {
|
|
|
+ "battery": 85,
|
|
|
+ "isOn": True,
|
|
|
+ "oscillating": True,
|
|
|
+ "oscillating_horizontal": True,
|
|
|
+ "oscillating_vertical": True,
|
|
|
+ "mode": "custom_natural",
|
|
|
+ "speed": 43,
|
|
|
+ "firmware": 1.1,
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+)
|
|
|
+async def test_standing_fan_get_basic_info(basic_info, firmware_info, result):
|
|
|
+ # Preload nightLight via the fixture adv data so get_basic_info can surface it.
|
|
|
+ standing_fan = create_standing_fan_for_testing({"nightLight": 3})
|
|
|
+
|
|
|
+ async def mock_get_basic_info(arg):
|
|
|
+ if arg == fan.COMMAND_GET_BASIC_INFO:
|
|
|
+ return basic_info
|
|
|
+ if arg == fan.DEVICE_GET_BASIC_SETTINGS_KEY:
|
|
|
+ return firmware_info
|
|
|
+ return None
|
|
|
+
|
|
|
+ standing_fan._get_basic_info = AsyncMock(side_effect=mock_get_basic_info)
|
|
|
+
|
|
|
+ info = await standing_fan.get_basic_info()
|
|
|
+ assert info == result | {"nightLight": 3}
|
|
|
+
|
|
|
+
|
|
|
+@pytest.mark.asyncio
|
|
|
+@pytest.mark.parametrize(
|
|
|
+ ("basic_info", "firmware_info"),
|
|
|
+ [(True, False), (False, True), (False, False)],
|
|
|
+)
|
|
|
+async def test_standing_fan_get_basic_info_returns_none(basic_info, firmware_info):
|
|
|
+ standing_fan = create_standing_fan_for_testing()
|
|
|
+
|
|
|
+ async def mock_get_basic_info(arg):
|
|
|
+ if arg == fan.COMMAND_GET_BASIC_INFO:
|
|
|
+ return basic_info
|
|
|
+ if arg == fan.DEVICE_GET_BASIC_SETTINGS_KEY:
|
|
|
+ return firmware_info
|
|
|
+ return None
|
|
|
+
|
|
|
+ standing_fan._get_basic_info = AsyncMock(side_effect=mock_get_basic_info)
|
|
|
+
|
|
|
+ assert await standing_fan.get_basic_info() is None
|
|
|
+
|
|
|
+
|
|
|
+@pytest.mark.asyncio
|
|
|
+@pytest.mark.parametrize(
|
|
|
+ "angle",
|
|
|
+ [
|
|
|
+ HorizontalOscillationAngle.ANGLE_30,
|
|
|
+ HorizontalOscillationAngle.ANGLE_60,
|
|
|
+ HorizontalOscillationAngle.ANGLE_90,
|
|
|
+ ],
|
|
|
+)
|
|
|
+async def test_standing_fan_set_horizontal_oscillation_angle(angle):
|
|
|
+ standing_fan = create_standing_fan_for_testing()
|
|
|
+ await standing_fan.set_horizontal_oscillation_angle(angle)
|
|
|
+ standing_fan._send_command.assert_called_once()
|
|
|
+ cmd = standing_fan._send_command.call_args[0][0]
|
|
|
+ assert cmd == f"{fan.COMMAND_SET_OSCILLATION_PARAMS}{angle.value:02X}FFFFFF"
|
|
|
+
|
|
|
+
|
|
|
+@pytest.mark.asyncio
|
|
|
+@pytest.mark.parametrize("angle", [30, 60, 90])
|
|
|
+async def test_standing_fan_set_horizontal_oscillation_angle_int(angle):
|
|
|
+ """Raw int inputs are coerced through HorizontalOscillationAngle(angle)."""
|
|
|
+ standing_fan = create_standing_fan_for_testing()
|
|
|
+ await standing_fan.set_horizontal_oscillation_angle(angle)
|
|
|
+ cmd = standing_fan._send_command.call_args[0][0]
|
|
|
+ assert cmd == f"{fan.COMMAND_SET_OSCILLATION_PARAMS}{angle:02X}FFFFFF"
|
|
|
+
|
|
|
+
|
|
|
+@pytest.mark.asyncio
|
|
|
+@pytest.mark.parametrize("angle", [0, 45, 120, -1])
|
|
|
+async def test_standing_fan_set_horizontal_oscillation_angle_invalid(angle):
|
|
|
+ standing_fan = create_standing_fan_for_testing()
|
|
|
+ with pytest.raises(ValueError, match="is not a valid"):
|
|
|
+ await standing_fan.set_horizontal_oscillation_angle(angle)
|
|
|
+ standing_fan._send_command.assert_not_called()
|
|
|
+
|
|
|
+
|
|
|
+@pytest.mark.asyncio
|
|
|
+@pytest.mark.parametrize(
|
|
|
+ "angle",
|
|
|
+ [
|
|
|
+ VerticalOscillationAngle.ANGLE_30,
|
|
|
+ VerticalOscillationAngle.ANGLE_60,
|
|
|
+ # Vertical 90° maps to byte 0x5F (95); byte 0x5A (90) halts the axis.
|
|
|
+ VerticalOscillationAngle.ANGLE_90,
|
|
|
+ ],
|
|
|
+)
|
|
|
+async def test_standing_fan_set_vertical_oscillation_angle(angle):
|
|
|
+ standing_fan = create_standing_fan_for_testing()
|
|
|
+ await standing_fan.set_vertical_oscillation_angle(angle)
|
|
|
+ standing_fan._send_command.assert_called_once()
|
|
|
+ cmd = standing_fan._send_command.call_args[0][0]
|
|
|
+ assert cmd == f"{fan.COMMAND_SET_OSCILLATION_PARAMS}FFFF{angle.value:02X}FF"
|
|
|
+
|
|
|
+
|
|
|
+@pytest.mark.asyncio
|
|
|
+@pytest.mark.parametrize("byte_value", [30, 60, 95])
|
|
|
+async def test_standing_fan_set_vertical_oscillation_angle_int(byte_value):
|
|
|
+ """Raw-int callers pass the device byte value (30 / 60 / 95)."""
|
|
|
+ standing_fan = create_standing_fan_for_testing()
|
|
|
+ await standing_fan.set_vertical_oscillation_angle(byte_value)
|
|
|
+ cmd = standing_fan._send_command.call_args[0][0]
|
|
|
+ assert cmd == f"{fan.COMMAND_SET_OSCILLATION_PARAMS}FFFF{byte_value:02X}FF"
|
|
|
+
|
|
|
+
|
|
|
+@pytest.mark.asyncio
|
|
|
+@pytest.mark.parametrize("angle", [0, 45, 120, -1])
|
|
|
+async def test_standing_fan_set_vertical_oscillation_angle_invalid(angle):
|
|
|
+ standing_fan = create_standing_fan_for_testing()
|
|
|
+ with pytest.raises(ValueError, match="is not a valid"):
|
|
|
+ await standing_fan.set_vertical_oscillation_angle(angle)
|
|
|
+ standing_fan._send_command.assert_not_called()
|
|
|
+
|
|
|
+
|
|
|
+@pytest.mark.asyncio
|
|
|
+@pytest.mark.parametrize(
|
|
|
+ "state",
|
|
|
+ [NightLightState.LEVEL_1, NightLightState.LEVEL_2, NightLightState.OFF],
|
|
|
+)
|
|
|
+async def test_standing_fan_set_night_light(state):
|
|
|
+ standing_fan = create_standing_fan_for_testing()
|
|
|
+ await standing_fan.set_night_light(state)
|
|
|
+ standing_fan._send_command.assert_called_once()
|
|
|
+ cmd = standing_fan._send_command.call_args[0][0]
|
|
|
+ assert cmd == f"{fan.COMMAND_SET_NIGHT_LIGHT}{state.value:02X}FFFF"
|
|
|
+
|
|
|
+
|
|
|
+@pytest.mark.asyncio
|
|
|
+@pytest.mark.parametrize("state", [1, 2, 3])
|
|
|
+async def test_standing_fan_set_night_light_int(state):
|
|
|
+ """Raw int inputs are coerced through NightLightState(state)."""
|
|
|
+ standing_fan = create_standing_fan_for_testing()
|
|
|
+ await standing_fan.set_night_light(state)
|
|
|
+ cmd = standing_fan._send_command.call_args[0][0]
|
|
|
+ assert cmd == f"{fan.COMMAND_SET_NIGHT_LIGHT}{state:02X}FFFF"
|
|
|
+
|
|
|
+
|
|
|
+@pytest.mark.asyncio
|
|
|
+@pytest.mark.parametrize("state", [0, 4, 99, -1])
|
|
|
+async def test_standing_fan_set_night_light_invalid(state):
|
|
|
+ standing_fan = create_standing_fan_for_testing()
|
|
|
+ with pytest.raises(ValueError, match="is not a valid"):
|
|
|
+ await standing_fan.set_night_light(state)
|
|
|
+ standing_fan._send_command.assert_not_called()
|
|
|
+
|
|
|
+
|
|
|
+def test_standing_fan_get_night_light_state():
|
|
|
+ standing_fan = create_standing_fan_for_testing({"nightLight": 1})
|
|
|
+ assert standing_fan.get_night_light_state() == 1
|
|
|
+
|
|
|
+
|
|
|
+@pytest.mark.asyncio
|
|
|
+@pytest.mark.parametrize(
|
|
|
+ ("oscillating", "expected_cmd"),
|
|
|
+ [
|
|
|
+ (True, fan.COMMAND_START_HORIZONTAL_OSCILLATION),
|
|
|
+ (False, fan.COMMAND_STOP_HORIZONTAL_OSCILLATION),
|
|
|
+ ],
|
|
|
+)
|
|
|
+async def test_standing_fan_set_horizontal_oscillation(oscillating, expected_cmd):
|
|
|
+ standing_fan = create_standing_fan_for_testing({"oscillating": oscillating})
|
|
|
+ await standing_fan.set_horizontal_oscillation(oscillating)
|
|
|
+ standing_fan._send_command.assert_called_once()
|
|
|
+ cmd = standing_fan._send_command.call_args[0][0]
|
|
|
+ assert cmd == expected_cmd
|
|
|
+
|
|
|
+
|
|
|
+@pytest.mark.asyncio
|
|
|
+@pytest.mark.parametrize(
|
|
|
+ ("oscillating", "expected_cmd"),
|
|
|
+ [
|
|
|
+ (True, fan.COMMAND_START_VERTICAL_OSCILLATION),
|
|
|
+ (False, fan.COMMAND_STOP_VERTICAL_OSCILLATION),
|
|
|
+ ],
|
|
|
+)
|
|
|
+async def test_standing_fan_set_vertical_oscillation(oscillating, expected_cmd):
|
|
|
+ standing_fan = create_standing_fan_for_testing({"oscillating": oscillating})
|
|
|
+ await standing_fan.set_vertical_oscillation(oscillating)
|
|
|
+ standing_fan._send_command.assert_called_once()
|
|
|
+ cmd = standing_fan._send_command.call_args[0][0]
|
|
|
+ assert cmd == expected_cmd
|
|
|
+
|
|
|
+
|
|
|
+def test_standing_fan_get_horizontal_oscillating_state():
|
|
|
+ standing_fan = create_standing_fan_for_testing({"oscillating_horizontal": True})
|
|
|
+ assert standing_fan.get_horizontal_oscillating_state() is True
|
|
|
+
|
|
|
+
|
|
|
+def test_standing_fan_get_vertical_oscillating_state():
|
|
|
+ standing_fan = create_standing_fan_for_testing({"oscillating_vertical": True})
|
|
|
+ assert standing_fan.get_vertical_oscillating_state() is True
|