mirror of
https://github.com/esphome/esphome.git
synced 2025-09-11 15:52:20 +01:00
fixes
This commit is contained in:
@@ -1,211 +0,0 @@
|
|||||||
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
|
|
||||||
|
|
||||||
host:
|
|
||||||
api: # Port will be automatically injected
|
|
||||||
logger:
|
|
||||||
|
|
||||||
# Create duplicate entities across different scenarios
|
|
||||||
|
|
||||||
# Scenario 1: Multiple sensors with same name on same device (should get _2, _3, _4)
|
|
||||||
sensor:
|
|
||||||
- platform: template
|
|
||||||
name: Temperature
|
|
||||||
lambda: return 1.0;
|
|
||||||
update_interval: 0.1s
|
|
||||||
|
|
||||||
- platform: template
|
|
||||||
name: Temperature
|
|
||||||
lambda: return 2.0;
|
|
||||||
update_interval: 0.1s
|
|
||||||
|
|
||||||
- platform: template
|
|
||||||
name: Temperature
|
|
||||||
lambda: return 3.0;
|
|
||||||
update_interval: 0.1s
|
|
||||||
|
|
||||||
- platform: template
|
|
||||||
name: Temperature
|
|
||||||
lambda: return 4.0;
|
|
||||||
update_interval: 0.1s
|
|
||||||
|
|
||||||
# Scenario 2: Device-specific duplicates using device_id configuration
|
|
||||||
- platform: template
|
|
||||||
name: Device Temperature
|
|
||||||
device_id: controller_1
|
|
||||||
lambda: return 10.0;
|
|
||||||
update_interval: 0.1s
|
|
||||||
|
|
||||||
- platform: template
|
|
||||||
name: Device Temperature
|
|
||||||
device_id: controller_1
|
|
||||||
lambda: return 11.0;
|
|
||||||
update_interval: 0.1s
|
|
||||||
|
|
||||||
- platform: template
|
|
||||||
name: Device Temperature
|
|
||||||
device_id: controller_1
|
|
||||||
lambda: return 12.0;
|
|
||||||
update_interval: 0.1s
|
|
||||||
|
|
||||||
# Different device, same name - should not conflict
|
|
||||||
- platform: template
|
|
||||||
name: Device Temperature
|
|
||||||
device_id: controller_2
|
|
||||||
lambda: return 20.0;
|
|
||||||
update_interval: 0.1s
|
|
||||||
|
|
||||||
# Scenario 3: Binary sensors (different platform, same name)
|
|
||||||
binary_sensor:
|
|
||||||
- platform: template
|
|
||||||
name: Temperature
|
|
||||||
lambda: return true;
|
|
||||||
|
|
||||||
- platform: template
|
|
||||||
name: Temperature
|
|
||||||
lambda: return false;
|
|
||||||
|
|
||||||
- platform: template
|
|
||||||
name: Temperature
|
|
||||||
lambda: return true;
|
|
||||||
|
|
||||||
# Scenario 5: Binary sensors on devices
|
|
||||||
- platform: template
|
|
||||||
name: Device Temperature
|
|
||||||
device_id: controller_1
|
|
||||||
lambda: return true;
|
|
||||||
|
|
||||||
- platform: template
|
|
||||||
name: Device Temperature
|
|
||||||
device_id: controller_2
|
|
||||||
lambda: return false;
|
|
||||||
|
|
||||||
# Issue #6953: Empty names on binary sensors
|
|
||||||
- platform: template
|
|
||||||
name: ""
|
|
||||||
lambda: return true;
|
|
||||||
- platform: template
|
|
||||||
name: ""
|
|
||||||
lambda: return false;
|
|
||||||
|
|
||||||
- platform: template
|
|
||||||
name: ""
|
|
||||||
lambda: return true;
|
|
||||||
|
|
||||||
- platform: template
|
|
||||||
name: ""
|
|
||||||
lambda: return false;
|
|
||||||
|
|
||||||
# Scenario 6: Test with special characters that need sanitization
|
|
||||||
text_sensor:
|
|
||||||
- platform: template
|
|
||||||
name: "Status Message!"
|
|
||||||
lambda: return {"status1"};
|
|
||||||
update_interval: 0.1s
|
|
||||||
|
|
||||||
- platform: template
|
|
||||||
name: "Status Message!"
|
|
||||||
lambda: return {"status2"};
|
|
||||||
update_interval: 0.1s
|
|
||||||
|
|
||||||
- platform: template
|
|
||||||
name: "Status Message!"
|
|
||||||
lambda: return {"status3"};
|
|
||||||
update_interval: 0.1s
|
|
||||||
|
|
||||||
# Scenario 7: More switch duplicates
|
|
||||||
switch:
|
|
||||||
- platform: template
|
|
||||||
name: "Power Switch"
|
|
||||||
lambda: return false;
|
|
||||||
turn_on_action: []
|
|
||||||
turn_off_action: []
|
|
||||||
|
|
||||||
- platform: template
|
|
||||||
name: "Power Switch"
|
|
||||||
lambda: return true;
|
|
||||||
turn_on_action: []
|
|
||||||
turn_off_action: []
|
|
||||||
|
|
||||||
# Scenario 8: Issue #6953 - Multiple entities with empty names
|
|
||||||
# Empty names on main device - should use device name with suffixes
|
|
||||||
- platform: template
|
|
||||||
name: ""
|
|
||||||
lambda: return false;
|
|
||||||
turn_on_action: []
|
|
||||||
turn_off_action: []
|
|
||||||
|
|
||||||
- platform: template
|
|
||||||
name: ""
|
|
||||||
lambda: return true;
|
|
||||||
turn_on_action: []
|
|
||||||
turn_off_action: []
|
|
||||||
|
|
||||||
- platform: template
|
|
||||||
name: ""
|
|
||||||
lambda: return false;
|
|
||||||
turn_on_action: []
|
|
||||||
turn_off_action: []
|
|
||||||
|
|
||||||
# Scenario 9: Issue #6953 - Empty names on sub-devices
|
|
||||||
# Empty names on sub-device - should use sub-device name with suffixes
|
|
||||||
- platform: template
|
|
||||||
name: ""
|
|
||||||
device_id: controller_1
|
|
||||||
lambda: return false;
|
|
||||||
turn_on_action: []
|
|
||||||
turn_off_action: []
|
|
||||||
|
|
||||||
- platform: template
|
|
||||||
name: ""
|
|
||||||
device_id: controller_1
|
|
||||||
lambda: return true;
|
|
||||||
turn_on_action: []
|
|
||||||
turn_off_action: []
|
|
||||||
|
|
||||||
- platform: template
|
|
||||||
name: ""
|
|
||||||
device_id: controller_1
|
|
||||||
lambda: return false;
|
|
||||||
turn_on_action: []
|
|
||||||
turn_off_action: []
|
|
||||||
|
|
||||||
# Empty names on different sub-device
|
|
||||||
- platform: template
|
|
||||||
name: ""
|
|
||||||
device_id: controller_2
|
|
||||||
lambda: return false;
|
|
||||||
turn_on_action: []
|
|
||||||
turn_off_action: []
|
|
||||||
|
|
||||||
- platform: template
|
|
||||||
name: ""
|
|
||||||
device_id: controller_2
|
|
||||||
lambda: return true;
|
|
||||||
turn_on_action: []
|
|
||||||
turn_off_action: []
|
|
||||||
|
|
||||||
# Scenario 10: Issue #6953 - Duplicate "xyz" names
|
|
||||||
- platform: template
|
|
||||||
name: "xyz"
|
|
||||||
lambda: return false;
|
|
||||||
turn_on_action: []
|
|
||||||
turn_off_action: []
|
|
||||||
|
|
||||||
- platform: template
|
|
||||||
name: "xyz"
|
|
||||||
lambda: return true;
|
|
||||||
turn_on_action: []
|
|
||||||
turn_off_action: []
|
|
||||||
|
|
||||||
- platform: template
|
|
||||||
name: "xyz"
|
|
||||||
lambda: return false;
|
|
||||||
turn_on_action: []
|
|
||||||
turn_off_action: []
|
|
@@ -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: []
|
@@ -1,4 +1,4 @@
|
|||||||
"""Integration test for duplicate entity handling."""
|
"""Integration test for duplicate entity handling with new validation."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
@@ -11,12 +11,12 @@ from .types import APIClientConnectedFactory, RunCompiledFunction
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_duplicate_entities(
|
async def test_duplicate_entities_on_different_devices(
|
||||||
yaml_config: str,
|
yaml_config: str,
|
||||||
run_compiled: RunCompiledFunction,
|
run_compiled: RunCompiledFunction,
|
||||||
api_client_connected: APIClientConnectedFactory,
|
api_client_connected: APIClientConnectedFactory,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test that duplicate entity names are automatically suffixed with _2, _3, _4."""
|
"""Test that duplicate entity names are allowed on different devices."""
|
||||||
async with run_compiled(yaml_config), api_client_connected() as client:
|
async with run_compiled(yaml_config), api_client_connected() as client:
|
||||||
# Get device info
|
# Get device info
|
||||||
device_info = await client.device_info()
|
device_info = await client.device_info()
|
||||||
@@ -24,14 +24,16 @@ async def test_duplicate_entities(
|
|||||||
|
|
||||||
# Get devices
|
# Get devices
|
||||||
devices = device_info.devices
|
devices = device_info.devices
|
||||||
assert len(devices) >= 2, f"Expected at least 2 devices, got {len(devices)}"
|
assert len(devices) >= 3, f"Expected at least 3 devices, got {len(devices)}"
|
||||||
|
|
||||||
# Find our test devices
|
# Find our test devices
|
||||||
controller_1 = next((d for d in devices if d.name == "Controller 1"), None)
|
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_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_1 is not None, "Controller 1 device not found"
|
||||||
assert controller_2 is not None, "Controller 2 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
|
# Get entity list
|
||||||
entities = await client.list_entities_services()
|
entities = await client.list_entities_services()
|
||||||
@@ -48,203 +50,129 @@ async def test_duplicate_entities(
|
|||||||
e for e in all_entities if e.__class__.__name__ == "TextSensorInfo"
|
e for e in all_entities if e.__class__.__name__ == "TextSensorInfo"
|
||||||
]
|
]
|
||||||
switches = [e for e in all_entities if e.__class__.__name__ == "SwitchInfo"]
|
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 duplicate "Temperature" names
|
# Scenario 1: Check sensors with same "Temperature" name on different devices
|
||||||
temp_sensors = [s for s in sensors if s.name == "Temperature"]
|
temp_sensors = [s for s in sensors if s.name == "Temperature"]
|
||||||
temp_object_ids = sorted([s.object_id for s in temp_sensors])
|
assert len(temp_sensors) == 4, (
|
||||||
|
f"Expected exactly 4 temperature sensors, got {len(temp_sensors)}"
|
||||||
# Should have temperature, temperature_2, temperature_3, temperature_4
|
|
||||||
assert len(temp_object_ids) >= 4, (
|
|
||||||
f"Expected at least 4 temperature sensors, got {len(temp_object_ids)}"
|
|
||||||
)
|
|
||||||
assert "temperature" in temp_object_ids, (
|
|
||||||
"First temperature sensor should not have suffix"
|
|
||||||
)
|
|
||||||
assert "temperature_2" in temp_object_ids, (
|
|
||||||
"Second temperature sensor should be temperature_2"
|
|
||||||
)
|
|
||||||
assert "temperature_3" in temp_object_ids, (
|
|
||||||
"Third temperature sensor should be temperature_3"
|
|
||||||
)
|
|
||||||
assert "temperature_4" in temp_object_ids, (
|
|
||||||
"Fourth temperature sensor should be temperature_4"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Scenario 2: Check device-specific sensors don't conflict
|
# Verify each sensor is on a different device
|
||||||
device_temp_sensors = [s for s in sensors if s.name == "Device Temperature"]
|
temp_device_ids = set()
|
||||||
|
temp_object_ids = set()
|
||||||
|
|
||||||
# Group by device
|
for sensor in temp_sensors:
|
||||||
controller_1_temps = [
|
device_id = getattr(sensor, "device_id", None)
|
||||||
s
|
temp_device_ids.add(device_id)
|
||||||
for s in device_temp_sensors
|
temp_object_ids.add(sensor.object_id)
|
||||||
if getattr(s, "device_id", None) == controller_1.device_id
|
|
||||||
]
|
|
||||||
controller_2_temps = [
|
|
||||||
s
|
|
||||||
for s in device_temp_sensors
|
|
||||||
if getattr(s, "device_id", None) == controller_2.device_id
|
|
||||||
]
|
|
||||||
|
|
||||||
# Controller 1 should have device_temperature, device_temperature_2, device_temperature_3
|
# All should have object_id "temperature" (no suffix)
|
||||||
c1_object_ids = sorted([s.object_id for s in controller_1_temps])
|
assert sensor.object_id == "temperature", (
|
||||||
assert len(c1_object_ids) >= 3, (
|
f"Expected object_id 'temperature', got '{sensor.object_id}'"
|
||||||
f"Expected at least 3 sensors on controller_1, got {len(c1_object_ids)}"
|
)
|
||||||
)
|
|
||||||
assert "device_temperature" in c1_object_ids, (
|
# Should have 4 different device IDs (including None for main device)
|
||||||
"First device sensor should not have suffix"
|
assert len(temp_device_ids) == 4, (
|
||||||
)
|
f"Temperature sensors should be on different devices, got {temp_device_ids}"
|
||||||
assert "device_temperature_2" in c1_object_ids, (
|
|
||||||
"Second device sensor should be device_temperature_2"
|
|
||||||
)
|
|
||||||
assert "device_temperature_3" in c1_object_ids, (
|
|
||||||
"Third device sensor should be device_temperature_3"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Controller 2 should have only device_temperature (no suffix)
|
# Scenario 2: Check binary sensors "Status" on different devices
|
||||||
c2_object_ids = [s.object_id for s in controller_2_temps]
|
status_binary = [b for b in binary_sensors if b.name == "Status"]
|
||||||
assert len(c2_object_ids) >= 1, (
|
assert len(status_binary) == 3, (
|
||||||
f"Expected at least 1 sensor on controller_2, got {len(c2_object_ids)}"
|
f"Expected exactly 3 status binary sensors, got {len(status_binary)}"
|
||||||
)
|
|
||||||
assert "device_temperature" in c2_object_ids, (
|
|
||||||
"Controller 2 sensor should not have suffix"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Scenario 3: Check binary sensors (different platform, same name)
|
# 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"]
|
temp_binary = [b for b in binary_sensors if b.name == "Temperature"]
|
||||||
binary_object_ids = sorted([b.object_id for b in temp_binary])
|
assert len(temp_binary) == 1, (
|
||||||
|
f"Expected exactly 1 temperature binary sensor, got {len(temp_binary)}"
|
||||||
|
)
|
||||||
|
assert temp_binary[0].object_id == "temperature"
|
||||||
|
|
||||||
# Should have temperature, temperature_2, temperature_3 (no conflict with sensor platform)
|
# Scenario 4: Check text sensors "Device Info" on different devices
|
||||||
assert len(binary_object_ids) >= 3, (
|
info_text = [t for t in text_sensors if t.name == "Device Info"]
|
||||||
f"Expected at least 3 binary sensors, got {len(binary_object_ids)}"
|
assert len(info_text) == 3, (
|
||||||
)
|
f"Expected exactly 3 device info text sensors, got {len(info_text)}"
|
||||||
assert "temperature" in binary_object_ids, (
|
|
||||||
"First binary sensor should not have suffix"
|
|
||||||
)
|
|
||||||
assert "temperature_2" in binary_object_ids, (
|
|
||||||
"Second binary sensor should be temperature_2"
|
|
||||||
)
|
|
||||||
assert "temperature_3" in binary_object_ids, (
|
|
||||||
"Third binary sensor should be temperature_3"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Scenario 4: Check text sensors with special characters
|
# All should have object_id "device_info"
|
||||||
status_sensors = [t for t in text_sensors if t.name == "Status Message!"]
|
for text in info_text:
|
||||||
status_object_ids = sorted([t.object_id for t in status_sensors])
|
assert text.object_id == "device_info", (
|
||||||
|
f"Expected object_id 'device_info', got '{text.object_id}'"
|
||||||
|
)
|
||||||
|
|
||||||
# Special characters should be sanitized to _
|
# Scenario 5: Check switches "Power" on different devices
|
||||||
assert len(status_object_ids) >= 3, (
|
power_switches = [s for s in switches if s.name == "Power"]
|
||||||
f"Expected at least 3 status sensors, got {len(status_object_ids)}"
|
assert len(power_switches) == 3, (
|
||||||
)
|
f"Expected exactly 3 power switches, got {len(power_switches)}"
|
||||||
assert "status_message_" in status_object_ids, (
|
|
||||||
"First status sensor should be status_message_"
|
|
||||||
)
|
|
||||||
assert "status_message__2" in status_object_ids, (
|
|
||||||
"Second status sensor should be status_message__2"
|
|
||||||
)
|
|
||||||
assert "status_message__3" in status_object_ids, (
|
|
||||||
"Third status sensor should be status_message__3"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Scenario 5: Check switches with duplicate names
|
# All should have object_id "power"
|
||||||
power_switches = [s for s in switches if s.name == "Power Switch"]
|
for switch in power_switches:
|
||||||
power_object_ids = sorted([s.object_id for s in power_switches])
|
assert switch.object_id == "power", (
|
||||||
|
f"Expected object_id 'power', got '{switch.object_id}'"
|
||||||
|
)
|
||||||
|
|
||||||
# Should have power_switch, power_switch_2
|
# Scenario 6: Check empty name buttons (should use device name)
|
||||||
assert len(power_object_ids) >= 2, (
|
empty_buttons = [b for b in buttons if b.name == ""]
|
||||||
f"Expected at least 2 power switches, got {len(power_object_ids)}"
|
assert len(empty_buttons) == 3, (
|
||||||
|
f"Expected exactly 3 empty name buttons, got {len(empty_buttons)}"
|
||||||
)
|
)
|
||||||
assert "power_switch" in power_object_ids, (
|
|
||||||
"First power switch should be power_switch"
|
|
||||||
)
|
|
||||||
assert "power_switch_2" in power_object_ids, (
|
|
||||||
"Second power switch should be power_switch_2"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Scenario 6: Check empty names on main device (Issue #6953)
|
|
||||||
empty_binary = [b for b in binary_sensors if b.name == ""]
|
|
||||||
empty_binary_ids = sorted([b.object_id for b in empty_binary])
|
|
||||||
|
|
||||||
# Should use device name "duplicate-entities-test" (sanitized, not snake_case)
|
|
||||||
assert len(empty_binary_ids) >= 4, (
|
|
||||||
f"Expected at least 4 empty name binary sensors, got {len(empty_binary_ids)}"
|
|
||||||
)
|
|
||||||
assert "duplicate-entities-test" in empty_binary_ids, (
|
|
||||||
"First empty binary sensor should use device name"
|
|
||||||
)
|
|
||||||
assert "duplicate-entities-test_2" in empty_binary_ids, (
|
|
||||||
"Second empty binary sensor should be duplicate-entities-test_2"
|
|
||||||
)
|
|
||||||
assert "duplicate-entities-test_3" in empty_binary_ids, (
|
|
||||||
"Third empty binary sensor should be duplicate-entities-test_3"
|
|
||||||
)
|
|
||||||
assert "duplicate-entities-test_4" in empty_binary_ids, (
|
|
||||||
"Fourth empty binary sensor should be duplicate-entities-test_4"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Scenario 7: Check empty names on sub-devices (Issue #6953)
|
|
||||||
empty_switches = [s for s in switches if s.name == ""]
|
|
||||||
|
|
||||||
# Group by device
|
# Group by device
|
||||||
c1_empty_switches = [
|
c1_buttons = [
|
||||||
s
|
b
|
||||||
for s in empty_switches
|
for b in empty_buttons
|
||||||
if getattr(s, "device_id", None) == controller_1.device_id
|
if getattr(b, "device_id", 0) == controller_1.device_id
|
||||||
]
|
]
|
||||||
c2_empty_switches = [
|
c2_buttons = [
|
||||||
s
|
b
|
||||||
for s in empty_switches
|
for b in empty_buttons
|
||||||
if getattr(s, "device_id", None) == controller_2.device_id
|
if getattr(b, "device_id", 0) == controller_2.device_id
|
||||||
]
|
|
||||||
main_empty_switches = [
|
|
||||||
s
|
|
||||||
for s in empty_switches
|
|
||||||
if getattr(s, "device_id", None)
|
|
||||||
not in [controller_1.device_id, controller_2.device_id]
|
|
||||||
]
|
]
|
||||||
|
|
||||||
# Controller 1 empty switches should use "controller_1"
|
# For main device, device_id is 0
|
||||||
c1_empty_ids = sorted([s.object_id for s in c1_empty_switches])
|
main_buttons = [b for b in empty_buttons if getattr(b, "device_id", 0) == 0]
|
||||||
assert len(c1_empty_ids) >= 3, (
|
|
||||||
f"Expected at least 3 empty switches on controller_1, got {len(c1_empty_ids)}"
|
|
||||||
)
|
|
||||||
assert "controller_1" in c1_empty_ids, "First should be controller_1"
|
|
||||||
assert "controller_1_2" in c1_empty_ids, "Second should be controller_1_2"
|
|
||||||
assert "controller_1_3" in c1_empty_ids, "Third should be controller_1_3"
|
|
||||||
|
|
||||||
# Controller 2 empty switches
|
# Check object IDs for empty name entities
|
||||||
c2_empty_ids = sorted([s.object_id for s in c2_empty_switches])
|
assert len(c1_buttons) == 1 and c1_buttons[0].object_id == "controller_1"
|
||||||
assert len(c2_empty_ids) >= 2, (
|
assert len(c2_buttons) == 1 and c2_buttons[0].object_id == "controller_2"
|
||||||
f"Expected at least 2 empty switches on controller_2, got {len(c2_empty_ids)}"
|
assert (
|
||||||
|
len(main_buttons) == 1
|
||||||
|
and main_buttons[0].object_id == "duplicate-entities-test"
|
||||||
)
|
)
|
||||||
assert "controller_2" in c2_empty_ids, "First should be controller_2"
|
|
||||||
assert "controller_2_2" in c2_empty_ids, "Second should be controller_2_2"
|
|
||||||
|
|
||||||
# Main device empty switches
|
# Scenario 7: Check special characters in number names
|
||||||
main_empty_ids = sorted([s.object_id for s in main_empty_switches])
|
temp_numbers = [n for n in numbers if n.name == "Temperature Setpoint!"]
|
||||||
assert len(main_empty_ids) >= 3, (
|
assert len(temp_numbers) == 2, (
|
||||||
f"Expected at least 3 empty switches on main device, got {len(main_empty_ids)}"
|
f"Expected exactly 2 temperature setpoint numbers, got {len(temp_numbers)}"
|
||||||
)
|
)
|
||||||
assert "duplicate-entities-test" in main_empty_ids
|
|
||||||
assert "duplicate-entities-test_2" in main_empty_ids
|
|
||||||
assert "duplicate-entities-test_3" in main_empty_ids
|
|
||||||
|
|
||||||
# Scenario 8: Check "xyz" duplicates (Issue #6953)
|
# Special characters should be sanitized to _ in object_id
|
||||||
xyz_switches = [s for s in switches if s.name == "xyz"]
|
for number in temp_numbers:
|
||||||
xyz_ids = sorted([s.object_id for s in xyz_switches])
|
assert number.object_id == "temperature_setpoint_", (
|
||||||
|
f"Expected object_id 'temperature_setpoint_', got '{number.object_id}'"
|
||||||
assert len(xyz_ids) >= 3, (
|
)
|
||||||
f"Expected at least 3 xyz switches, got {len(xyz_ids)}"
|
|
||||||
)
|
|
||||||
assert "xyz" in xyz_ids, "First xyz switch should be xyz"
|
|
||||||
assert "xyz_2" in xyz_ids, "Second xyz switch should be xyz_2"
|
|
||||||
assert "xyz_3" in xyz_ids, "Third xyz switch should be xyz_3"
|
|
||||||
|
|
||||||
# Verify we can get states for all entities (ensures they're functional)
|
# Verify we can get states for all entities (ensures they're functional)
|
||||||
loop = asyncio.get_running_loop()
|
loop = asyncio.get_running_loop()
|
||||||
states_future: asyncio.Future[None] = loop.create_future()
|
states_future: asyncio.Future[None] = loop.create_future()
|
||||||
state_count = 0
|
state_count = 0
|
||||||
expected_count = (
|
expected_count = (
|
||||||
len(sensors) + len(binary_sensors) + len(text_sensors) + len(switches)
|
len(sensors)
|
||||||
|
+ len(binary_sensors)
|
||||||
|
+ len(text_sensors)
|
||||||
|
+ len(switches)
|
||||||
|
+ len(buttons)
|
||||||
|
+ len(numbers)
|
||||||
)
|
)
|
||||||
|
|
||||||
def on_state(state) -> None:
|
def on_state(state) -> None:
|
||||||
|
Reference in New Issue
Block a user