"""Thermal zones: parsing, sensor weighting and validation, without Home Assistant.""" import sys import types import unittest from pathlib import Path COMPONENTS = Path(__file__).resolve().parents[1] / 'custom_components' / 'heating_mpc' sys.modules.setdefault('heating_mpc', types.ModuleType('heating_mpc')) sys.modules['heating_mpc'].__path__ = [str(COMPONENTS)] from heating_mpc import zones as z # noqa: E402 def zone(key, sensors, **extra): return {'key': key, 'name': key.title(), 'sensors': sensors, **extra} class ZoneTests(unittest.TestCase): def test_legacy_rooms_become_one_sensor_zones_keyed_by_entity(self): data = z.legacy_zone_data(['sensor.a', 'sensor.b', 'sensor.a'], {'sensor.a': 3, 'sensor.b': 'x'}, {'sensor.a': 'Living'}) zones = z.build_zones((d, None) for d in data) self.assertEqual([(q.key, q.name, q.sensors, q.comfort_weight) for q in zones], [('sensor.a', 'Living', ('sensor.a',), 3.0), ('sensor.b', 'sensor.b', ('sensor.b',), 1.0)]) def test_invalid_values_fall_back_and_bad_zones_are_skipped(self): parsed = z.zone_from_data(zone('bed', ['sensor.b', 'light.x', 'sensor.b'], comfort_weight=99, target_offset='bad', sensor_weights={'sensor.b': -1})) self.assertEqual((parsed.sensors, parsed.comfort_weight, parsed.target_offset, parsed.sensor_weight('sensor.b')), (('sensor.b',), 1.0, 0.0, 1.0)) for bad in ({'key': 'x', 'sensors': []}, {'sensors': ['sensor.a']}, {'key': 'x', 'sensors': 'sensor.a'}): self.assertIsNone(z.zone_from_data(bad)) # The subentry ID stands in for a missing key. self.assertEqual(z.zone_from_data({'sensors': ['sensor.a']}, 'sub1').key, 'sub1') def test_duplicates_shared_sensors_and_neighbours(self): zones = z.build_zones([ (zone('living', ['sensor.l1', 'sensor.l2'], neighbors=['bed', 'ghost', 'living']), 's1'), (zone('bed', ['sensor.b']), 's2'), (zone('living', ['sensor.x']), 's3'), # duplicate key (zone('hall', ['sensor.l2']), 's4'), # sensor already in another zone ]) self.assertEqual([q.key for q in zones], ['living', 'bed']) # Neighbours exist and are symmetric. self.assertEqual([q.neighbors for q in zones], [('bed',), ('living',)]) def test_zone_temperature_weights_and_reports_sensors_used(self): living = z.zone_from_data(zone('living', ['sensor.a', 'sensor.b'], sensor_weights={'sensor.a': 3, 'sensor.b': 1})) self.assertEqual(z.zone_temperature(living, {'sensor.a': 20.0, 'sensor.b': 22.0}), (20.5, frozenset({'sensor.a', 'sensor.b'}))) self.assertEqual(z.zone_temperature(living, {'sensor.a': None, 'sensor.b': 22.0}), (22.0, frozenset({'sensor.b'}))) self.assertEqual(z.zone_temperature(living, {}), (None, frozenset())) def test_form_validation(self): others = [z.zone_from_data(zone('bed', ['sensor.b']))] cases = { 'zone_name_required': {'name': ' ', 'sensors': ['sensor.a']}, 'zone_sensors': {'name': 'Living', 'sensors': []}, 'sensor_in_other_zone': {'name': 'Living', 'sensors': ['sensor.b']}, 'sensor_is_weather_source': {'name': 'Living', 'sensors': ['sensor.out']}, None: {'name': 'Living', 'sensors': ['sensor.a', 'sensor.c']}, } for expected, data in cases.items(): with self.subTest(expected=expected): self.assertEqual(z.validate_zone(data, others, ['sensor.out']), expected) self.assertEqual(z.validate_zone({'name': 'x', 'sensors': [f'sensor.s{i}' for i in range(7)]}, [], []), 'zone_sensors') twelve = [z.zone_from_data(zone(f'z{i}', [f'sensor.z{i}'])) for i in range(12)] self.assertEqual(z.validate_zone({'name': 'x', 'sensors': ['sensor.new']}, twelve, []), 'too_many_zones') if __name__ == '__main__': unittest.main()