Files
localthings/tests/test_unique_ids.py
T
Marc Billow 9e85395b44 rename: unit/sub-unit -> subdevice, matching OCF terminology
"Unit"/"sub-unit" from #199's multi-indoor-device support wasn't OCF
idiomatic -- OCF calls each component of a composite device a
"subdevice" (see subdeviceIdList), so rename SubUnit -> Subdevice
throughout: the registry module, coordinator state, BoundEntity's
subdevice field, diagnostics keys (subdevices/subdevices_skipped/
subdevice_probes), entity unique_id prefixes (unit1_/sub_<uuid>_ ->
subdevice1_/subdevice_<uuid>_), device-name fallback labels, golden
fixtures, tests, README, and the adding-device-support skill.

Breaking change to entity unique_ids and diagnostics keys, acceptable
since this hasn't been released yet.
2026-07-29 22:57:11 +00:00

83 lines
4.0 KiB
Python

"""Corpus-wide invariant: adapter._key must be unique across every bound
entity that would actually be registered as an HA entity, for every fixture
in the corpus -- issue #177's whole Subdevice/key_prefix design exists to
protect this (see DESIGN-177.md section 3/6). Run over the entire fixture
set, not just the two new subdevice fixtures, so a future dump -- subdevice-
capable or not -- exercises it automatically.
"""
from collections import Counter
import pytest
from custom_components.localthings.entity import _is_included
from custom_components.localthings.registry.adapter import _key
from custom_components.localthings.registry.entities import PLATFORM_OF
from tests.conftest import FIXTURES, _discover_full, _load_device_full
_FIXTURE_NAMES = sorted(
p.name[:-len('_device.json')] for p in FIXTURES.glob('*_device.json')
)
class _FakeCoordinator:
"""Just enough of LocalThingsCoordinator's surface for entity.py's
_is_included -- the same one-time entity-creation gate every platform's
async_setup_entry runs (see entity.py's own module docstring)."""
def __init__(self, resources: dict[str, dict], subdevices):
self.last_resources = resources
self._subdevices = list(subdevices)
def canonical_resources(self, subdevice):
from custom_components.localthings.registry.subdevices import canonical_view
return canonical_view(subdevice, self.last_resources, self._subdevices)
@pytest.mark.parametrize('name', _FIXTURE_NAMES)
def test_key_is_unique_across_all_bound_entities(name):
"""`_key(b)` only has to be unique among entities `_is_included` would
actually register -- discover() alone can (deliberately) produce two
BoundEntity rows sharing a key on the same href when they're gated by
mutually-exclusive `exists_fn`s (see fridge.py's
REFRIGERATION_FALLBACK.defrost_active, which only exists when
DEFROST_BLOCK_STATUS's own `/defrost/block/vs/0` href is absent) --
only one of the two is ever actually included for a real device, so
checking the raw `bound` list would flag devices that have always
worked correctly. It also only has to be unique *within one HA
platform* -- unique_id collisions are scoped by (integration, platform)
in HA's entity registry (see entity.py's `_attr_unique_id`, which
doesn't itself encode the platform), and several devices in this corpus
deliberately bind the same key to the same href on two different
platforms discriminated the same way (e.g. a SwitchDesc and a
BinarySensorDesc both named 'power_switch' on /power/0).
"""
resources, oic_res, seeds = _load_device_full(name)
bound, materialized, skipped, full_resources, device_type_name = _discover_full(
resources, oic_res, seeds,
)
coordinator = _FakeCoordinator(full_resources, materialized)
included = [b for b in bound if _is_included(b, coordinator)]
keys = [(PLATFORM_OF[type(b.desc)], _key(b)) for b in included]
dupes = {k: n for k, n in Counter(keys).items() if n > 1}
assert not dupes, (
f"{name}: duplicate (platform, _key) values across {len(included)} "
f"included entities (materialized subdevices: "
f"{[su.key for su in materialized]}): {dupes}"
)
def test_subdevice_capable_fixtures_actually_exercise_a_subdevice():
"""A meta-check on the test above: if both subdevice fixtures somehow
stopped materializing any subdevice (a regression in enumeration or the
materialization gate), the corpus-wide uniqueness test above would keep
passing vacuously -- it never gets to check a single collision. Assert
the two fixtures this design added actually produce a materialized
subdevice, so that silent-vacuous-pass failure mode is caught here
instead."""
for name in ('airconditioner_artik051_dongle_fac_18k',
'airconditioner_fac_bora_2in1'):
resources, oic_res, seeds = _load_device_full(name)
_, materialized, _, _, _ = _discover_full(resources, oic_res, seeds)
assert materialized, f"{name}: expected at least one materialized subdevice"