Skip to content

Commit cb500a3

Browse files
authored
Limit fabric label to 32 characters with truncation and logging (#1229)
1 parent 6565bff commit cb500a3

File tree

2 files changed

+127
-0
lines changed

2 files changed

+127
-0
lines changed

matter_server/server/device_controller.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,13 @@ def get_node(self, node_id: int) -> MatterNodeData:
296296
@api_command(APICommand.SET_DEFAULT_FABRIC_LABEL)
297297
async def set_default_fabric_label(self, label: str | None) -> None:
298298
"""Set the default fabric label."""
299+
if label is not None and len(label) > 32:
300+
LOGGER.info(
301+
"Fabric label '%s' exceeds 32 characters, truncating to '%s'",
302+
label,
303+
label[:32],
304+
)
305+
label = label[:32]
299306
self._default_fabric_label = label
300307

301308
@api_command(APICommand.COMMISSION_WITH_CODE)

tests/test_fabric_label_validation.py

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
"""Test fabric label validation functionality."""
2+
# pylint: disable=protected-access
3+
4+
import logging
5+
6+
import pytest
7+
8+
9+
class MockDeviceController:
10+
"""Mock device controller with fabric label validation logic."""
11+
12+
def __init__(self):
13+
"""Initialize mock device controller."""
14+
self._default_fabric_label = None
15+
self.logger = logging.getLogger(__name__)
16+
17+
async def set_default_fabric_label(self, label: str | None) -> None:
18+
"""Set the default fabric label with validation."""
19+
if label is not None and len(label) > 32:
20+
self.logger.info(
21+
"Fabric label '%s' exceeds 32 characters, truncating to '%s'",
22+
label,
23+
label[:32],
24+
)
25+
label = label[:32]
26+
self._default_fabric_label = label
27+
28+
29+
class TestFabricLabelValidation:
30+
"""Test fabric label length validation."""
31+
32+
@pytest.fixture
33+
def mock_device_controller(self):
34+
"""Create a mock device controller for testing."""
35+
return MockDeviceController()
36+
37+
@pytest.mark.asyncio
38+
async def test_fabric_label_under_32_chars(self, mock_device_controller, caplog):
39+
"""Test that fabric labels under 32 characters are not modified."""
40+
label = "Short Label"
41+
42+
with caplog.at_level(logging.INFO):
43+
await mock_device_controller.set_default_fabric_label(label)
44+
45+
assert mock_device_controller._default_fabric_label == label
46+
assert not caplog.records # No log messages should be generated
47+
48+
@pytest.mark.asyncio
49+
async def test_fabric_label_exactly_32_chars(self, mock_device_controller, caplog):
50+
"""Test that fabric labels exactly 32 characters are not modified."""
51+
label = "A" * 32 # Exactly 32 characters
52+
53+
with caplog.at_level(logging.INFO):
54+
await mock_device_controller.set_default_fabric_label(label)
55+
56+
assert mock_device_controller._default_fabric_label == label
57+
assert len(mock_device_controller._default_fabric_label) == 32
58+
assert not caplog.records # No log messages should be generated
59+
60+
@pytest.mark.asyncio
61+
async def test_fabric_label_over_32_chars_truncated(
62+
self, mock_device_controller, caplog
63+
):
64+
"""Test that fabric labels over 32 characters are truncated."""
65+
long_label = (
66+
"This is a very long fabric label that exceeds the 32 character limit"
67+
)
68+
expected_truncated = long_label[:32]
69+
70+
with caplog.at_level(logging.INFO):
71+
await mock_device_controller.set_default_fabric_label(long_label)
72+
73+
assert mock_device_controller._default_fabric_label == expected_truncated
74+
assert len(mock_device_controller._default_fabric_label) == 32
75+
76+
# Check that a log message was generated
77+
assert len(caplog.records) == 1
78+
assert caplog.records[0].levelname == "INFO"
79+
assert "exceeds 32 characters, truncating" in caplog.records[0].message
80+
assert long_label in caplog.records[0].message
81+
assert expected_truncated in caplog.records[0].message
82+
83+
@pytest.mark.asyncio
84+
async def test_fabric_label_none_value(self, mock_device_controller, caplog):
85+
"""Test that None values are handled properly."""
86+
with caplog.at_level(logging.INFO):
87+
await mock_device_controller.set_default_fabric_label(None)
88+
89+
assert mock_device_controller._default_fabric_label is None
90+
assert not caplog.records # No log messages should be generated
91+
92+
@pytest.mark.asyncio
93+
async def test_fabric_label_empty_string(self, mock_device_controller, caplog):
94+
"""Test that empty strings are handled properly."""
95+
label = ""
96+
97+
with caplog.at_level(logging.INFO):
98+
await mock_device_controller.set_default_fabric_label(label)
99+
100+
assert mock_device_controller._default_fabric_label == label
101+
assert not caplog.records # No log messages should be generated
102+
103+
@pytest.mark.asyncio
104+
async def test_fabric_label_unicode_characters(
105+
self, mock_device_controller, caplog
106+
):
107+
"""Test that unicode characters are handled properly in truncation."""
108+
# Unicode string that's longer than 32 characters
109+
unicode_label = "Café™ ★ 🏠 This is a unicode string that exceeds 32 chars"
110+
expected_truncated = unicode_label[:32]
111+
112+
with caplog.at_level(logging.INFO):
113+
await mock_device_controller.set_default_fabric_label(unicode_label)
114+
115+
assert mock_device_controller._default_fabric_label == expected_truncated
116+
assert len(mock_device_controller._default_fabric_label) == 32
117+
118+
# Check that a log message was generated
119+
assert len(caplog.records) == 1
120+
assert "exceeds 32 characters, truncating" in caplog.records[0].message

0 commit comments

Comments
 (0)