Skip to content

Commit 3a1cfb4

Browse files
rsnodgrassSage-Ox
andcommitted
Add entity ID stability tests to prevent regressions
Golden tests that verify unique_id formats don't change between releases. Changing unique_id formats breaks user automations, dashboards, and history. - Tests media_player unique_id: {domain}_{amp_name}_zone_{zone_id} - Tests number entity unique_ids: {domain}_{amp_name}_zone_{zone_id}_{control} Co-Authored-By: SageOx <ox@sageox.ai>
1 parent 458833b commit 3a1cfb4

File tree

1 file changed

+111
-0
lines changed

1 file changed

+111
-0
lines changed

tests/test_entity_id_stability.py

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
"""Tests to prevent entity ID regressions across versions.
2+
3+
WARNING: These tests protect user installations. Changing unique_id formats
4+
breaks automations, dashboards, and entity history. You MUST provide a
5+
migration path if changes are absolutely necessary.
6+
"""
7+
8+
import pytest
9+
10+
DOMAIN = 'xantech'
11+
12+
# Golden unique_id format documentation
13+
# These patterns are part of the public API - do not change without migration
14+
GOLDEN_FORMATS = {
15+
'media_player': '{domain}_{amp_name}_zone_{zone_id}',
16+
'number_bass': '{domain}_{amp_name}_zone_{zone_id}_bass',
17+
'number_treble': '{domain}_{amp_name}_zone_{zone_id}_treble',
18+
'number_balance': '{domain}_{amp_name}_zone_{zone_id}_balance',
19+
}
20+
21+
22+
def generate_media_player_unique_id(amp_name: str, zone_id: int) -> str:
23+
"""Generate unique_id using same logic as ZoneMediaPlayer.__init__."""
24+
# mirrors: custom_components/xantech/media_player.py line 108-110
25+
return f'{DOMAIN}_{amp_name}_zone_{zone_id}'.lower().replace(' ', '_')
26+
27+
28+
def generate_number_unique_id(amp_name: str, zone_id: int, control_key: str) -> str:
29+
"""Generate unique_id using same logic as ZoneAudioControlNumber.__init__."""
30+
# mirrors: custom_components/xantech/number.py line 143-146
31+
return (
32+
f'{DOMAIN}_{amp_name}_zone_{zone_id}_{control_key}'.lower().replace(' ', '_')
33+
)
34+
35+
36+
class TestMediaPlayerUniqueIdStability:
37+
"""Test media_player unique_id format stability."""
38+
39+
@pytest.mark.parametrize(
40+
'amp_name,zone_id,expected',
41+
[
42+
('My Amp', 11, 'xantech_my_amp_zone_11'),
43+
('DAX88', 11, 'xantech_dax88_zone_11'),
44+
('Living Room Amp', 12, 'xantech_living_room_amp_zone_12'),
45+
('monoprice6', 15, 'xantech_monoprice6_zone_15'),
46+
],
47+
)
48+
def test_format_stability(self, amp_name: str, zone_id: int, expected: str):
49+
"""Ensure media_player unique_id format matches golden values.
50+
51+
WARNING: If this test fails, you are about to break user automations,
52+
dashboards, and entity history. You MUST provide a migration path.
53+
"""
54+
result = generate_media_player_unique_id(amp_name, zone_id)
55+
assert result == expected, (
56+
f'BREAKING CHANGE: media_player unique_id format changed!\n'
57+
f' Amp: {amp_name}, Zone: {zone_id}\n'
58+
f' Expected: {expected}\n'
59+
f' Got: {result}\n'
60+
f'This will break existing user installations.'
61+
)
62+
63+
def test_special_characters_normalized(self):
64+
"""Verify spaces are converted to underscores consistently."""
65+
assert generate_media_player_unique_id('My Amp', 11) == 'xantech_my_amp_zone_11'
66+
assert (
67+
generate_media_player_unique_id('Multi Word Amp Name', 13)
68+
== 'xantech_multi_word_amp_name_zone_13'
69+
)
70+
71+
def test_case_normalized_to_lowercase(self):
72+
"""Verify mixed case is normalized to lowercase."""
73+
assert generate_media_player_unique_id('MyAMP', 11) == 'xantech_myamp_zone_11'
74+
assert generate_media_player_unique_id('DAX-88', 11) == 'xantech_dax-88_zone_11'
75+
76+
77+
class TestNumberEntityUniqueIdStability:
78+
"""Test number entity unique_id format stability."""
79+
80+
@pytest.mark.parametrize(
81+
'amp_name,zone_id,control_key,expected',
82+
[
83+
('My Amp', 11, 'bass', 'xantech_my_amp_zone_11_bass'),
84+
('My Amp', 11, 'treble', 'xantech_my_amp_zone_11_treble'),
85+
('My Amp', 11, 'balance', 'xantech_my_amp_zone_11_balance'),
86+
('DAX88', 12, 'bass', 'xantech_dax88_zone_12_bass'),
87+
('Living Room', 15, 'treble', 'xantech_living_room_zone_15_treble'),
88+
],
89+
)
90+
def test_format_stability(
91+
self, amp_name: str, zone_id: int, control_key: str, expected: str
92+
):
93+
"""Ensure number entity unique_id format matches golden values.
94+
95+
WARNING: If this test fails, you are about to break user automations,
96+
dashboards, and entity history. You MUST provide a migration path.
97+
"""
98+
result = generate_number_unique_id(amp_name, zone_id, control_key)
99+
assert result == expected, (
100+
f'BREAKING CHANGE: number entity unique_id format changed!\n'
101+
f' Amp: {amp_name}, Zone: {zone_id}, Control: {control_key}\n'
102+
f' Expected: {expected}\n'
103+
f' Got: {result}\n'
104+
f'This will break existing user installations.'
105+
)
106+
107+
def test_all_control_keys_supported(self):
108+
"""Verify all audio control types generate valid unique_ids."""
109+
for control_key in ('bass', 'treble', 'balance'):
110+
result = generate_number_unique_id('Test', 11, control_key)
111+
assert result == f'xantech_test_zone_11_{control_key}'

0 commit comments

Comments
 (0)