Skip to content

Commit 5de1d6e

Browse files
test: escape ideographic space and format file (#1237) (#1410)
Replace the raw U+3000 codepoint in test_identifier_validation_family.py with the Python escape sequence "\u3000" so Renovate's hidden-Unicode scan stops flagging the file. Same runtime value (Python parses the escape to the identical character). Also apply ruff format to the file (pre-existing drift; required for CI's per-file format check to pass). Refs #1237 Co-authored-by: kingpanther13 <kingpanther13@users.noreply.github.com>
1 parent 401b7b4 commit 5de1d6e

1 file changed

Lines changed: 30 additions & 88 deletions

File tree

tests/src/unit/test_identifier_validation_family.py

Lines changed: 30 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ class TestValidateIdentifierNotEmptyHelper:
5353
"\r",
5454
"\v",
5555
"\xa0", # non-breaking space (U+00A0)
56-
" ", # ideographic space (U+3000)
56+
"\u3000", # ideographic space (U+3000)
5757
],
5858
)
5959
def test_rejects_empty_or_whitespace(self, bad):
@@ -219,9 +219,7 @@ async def test_get_rejects_empty_category_id(self, tools, bad):
219219
@pytest.mark.parametrize("bad", ["", " "])
220220
async def test_remove_rejects_empty_category_id(self, tools, bad):
221221
with pytest.raises(ToolError) as excinfo:
222-
await tools.ha_config_remove_category(
223-
scope="automation", category_id=bad
224-
)
222+
await tools.ha_config_remove_category(scope="automation", category_id=bad)
225223
_assert_invalid_param(excinfo)
226224
tools._client.send_websocket_message.assert_not_called()
227225

@@ -327,9 +325,7 @@ def decorator(fn):
327325
async def test_create_rejects_whitespace_name(self, register_tools, mock_ws_client):
328326
set_helper = register_tools["ha_config_set_helper"]
329327
with pytest.raises(ToolError) as excinfo:
330-
await set_helper(
331-
helper_type="input_boolean", action="create", name=" "
332-
)
328+
await set_helper(helper_type="input_boolean", action="create", name=" ")
333329
_assert_invalid_param(excinfo)
334330
mock_ws_client.send_websocket_message.assert_not_called()
335331

