Skip to content

Commit 4e72197

Browse files
authored
Add exhaustiveness guards for entity dispatch tables (#1735)
1 parent 0128ace commit 4e72197

1 file changed

Lines changed: 45 additions & 1 deletion

File tree

tests/test_model_conversions.py

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,56 @@
11
from __future__ import annotations
22

3-
from aioesphomeapi.model import CameraInfo, CameraState
3+
from aioesphomeapi.model import CameraInfo, CameraState, EntityInfo, EntityState
44
from aioesphomeapi.model_conversions import (
5+
LIST_ENTITIES_SERVICES_RESPONSE_TYPES,
56
STATE_TYPE_TO_INFO_TYPE,
67
SUBSCRIBE_STATES_RESPONSE_TYPES,
78
)
89

910

11+
def _model_subclass_names(cls: type) -> set[str]:
12+
# Compare by name, not identity: @dataclass(slots=True) leaves the
13+
# pre-slots intermediate class registered in __subclasses__() too, so
14+
# identity comparison double-counts every model class. Restrict to the
15+
# model module so ad-hoc test doubles or downstream consumer subclasses
16+
# don't pollute the set.
17+
module = cls.__module__
18+
names: set[str] = set()
19+
for sub in cls.__subclasses__():
20+
if sub.__module__ == module:
21+
names.add(sub.__name__)
22+
names |= _model_subclass_names(sub)
23+
return names
24+
25+
26+
def test_subscribe_states_response_types_covers_all_state_types() -> None:
27+
"""Pin proto->state decoding coverage for every EntityState subclass.
28+
29+
A new state type defined in model.py but not wired into
30+
SUBSCRIBE_STATES_RESPONSE_TYPES is silently undecodable. CameraState is
31+
the lone exception — it is produced from CameraImageResponse.
32+
"""
33+
decoded = {state.__name__ for state in SUBSCRIBE_STATES_RESPONSE_TYPES.values()}
34+
missing = _model_subclass_names(EntityState) - decoded - {"CameraState"}
35+
assert not missing, f"SUBSCRIBE_STATES_RESPONSE_TYPES is missing: {missing}"
36+
37+
38+
def test_list_entities_response_types_covers_all_info_types() -> None:
39+
"""Pin proto->info decoding coverage for every EntityInfo subclass.
40+
41+
A new info type defined in model.py but not wired into
42+
LIST_ENTITIES_SERVICES_RESPONSE_TYPES is silently unrecognized when the
43+
device lists its entities.
44+
"""
45+
listed = {
46+
info.__name__
47+
for info in LIST_ENTITIES_SERVICES_RESPONSE_TYPES.values()
48+
if info is not None
49+
}
50+
missing = _model_subclass_names(EntityInfo) - listed
51+
assert not missing, f"LIST_ENTITIES_SERVICES_RESPONSE_TYPES is missing: {missing}"
52+
53+
1054
def test_state_type_to_info_type_covers_all_state_types() -> None:
1155
"""Pin info-type mapping coverage for every decoded state type.
1256

0 commit comments

Comments
 (0)