From b203a04f1bf6c5fa6d9eb143ee47e80da32275bb Mon Sep 17 00:00:00 2001 From: hanwg Date: Tue, 26 Aug 2025 04:55:01 +0800 Subject: [PATCH] Add websocket command to rename config subentry (#150843) Co-authored-by: Artur Pragacz <49985303+arturpragacz@users.noreply.github.com> --- .../components/config/config_entries.py | 42 ++++++++++ .../components/config/test_config_entries.py | 76 +++++++++++++++++++ 2 files changed, 118 insertions(+) diff --git a/homeassistant/components/config/config_entries.py b/homeassistant/components/config/config_entries.py index 176c9e2b047..6766bce3f0a 100644 --- a/homeassistant/components/config/config_entries.py +++ b/homeassistant/components/config/config_entries.py @@ -62,6 +62,7 @@ def async_setup(hass: HomeAssistant) -> bool: websocket_api.async_register_command(hass, config_entries_flow_subscribe) websocket_api.async_register_command(hass, ignore_config_flow) + websocket_api.async_register_command(hass, config_subentry_update) websocket_api.async_register_command(hass, config_subentry_delete) websocket_api.async_register_command(hass, config_subentry_list) @@ -731,6 +732,47 @@ async def config_subentry_list( connection.send_result(msg["id"], result) +@websocket_api.require_admin +@websocket_api.websocket_command( + { + "type": "config_entries/subentries/update", + "entry_id": str, + "subentry_id": str, + vol.Optional("title"): str, + } +) +@websocket_api.async_response +async def config_subentry_update( + hass: HomeAssistant, + connection: websocket_api.ActiveConnection, + msg: dict[str, Any], +) -> None: + """Update a subentry of a config entry.""" + entry = get_entry(hass, connection, msg["entry_id"], msg["id"]) + if entry is None: + connection.send_error( + msg["entry_id"], websocket_api.const.ERR_NOT_FOUND, "Config entry not found" + ) + return + + subentry = entry.subentries.get(msg["subentry_id"]) + if subentry is None: + connection.send_error( + msg["id"], websocket_api.const.ERR_NOT_FOUND, "Config subentry not found" + ) + return + + changes = dict(msg) + changes.pop("id") + changes.pop("type") + changes.pop("entry_id") + changes.pop("subentry_id") + + hass.config_entries.async_update_subentry(entry, subentry, **changes) + + connection.send_result(msg["id"]) + + @websocket_api.require_admin @websocket_api.websocket_command( { diff --git a/tests/components/config/test_config_entries.py b/tests/components/config/test_config_entries.py index 8f89549944c..5819e632d60 100644 --- a/tests/components/config/test_config_entries.py +++ b/tests/components/config/test_config_entries.py @@ -3351,6 +3351,82 @@ async def test_list_subentries( } +async def test_update_subentry( + hass: HomeAssistant, hass_ws_client: WebSocketGenerator +) -> None: + """Test that we can update a subentry.""" + assert await async_setup_component(hass, "config", {}) + ws_client = await hass_ws_client(hass) + + entry = MockConfigEntry( + domain="test", + state=core_ce.ConfigEntryState.LOADED, + subentries_data=[ + core_ce.ConfigSubentryData( + data={"test": "test"}, + subentry_id="mock_id", + subentry_type="test", + title="Mock title", + unique_id="mock_unique_id", + ) + ], + ) + entry.add_to_hass(hass) + + await ws_client.send_json_auto_id( + { + "type": "config_entries/subentries/update", + "entry_id": entry.entry_id, + "subentry_id": "mock_id", + "title": "Updated Title", + } + ) + response = await ws_client.receive_json() + + assert response["success"] + assert response["result"] is None + + assert list(entry.subentries.values())[0].title == "Updated Title" + assert list(entry.subentries.values())[0].unique_id == "mock_unique_id" + assert list(entry.subentries.values())[0].data["test"] == "test" + + # Try renaming subentry from an unknown entry + ws_client = await hass_ws_client(hass) + await ws_client.send_json_auto_id( + { + "type": "config_entries/subentries/update", + "entry_id": "no_such_entry", + "subentry_id": "mock_id", + "title": "Updated Title", + } + ) + response = await ws_client.receive_json() + + assert not response["success"] + assert response["error"] == { + "code": "not_found", + "message": "Config entry not found", + } + + # Try renaming subentry from an unknown subentry + ws_client = await hass_ws_client(hass) + await ws_client.send_json_auto_id( + { + "type": "config_entries/subentries/update", + "entry_id": entry.entry_id, + "subentry_id": "no_such_entry2", + "title": "Updated Title2", + } + ) + response = await ws_client.receive_json() + + assert not response["success"] + assert response["error"] == { + "code": "not_found", + "message": "Config subentry not found", + } + + async def test_delete_subentry( hass: HomeAssistant, hass_ws_client: WebSocketGenerator ) -> None: