Improve test of WS command get_services (#150901)

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Erik Montnemery
2025-08-19 17:47:12 +02:00
committed by GitHub
parent 26582cecbd
commit 48091e5995
2 changed files with 207 additions and 6 deletions

View File

@@ -0,0 +1,136 @@
# serializer version: 1
# name: test_get_services
dict({
'reload': dict({
'description': 'Reloads group configuration, entities, and notify services from YAML-configuration.',
'fields': dict({
}),
'name': 'Reload',
}),
'remove': dict({
'description': 'Removes a group.',
'fields': dict({
'object_id': dict({
'description': 'Object ID of this group. This object ID is used as part of the entity ID. Entity ID format: [domain].[object_id].',
'example': 'test_group',
'name': 'Object ID',
'required': True,
'selector': dict({
'object': dict({
}),
}),
}),
}),
'name': 'Remove',
}),
'set': dict({
'description': 'Creates/Updates a group.',
'fields': dict({
'add_entities': dict({
'description': 'List of members to be added to the group. Cannot be used in combination with `Entities` or `Remove entities`.',
'example': 'domain.entity_id1, domain.entity_id2',
'name': 'Add entities',
'selector': dict({
'entity': dict({
'multiple': True,
'reorder': False,
}),
}),
}),
'all': dict({
'description': 'Enable this option if the group should only be used when all entities are in state `on`.',
'name': 'All',
'selector': dict({
'boolean': dict({
}),
}),
}),
'entities': dict({
'description': 'List of all members in the group. Cannot be used in combination with `Add entities` or `Remove entities`.',
'example': 'domain.entity_id1, domain.entity_id2',
'name': 'Entities',
'selector': dict({
'entity': dict({
'multiple': True,
'reorder': False,
}),
}),
}),
'icon': dict({
'description': 'Name of the icon for the group.',
'example': 'mdi:camera',
'name': 'Icon',
'selector': dict({
'icon': dict({
}),
}),
}),
'name': dict({
'description': 'Name of the group.',
'example': 'My test group',
'name': 'Name',
'selector': dict({
'text': dict({
}),
}),
}),
'object_id': dict({
'description': 'Object ID of this group. This object ID is used as part of the entity ID. Entity ID format: [domain].[object_id].',
'example': 'test_group',
'name': 'Object ID',
'required': True,
'selector': dict({
'text': dict({
}),
}),
}),
'remove_entities': dict({
'description': 'List of members to be removed from a group. Cannot be used in combination with `Entities` or `Add entities`.',
'example': 'domain.entity_id1, domain.entity_id2',
'name': 'Remove entities',
'selector': dict({
'entity': dict({
'multiple': True,
'reorder': False,
}),
}),
}),
}),
'name': 'Set',
}),
})
# ---
# name: test_get_services.1
dict({
'set_default_level': dict({
'description': 'Translated description',
'fields': dict({
'level': dict({
'description': 'Field description',
'example': 'Field example',
'name': 'Field name',
'selector': dict({
'select': dict({
'options': list([
'debug',
'info',
'warning',
'error',
'fatal',
'critical',
]),
'translation_key': 'level',
}),
}),
}),
}),
'name': 'Translated name',
}),
'set_level': dict({
'description': '',
'fields': dict({
}),
'name': '',
}),
})
# ---

View File

@@ -8,10 +8,13 @@ from typing import Any
from unittest.mock import ANY, AsyncMock, Mock, patch from unittest.mock import ANY, AsyncMock, Mock, patch
import pytest import pytest
from syrupy.assertion import SnapshotAssertion
import voluptuous as vol import voluptuous as vol
from homeassistant import loader from homeassistant import loader
from homeassistant.components.device_automation import toggle_entity from homeassistant.components.device_automation import toggle_entity
from homeassistant.components.group import DOMAIN as DOMAIN_GROUP
from homeassistant.components.logger import DOMAIN as DOMAIN_LOGGER
from homeassistant.components.websocket_api import const from homeassistant.components.websocket_api import const
from homeassistant.components.websocket_api.auth import ( from homeassistant.components.websocket_api.auth import (
TYPE_AUTH, TYPE_AUTH,
@@ -34,7 +37,7 @@ from homeassistant.helpers.event import async_track_state_change_event
from homeassistant.loader import Integration, async_get_integration from homeassistant.loader import Integration, async_get_integration
from homeassistant.setup import async_set_domains_to_be_loaded, async_setup_component from homeassistant.setup import async_set_domains_to_be_loaded, async_setup_component
from homeassistant.util.json import json_loads from homeassistant.util.json import json_loads
from homeassistant.util.yaml.loader import parse_yaml from homeassistant.util.yaml.loader import JSON_TYPE, parse_yaml
from tests.common import ( from tests.common import (
MockConfigEntry, MockConfigEntry,
@@ -671,7 +674,9 @@ async def test_get_states(
async def test_get_services( async def test_get_services(
hass: HomeAssistant, websocket_client: MockHAClientWebSocket hass: HomeAssistant,
websocket_client: MockHAClientWebSocket,
snapshot: SnapshotAssertion,
) -> None: ) -> None:
"""Test get_services command.""" """Test get_services command."""
assert ALL_SERVICE_DESCRIPTIONS_JSON_CACHE not in hass.data assert ALL_SERVICE_DESCRIPTIONS_JSON_CACHE not in hass.data
@@ -686,16 +691,18 @@ async def test_get_services(
assert msg == {"id": 2, "result": {}, "success": True, "type": "result"} assert msg == {"id": 2, "result": {}, "success": True, "type": "result"}
assert hass.data[ALL_SERVICE_DESCRIPTIONS_JSON_CACHE] is old_cache assert hass.data[ALL_SERVICE_DESCRIPTIONS_JSON_CACHE] is old_cache
# Load a service and check cache is updated # Set up an integration that has services and check cache is updated
assert await async_setup_component(hass, "logger", {}) assert await async_setup_component(hass, DOMAIN_GROUP, {DOMAIN_GROUP: {}})
await websocket_client.send_json_auto_id({"type": "get_services"}) await websocket_client.send_json_auto_id({"type": "get_services"})
msg = await websocket_client.receive_json() msg = await websocket_client.receive_json()
assert msg == { assert msg == {
"id": 3, "id": 3,
"result": {"logger": {"set_default_level": ANY, "set_level": ANY}}, "result": {DOMAIN_GROUP: ANY},
"success": True, "success": True,
"type": "result", "type": "result",
} }
group_services = msg["result"][DOMAIN_GROUP]
assert group_services == snapshot
assert hass.data[ALL_SERVICE_DESCRIPTIONS_JSON_CACHE] is not old_cache assert hass.data[ALL_SERVICE_DESCRIPTIONS_JSON_CACHE] is not old_cache
# Check cache is reused # Check cache is reused
@@ -704,12 +711,70 @@ async def test_get_services(
msg = await websocket_client.receive_json() msg = await websocket_client.receive_json()
assert msg == { assert msg == {
"id": 4, "id": 4,
"result": {"logger": {"set_default_level": ANY, "set_level": ANY}}, "result": {DOMAIN_GROUP: group_services},
"success": True, "success": True,
"type": "result", "type": "result",
} }
assert hass.data[ALL_SERVICE_DESCRIPTIONS_JSON_CACHE] is old_cache assert hass.data[ALL_SERVICE_DESCRIPTIONS_JSON_CACHE] is old_cache
# Set up an integration with legacy translations in services.yaml
def _load_services_file(hass: HomeAssistant, integration: Integration) -> JSON_TYPE:
return {
"set_default_level": {
"description": "Translated description",
"fields": {
"level": {
"description": "Field description",
"example": "Field example",
"name": "Field name",
"selector": {
"select": {
"options": [
"debug",
"info",
"warning",
"error",
"fatal",
"critical",
],
"translation_key": "level",
}
},
}
},
"name": "Translated name",
},
"set_level": None,
}
await async_setup_component(hass, DOMAIN_LOGGER, {DOMAIN_LOGGER: {}})
await hass.async_block_till_done()
with (
patch(
"homeassistant.helpers.service._load_services_file",
side_effect=_load_services_file,
),
patch(
"homeassistant.helpers.service.translation.async_get_translations",
return_value={},
),
):
await websocket_client.send_json_auto_id({"type": "get_services"})
msg = await websocket_client.receive_json()
assert msg == {
"id": 5,
"result": {
DOMAIN_LOGGER: ANY,
DOMAIN_GROUP: group_services,
},
"success": True,
"type": "result",
}
logger_services = msg["result"][DOMAIN_LOGGER]
assert logger_services == snapshot
@patch("annotatedyaml.loader.load_yaml") @patch("annotatedyaml.loader.load_yaml")
@patch.object(Integration, "has_conditions", return_value=True) @patch.object(Integration, "has_conditions", return_value=True)