mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	Merge branch 'dev' into sensor_memory
This commit is contained in:
		| @@ -203,6 +203,7 @@ async def compile_esphome( | ||||
|         loop = asyncio.get_running_loop() | ||||
|  | ||||
|         def _read_config_and_get_binary(): | ||||
|             CORE.reset()  # Reset CORE state between test runs | ||||
|             CORE.config_path = str(config_path) | ||||
|             config = esphome.config.read_config( | ||||
|                 {"command": "compile", "config": str(config_path)} | ||||
|   | ||||
							
								
								
									
										57
									
								
								tests/integration/fixtures/areas_and_devices.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								tests/integration/fixtures/areas_and_devices.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | ||||
| esphome: | ||||
|   name: areas-devices-test | ||||
|   # Define top-level area | ||||
|   area: | ||||
|     id: living_room_area | ||||
|     name: Living Room | ||||
|   # Define additional areas | ||||
|   areas: | ||||
|     - id: bedroom_area | ||||
|       name: Bedroom | ||||
|     - id: kitchen_area | ||||
|       name: Kitchen | ||||
|   # Define devices with area assignments | ||||
|   devices: | ||||
|     - id: light_controller_device | ||||
|       name: Light Controller | ||||
|       area_id: living_room_area  # Uses top-level area | ||||
|     - id: temp_sensor_device | ||||
|       name: Temperature Sensor | ||||
|       area_id: bedroom_area | ||||
|     - id: motion_detector_device | ||||
|       name: Motion Detector | ||||
|       area_id: living_room_area  # Reuses top-level area | ||||
|     - id: smart_switch_device | ||||
|       name: Smart Switch | ||||
|       area_id: kitchen_area | ||||
|  | ||||
| host: | ||||
| api: | ||||
| logger: | ||||
|  | ||||
| # Sensors assigned to different devices | ||||
| sensor: | ||||
|   - platform: template | ||||
|     name: Light Controller Sensor | ||||
|     device_id: light_controller_device | ||||
|     lambda: return 1.0; | ||||
|     update_interval: 0.1s | ||||
|  | ||||
|   - platform: template | ||||
|     name: Temperature Sensor Reading | ||||
|     device_id: temp_sensor_device | ||||
|     lambda: return 2.0; | ||||
|     update_interval: 0.1s | ||||
|  | ||||
|   - platform: template | ||||
|     name: Motion Detector Status | ||||
|     device_id: motion_detector_device | ||||
|     lambda: return 3.0; | ||||
|     update_interval: 0.1s | ||||
|  | ||||
|   - platform: template | ||||
|     name: Smart Switch Power | ||||
|     device_id: smart_switch_device | ||||
|     lambda: return 4.0; | ||||
|     update_interval: 0.1s | ||||
|  | ||||
| @@ -0,0 +1,154 @@ | ||||
| esphome: | ||||
|   name: duplicate-entities-test | ||||
|   # Define devices to test multi-device duplicate handling | ||||
|   devices: | ||||
|     - id: controller_1 | ||||
|       name: Controller 1 | ||||
|     - id: controller_2 | ||||
|       name: Controller 2 | ||||
|     - id: controller_3 | ||||
|       name: Controller 3 | ||||
|  | ||||
| host: | ||||
| api:  # Port will be automatically injected | ||||
| logger: | ||||
|  | ||||
| # Test that duplicate entity names are allowed on different devices | ||||
|  | ||||
| # Scenario 1: Same sensor name on different devices (allowed) | ||||
| sensor: | ||||
|   - platform: template | ||||
|     name: Temperature | ||||
|     device_id: controller_1 | ||||
|     lambda: return 21.0; | ||||
|     update_interval: 0.1s | ||||
|  | ||||
|   - platform: template | ||||
|     name: Temperature | ||||
|     device_id: controller_2 | ||||
|     lambda: return 22.0; | ||||
|     update_interval: 0.1s | ||||
|  | ||||
|   - platform: template | ||||
|     name: Temperature | ||||
|     device_id: controller_3 | ||||
|     lambda: return 23.0; | ||||
|     update_interval: 0.1s | ||||
|  | ||||
|   # Main device sensor (no device_id) | ||||
|   - platform: template | ||||
|     name: Temperature | ||||
|     lambda: return 20.0; | ||||
|     update_interval: 0.1s | ||||
|  | ||||
|   # Different sensor with unique name | ||||
|   - platform: template | ||||
|     name: Humidity | ||||
|     lambda: return 60.0; | ||||
|     update_interval: 0.1s | ||||
|  | ||||
| # Scenario 2: Same binary sensor name on different devices (allowed) | ||||
| binary_sensor: | ||||
|   - platform: template | ||||
|     name: Status | ||||
|     device_id: controller_1 | ||||
|     lambda: return true; | ||||
|  | ||||
|   - platform: template | ||||
|     name: Status | ||||
|     device_id: controller_2 | ||||
|     lambda: return false; | ||||
|  | ||||
|   - platform: template | ||||
|     name: Status | ||||
|     lambda: return true;  # Main device | ||||
|  | ||||
|   # Different platform can have same name as sensor | ||||
|   - platform: template | ||||
|     name: Temperature | ||||
|     lambda: return true; | ||||
|  | ||||
| # Scenario 3: Same text sensor name on different devices | ||||
| text_sensor: | ||||
|   - platform: template | ||||
|     name: Device Info | ||||
|     device_id: controller_1 | ||||
|     lambda: return {"Controller 1 Active"}; | ||||
|     update_interval: 0.1s | ||||
|  | ||||
|   - platform: template | ||||
|     name: Device Info | ||||
|     device_id: controller_2 | ||||
|     lambda: return {"Controller 2 Active"}; | ||||
|     update_interval: 0.1s | ||||
|  | ||||
|   - platform: template | ||||
|     name: Device Info | ||||
|     lambda: return {"Main Device Active"}; | ||||
|     update_interval: 0.1s | ||||
|  | ||||
| # Scenario 4: Same switch name on different devices | ||||
| switch: | ||||
|   - platform: template | ||||
|     name: Power | ||||
|     device_id: controller_1 | ||||
|     lambda: return false; | ||||
|     turn_on_action: [] | ||||
|     turn_off_action: [] | ||||
|  | ||||
|   - platform: template | ||||
|     name: Power | ||||
|     device_id: controller_2 | ||||
|     lambda: return true; | ||||
|     turn_on_action: [] | ||||
|     turn_off_action: [] | ||||
|  | ||||
|   - platform: template | ||||
|     name: Power | ||||
|     device_id: controller_3 | ||||
|     lambda: return false; | ||||
|     turn_on_action: [] | ||||
|     turn_off_action: [] | ||||
|  | ||||
|   # Unique switch on main device | ||||
|   - platform: template | ||||
|     name: Main Power | ||||
|     lambda: return true; | ||||
|     turn_on_action: [] | ||||
|     turn_off_action: [] | ||||
|  | ||||
| # Scenario 5: Empty names on different devices (should use device name) | ||||
| button: | ||||
|   - platform: template | ||||
|     name: "" | ||||
|     device_id: controller_1 | ||||
|     on_press: [] | ||||
|  | ||||
|   - platform: template | ||||
|     name: "" | ||||
|     device_id: controller_2 | ||||
|     on_press: [] | ||||
|  | ||||
|   - platform: template | ||||
|     name: "" | ||||
|     on_press: []  # Main device | ||||
|  | ||||
| # Scenario 6: Special characters in names | ||||
| number: | ||||
|   - platform: template | ||||
|     name: "Temperature Setpoint!" | ||||
|     device_id: controller_1 | ||||
|     min_value: 10.0 | ||||
|     max_value: 30.0 | ||||
|     step: 0.1 | ||||
|     lambda: return 21.0; | ||||
|     set_action: [] | ||||
|  | ||||
|   - platform: template | ||||
|     name: "Temperature Setpoint!" | ||||
|     device_id: controller_2 | ||||
|     min_value: 10.0 | ||||
|     max_value: 30.0 | ||||
|     step: 0.1 | ||||
|     lambda: return 22.0; | ||||
|     set_action: [] | ||||
							
								
								
									
										15
									
								
								tests/integration/fixtures/legacy_area.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								tests/integration/fixtures/legacy_area.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| esphome: | ||||
|   name: legacy-area-test | ||||
|   # Using legacy string-based area configuration | ||||
|   area: Master Bedroom | ||||
|  | ||||
| host: | ||||
| api: | ||||
| logger: | ||||
|  | ||||
| # Simple sensor to ensure the device compiles and runs | ||||
| sensor: | ||||
|   - platform: template | ||||
|     name: Test Sensor | ||||
|     lambda: return 42.0; | ||||
|     update_interval: 1s | ||||
							
								
								
									
										121
									
								
								tests/integration/test_areas_and_devices.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								tests/integration/test_areas_and_devices.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,121 @@ | ||||
| """Integration test for areas and devices feature.""" | ||||
|  | ||||
| from __future__ import annotations | ||||
|  | ||||
| import asyncio | ||||
|  | ||||
| from aioesphomeapi import EntityState | ||||
| import pytest | ||||
|  | ||||
| from .types import APIClientConnectedFactory, RunCompiledFunction | ||||
|  | ||||
|  | ||||
| @pytest.mark.asyncio | ||||
| async def test_areas_and_devices( | ||||
|     yaml_config: str, | ||||
|     run_compiled: RunCompiledFunction, | ||||
|     api_client_connected: APIClientConnectedFactory, | ||||
| ) -> None: | ||||
|     """Test areas and devices configuration with entity mapping.""" | ||||
|     async with run_compiled(yaml_config), api_client_connected() as client: | ||||
|         # Get device info which includes areas and devices | ||||
|         device_info = await client.device_info() | ||||
|         assert device_info is not None | ||||
|  | ||||
|         # Verify areas are reported | ||||
|         areas = device_info.areas | ||||
|         assert len(areas) >= 2, f"Expected at least 2 areas, got {len(areas)}" | ||||
|  | ||||
|         # Find our specific areas | ||||
|         main_area = next((a for a in areas if a.name == "Living Room"), None) | ||||
|         bedroom_area = next((a for a in areas if a.name == "Bedroom"), None) | ||||
|         kitchen_area = next((a for a in areas if a.name == "Kitchen"), None) | ||||
|  | ||||
|         assert main_area is not None, "Living Room area not found" | ||||
|         assert bedroom_area is not None, "Bedroom area not found" | ||||
|         assert kitchen_area is not None, "Kitchen area not found" | ||||
|  | ||||
|         # Verify devices are reported | ||||
|         devices = device_info.devices | ||||
|         assert len(devices) >= 4, f"Expected at least 4 devices, got {len(devices)}" | ||||
|  | ||||
|         # Find our specific devices | ||||
|         light_controller = next( | ||||
|             (d for d in devices if d.name == "Light Controller"), None | ||||
|         ) | ||||
|         temp_sensor = next((d for d in devices if d.name == "Temperature Sensor"), None) | ||||
|         motion_detector = next( | ||||
|             (d for d in devices if d.name == "Motion Detector"), None | ||||
|         ) | ||||
|         smart_switch = next((d for d in devices if d.name == "Smart Switch"), None) | ||||
|  | ||||
|         assert light_controller is not None, "Light Controller device not found" | ||||
|         assert temp_sensor is not None, "Temperature Sensor device not found" | ||||
|         assert motion_detector is not None, "Motion Detector device not found" | ||||
|         assert smart_switch is not None, "Smart Switch device not found" | ||||
|  | ||||
|         # Verify device area assignments | ||||
|         assert light_controller.area_id == main_area.area_id, ( | ||||
|             "Light Controller should be in Living Room" | ||||
|         ) | ||||
|         assert temp_sensor.area_id == bedroom_area.area_id, ( | ||||
|             "Temperature Sensor should be in Bedroom" | ||||
|         ) | ||||
|         assert motion_detector.area_id == main_area.area_id, ( | ||||
|             "Motion Detector should be in Living Room" | ||||
|         ) | ||||
|         assert smart_switch.area_id == kitchen_area.area_id, ( | ||||
|             "Smart Switch should be in Kitchen" | ||||
|         ) | ||||
|  | ||||
|         # Verify suggested_area is set to the top-level area name | ||||
|         assert device_info.suggested_area == "Living Room", ( | ||||
|             f"Expected suggested_area to be 'Living Room', got '{device_info.suggested_area}'" | ||||
|         ) | ||||
|  | ||||
|         # Get entity list to verify device_id mapping | ||||
|         entities = await client.list_entities_services() | ||||
|  | ||||
|         # Collect sensor entities | ||||
|         sensor_entities = [e for e in entities[0] if hasattr(e, "device_id")] | ||||
|         assert len(sensor_entities) >= 4, ( | ||||
|             f"Expected at least 4 sensor entities, got {len(sensor_entities)}" | ||||
|         ) | ||||
|  | ||||
|         # Subscribe to states to get sensor values | ||||
|         loop = asyncio.get_running_loop() | ||||
|         states: dict[int, EntityState] = {} | ||||
|         states_future: asyncio.Future[bool] = loop.create_future() | ||||
|  | ||||
|         def on_state(state: EntityState) -> None: | ||||
|             states[state.key] = state | ||||
|             # Check if we have all expected sensor states | ||||
|             if len(states) >= 4 and not states_future.done(): | ||||
|                 states_future.set_result(True) | ||||
|  | ||||
|         client.subscribe_states(on_state) | ||||
|  | ||||
|         # Wait for sensor states | ||||
|         try: | ||||
|             await asyncio.wait_for(states_future, timeout=10.0) | ||||
|         except asyncio.TimeoutError: | ||||
|             pytest.fail( | ||||
|                 f"Did not receive all sensor states within 10 seconds. " | ||||
|                 f"Received {len(states)} states" | ||||
|             ) | ||||
|  | ||||
|         # Verify we have sensor entities with proper device_id assignments | ||||
|         device_id_mapping = { | ||||
|             "Light Controller Sensor": light_controller.device_id, | ||||
|             "Temperature Sensor Reading": temp_sensor.device_id, | ||||
|             "Motion Detector Status": motion_detector.device_id, | ||||
|             "Smart Switch Power": smart_switch.device_id, | ||||
|         } | ||||
|  | ||||
|         for entity in sensor_entities: | ||||
|             if entity.name in device_id_mapping: | ||||
|                 expected_device_id = device_id_mapping[entity.name] | ||||
|                 assert entity.device_id == expected_device_id, ( | ||||
|                     f"{entity.name} has device_id {entity.device_id}, " | ||||
|                     f"expected {expected_device_id}" | ||||
|                 ) | ||||
							
								
								
									
										184
									
								
								tests/integration/test_duplicate_entities.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										184
									
								
								tests/integration/test_duplicate_entities.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,184 @@ | ||||
| """Integration test for duplicate entity handling with new validation.""" | ||||
|  | ||||
| from __future__ import annotations | ||||
|  | ||||
| import asyncio | ||||
|  | ||||
| from aioesphomeapi import EntityInfo | ||||
| import pytest | ||||
|  | ||||
| from .types import APIClientConnectedFactory, RunCompiledFunction | ||||
|  | ||||
|  | ||||
| @pytest.mark.asyncio | ||||
| async def test_duplicate_entities_on_different_devices( | ||||
|     yaml_config: str, | ||||
|     run_compiled: RunCompiledFunction, | ||||
|     api_client_connected: APIClientConnectedFactory, | ||||
| ) -> None: | ||||
|     """Test that duplicate entity names are allowed on different devices.""" | ||||
|     async with run_compiled(yaml_config), api_client_connected() as client: | ||||
|         # Get device info | ||||
|         device_info = await client.device_info() | ||||
|         assert device_info is not None | ||||
|  | ||||
|         # Get devices | ||||
|         devices = device_info.devices | ||||
|         assert len(devices) >= 3, f"Expected at least 3 devices, got {len(devices)}" | ||||
|  | ||||
|         # Find our test devices | ||||
|         controller_1 = next((d for d in devices if d.name == "Controller 1"), None) | ||||
|         controller_2 = next((d for d in devices if d.name == "Controller 2"), None) | ||||
|         controller_3 = next((d for d in devices if d.name == "Controller 3"), None) | ||||
|  | ||||
|         assert controller_1 is not None, "Controller 1 device not found" | ||||
|         assert controller_2 is not None, "Controller 2 device not found" | ||||
|         assert controller_3 is not None, "Controller 3 device not found" | ||||
|  | ||||
|         # Get entity list | ||||
|         entities = await client.list_entities_services() | ||||
|         all_entities: list[EntityInfo] = [] | ||||
|         for entity_list in entities[0]: | ||||
|             all_entities.append(entity_list) | ||||
|  | ||||
|         # Group entities by type for easier testing | ||||
|         sensors = [e for e in all_entities if e.__class__.__name__ == "SensorInfo"] | ||||
|         binary_sensors = [ | ||||
|             e for e in all_entities if e.__class__.__name__ == "BinarySensorInfo" | ||||
|         ] | ||||
|         text_sensors = [ | ||||
|             e for e in all_entities if e.__class__.__name__ == "TextSensorInfo" | ||||
|         ] | ||||
|         switches = [e for e in all_entities if e.__class__.__name__ == "SwitchInfo"] | ||||
|         buttons = [e for e in all_entities if e.__class__.__name__ == "ButtonInfo"] | ||||
|         numbers = [e for e in all_entities if e.__class__.__name__ == "NumberInfo"] | ||||
|  | ||||
|         # Scenario 1: Check sensors with same "Temperature" name on different devices | ||||
|         temp_sensors = [s for s in sensors if s.name == "Temperature"] | ||||
|         assert len(temp_sensors) == 4, ( | ||||
|             f"Expected exactly 4 temperature sensors, got {len(temp_sensors)}" | ||||
|         ) | ||||
|  | ||||
|         # Verify each sensor is on a different device | ||||
|         temp_device_ids = set() | ||||
|         temp_object_ids = set() | ||||
|  | ||||
|         for sensor in temp_sensors: | ||||
|             temp_device_ids.add(sensor.device_id) | ||||
|             temp_object_ids.add(sensor.object_id) | ||||
|  | ||||
|             # All should have object_id "temperature" (no suffix) | ||||
|             assert sensor.object_id == "temperature", ( | ||||
|                 f"Expected object_id 'temperature', got '{sensor.object_id}'" | ||||
|             ) | ||||
|  | ||||
|         # Should have 4 different device IDs (including None for main device) | ||||
|         assert len(temp_device_ids) == 4, ( | ||||
|             f"Temperature sensors should be on different devices, got {temp_device_ids}" | ||||
|         ) | ||||
|  | ||||
|         # Scenario 2: Check binary sensors "Status" on different devices | ||||
|         status_binary = [b for b in binary_sensors if b.name == "Status"] | ||||
|         assert len(status_binary) == 3, ( | ||||
|             f"Expected exactly 3 status binary sensors, got {len(status_binary)}" | ||||
|         ) | ||||
|  | ||||
|         # All should have object_id "status" | ||||
|         for binary in status_binary: | ||||
|             assert binary.object_id == "status", ( | ||||
|                 f"Expected object_id 'status', got '{binary.object_id}'" | ||||
|             ) | ||||
|  | ||||
|         # Scenario 3: Check that sensor and binary_sensor can have same name | ||||
|         temp_binary = [b for b in binary_sensors if b.name == "Temperature"] | ||||
|         assert len(temp_binary) == 1, ( | ||||
|             f"Expected exactly 1 temperature binary sensor, got {len(temp_binary)}" | ||||
|         ) | ||||
|         assert temp_binary[0].object_id == "temperature" | ||||
|  | ||||
|         # Scenario 4: Check text sensors "Device Info" on different devices | ||||
|         info_text = [t for t in text_sensors if t.name == "Device Info"] | ||||
|         assert len(info_text) == 3, ( | ||||
|             f"Expected exactly 3 device info text sensors, got {len(info_text)}" | ||||
|         ) | ||||
|  | ||||
|         # All should have object_id "device_info" | ||||
|         for text in info_text: | ||||
|             assert text.object_id == "device_info", ( | ||||
|                 f"Expected object_id 'device_info', got '{text.object_id}'" | ||||
|             ) | ||||
|  | ||||
|         # Scenario 5: Check switches "Power" on different devices | ||||
|         power_switches = [s for s in switches if s.name == "Power"] | ||||
|         assert len(power_switches) == 3, ( | ||||
|             f"Expected exactly 3 power switches, got {len(power_switches)}" | ||||
|         ) | ||||
|  | ||||
|         # All should have object_id "power" | ||||
|         for switch in power_switches: | ||||
|             assert switch.object_id == "power", ( | ||||
|                 f"Expected object_id 'power', got '{switch.object_id}'" | ||||
|             ) | ||||
|  | ||||
|         # Scenario 6: Check empty name buttons (should use device name) | ||||
|         empty_buttons = [b for b in buttons if b.name == ""] | ||||
|         assert len(empty_buttons) == 3, ( | ||||
|             f"Expected exactly 3 empty name buttons, got {len(empty_buttons)}" | ||||
|         ) | ||||
|  | ||||
|         # Group by device | ||||
|         c1_buttons = [b for b in empty_buttons if b.device_id == controller_1.device_id] | ||||
|         c2_buttons = [b for b in empty_buttons if b.device_id == controller_2.device_id] | ||||
|  | ||||
|         # For main device, device_id is 0 | ||||
|         main_buttons = [b for b in empty_buttons if b.device_id == 0] | ||||
|  | ||||
|         # Check object IDs for empty name entities | ||||
|         assert len(c1_buttons) == 1 and c1_buttons[0].object_id == "controller_1" | ||||
|         assert len(c2_buttons) == 1 and c2_buttons[0].object_id == "controller_2" | ||||
|         assert ( | ||||
|             len(main_buttons) == 1 | ||||
|             and main_buttons[0].object_id == "duplicate-entities-test" | ||||
|         ) | ||||
|  | ||||
|         # Scenario 7: Check special characters in number names | ||||
|         temp_numbers = [n for n in numbers if n.name == "Temperature Setpoint!"] | ||||
|         assert len(temp_numbers) == 2, ( | ||||
|             f"Expected exactly 2 temperature setpoint numbers, got {len(temp_numbers)}" | ||||
|         ) | ||||
|  | ||||
|         # Special characters should be sanitized to _ in object_id | ||||
|         for number in temp_numbers: | ||||
|             assert number.object_id == "temperature_setpoint_", ( | ||||
|                 f"Expected object_id 'temperature_setpoint_', got '{number.object_id}'" | ||||
|             ) | ||||
|  | ||||
|         # Verify we can get states for all entities (ensures they're functional) | ||||
|         loop = asyncio.get_running_loop() | ||||
|         states_future: asyncio.Future[None] = loop.create_future() | ||||
|         state_count = 0 | ||||
|         expected_count = ( | ||||
|             len(sensors) | ||||
|             + len(binary_sensors) | ||||
|             + len(text_sensors) | ||||
|             + len(switches) | ||||
|             + len(buttons) | ||||
|             + len(numbers) | ||||
|         ) | ||||
|  | ||||
|         def on_state(state) -> None: | ||||
|             nonlocal state_count | ||||
|             state_count += 1 | ||||
|             if state_count >= expected_count and not states_future.done(): | ||||
|                 states_future.set_result(None) | ||||
|  | ||||
|         client.subscribe_states(on_state) | ||||
|  | ||||
|         # Wait for all entity states | ||||
|         try: | ||||
|             await asyncio.wait_for(states_future, timeout=10.0) | ||||
|         except asyncio.TimeoutError: | ||||
|             pytest.fail( | ||||
|                 f"Did not receive all entity states within 10 seconds. " | ||||
|                 f"Expected {expected_count}, received {state_count}" | ||||
|             ) | ||||
							
								
								
									
										41
									
								
								tests/integration/test_legacy_area.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								tests/integration/test_legacy_area.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | ||||
| """Integration test for legacy string-based area configuration.""" | ||||
|  | ||||
| from __future__ import annotations | ||||
|  | ||||
| import pytest | ||||
|  | ||||
| from .types import APIClientConnectedFactory, RunCompiledFunction | ||||
|  | ||||
|  | ||||
| @pytest.mark.asyncio | ||||
| async def test_legacy_area( | ||||
|     yaml_config: str, | ||||
|     run_compiled: RunCompiledFunction, | ||||
|     api_client_connected: APIClientConnectedFactory, | ||||
| ) -> None: | ||||
|     """Test legacy string-based area configuration.""" | ||||
|     async with run_compiled(yaml_config), api_client_connected() as client: | ||||
|         # Get device info which includes areas | ||||
|         device_info = await client.device_info() | ||||
|         assert device_info is not None | ||||
|  | ||||
|         # Verify the area is reported (should be converted to structured format) | ||||
|         areas = device_info.areas | ||||
|         assert len(areas) == 1, f"Expected exactly 1 area, got {len(areas)}" | ||||
|  | ||||
|         # Find the area - should be slugified from "Master Bedroom" | ||||
|         area = areas[0] | ||||
|         assert area.name == "Master Bedroom", ( | ||||
|             f"Expected area name 'Master Bedroom', got '{area.name}'" | ||||
|         ) | ||||
|  | ||||
|         # Verify area.id is set (it should be a hash) | ||||
|         assert area.area_id > 0, "Area ID should be a positive hash value" | ||||
|  | ||||
|         # The suggested_area field should be set for backward compatibility | ||||
|         assert device_info.suggested_area == "Master Bedroom", ( | ||||
|             f"Expected suggested_area to be 'Master Bedroom', got '{device_info.suggested_area}'" | ||||
|         ) | ||||
|  | ||||
|         # Verify deprecated warning would have been logged during compilation | ||||
|         # (We can't check logs directly in integration tests, but the code should work) | ||||
		Reference in New Issue
	
	Block a user