Integrations v2.1: Differentiating hubs, devices and services (#80524)

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
This commit is contained in:
Franck Nijhof 2022-10-19 12:41:43 +02:00 committed by GitHub
parent 0c8884fd51
commit c4bbc439a5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 1376 additions and 105 deletions

View file

@ -6,5 +6,6 @@
"requirements": ["adguardhome==0.5.1"], "requirements": ["adguardhome==0.5.1"],
"codeowners": ["@frenck"], "codeowners": ["@frenck"],
"iot_class": "local_polling", "iot_class": "local_polling",
"integration_type": "service",
"loggers": ["adguardhome"] "loggers": ["adguardhome"]
} }

View file

@ -15,6 +15,7 @@ from homeassistant.components import websocket_api
from homeassistant.components.http import HomeAssistantView from homeassistant.components.http import HomeAssistantView
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import DependencyError, Unauthorized from homeassistant.exceptions import DependencyError, Unauthorized
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.data_entry_flow import ( from homeassistant.helpers.data_entry_flow import (
FlowManagerIndexView, FlowManagerIndexView,
FlowManagerResourceView, FlowManagerResourceView,
@ -64,7 +65,7 @@ class ConfigManagerEntryIndexView(HomeAssistantView):
domain = request.query["domain"] domain = request.query["domain"]
type_filter = None type_filter = None
if "type" in request.query: if "type" in request.query:
type_filter = request.query["type"] type_filter = [request.query["type"]]
return self.json(await async_matching_config_entries(hass, type_filter, domain)) return self.json(await async_matching_config_entries(hass, type_filter, domain))
@ -405,7 +406,7 @@ async def ignore_config_flow(
@websocket_api.websocket_command( @websocket_api.websocket_command(
{ {
vol.Required("type"): "config_entries/get", vol.Required("type"): "config_entries/get",
vol.Optional("type_filter"): str, vol.Optional("type_filter"): vol.All(cv.ensure_list, [str]),
vol.Optional("domain"): str, vol.Optional("domain"): str,
} }
) )
@ -427,7 +428,7 @@ async def config_entries_get(
@websocket_api.websocket_command( @websocket_api.websocket_command(
{ {
vol.Required("type"): "config_entries/subscribe", vol.Required("type"): "config_entries/subscribe",
vol.Optional("type_filter"): str, vol.Optional("type_filter"): vol.All(cv.ensure_list, [str]),
} }
) )
@websocket_api.async_response @websocket_api.async_response
@ -445,7 +446,7 @@ async def config_entries_subscribe(
"""Forward config entry state events to websocket.""" """Forward config entry state events to websocket."""
if type_filter: if type_filter:
integration = await async_get_integration(hass, entry.domain) integration = await async_get_integration(hass, entry.domain)
if integration.integration_type != type_filter: if integration.integration_type not in type_filter:
return return
connection.send_message( connection.send_message(
@ -475,7 +476,7 @@ async def config_entries_subscribe(
async def async_matching_config_entries( async def async_matching_config_entries(
hass: HomeAssistant, type_filter: str | None, domain: str | None hass: HomeAssistant, type_filter: list[str] | None, domain: str | None
) -> list[dict[str, Any]]: ) -> list[dict[str, Any]]:
"""Return matching config entries by type and/or domain.""" """Return matching config entries by type and/or domain."""
kwargs = {} kwargs = {}
@ -483,7 +484,7 @@ async def async_matching_config_entries(
kwargs["domain"] = domain kwargs["domain"] = domain
entries = hass.config_entries.async_entries(**kwargs) entries = hass.config_entries.async_entries(**kwargs)
if type_filter is None: if not type_filter:
return [entry_json(entry) for entry in entries] return [entry_json(entry) for entry in entries]
integrations = {} integrations = {}
@ -499,13 +500,17 @@ async def async_matching_config_entries(
elif not isinstance(integration_or_exc, IntegrationNotFound): elif not isinstance(integration_or_exc, IntegrationNotFound):
raise integration_or_exc raise integration_or_exc
# Filter out entries that don't match the type filter
# when only helpers are requested, also filter out entries
# from unknown integrations. This prevent them from showing
# up in the helpers UI.
entries = [ entries = [
entry entry
for entry in entries for entry in entries
if (type_filter != "helper" and entry.domain not in integrations) if (type_filter != ["helper"] and entry.domain not in integrations)
or ( or (
entry.domain in integrations entry.domain in integrations
and integrations[entry.domain].integration_type == type_filter and integrations[entry.domain].integration_type in type_filter
) )
] ]

View file

@ -9,5 +9,6 @@
"codeowners": ["@OttoWinter", "@jesserockz"], "codeowners": ["@OttoWinter", "@jesserockz"],
"after_dependencies": ["bluetooth", "zeroconf", "tag"], "after_dependencies": ["bluetooth", "zeroconf", "tag"],
"iot_class": "local_push", "iot_class": "local_push",
"integration_type": "device",
"loggers": ["aioesphomeapi", "noiseprotocol"] "loggers": ["aioesphomeapi", "noiseprotocol"]
} }

View file

@ -25,5 +25,6 @@
"codeowners": ["@balloob", "@marcelveldt"], "codeowners": ["@balloob", "@marcelveldt"],
"quality_scale": "platinum", "quality_scale": "platinum",
"iot_class": "local_push", "iot_class": "local_push",
"integration_type": "hub",
"loggers": ["aiohue"] "loggers": ["aiohue"]
} }

File diff suppressed because it is too large Load diff

View file

@ -130,7 +130,9 @@ class Manifest(TypedDict, total=False):
name: str name: str
disabled: str disabled: str
domain: str domain: str
integration_type: Literal["entity", "integration", "hardware", "helper", "system"] integration_type: Literal[
"entity", "device", "hardware", "helper", "hub", "service", "system"
]
dependencies: list[str] dependencies: list[str]
after_dependencies: list[str] after_dependencies: list[str]
requirements: list[str] requirements: list[str]
@ -224,7 +226,7 @@ async def async_get_custom_components(
async def async_get_config_flows( async def async_get_config_flows(
hass: HomeAssistant, hass: HomeAssistant,
type_filter: Literal["helper", "integration"] | None = None, type_filter: Literal["device", "helper", "hub", "service"] | None = None,
) -> set[str]: ) -> set[str]:
"""Return cached list of config flows.""" """Return cached list of config flows."""
# pylint: disable=import-outside-toplevel # pylint: disable=import-outside-toplevel
@ -262,9 +264,11 @@ async def async_get_integration_descriptions(
core_flows: dict[str, Any] = json_loads(flow) core_flows: dict[str, Any] = json_loads(flow)
custom_integrations = await async_get_custom_components(hass) custom_integrations = await async_get_custom_components(hass)
custom_flows: dict[str, Any] = { custom_flows: dict[str, Any] = {
"integration": {}, "device": {},
"hardware": {}, "hardware": {},
"helper": {}, "helper": {},
"hub": {},
"service": {},
} }
for integration in custom_integrations.values(): for integration in custom_integrations.values():
@ -272,7 +276,7 @@ async def async_get_integration_descriptions(
if integration.integration_type in ("entity", "system"): if integration.integration_type in ("entity", "system"):
continue continue
for integration_type in ("integration", "hardware", "helper"): for integration_type in ("device", "hardware", "helper", "hub", "service"):
if integration.domain not in core_flows[integration_type]: if integration.domain not in core_flows[integration_type]:
continue continue
del core_flows[integration_type][integration.domain] del core_flows[integration_type][integration.domain]
@ -281,6 +285,7 @@ async def async_get_integration_descriptions(
metadata = { metadata = {
"config_flow": integration.config_flow, "config_flow": integration.config_flow,
"integration_type": integration.integration_type,
"iot_class": integration.iot_class, "iot_class": integration.iot_class,
"name": integration.name, "name": integration.name,
} }
@ -599,9 +604,9 @@ class Integration:
@property @property
def integration_type( def integration_type(
self, self,
) -> Literal["entity", "integration", "hardware", "helper", "system"]: ) -> Literal["entity", "device", "hardware", "helper", "hub", "service", "system"]:
"""Return the integration type.""" """Return the integration type."""
return self.manifest.get("integration_type", "integration") return self.manifest.get("integration_type", "hub")
@property @property
def mqtt(self) -> list[str] | None: def mqtt(self) -> list[str] | None:

View file

@ -86,7 +86,10 @@ def _generate_and_validate(integrations: dict[str, Integration], config: Config)
_validate_integration(config, integration) _validate_integration(config, integration)
domains[integration.integration_type].append(domain) if integration.integration_type == "helper":
domains["helper"].append(domain)
else:
domains["integration"].append(domain)
return black.format_str(BASE.format(to_string(domains)), mode=black.Mode()) return black.format_str(BASE.format(to_string(domains)), mode=black.Mode())
@ -106,6 +109,7 @@ def _populate_brand_integrations(
metadata = {} metadata = {}
metadata["config_flow"] = integration.config_flow metadata["config_flow"] = integration.config_flow
metadata["iot_class"] = integration.iot_class metadata["iot_class"] = integration.iot_class
metadata["integration_type"] = integration.integration_type
if integration.translated_name: if integration.translated_name:
integration_data["translated_name"].add(domain) integration_data["translated_name"].add(domain)
else: else:
@ -169,11 +173,16 @@ def _generate_integrations(
continue continue
metadata["config_flow"] = integration.config_flow metadata["config_flow"] = integration.config_flow
metadata["iot_class"] = integration.iot_class metadata["iot_class"] = integration.iot_class
metadata["integration_type"] = integration.integration_type
if integration.translated_name: if integration.translated_name:
result["translated_name"].add(domain) result["translated_name"].add(domain)
else: else:
metadata["name"] = integration.name metadata["name"] = integration.name
result[integration.integration_type][domain] = metadata
if integration.integration_type == "helper":
result["helper"][domain] = metadata
else:
result["integration"][domain] = metadata
return json.dumps( return json.dumps(
result | {"translated_name": sorted(result["translated_name"])}, indent=2 result | {"translated_name": sorted(result["translated_name"])}, indent=2

View file

@ -162,8 +162,16 @@ MANIFEST_SCHEMA = vol.Schema(
{ {
vol.Required("domain"): str, vol.Required("domain"): str,
vol.Required("name"): str, vol.Required("name"): str,
vol.Optional("integration_type"): vol.In( vol.Optional("integration_type", default="hub"): vol.In(
["entity", "hardware", "helper", "system"] [
"device",
"entity",
"hardware",
"helper",
"hub",
"service",
"system",
]
), ),
vol.Optional("config_flow"): bool, vol.Optional("config_flow"): bool,
vol.Optional("mqtt"): [str], vol.Optional("mqtt"): [str],

View file

@ -177,7 +177,7 @@ class Integration:
@property @property
def integration_type(self) -> str: def integration_type(self) -> str:
"""Get integration_type.""" """Get integration_type."""
return self.manifest.get("integration_type", "integration") return self.manifest.get("integration_type", "hub")
@property @property
def iot_class(self) -> str | None: def iot_class(self) -> str | None:

View file

@ -37,7 +37,6 @@ async def test_options_flow_disabled_not_setup(
"id": 5, "id": 5,
"type": "config_entries/get", "type": "config_entries/get",
"domain": "bluetooth", "domain": "bluetooth",
"type_filter": "integration",
} }
) )
response = await ws_client.receive_json() response = await ws_client.receive_json()
@ -341,7 +340,6 @@ async def test_options_flow_disabled_macos(
"id": 5, "id": 5,
"type": "config_entries/get", "type": "config_entries/get",
"domain": "bluetooth", "domain": "bluetooth",
"type_filter": "integration",
} }
) )
response = await ws_client.receive_json() response = await ws_client.receive_json()
@ -371,7 +369,6 @@ async def test_options_flow_enabled_linux(
"id": 5, "id": 5,
"type": "config_entries/get", "type": "config_entries/get",
"domain": "bluetooth", "domain": "bluetooth",
"type_filter": "integration",
} }
) )
response = await ws_client.receive_json() response = await ws_client.receive_json()

View file

@ -51,7 +51,15 @@ async def test_get_entries(hass, client, clear_handlers):
mock_integration( mock_integration(
hass, MockModule("comp2", partial_manifest={"integration_type": "helper"}) hass, MockModule("comp2", partial_manifest={"integration_type": "helper"})
) )
mock_integration(hass, MockModule("comp3")) mock_integration(
hass, MockModule("comp3", partial_manifest={"integration_type": "hub"})
)
mock_integration(
hass, MockModule("comp4", partial_manifest={"integration_type": "device"})
)
mock_integration(
hass, MockModule("comp5", partial_manifest={"integration_type": "service"})
)
@HANDLERS.register("comp1") @HANDLERS.register("comp1")
class Comp1ConfigFlow: class Comp1ConfigFlow:
@ -91,6 +99,16 @@ async def test_get_entries(hass, client, clear_handlers):
source="bla3", source="bla3",
disabled_by=core_ce.ConfigEntryDisabler.USER, disabled_by=core_ce.ConfigEntryDisabler.USER,
).add_to_hass(hass) ).add_to_hass(hass)
MockConfigEntry(
domain="comp4",
title="Test 4",
source="bla4",
).add_to_hass(hass)
MockConfigEntry(
domain="comp5",
title="Test 5",
source="bla5",
).add_to_hass(hass)
resp = await client.get("/api/config/config_entries/entry") resp = await client.get("/api/config/config_entries/entry")
assert resp.status == HTTPStatus.OK assert resp.status == HTTPStatus.OK
@ -137,6 +155,32 @@ async def test_get_entries(hass, client, clear_handlers):
"disabled_by": core_ce.ConfigEntryDisabler.USER, "disabled_by": core_ce.ConfigEntryDisabler.USER,
"reason": None, "reason": None,
}, },
{
"domain": "comp4",
"title": "Test 4",
"source": "bla4",
"state": core_ce.ConfigEntryState.NOT_LOADED.value,
"supports_options": False,
"supports_remove_device": False,
"supports_unload": False,
"pref_disable_new_entities": False,
"pref_disable_polling": False,
"disabled_by": None,
"reason": None,
},
{
"domain": "comp5",
"title": "Test 5",
"source": "bla5",
"state": core_ce.ConfigEntryState.NOT_LOADED.value,
"supports_options": False,
"supports_remove_device": False,
"supports_unload": False,
"pref_disable_new_entities": False,
"pref_disable_polling": False,
"disabled_by": None,
"reason": None,
},
] ]
resp = await client.get("/api/config/config_entries/entry?domain=comp3") resp = await client.get("/api/config/config_entries/entry?domain=comp3")
@ -150,20 +194,25 @@ async def test_get_entries(hass, client, clear_handlers):
data = await resp.json() data = await resp.json()
assert len(data) == 0 assert len(data) == 0
resp = await client.get( resp = await client.get("/api/config/config_entries/entry?type=hub")
"/api/config/config_entries/entry?domain=comp3&type=integration"
)
assert resp.status == HTTPStatus.OK
data = await resp.json()
assert len(data) == 1
resp = await client.get("/api/config/config_entries/entry?type=integration")
assert resp.status == HTTPStatus.OK assert resp.status == HTTPStatus.OK
data = await resp.json() data = await resp.json()
assert len(data) == 2 assert len(data) == 2
assert data[0]["domain"] == "comp1" assert data[0]["domain"] == "comp1"
assert data[1]["domain"] == "comp3" assert data[1]["domain"] == "comp3"
resp = await client.get("/api/config/config_entries/entry?type=device")
assert resp.status == HTTPStatus.OK
data = await resp.json()
assert len(data) == 1
assert data[0]["domain"] == "comp4"
resp = await client.get("/api/config/config_entries/entry?type=service")
assert resp.status == HTTPStatus.OK
data = await resp.json()
assert len(data) == 1
assert data[0]["domain"] == "comp5"
async def test_remove_entry(hass, client): async def test_remove_entry(hass, client):
"""Test removing an entry via the API.""" """Test removing an entry via the API."""
@ -1123,7 +1172,16 @@ async def test_get_entries_ws(hass, hass_ws_client, clear_handlers):
mock_integration( mock_integration(
hass, MockModule("comp2", partial_manifest={"integration_type": "helper"}) hass, MockModule("comp2", partial_manifest={"integration_type": "helper"})
) )
mock_integration(hass, MockModule("comp3")) mock_integration(
hass, MockModule("comp3", partial_manifest={"integration_type": "hub"})
)
mock_integration(
hass, MockModule("comp4", partial_manifest={"integration_type": "device"})
)
mock_integration(
hass, MockModule("comp5", partial_manifest={"integration_type": "service"})
)
entry = MockConfigEntry( entry = MockConfigEntry(
domain="comp1", domain="comp1",
title="Test 1", title="Test 1",
@ -1143,6 +1201,16 @@ async def test_get_entries_ws(hass, hass_ws_client, clear_handlers):
source="bla3", source="bla3",
disabled_by=core_ce.ConfigEntryDisabler.USER, disabled_by=core_ce.ConfigEntryDisabler.USER,
).add_to_hass(hass) ).add_to_hass(hass)
MockConfigEntry(
domain="comp4",
title="Test 4",
source="bla4",
).add_to_hass(hass)
MockConfigEntry(
domain="comp5",
title="Test 5",
source="bla5",
).add_to_hass(hass)
ws_client = await hass_ws_client(hass) ws_client = await hass_ws_client(hass)
@ -1197,6 +1265,34 @@ async def test_get_entries_ws(hass, hass_ws_client, clear_handlers):
"supports_unload": False, "supports_unload": False,
"title": "Test 3", "title": "Test 3",
}, },
{
"disabled_by": None,
"domain": "comp4",
"entry_id": ANY,
"pref_disable_new_entities": False,
"pref_disable_polling": False,
"reason": None,
"source": "bla4",
"state": "not_loaded",
"supports_options": False,
"supports_remove_device": False,
"supports_unload": False,
"title": "Test 4",
},
{
"disabled_by": None,
"domain": "comp5",
"entry_id": ANY,
"pref_disable_new_entities": False,
"pref_disable_polling": False,
"reason": None,
"source": "bla5",
"state": "not_loaded",
"supports_options": False,
"supports_remove_device": False,
"supports_unload": False,
"title": "Test 5",
},
] ]
await ws_client.send_json( await ws_client.send_json(
@ -1204,7 +1300,7 @@ async def test_get_entries_ws(hass, hass_ws_client, clear_handlers):
"id": 6, "id": 6,
"type": "config_entries/get", "type": "config_entries/get",
"domain": "comp1", "domain": "comp1",
"type_filter": "integration", "type_filter": "hub",
} }
) )
response = await ws_client.receive_json() response = await ws_client.receive_json()
@ -1225,22 +1321,102 @@ async def test_get_entries_ws(hass, hass_ws_client, clear_handlers):
"title": "Test 1", "title": "Test 1",
} }
] ]
# Verify we skip broken integrations
await ws_client.send_json(
{
"id": 7,
"type": "config_entries/get",
"type_filter": ["service", "device"],
}
)
response = await ws_client.receive_json()
assert response["id"] == 7
assert response["result"] == [
{
"disabled_by": None,
"domain": "comp4",
"entry_id": ANY,
"pref_disable_new_entities": False,
"pref_disable_polling": False,
"reason": None,
"source": "bla4",
"state": "not_loaded",
"supports_options": False,
"supports_remove_device": False,
"supports_unload": False,
"title": "Test 4",
},
{
"disabled_by": None,
"domain": "comp5",
"entry_id": ANY,
"pref_disable_new_entities": False,
"pref_disable_polling": False,
"reason": None,
"source": "bla5",
"state": "not_loaded",
"supports_options": False,
"supports_remove_device": False,
"supports_unload": False,
"title": "Test 5",
},
]
await ws_client.send_json(
{
"id": 8,
"type": "config_entries/get",
"type_filter": "hub",
}
)
response = await ws_client.receive_json()
assert response["id"] == 8
assert response["result"] == [
{
"disabled_by": None,
"domain": "comp1",
"entry_id": ANY,
"pref_disable_new_entities": False,
"pref_disable_polling": False,
"reason": None,
"source": "bla",
"state": "not_loaded",
"supports_options": False,
"supports_remove_device": False,
"supports_unload": False,
"title": "Test 1",
},
{
"disabled_by": "user",
"domain": "comp3",
"entry_id": ANY,
"pref_disable_new_entities": False,
"pref_disable_polling": False,
"reason": None,
"source": "bla3",
"state": "not_loaded",
"supports_options": False,
"supports_remove_device": False,
"supports_unload": False,
"title": "Test 3",
},
]
# Verify we skip broken integrations
with patch( with patch(
"homeassistant.components.config.config_entries.async_get_integration", "homeassistant.components.config.config_entries.async_get_integration",
side_effect=IntegrationNotFound("any"), side_effect=IntegrationNotFound("any"),
): ):
await ws_client.send_json( await ws_client.send_json(
{ {
"id": 7, "id": 9,
"type": "config_entries/get", "type": "config_entries/get",
"type_filter": "integration", "type_filter": "hub",
} }
) )
response = await ws_client.receive_json() response = await ws_client.receive_json()
assert response["id"] == 7 assert response["id"] == 9
assert response["result"] == [ assert response["result"] == [
{ {
"disabled_by": None, "disabled_by": None,
@ -1284,8 +1460,53 @@ async def test_get_entries_ws(hass, hass_ws_client, clear_handlers):
"supports_unload": False, "supports_unload": False,
"title": "Test 3", "title": "Test 3",
}, },
{
"disabled_by": None,
"domain": "comp4",
"entry_id": ANY,
"pref_disable_new_entities": False,
"pref_disable_polling": False,
"reason": None,
"source": "bla4",
"state": "not_loaded",
"supports_options": False,
"supports_remove_device": False,
"supports_unload": False,
"title": "Test 4",
},
{
"disabled_by": None,
"domain": "comp5",
"entry_id": ANY,
"pref_disable_new_entities": False,
"pref_disable_polling": False,
"reason": None,
"source": "bla5",
"state": "not_loaded",
"supports_options": False,
"supports_remove_device": False,
"supports_unload": False,
"title": "Test 5",
},
] ]
# Verify we don't send config entries when only helpers are requested
with patch(
"homeassistant.components.config.config_entries.async_get_integration",
side_effect=IntegrationNotFound("any"),
):
await ws_client.send_json(
{
"id": 10,
"type": "config_entries/get",
"type_filter": ["helper"],
}
)
response = await ws_client.receive_json()
assert response["id"] == 10
assert response["result"] == []
# Verify we raise if something really goes wrong # Verify we raise if something really goes wrong
with patch( with patch(
@ -1294,14 +1515,14 @@ async def test_get_entries_ws(hass, hass_ws_client, clear_handlers):
): ):
await ws_client.send_json( await ws_client.send_json(
{ {
"id": 8, "id": 11,
"type": "config_entries/get", "type": "config_entries/get",
"type_filter": "integration", "type_filter": ["device", "hub", "service"],
} }
) )
response = await ws_client.receive_json() response = await ws_client.receive_json()
assert response["id"] == 8 assert response["id"] == 11
assert response["success"] is False assert response["success"] is False
@ -1312,7 +1533,9 @@ async def test_subscribe_entries_ws(hass, hass_ws_client, clear_handlers):
mock_integration( mock_integration(
hass, MockModule("comp2", partial_manifest={"integration_type": "helper"}) hass, MockModule("comp2", partial_manifest={"integration_type": "helper"})
) )
mock_integration(hass, MockModule("comp3")) mock_integration(
hass, MockModule("comp3", partial_manifest={"integration_type": "device"})
)
entry = MockConfigEntry( entry = MockConfigEntry(
domain="comp1", domain="comp1",
title="Test 1", title="Test 1",
@ -1476,7 +1699,12 @@ async def test_subscribe_entries_ws_filtered(hass, hass_ws_client, clear_handler
mock_integration( mock_integration(
hass, MockModule("comp2", partial_manifest={"integration_type": "helper"}) hass, MockModule("comp2", partial_manifest={"integration_type": "helper"})
) )
mock_integration(hass, MockModule("comp3")) mock_integration(
hass, MockModule("comp3", partial_manifest={"integration_type": "device"})
)
mock_integration(
hass, MockModule("comp4", partial_manifest={"integration_type": "service"})
)
entry = MockConfigEntry( entry = MockConfigEntry(
domain="comp1", domain="comp1",
title="Test 1", title="Test 1",
@ -1491,12 +1719,19 @@ async def test_subscribe_entries_ws_filtered(hass, hass_ws_client, clear_handler
reason="Unsupported API", reason="Unsupported API",
) )
entry2.add_to_hass(hass) entry2.add_to_hass(hass)
MockConfigEntry( entry3 = MockConfigEntry(
domain="comp3", domain="comp3",
title="Test 3", title="Test 3",
source="bla3", source="bla3",
disabled_by=core_ce.ConfigEntryDisabler.USER, disabled_by=core_ce.ConfigEntryDisabler.USER,
).add_to_hass(hass) )
entry3.add_to_hass(hass)
entry4 = MockConfigEntry(
domain="comp4",
title="Test 4",
source="bla4",
)
entry4.add_to_hass(hass)
ws_client = await hass_ws_client(hass) ws_client = await hass_ws_client(hass)
@ -1504,7 +1739,7 @@ async def test_subscribe_entries_ws_filtered(hass, hass_ws_client, clear_handler
{ {
"id": 5, "id": 5,
"type": "config_entries/subscribe", "type": "config_entries/subscribe",
"type_filter": "integration", "type_filter": ["hub", "device"],
} }
) )
response = await ws_client.receive_json() response = await ws_client.receive_json()
@ -1551,6 +1786,8 @@ async def test_subscribe_entries_ws_filtered(hass, hass_ws_client, clear_handler
}, },
] ]
assert hass.config_entries.async_update_entry(entry, title="changed") assert hass.config_entries.async_update_entry(entry, title="changed")
assert hass.config_entries.async_update_entry(entry3, title="changed too")
assert hass.config_entries.async_update_entry(entry4, title="changed but ignored")
response = await ws_client.receive_json() response = await ws_client.receive_json()
assert response["id"] == 5 assert response["id"] == 5
assert response["event"] == [ assert response["event"] == [
@ -1572,6 +1809,27 @@ async def test_subscribe_entries_ws_filtered(hass, hass_ws_client, clear_handler
"type": "updated", "type": "updated",
} }
] ]
response = await ws_client.receive_json()
assert response["id"] == 5
assert response["event"] == [
{
"entry": {
"disabled_by": "user",
"domain": "comp3",
"entry_id": ANY,
"pref_disable_new_entities": False,
"pref_disable_polling": False,
"reason": None,
"source": "bla3",
"state": "not_loaded",
"supports_options": False,
"supports_remove_device": False,
"supports_unload": False,
"title": "changed too",
},
"type": "updated",
}
]
await hass.config_entries.async_remove(entry.entry_id) await hass.config_entries.async_remove(entry.entry_id)
await hass.config_entries.async_remove(entry2.entry_id) await hass.config_entries.async_remove(entry2.entry_id)
response = await ws_client.receive_json() response = await ws_client.receive_json()