From da8e23f968840c360bca01bbcec8598463023749 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 22 Dec 2025 21:58:58 -1000 Subject: [PATCH] more cover --- .../fixtures/object_id_api_verification.yaml | 30 +++++++ .../test_object_id_api_verification.py | 86 +++++++++++++------ 2 files changed, 92 insertions(+), 24 deletions(-) diff --git a/tests/integration/fixtures/object_id_api_verification.yaml b/tests/integration/fixtures/object_id_api_verification.yaml index 0a8deff4da..386270fc2c 100644 --- a/tests/integration/fixtures/object_id_api_verification.yaml +++ b/tests/integration/fixtures/object_id_api_verification.yaml @@ -4,6 +4,12 @@ esphome: # Enable MAC suffix - host MAC is 98:35:69:ab:f6:79, suffix is "abf679" # friendly_name becomes "Test Device abf679" name_add_mac_suffix: true + # Sub-devices for testing empty-name entities on devices + devices: + - id: sub_device_1 + name: Sub Device One + - id: sub_device_2 + name: Sub Device Two host: @@ -93,3 +99,27 @@ text_sensor: id: text_num_start lambda: return {"test"}; update_interval: 60s + +button: + # Test 12: Named entity on sub-device -> object_id from entity name + - platform: template + name: "Device Button" + id: button_on_device + device_id: sub_device_1 + on_press: [] + + # Test 13: Empty name on sub-device -> object_id from device name + # Device name "Sub Device One" -> object_id = "sub_device_one" + - platform: template + name: "" + id: button_empty_on_device1 + device_id: sub_device_1 + on_press: [] + + # Test 14: Empty name on different sub-device + # Device name "Sub Device Two" -> object_id = "sub_device_two" + - platform: template + name: "" + id: button_empty_on_device2 + device_id: sub_device_2 + on_press: [] diff --git a/tests/integration/test_object_id_api_verification.py b/tests/integration/test_object_id_api_verification.py index e90f6c273d..c19c3a22df 100644 --- a/tests/integration/test_object_id_api_verification.py +++ b/tests/integration/test_object_id_api_verification.py @@ -14,7 +14,9 @@ See: https://github.com/esphome/backlog/issues/76 Test cases covered: - Named entities with various characters (uppercase, special chars, hyphens, etc.) -- Empty-name entities (has_own_name=false, uses device's friendly_name) +- Empty-name entities on main device (uses device's friendly_name with MAC suffix) +- Empty-name entities on sub-devices (uses sub-device's name) +- Named entities on sub-devices (uses entity name, not device name) - MAC suffix handling (name_add_mac_suffix modifies friendly_name at runtime) - Both object_id string and hash (key) verification """ @@ -48,6 +50,15 @@ NAMED_ENTITIES = [ ("My Very Long Switch Name Here", "my_very_long_switch_name_here"), # text_sensor platform ("123 Start", "123_start"), + # button platform - named entity on sub-device (uses entity name, not device name) + ("Device Button", "device_button"), +] + +# Sub-device names and their expected object_ids for empty-name entities +# Format: (device_name, expected_object_id) +SUB_DEVICE_EMPTY_NAME_ENTITIES = [ + ("Sub Device One", "sub_device_one"), + ("Sub Device Two", "sub_device_two"), ] @@ -122,47 +133,74 @@ async def test_object_id_api_verification( f"Python hash {hash_from_name:#x}, API key {entity.key:#x}" ) - # === Test 2: Verify empty-name entity (has_own_name=false) === - # When entity has no name, the name field is empty in the API message - # and the entity uses device's friendly_name (with MAC suffix) for display - assert "" in entity_map, ( - "Empty-name entity not found. " - f"Available entity names: {list(entity_map.keys())}" - ) - empty_name_entity = entity_map[""] + # === Test 2: Verify empty-name entities === + # Empty-name entities have name="" in API, object_id comes from: + # - Main device: friendly_name (with MAC suffix) + # - Sub-device: device name - # object_id is computed from friendly_name (which includes MAC suffix) - expected_object_id_empty = compute_expected_object_id(expected_friendly_name) - assert empty_name_entity.object_id == expected_object_id_empty, ( - f"Empty-name entity: object_id mismatch. " - f"API: '{empty_name_entity.object_id}', expected: '{expected_object_id_empty}'" + # Get all empty-name entities + empty_name_entities = [e for e in entities if e.name == ""] + # We expect 3: 1 on main device, 2 on sub-devices + assert len(empty_name_entities) == 3, ( + f"Expected 3 empty-name entities, got {len(empty_name_entities)}" ) - # Hash is also computed from friendly_name with MAC suffix - expected_hash_empty = fnv1_hash_object_id(expected_friendly_name) - assert empty_name_entity.key == expected_hash_empty, ( - f"Empty-name entity: hash mismatch. " - f"API key: {empty_name_entity.key:#x}, expected: {expected_hash_empty:#x}" - ) + # Build device_id -> device_name map from device_info + device_id_to_name = {d.device_id: d.name for d in device_info.devices} + + # Verify each empty-name entity + for entity in empty_name_entities: + if entity.device_id == 0: + # Main device - uses friendly_name with MAC suffix + expected_name = expected_friendly_name + else: + # Sub-device - uses device name + assert entity.device_id in device_id_to_name, ( + f"Entity device_id {entity.device_id} not found in devices" + ) + expected_name = device_id_to_name[entity.device_id] + + expected_object_id = compute_expected_object_id(expected_name) + assert entity.object_id == expected_object_id, ( + f"Empty-name entity (device_id={entity.device_id}): object_id mismatch. " + f"API: '{entity.object_id}', expected: '{expected_object_id}' " + f"(from name '{expected_name}')" + ) + + # Verify hash matches + expected_hash = fnv1_hash_object_id(expected_name) + assert entity.key == expected_hash, ( + f"Empty-name entity (device_id={entity.device_id}): hash mismatch. " + f"API key: {entity.key:#x}, expected: {expected_hash:#x}" + ) # === Test 3: Verify ALL entities can have object_id computed from API data === # This is the key property for removing object_id from the API protocol for entity in entities: - # Use entity name if present, otherwise device's friendly_name - name_for_object_id = entity.name or device_info.friendly_name + if entity.name: + # Named entity - use entity name + name_for_object_id = entity.name + elif entity.device_id == 0: + # Empty name on main device - use friendly_name + name_for_object_id = device_info.friendly_name + else: + # Empty name on sub-device - use device name + name_for_object_id = device_id_to_name[entity.device_id] # Compute object_id from the appropriate name computed_object_id = compute_expected_object_id(name_for_object_id) # Verify it matches what the API returned assert entity.object_id == computed_object_id, ( - f"Entity (name='{entity.name}'): object_id cannot be computed. " + f"Entity (name='{entity.name}', device_id={entity.device_id}): " + f"object_id cannot be computed. " f"API: '{entity.object_id}', Computed from '{name_for_object_id}': '{computed_object_id}'" ) # Verify hash can also be computed computed_hash = fnv1_hash_object_id(name_for_object_id) assert entity.key == computed_hash, ( - f"Entity (name='{entity.name}'): hash cannot be computed. " + f"Entity (name='{entity.name}', device_id={entity.device_id}): " + f"hash cannot be computed. " f"API key: {entity.key:#x}, Computed: {computed_hash:#x}" )