| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202 |
- """
- Regression tests: parsers must not raise on short payloads.
- The dispatcher in `switchbot/adv_parser.py` does not pre-validate
- `mfr_data` / `data` length before invoking parsers. A malformed BLE
- advertisement (untrusted but range-limited input) should degrade to
- an empty dict / None values, not raise an `IndexError` / `ValueError`
- that bubbles up to callers.
- """
- from __future__ import annotations
- import pytest
- from switchbot.adv_parsers.air_purifier import process_air_purifier
- from switchbot.adv_parsers.art_frame import process_art_frame
- from switchbot.adv_parsers.blind_tilt import process_woblindtilt
- from switchbot.adv_parsers.bot import process_wohand
- from switchbot.adv_parsers.bulb import process_color_bulb
- from switchbot.adv_parsers.ceiling_light import process_woceiling
- from switchbot.adv_parsers.climate_panel import process_climate_panel
- from switchbot.adv_parsers.curtain import process_wocurtain
- from switchbot.adv_parsers.fan import process_fan, process_standing_fan
- from switchbot.adv_parsers.hub2 import process_wohub2
- from switchbot.adv_parsers.hub3 import process_hub3
- from switchbot.adv_parsers.hubmini_matter import process_hubmini_matter
- from switchbot.adv_parsers.humidifier import (
- process_evaporative_humidifier,
- process_wohumidifier,
- )
- from switchbot.adv_parsers.keypad import process_wokeypad
- from switchbot.adv_parsers.keypad_vision import (
- process_keypad_vision,
- process_keypad_vision_pro,
- )
- from switchbot.adv_parsers.light_strip import (
- process_candle_warmer_lamp,
- process_light,
- process_rgbic_light,
- process_wostrip,
- )
- from switchbot.adv_parsers.lock import (
- parse_common_data,
- process_lock2,
- process_locklite,
- process_wolock,
- process_wolock_pro,
- )
- from switchbot.adv_parsers.meter import process_wosensorth
- from switchbot.adv_parsers.motion import process_wopresence
- from switchbot.adv_parsers.plug import process_woplugmini
- from switchbot.adv_parsers.remote import process_woremote
- from switchbot.adv_parsers.roller_shade import process_worollershade
- from switchbot.adv_parsers.smart_thermostat_radiator import (
- process_smart_thermostat_radiator,
- )
- from switchbot.adv_parsers.vacuum import process_vacuum, process_vacuum_k
- EMPTY = b""
- SHORT = b"\x00" * 4
- @pytest.mark.parametrize(
- "parser",
- [
- process_air_purifier,
- process_art_frame,
- process_woblindtilt,
- process_color_bulb,
- process_woceiling,
- process_climate_panel,
- process_wocurtain,
- process_fan,
- process_standing_fan,
- process_wohub2,
- process_hub3,
- process_hubmini_matter,
- process_evaporative_humidifier,
- process_keypad_vision,
- process_keypad_vision_pro,
- process_wostrip,
- process_candle_warmer_lamp,
- process_woplugmini,
- process_worollershade,
- process_smart_thermostat_radiator,
- process_vacuum,
- process_vacuum_k,
- ],
- )
- @pytest.mark.parametrize("payload", [None, EMPTY, SHORT])
- def test_mfr_only_parsers_return_empty_on_short(parser, payload):
- """Parsers that read only mfr_data must return {} for short payloads."""
- assert parser(None, payload) == {}
- @pytest.mark.parametrize("payload", [None, EMPTY, SHORT, b"\x00" * 17])
- def test_process_light_short_payload(payload):
- """process_light needs cw_offset + 2 bytes (default 18)."""
- assert process_light(None, payload) == {}
- @pytest.mark.parametrize("payload", [None, EMPTY, SHORT, b"\x00" * 11])
- def test_process_rgbic_light_short_payload(payload):
- """process_rgbic_light uses cw_offset=10, so needs >= 12 bytes."""
- assert process_rgbic_light(None, payload) == {}
- @pytest.mark.parametrize(
- ("data", "mfr_data"),
- [
- (None, None),
- (EMPTY, None),
- (b"\x00\x00", None),
- ],
- )
- def test_process_wohand_short_data(data, mfr_data):
- """process_wohand must not crash on short `data`."""
- out = process_wohand(data, mfr_data)
- # Either an empty dict (both None) or all-None values
- assert "isOn" in out or out == {}
- assert out.get("battery") in (None, *out.values())
- @pytest.mark.parametrize("data", [None, EMPTY, b"\x00\x00"])
- def test_process_woremote_short_data(data):
- out = process_woremote(data, None)
- assert out == {"battery": None}
- @pytest.mark.parametrize(
- ("data", "mfr_data"),
- [
- (None, None),
- (b"\x00", None),
- (None, b"\x00"),
- (b"\x00\x00", b"\x00\x00"),
- ],
- )
- def test_process_wokeypad_short(data, mfr_data):
- out = process_wokeypad(data, mfr_data)
- assert out == {"battery": None, "attempt_state": None}
- @pytest.mark.parametrize("data", [None, EMPTY, b"\x00\x00\x00"])
- def test_process_wohumidifier_short_data(data):
- out = process_wohumidifier(data, None)
- assert out == {"isOn": None, "level": None, "switchMode": True}
- @pytest.mark.parametrize("data", [None, EMPTY, b"\x00\x00\x00"])
- @pytest.mark.parametrize("mfr_data", [None, EMPTY, b"\x00\x00\x00"])
- def test_process_wosensorth_short(data, mfr_data):
- """process_wosensorth must not crash; returns {} when no usable payload."""
- out = process_wosensorth(data, mfr_data)
- assert isinstance(out, dict)
- @pytest.mark.parametrize("data", [None, EMPTY, b"\x00\x00\x00"])
- @pytest.mark.parametrize("mfr_data", [None, EMPTY, b"\x00\x00\x00"])
- def test_process_wopresence_short(data, mfr_data):
- """process_wopresence must not crash even when both inputs are short."""
- out = process_wopresence(data, mfr_data)
- assert isinstance(out, dict)
- @pytest.mark.parametrize("mfr_data", [None, EMPTY, SHORT])
- def test_lock_parsers_short_mfr(mfr_data):
- assert process_locklite(None, mfr_data) == {}
- assert process_wolock(None, mfr_data) == {}
- assert parse_common_data(mfr_data) == {}
- assert process_wolock_pro(None, mfr_data) == {}
- assert process_lock2(None, mfr_data) == {}
- @pytest.mark.parametrize("data", [None, EMPTY, b"\x00\x00"])
- def test_blind_tilt_short_data(data):
- """blind_tilt with full mfr but short data must not crash on data[2]."""
- mfr = b"\x00" * 10
- out = process_woblindtilt(data, mfr)
- assert out["battery"] is None
- @pytest.mark.parametrize("data", [None, EMPTY, b"\x00\x00"])
- def test_curtain_short_data_with_long_mfr(data):
- """Curtain >=11 path uses data[2] for battery; must not crash on short data."""
- mfr = b"\x00" * 11
- out = process_wocurtain(data, mfr)
- assert out["battery"] is None
- @pytest.mark.parametrize("data", [b"\x00\x00\x00\x00", b"\x00" * 5])
- def test_curtain_short_data_only(data):
- """Curtain data-only path needs len >= 6; shorter -> {}."""
- assert process_wocurtain(data, None) == {}
- @pytest.mark.parametrize("data", [None, EMPTY, b"\x00\x00"])
- def test_roller_shade_short_data(data):
- """roller_shade with short data must not crash on data[2]."""
- mfr = b"\x00" * 10
- out = process_worollershade(data, mfr)
- assert out["battery"] is None
|