Files
localthings/tests/test_entity.py
T
Marc Billow daf7e3787f Add ruff (lint + format) and ty (type checking) to the project
Adds [tool.ruff] and [tool.ty] config to pyproject.toml with a curated
ruff rule set (E, F, W, I, UP, B, C4, SIM, RUF, ASYNC, LOG, G, PIE, RET,
PERF, N), pins ruff/ty in requirements-dev.txt, reformats the whole tree
with `ruff format`, and fixes the pre-existing lint and type-check debt
those tools surfaced so both run clean.

Production-code type fixes include: HA's ConfigFlowResult vs. the
generic FlowResult in config_flow.py, narrowing BoundEntity.desc to its
platform-specific subclass (SelectDesc/NumberDesc/SensorDesc/etc.) via
cast() instead of an unchecked annotation, converting HA device_class
strings to their proper enum types, a resolve_registry callback typed
as `object` instead of `DeviceRegistry | None`, and a couple of other
narrow correctness fixes (CA key type validation, an index-out-of-bounds
false positive from an empty-tuple fallback, a bool/dict argument swap).

Test-file fixes are mechanical: narrowing SamsungEntityDescription to
the correct subclass via isinstance()/cast() before accessing
subclass-only fields, and asserting Optional write_fn/unit_fn fields
are set before calling them.
2026-08-02 23:56:38 +00:00

92 lines
3.8 KiB
Python

"""Tests for entity._is_included -- the one-time entity-creation gate run
per platform at async_setup_entry (see sensor.py etc.), separate from
adapter.flatten()'s per-poll state values. issue #127 review: this default
gate has to stay permissive on a genuinely empty {} rep, not just a true
is_stub_rep stub -- a resource like /alarms/vs/0 is validly empty in its
normal, no-alarm state (fridge._active_alarm_codes), so excluding the
entity there would silently drop working sensors on first-poll timing,
not just fix phantom ones.
"""
from custom_components.localthings.entity import _is_included
from custom_components.localthings.registry.capability import Capability
from custom_components.localthings.registry.discovery import BoundEntity
from custom_components.localthings.registry.entities import SensorDesc
class _FakeCoordinator:
def __init__(self, last_resources):
self.last_resources = last_resources
def canonical_resources(self, subdevice):
# Every bound entity in this test file uses the default MAIN
# subdevice (identity transform), so the canonical view is just the
# raw snapshot -- same shape as the real
# LocalThingsCoordinator.canonical_resources for a device with no
# subdevices (issue #177).
return self.last_resources
def _bound(desc, href):
capability = Capability(href=href, entities=(desc,))
return BoundEntity(href=href, capability=capability, desc=desc)
class TestDefaultFieldGate:
"""No explicit exists_fn -- the field-presence default in entity.py."""
def _bound(self):
return _bound(SensorDesc(key="x", field="x.com.samsung.da.value"), "/x/vs/0")
def test_included_when_field_present(self):
bound = self._bound()
coord = _FakeCoordinator({"/x/vs/0": {"x.com.samsung.da.value": "1"}})
assert _is_included(bound, coord) is True
def test_excluded_when_field_absent_from_populated_rep(self):
bound = self._bound()
coord = _FakeCoordinator({"/x/vs/0": {"x.com.samsung.da.other": "1"}})
assert _is_included(bound, coord) is False
def test_included_on_true_stub(self):
bound = self._bound()
coord = _FakeCoordinator({"/x/vs/0": {"href": "/x/vs/0"}})
assert _is_included(bound, coord) is True
def test_included_on_genuinely_empty_rep(self):
"""The default gate stays permissive on a real {} -- see
/alarms/vs/0, whose empty rep is the normal no-alarm state, not a
signal the hardware is unsupported. Only a capability-specific
exists_fn (e.g. common.ENERGY_METER) opts into excluding on
confirmed-empty, after verifying that's actually safe for its field."""
bound = self._bound()
coord = _FakeCoordinator({"/x/vs/0": {}})
assert _is_included(bound, coord) is True
def test_excluded_when_href_missing_from_resources(self):
bound = self._bound()
coord = _FakeCoordinator({})
assert _is_included(bound, coord) is False
class TestExplicitExistsFnGate:
def test_explicit_exists_fn_overrides_default_field_gate(self):
desc = SensorDesc(
key="x",
field="x.com.samsung.da.value",
exists_fn=lambda rep, resources: rep.get("flag") is True,
)
bound = _bound(desc, "/x/vs/0")
coord = _FakeCoordinator({"/x/vs/0": {"flag": True}})
assert _is_included(bound, coord) is True
coord = _FakeCoordinator({"/x/vs/0": {"flag": False, "x.com.samsung.da.value": "1"}})
assert _is_included(bound, coord) is False
class TestNoFieldEntities:
def test_rep_fn_entity_always_included(self):
desc = SensorDesc(key="x", rep_fn=lambda rep: rep.get("x.com.samsung.da.value"))
bound = _bound(desc, "/x/vs/0")
coord = _FakeCoordinator({"/x/vs/0": {}})
assert _is_included(bound, coord) is True