"""Every battery key must be classified exactly once. The station reports batteries in two incompatible ways: - 0/1 (low/normal) -> ``BATTERY_LIST`` -> binary sensors - 0-5 level (5 = full) -> ``BATTERY_NON_BINARY`` -> percentage sensors Both entity description sets are generated from those tuples, so a key appearing in both would produce a binary sensor *and* a percentage sensor for the same battery - and the binary one would collapse levels 1-5 into a single "not low", silently discarding the reading. A key appearing in neither simply never gets an entity. The WSLink API has more of both kinds still to implement (`t5lsbat`, `t6c1-7bat` are 0/1; `t8bat`, `t10bat`, `t11bat` are 0-5), so these tests exist to fail the moment a new `*_BATTERY` constant is added without deciding which kind it is. """ from __future__ import annotations from homeassistant.components.binary_sensor import BinarySensorDeviceClass from homeassistant.components.sensor import SensorDeviceClass from homeassistant.const import PERCENTAGE from custom_components.sws12500 import const from custom_components.sws12500.battery_sensors_def import BATTERY_BINARY_SENSORS, BATTERY_LEVEL_SENSORS from custom_components.sws12500.const import BATTERY_LIST, BATTERY_NON_BINARY from custom_components.sws12500.sensors_weather import SENSOR_TYPES_WEATHER_API from custom_components.sws12500.sensors_wslink import SENSOR_TYPES_WSLINK def _all_battery_constants() -> set[str]: """Every `*_BATTERY` value defined in const.py.""" return {value for name, value in vars(const).items() if name.endswith("_BATTERY") and isinstance(value, str)} def test_classifications_are_disjoint() -> None: """A key in both tuples would get two conflicting entities.""" overlap = set(BATTERY_LIST) & set(BATTERY_NON_BINARY) assert not overlap, f"battery keys classified as both binary and 0-5: {sorted(overlap)}" def test_every_battery_constant_is_classified() -> None: """Adding a `*_BATTERY` constant must force a decision about its kind.""" classified = set(BATTERY_LIST) | set(BATTERY_NON_BINARY) unclassified = _all_battery_constants() - classified assert not unclassified, ( f"unclassified battery keys: {sorted(unclassified)} - add each to BATTERY_LIST " "(reported as 0/1) or BATTERY_NON_BINARY (reported as a 0-5 level) in const.py" ) def test_classification_covers_only_real_constants() -> None: """Neither tuple may list a key that is not an actual battery constant.""" stray = (set(BATTERY_LIST) | set(BATTERY_NON_BINARY)) - _all_battery_constants() assert not stray, f"not `*_BATTERY` constants: {sorted(stray)}" # --------------------------------------------------------------------------- # The description sets are generated from the tuples, not hand-maintained # --------------------------------------------------------------------------- def test_binary_descriptions_generated_from_battery_list() -> None: keys = [desc.key for desc in BATTERY_BINARY_SENSORS] assert keys == list(BATTERY_LIST) assert all(desc.device_class is BinarySensorDeviceClass.BATTERY for desc in BATTERY_BINARY_SENSORS) def test_level_descriptions_generated_from_battery_non_binary() -> None: keys = [desc.key for desc in BATTERY_LEVEL_SENSORS] assert keys == list(BATTERY_NON_BINARY) for desc in BATTERY_LEVEL_SENSORS: assert desc.device_class is SensorDeviceClass.BATTERY assert desc.native_unit_of_measurement == PERCENTAGE assert desc.value_fn is not None # 5 is full; the raw level must be rendered as a percentage. assert desc.value_fn("5") == 100 assert desc.value_fn("0") == 0 def test_level_batteries_are_not_also_plain_sensors() -> None: """A 0-5 battery must appear exactly once in each platform's description set.""" for sensor_types in (SENSOR_TYPES_WSLINK, SENSOR_TYPES_WEATHER_API): keys = [desc.key for desc in sensor_types] for key in BATTERY_NON_BINARY: assert keys.count(key) <= 1, f"{key} defined more than once" def test_binary_batteries_are_not_plain_sensors_too() -> None: """0/1 batteries belong to the binary platform only. The legacy plain-sensor variants are deprecated (see `legacy.py`) but still present; they must at least not be duplicated. """ for sensor_types in (SENSOR_TYPES_WSLINK, SENSOR_TYPES_WEATHER_API): keys = [desc.key for desc in sensor_types] for key in BATTERY_LIST: assert keys.count(key) <= 1, f"{key} defined more than once"