@@ -364,9 +360,7 @@ async def test_implicit_action_with_empty_helper_id_rejects(
364360
for bad in ("", " "):
365361
mock_ws_client.send_websocket_message.reset_mock()
366362
with pytest.raises(ToolError) as excinfo:
367-
await set_helper(
368-
helper_type=helper_type, helper_id=bad, name="X"
369-
)
363+
await set_helper(helper_type=helper_type, helper_id=bad, name="X")
370364
_assert_invalid_param(excinfo)
371365
mock_ws_client.send_websocket_message.assert_not_called()
372366

@@ -378,9 +372,7 @@ async def test_flow_helper_create_rejects_whitespace_name(
378372
# downstream config-flow build proceeds with a name HA cannot use.
379373
set_helper = register_tools["ha_config_set_helper"]
380374
with pytest.raises(ToolError) as excinfo:
381-
await set_helper(
382-
helper_type="utility_meter", action="create", name=" "
383-
)
375+
await set_helper(helper_type="utility_meter", action="create", name=" ")
384376
_assert_invalid_param(excinfo)
385377
# The pre-flow gate runs before any flow start — no WS round-trip.
386378
mock_ws_client.send_websocket_message.assert_not_called()
@@ -439,9 +431,7 @@ async def test_skips_ws_call_on_empty_or_whitespace_name(
439431
from ha_mcp.tools.tools_config_helpers import _check_name_collision
440432

441433
# The early-return runs before any WS message is constructed.
442-
result = await _check_name_collision(
443-
mock_ws_client, "input_boolean", bad_name
444-
)
434+
result = await _check_name_collision(mock_ws_client, "input_boolean", bad_name)
445435
assert result is None
446436
mock_ws_client.send_websocket_message.assert_not_called()
447437

@@ -562,9 +552,7 @@ async def test_get_rejects_empty_identifier(self, tools, bad):
562552
with pytest.raises(ToolError) as excinfo:
563553
await tools.ha_config_get_automation(identifier=bad)
564554
_assert_invalid_param(excinfo)
565-
assert '"parameter": "identifier"' in str(excinfo.value), str(
566-
excinfo.value
567-
)
555+
assert '"parameter": "identifier"' in str(excinfo.value), str(excinfo.value)
568556
tools._client.send_websocket_message.assert_not_called()
569557
# Also no REST GET on the automation config.
570558
tools._client.get_automation_config.assert_not_called()
@@ -583,9 +571,7 @@ async def test_set_rejects_empty_identifier_on_update(self, tools, bad):
583571
identifier=bad,
584572
)
585573
_assert_invalid_param(excinfo)
586-
assert '"parameter": "identifier"' in str(excinfo.value), str(
587-
excinfo.value
588-
)
574+
assert '"parameter": "identifier"' in str(excinfo.value), str(excinfo.value)
589575
# Guard fires before any upsert / WS round-trip.
590576
tools._client.upsert_automation_config.assert_not_called()
591577

@@ -638,9 +624,7 @@ async def test_get_rejects_empty_script_id(self, tools, bad):
638624
with pytest.raises(ToolError) as excinfo:
639625
await tools.ha_config_get_script(script_id=bad)
640626
_assert_invalid_param(excinfo)
641-
assert '"parameter": "script_id"' in str(excinfo.value), str(
642-
excinfo.value
643-
)
627+
assert '"parameter": "script_id"' in str(excinfo.value), str(excinfo.value)
644628
tools._client.get_script_config.assert_not_called()
645629

646630
@pytest.mark.parametrize("bad", ["", " "])
@@ -654,9 +638,7 @@ async def test_set_rejects_empty_script_id(self, tools, bad):
654638
config={"sequence": [{"delay": {"seconds": 1}}]},
655639
)
656640
_assert_invalid_param(excinfo)
657-
assert '"parameter": "script_id"' in str(excinfo.value), str(
658-
excinfo.value
659-
)
641+
assert '"parameter": "script_id"' in str(excinfo.value), str(excinfo.value)
660642
tools._client.upsert_script_config.assert_not_called()
661643

662644

@@ -689,9 +671,7 @@ def decorator(fn):
689671

690672
class TestDashboardsIdentifierValidation:
691673
@pytest.mark.parametrize("bad", ["", " "])
692-
async def test_get_dashboard_rejects_empty_url_path(
693-
self, mock_ws_client, bad
694-
):
674+
async def test_get_dashboard_rejects_empty_url_path(self, mock_ws_client, bad):
695675
# ``url_path`` is optional (omit + ``list_only=True`` lists all).
696676
# When provided, empty/whitespace would slip past the list_only
697677
# check and reach the search-mode / get-mode WS dispatch. Guard
@@ -702,9 +682,7 @@ async def test_get_dashboard_rejects_empty_url_path(
702682
with pytest.raises(ToolError) as excinfo:
703683
await ha_config_get_dashboard(url_path=bad)
704684
_assert_invalid_param(excinfo)
705-
assert '"parameter": "url_path"' in str(excinfo.value), str(
706-
excinfo.value
707-
)
685+
assert '"parameter": "url_path"' in str(excinfo.value), str(excinfo.value)
708686
mock_ws_client.send_websocket_message.assert_not_called()
709687

710688
async def test_get_dashboard_list_only_skips_guard(self, mock_ws_client):
@@ -764,9 +742,7 @@ async def test_get_dashboard_with_none_url_path_routes_to_default(
764742
)
765743

766744
@pytest.mark.parametrize("bad", ["", " "])
767-
async def test_set_dashboard_rejects_empty_url_path(
768-
self, mock_ws_client, bad
769-
):
745+
async def test_set_dashboard_rejects_empty_url_path(self, mock_ws_client, bad):
770746
# ``url_path`` is required for set_dashboard. Empty/whitespace
771747
# would pass the ``"default"`` alias (False), reach the
772748
# pre-resolver / hyphen-check, and surface as a misleading
@@ -778,15 +754,11 @@ async def test_set_dashboard_rejects_empty_url_path(
778754
with pytest.raises(ToolError) as excinfo:
779755
await ha_config_set_dashboard(url_path=bad, config={"views": []})
780756
_assert_invalid_param(excinfo)
781-
assert '"parameter": "url_path"' in str(excinfo.value), str(
782-
excinfo.value
783-
)
757+
assert '"parameter": "url_path"' in str(excinfo.value), str(excinfo.value)
784758
mock_ws_client.send_websocket_message.assert_not_called()
785759

786760
@pytest.mark.parametrize("bad", ["", " "])
787-
async def test_delete_dashboard_rejects_empty_url_path(
788-
self, mock_ws_client, bad
789-
):
761+
async def test_delete_dashboard_rejects_empty_url_path(self, mock_ws_client, bad):
790762
# ``url_path`` is required for delete_dashboard. Empty/whitespace
791763
# would reach ``_resolve_dashboard`` and surface as a misleading
792764
# "no dashboard found" — the guard names the actual problem
@@ -797,9 +769,7 @@ async def test_delete_dashboard_rejects_empty_url_path(
797769
with pytest.raises(ToolError) as excinfo:
798770
await ha_config_delete_dashboard(url_path=bad)
799771
_assert_invalid_param(excinfo)
800-
assert '"parameter": "url_path"' in str(excinfo.value), str(
801-
excinfo.value
802-
)
772+
assert '"parameter": "url_path"' in str(excinfo.value), str(excinfo.value)
803773
mock_ws_client.send_websocket_message.assert_not_called()
804774

805775

@@ -835,9 +805,7 @@ async def test_set_rejects_empty_object_id(self, tools, bad):
835805
# misleading HA service-call failure. Symmetric with the
836806
# ``ha_config_remove_group`` pre-flight added in this PR.
837807
with pytest.raises(ToolError) as excinfo:
838-
await tools.ha_config_set_group(
839-
object_id=bad, entities=["light.example"]
840-
)
808+
await tools.ha_config_set_group(object_id=bad, entities=["light.example"])
841809
_assert_invalid_param(excinfo)
842810
assert '"parameter": "object_id"' in str(excinfo.value), str(excinfo.value)
843811
tools._client.call_service.assert_not_called()
@@ -958,9 +926,7 @@ async def test_delete_helpers_integrations_rejects_empty_target(
958926
tools._client.delete_config_entry.assert_not_called()
959927

960928
@pytest.mark.parametrize("bad", ["", " "])
961-
async def test_set_integration_enabled_rejects_empty_entry_id(
962-
self, tools, bad
963-
):
929+
async def test_set_integration_enabled_rejects_empty_entry_id(self, tools, bad):
964930
# ``entry_id`` is passed straight into ``config_entries/disable``;
965931
# without the guard, ``entry_id=""`` would surface as a misleading
966932
# HA "config entry not found".
@@ -1012,17 +978,13 @@ async def test_remove_item_rejects_empty_item(self, tools, bad):
1012978
# new guard, ``item=""`` would surface as a misleading HA
1013979
# "item not found".
1014980
with pytest.raises(ToolError) as excinfo:
1015-
await tools.ha_remove_todo_item(
1016-
entity_id="todo.shopping_list", item=bad
1017-
)
981+
await tools.ha_remove_todo_item(entity_id="todo.shopping_list", item=bad)
1018982
_assert_invalid_param(excinfo)
1019983
assert '"parameter": "item"' in str(excinfo.value), str(excinfo.value)
1020984
tools._client.call_service.assert_not_called()
1021985

1022986
@pytest.mark.parametrize("bad", ["", " "])
1023-
async def test_set_item_rejects_empty_item_on_implicit_update(
1024-
self, tools, bad
1025-
):
987+
async def test_set_item_rejects_empty_item_on_implicit_update(self, tools, bad):
1026988
# ``item`` is the implicit create/update discriminator: ``None``
1027989
# routes to create, non-None to update. Without the new guard,
1028990
# ``item=""`` would route to update (``"" is None`` is False) and
@@ -1069,9 +1031,7 @@ def decorator(fn):
10691031

10701032
class TestEntitiesIdentifierValidation:
10711033
@pytest.mark.parametrize("bad", ["", " "])
1072-
async def test_remove_entity_rejects_empty_entity_id(
1073-
self, mock_ws_client, bad
1074-
):
1034+
async def test_remove_entity_rejects_empty_entity_id(self, mock_ws_client, bad):
10751035
# ``entity_id`` is passed straight into the
10761036
# ``config/entity_registry/remove`` WS message; without the new
10771037
# guard, ``entity_id=""`` surfaces as a misleading HA
@@ -1082,15 +1042,11 @@ async def test_remove_entity_rejects_empty_entity_id(
10821042
with pytest.raises(ToolError) as excinfo:
10831043
await ha_remove_entity(entity_id=bad)
10841044
_assert_invalid_param(excinfo)
1085-
assert '"parameter": "entity_id"' in str(excinfo.value), str(
1086-
excinfo.value
1087-
)
1045+
assert '"parameter": "entity_id"' in str(excinfo.value), str(excinfo.value)
10881046
mock_ws_client.send_websocket_message.assert_not_called()
10891047

10901048
@pytest.mark.parametrize("bad", ["", " "])
1091-
async def test_set_entity_rejects_empty_entity_id_str(
1092-
self, mock_ws_client, bad
1093-
):
1049+
async def test_set_entity_rejects_empty_entity_id_str(self, mock_ws_client, bad):
10941050
# ``ha_set_entity`` accepts ``entity_id: str | list[str]``. The
10951051
# existing list-empty check rejects ``[]`` but lets ``[""]``
10961052
# through; for the string input path, ``""`` was normalised to
@@ -1102,9 +1058,7 @@ async def test_set_entity_rejects_empty_entity_id_str(
11021058
with pytest.raises(ToolError) as excinfo:
11031059
await ha_set_entity(entity_id=bad, name="New Name")
11041060
_assert_invalid_param(excinfo)
1105-
assert '"parameter": "entity_id"' in str(excinfo.value), str(
1106-
excinfo.value
1107-
)
1061+
assert '"parameter": "entity_id"' in str(excinfo.value), str(excinfo.value)
11081062
mock_ws_client.send_websocket_message.assert_not_called()
11091063

11101064
@pytest.mark.parametrize("bad", ["", " "])
@@ -1123,9 +1077,7 @@ async def test_set_entity_rejects_empty_entity_id_in_list(
11231077
categories={"automation": "cat_id"},
11241078
)
11251079
_assert_invalid_param(excinfo)
1126-
assert '"parameter": "entity_id"' in str(excinfo.value), str(
1127-
excinfo.value
1128-
)
1080+
assert '"parameter": "entity_id"' in str(excinfo.value), str(excinfo.value)
11291081
mock_ws_client.send_websocket_message.assert_not_called()
11301082

11311083

@@ -1152,9 +1104,7 @@ def decorator(fn):
11521104

11531105
class TestRegistryIdentifierValidation:
11541106
@pytest.mark.parametrize("bad", ["", " "])
1155-
async def test_remove_device_rejects_empty_device_id(
1156-
self, mock_ws_client, bad
1157-
):
1107+
async def test_remove_device_rejects_empty_device_id(self, mock_ws_client, bad):
11581108
# Empty/whitespace ``device_id`` would slip past the local-filter
11591109
# check (``next((d for d in devices if d.get("id") == device_id)...)``)
11601110
# after wasting a ``config/device_registry/list`` round-trip, and
@@ -1166,15 +1116,11 @@ async def test_remove_device_rejects_empty_device_id(
11661116
with pytest.raises(ToolError) as excinfo:
11671117
await ha_remove_device(device_id=bad)
11681118
_assert_invalid_param(excinfo)
1169-
assert '"parameter": "device_id"' in str(excinfo.value), str(
1170-
excinfo.value
1171-
)
1119+
assert '"parameter": "device_id"' in str(excinfo.value), str(excinfo.value)
11721120
mock_ws_client.send_websocket_message.assert_not_called()
11731121

11741122
@pytest.mark.parametrize("bad", ["", " "])
1175-
async def test_update_device_rejects_empty_device_id(
1176-
self, mock_ws_client, bad
1177-
):
1123+
async def test_update_device_rejects_empty_device_id(self, mock_ws_client, bad):
11781124
# ``device_id`` is passed straight through ``ha_update_device`` to
11791125
# ``_update_device_internal`` which builds a
11801126
# ``config/device_registry/update`` WS message; without the new
@@ -1187,9 +1133,7 @@ async def test_update_device_rejects_empty_device_id(
11871133
with pytest.raises(ToolError) as excinfo:
11881134
await ha_update_device(device_id=bad, name="New Name")
11891135
_assert_invalid_param(excinfo)
1190-
assert '"parameter": "device_id"' in str(excinfo.value), str(
1191-
excinfo.value
1192-
)
1136+
assert '"parameter": "device_id"' in str(excinfo.value), str(excinfo.value)
11931137
mock_ws_client.send_websocket_message.assert_not_called()
11941138

11951139

@@ -1295,7 +1239,5 @@ async def test_hacs_download_rejects_empty_repository_id(self, tools, bad):
12951239
with pytest.raises(ToolError) as excinfo:
12961240
await tools.ha_hacs_download(repository_id=bad)
12971241
_assert_invalid_param(excinfo)
1298-
assert '"parameter": "repository_id"' in str(excinfo.value), str(
1299-
excinfo.value
1300-
)
1242+
assert '"parameter": "repository_id"' in str(excinfo.value), str(excinfo.value)
13011243
tools._client.send_websocket_message.assert_not_called()

0 commit comments

Comments
 (0)