"""#42: backend quality contracts — error codes, mypy allowlist, noqa hygiene. Pure tests: no homeassistant import, runnable in any environment. """ from __future__ import annotations import importlib.util import json import re from pathlib import Path REPO = Path(__file__).resolve().parent.parent BACKEND = REPO / "custom_components" / "houseplan" # Loading const.py by file path with a standalone module name deliberately # avoids stubbing "custom_components"/"custom_components.houseplan" in # sys.modules: leftover ModuleType stand-ins poison the Home Assistant harness # running later in the same pytest process — HA's loader then sees a package # without async_setup and every harness test fails with "No setup or config # entry setup function defined" (the exact #389 incident, caused by the schema # dump script). const.py imports nothing, so no package context is needed. def _const(): spec = importlib.util.spec_from_file_location( "houseplan_quality_const", BACKEND / "const.py") module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) return module def _emitted_codes() -> tuple[set[str], set[str]]: """All fixed codes and family prefixes actually emitted by the sources. #42 AC5 (spec rev 4-6): BOTH emission paths — send_error literals and the err.code sources (class attrs, MarkerControlError literals, the conditional source_error/attribute_error assignments, the literal tuple codes, the f-string families) — fail-closed for anything else. """ api = (BACKEND / "websocket_api.py").read_text(encoding="utf-8") validation = (BACKEND / "validation.py").read_text(encoding="utf-8") junction = (BACKEND / "junction_limits.py").read_text(encoding="utf-8") fixed: set[str] = set() fixed |= set(re.findall(r'send_error\(\s*[^,]+,\s*"([a-z0-9_]+)"', api)) fixed |= set(re.findall(r'^\s+code = "([a-z0-9_]+)"', validation, re.M)) fixed |= set(re.findall(r'MarkerControlError\(\s*\n?\s*"([a-z0-9_]+)"', validation)) for pair in re.findall( r'(?:source_error|attribute_error)\s*=\s*"([a-z0-9_]+)"\s*if[^\n]*\\\s*\n\s*else\s*"([a-z0-9_]+)"', validation): fixed |= set(pair) fixed |= set(re.findall(r'"(invalid_(?:light|toggle)_entity)"', validation)) families: set[str] = set() families |= {match + "_" if not match.endswith("_") else match for match in re.findall(r'MarkerControlError\(\s*f"\{prefix\}_', validation) and {"value_badge", "value_source"} or set()} if re.search(r'MarkerControlError\(\s*f"\{prefix\}', validation): families |= {"value_badge_", "value_source_"} if re.search(r'f"junction_limit_\{', junction): families.add("junction_limit_") # fail-closed: a MarkerControlError call whose first argument is neither a # literal, a known variable, nor a known f-string pattern is a hole. for call in re.findall(r'(?= 10, ( f"{path.name}:{index}: a bare noqa hides a decision — add the reason")