Files
localthings/tests/test_registry_ignored.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

78 lines
2.9 KiB
Python

"""Tests that known-noise hrefs don't show up as coverage gaps.
Each device-type registry folds in registry.capabilities.ignored.IGNORED so
that Bixby/voice/network/OTA housekeeping resources aren't reported as
unbound. This guards two things at once: that the ignored hrefs are
actually suppressed, and (via a plain import) that no ignored href
collides with a real capability declared elsewhere for the same family.
"""
import json
from pathlib import Path
from custom_components.localthings.registry.batch import parse_device0_batch
from custom_components.localthings.registry.by_type import dishwasher, refrigerator
from custom_components.localthings.registry.capabilities.ignored import IGNORED
FIXTURES = Path(__file__).resolve().parent / "fixtures"
_IGNORED_HREFS = {cap.href for cap in IGNORED}
def _load(name: str) -> dict:
data = json.loads((FIXTURES / name).read_text())
return parse_device0_batch(data["device0"])
def _unbound_hrefs(resources, registry) -> list[str]:
unbound = []
from custom_components.localthings.registry.discovery import discover
discover(resources, registry.capabilities, registry.pattern_capabilities, log=unbound.append)
return unbound
def test_ignored_hrefs_absent_from_dishwasher_gaps():
resources = _load("dishwasher_device.json")
unbound = _unbound_hrefs(resources, dishwasher.REGISTRY)
present_ignored = _IGNORED_HREFS & set(unbound)
assert not present_ignored, f"ignored hrefs leaked into gap report: {present_ignored}"
def test_ignored_hrefs_absent_from_refrigerator_gaps():
resources = _load("refrigerator_device.json")
unbound = _unbound_hrefs(resources, refrigerator.REGISTRY)
present_ignored = _IGNORED_HREFS & set(unbound)
assert not present_ignored, f"ignored hrefs leaked into gap report: {present_ignored}"
def test_dishwasher_fixture_has_no_remaining_gaps():
"""Sanity check that the gap mechanism isn't silencing everything.
Every href in the dishwasher fixture is now either mapped to a
capability or listed in capabilities.ignored — if this starts failing
because a genuinely new gap appeared, that's the interesting case to
look at (add the href to ignored.py or map it, don't just delete this
assertion).
"""
resources = _load("dishwasher_device.json")
unbound = set(_unbound_hrefs(resources, dishwasher.REGISTRY))
assert unbound == set()
class TestWasherIgnoredHrefs:
def test_washer_hrefs_are_ignored(self):
from custom_components.localthings.registry.capabilities.ignored import IGNORED
ignored_hrefs = {cap.href for cap in IGNORED}
for href in (
"/st/washercourse/vs/0",
"/wm/welcomemsg/vs/0",
"/wm/personalcourse/vs/0",
"/energy/consumption/0",
"/cycleinterface/vs/0",
"/drlc/0",
"/operational/state/0",
):
assert href in ignored_hrefs, f"{href} should be in ignored.IGNORED"