Add system options to config entries (#25926)

* Add system options to config entries

* For feedback

* Follow most of balloobs comments

* Fix balloobs comments

* Improvements

* Fix second round of Balloobs comments

* Fix third round

* Add system options to mock config entry

* Fix integration tests

* Fix the last failing tests

* Fix disabled string

* Fix failing disabled_by tests

* New tests

* Config entry WS API tests

* Fix comments
This commit is contained in:
Robert Svensson 2019-08-18 06:34:11 +02:00 committed by Paulus Schoutsen
parent fc716a45c9
commit a2589f56e1
29 changed files with 254 additions and 27 deletions

View File

@ -1,6 +1,9 @@
"""Http views to control the config manager.""" """Http views to control the config manager."""
import voluptuous as vol
from homeassistant import config_entries, data_entry_flow from homeassistant import config_entries, data_entry_flow
from homeassistant.auth.permissions.const import CAT_CONFIG_ENTRIES from homeassistant.auth.permissions.const import CAT_CONFIG_ENTRIES
from homeassistant.components import websocket_api
from homeassistant.components.http import HomeAssistantView from homeassistant.components.http import HomeAssistantView
from homeassistant.exceptions import Unauthorized from homeassistant.exceptions import Unauthorized
from homeassistant.helpers.data_entry_flow import ( from homeassistant.helpers.data_entry_flow import (
@ -17,12 +20,17 @@ async def async_setup(hass):
hass.http.register_view(ConfigManagerFlowIndexView(hass.config_entries.flow)) hass.http.register_view(ConfigManagerFlowIndexView(hass.config_entries.flow))
hass.http.register_view(ConfigManagerFlowResourceView(hass.config_entries.flow)) hass.http.register_view(ConfigManagerFlowResourceView(hass.config_entries.flow))
hass.http.register_view(ConfigManagerAvailableFlowView) hass.http.register_view(ConfigManagerAvailableFlowView)
hass.http.register_view( hass.http.register_view(
OptionManagerFlowIndexView(hass.config_entries.options.flow) OptionManagerFlowIndexView(hass.config_entries.options.flow)
) )
hass.http.register_view( hass.http.register_view(
OptionManagerFlowResourceView(hass.config_entries.options.flow) OptionManagerFlowResourceView(hass.config_entries.options.flow)
) )
hass.components.websocket_api.async_register_command(system_options_list)
hass.components.websocket_api.async_register_command(system_options_update)
return True return True
@ -231,3 +239,40 @@ class OptionManagerFlowResourceView(FlowManagerResourceView):
# pylint: disable=no-value-for-parameter # pylint: disable=no-value-for-parameter
return await super().post(request, flow_id) return await super().post(request, flow_id)
@websocket_api.require_admin
@websocket_api.async_response
@websocket_api.websocket_command(
{"type": "config_entries/system_options/list", "entry_id": str}
)
async def system_options_list(hass, connection, msg):
"""List all system options for a config entry."""
entry_id = msg["entry_id"]
entry = hass.config_entries.async_get_entry(entry_id)
if entry:
connection.send_result(msg["id"], entry.system_options.as_dict())
@websocket_api.require_admin
@websocket_api.async_response
@websocket_api.websocket_command(
{
"type": "config_entries/system_options/update",
"entry_id": str,
vol.Optional("disable_new_entities"): bool,
}
)
async def system_options_update(hass, connection, msg):
"""Update config entry system options."""
changes = dict(msg)
changes.pop("id")
changes.pop("type")
entry_id = changes.pop("entry_id")
entry = hass.config_entries.async_get_entry(entry_id)
if entry and changes:
entry.system_options.update(**changes)
connection.send_result(msg["id"], entry.system_options.as_dict())

View File

@ -127,7 +127,7 @@ async def async_migrate_entry(hass, entry):
DOMAIN, DOMAIN,
unique_id, unique_id,
suggested_object_id=new_id, suggested_object_id=new_id,
config_entry_id=e_entry.config_entry_id, config_entry=entry,
device_id=e_entry.device_id, device_id=e_entry.device_id,
) )
entry.version = 3 entry.version = 3

View File

@ -12,13 +12,14 @@ from typing import (
) )
import weakref import weakref
import attr
from homeassistant import data_entry_flow, loader from homeassistant import data_entry_flow, loader
from homeassistant.core import callback, HomeAssistant from homeassistant.core import callback, HomeAssistant
from homeassistant.exceptions import HomeAssistantError, ConfigEntryNotReady from homeassistant.exceptions import HomeAssistantError, ConfigEntryNotReady
from homeassistant.setup import async_setup_component, async_process_deps_reqs from homeassistant.setup import async_setup_component, async_process_deps_reqs
from homeassistant.util.decorator import Registry from homeassistant.util.decorator import Registry
# mypy: allow-untyped-defs # mypy: allow-untyped-defs
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -88,6 +89,7 @@ class ConfigEntry:
"title", "title",
"data", "data",
"options", "options",
"system_options",
"source", "source",
"connection_class", "connection_class",
"state", "state",
@ -104,6 +106,7 @@ class ConfigEntry:
data: dict, data: dict,
source: str, source: str,
connection_class: str, connection_class: str,
system_options: dict,
options: Optional[dict] = None, options: Optional[dict] = None,
entry_id: Optional[str] = None, entry_id: Optional[str] = None,
state: str = ENTRY_STATE_NOT_LOADED, state: str = ENTRY_STATE_NOT_LOADED,
@ -127,6 +130,9 @@ class ConfigEntry:
# Entry options # Entry options
self.options = options or {} self.options = options or {}
# Entry system options
self.system_options = SystemOptions(**system_options)
# Source of the configuration (user, discovery, cloud) # Source of the configuration (user, discovery, cloud)
self.source = source self.source = source
@ -355,6 +361,7 @@ class ConfigEntry:
"title": self.title, "title": self.title,
"data": self.data, "data": self.data,
"options": self.options, "options": self.options,
"system_options": self.system_options.as_dict(),
"source": self.source, "source": self.source,
"connection_class": self.connection_class, "connection_class": self.connection_class,
} }
@ -457,6 +464,8 @@ class ConfigEntries:
connection_class=entry.get("connection_class", CONN_CLASS_UNKNOWN), connection_class=entry.get("connection_class", CONN_CLASS_UNKNOWN),
# New in 0.89 # New in 0.89
options=entry.get("options"), options=entry.get("options"),
# New in 0.98
system_options=entry.get("system_options", {}),
) )
for entry in config["entries"] for entry in config["entries"]
] ]
@ -580,6 +589,7 @@ class ConfigEntries:
title=result["title"], title=result["title"],
data=result["data"], data=result["data"],
options={}, options={},
system_options={},
source=flow.context["source"], source=flow.context["source"],
connection_class=flow.CONNECTION_CLASS, connection_class=flow.CONNECTION_CLASS,
) )
@ -722,3 +732,18 @@ class OptionsFlow(data_entry_flow.FlowHandler):
"""Base class for config option flows.""" """Base class for config option flows."""
pass pass
@attr.s(slots=True)
class SystemOptions:
"""Config entry system options."""
disable_new_entities = attr.ib(type=bool, default=False)
def update(self, *, disable_new_entities):
"""Update properties."""
self.disable_new_entities = disable_new_entities
def as_dict(self):
"""Return dictionary version of this config entrys system options."""
return {"disable_new_entities": self.disable_new_entities}

View File

@ -343,7 +343,7 @@ class EntityPlatform:
self.platform_name, self.platform_name,
entity.unique_id, entity.unique_id,
suggested_object_id=suggested_object_id, suggested_object_id=suggested_object_id,
config_entry_id=config_entry_id, config_entry=self.config_entry,
device_id=device_id, device_id=device_id,
known_object_ids=self.entities.keys(), known_object_ids=self.entities.keys(),
disabled_by=disabled_by, disabled_by=disabled_by,

View File

@ -33,6 +33,7 @@ EVENT_ENTITY_REGISTRY_UPDATED = "entity_registry_updated"
SAVE_DELAY = 10 SAVE_DELAY = 10
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
_UNDEF = object() _UNDEF = object()
DISABLED_CONFIG_ENTRY = "config_entry"
DISABLED_HASS = "hass" DISABLED_HASS = "hass"
DISABLED_USER = "user" DISABLED_USER = "user"
DISABLED_INTEGRATION = "integration" DISABLED_INTEGRATION = "integration"
@ -55,7 +56,13 @@ class RegistryEntry:
type=str, type=str,
default=None, default=None,
validator=attr.validators.in_( validator=attr.validators.in_(
(DISABLED_HASS, DISABLED_USER, DISABLED_INTEGRATION, None) (
DISABLED_HASS,
DISABLED_USER,
DISABLED_INTEGRATION,
DISABLED_CONFIG_ENTRY,
None,
)
), ),
) # type: Optional[str] ) # type: Optional[str]
domain = attr.ib(type=str, init=False, repr=False) domain = attr.ib(type=str, init=False, repr=False)
@ -132,13 +139,18 @@ class EntityRegistry:
unique_id, unique_id,
*, *,
suggested_object_id=None, suggested_object_id=None,
config_entry_id=None, config_entry=None,
device_id=None, device_id=None,
known_object_ids=None, known_object_ids=None,
disabled_by=None, disabled_by=None,
): ):
"""Get entity. Create if it doesn't exist.""" """Get entity. Create if it doesn't exist."""
config_entry_id = None
if config_entry:
config_entry_id = config_entry.entry_id
entity_id = self.async_get_entity_id(domain, platform, unique_id) entity_id = self.async_get_entity_id(domain, platform, unique_id)
if entity_id: if entity_id:
return self._async_update_entity( return self._async_update_entity(
entity_id, entity_id,
@ -159,6 +171,13 @@ class EntityRegistry:
known_object_ids, known_object_ids,
) )
if (
disabled_by is None
and config_entry
and config_entry.system_options.disable_new_entities
):
disabled_by = DISABLED_INTEGRATION
entity = RegistryEntry( entity = RegistryEntry(
entity_id=entity_id, entity_id=entity_id,
config_entry_id=config_entry_id, config_entry_id=config_entry_id,

View File

@ -665,6 +665,7 @@ class MockConfigEntry(config_entries.ConfigEntry):
title="Mock Title", title="Mock Title",
state=None, state=None,
options={}, options={},
system_options={},
connection_class=config_entries.CONN_CLASS_UNKNOWN, connection_class=config_entries.CONN_CLASS_UNKNOWN,
): ):
"""Initialize a mock config entry.""" """Initialize a mock config entry."""
@ -672,6 +673,7 @@ class MockConfigEntry(config_entries.ConfigEntry):
"entry_id": entry_id or uuid.uuid4().hex, "entry_id": entry_id or uuid.uuid4().hex,
"domain": domain, "domain": domain,
"data": data or {}, "data": data or {},
"system_options": system_options,
"options": options, "options": options,
"version": version, "version": version,
"title": title, "title": title,

View File

@ -57,6 +57,7 @@ async def setup_device(hass):
ENTRY_CONFIG, ENTRY_CONFIG,
"test", "test",
config_entries.CONN_CLASS_LOCAL_PUSH, config_entries.CONN_CLASS_LOCAL_PUSH,
system_options={},
options=ENTRY_OPTIONS, options=ENTRY_OPTIONS,
) )
device = axis.AxisNetworkDevice(hass, config_entry) device = axis.AxisNetworkDevice(hass, config_entry)

View File

@ -41,6 +41,7 @@ async def setup_device(hass):
ENTRY_CONFIG, ENTRY_CONFIG,
"test", "test",
config_entries.CONN_CLASS_LOCAL_PUSH, config_entries.CONN_CLASS_LOCAL_PUSH,
system_options={},
options=ENTRY_OPTIONS, options=ENTRY_OPTIONS,
) )
device = axis.AxisNetworkDevice(hass, config_entry) device = axis.AxisNetworkDevice(hass, config_entry)

View File

@ -59,6 +59,7 @@ async def setup_device(hass):
ENTRY_CONFIG, ENTRY_CONFIG,
"test", "test",
config_entries.CONN_CLASS_LOCAL_PUSH, config_entries.CONN_CLASS_LOCAL_PUSH,
system_options={},
options=ENTRY_OPTIONS, options=ENTRY_OPTIONS,
) )
device = axis.AxisNetworkDevice(hass, config_entry) device = axis.AxisNetworkDevice(hass, config_entry)

View File

@ -584,3 +584,47 @@ async def test_two_step_options_flow(hass, client):
"description": None, "description": None,
"description_placeholders": None, "description_placeholders": None,
} }
async def test_list_system_options(hass, hass_ws_client):
"""Test that we can list an entries system options."""
assert await async_setup_component(hass, "config", {})
ws_client = await hass_ws_client(hass)
entry = MockConfigEntry(domain="demo")
entry.add_to_hass(hass)
await ws_client.send_json(
{
"id": 5,
"type": "config_entries/system_options/list",
"entry_id": entry.entry_id,
}
)
response = await ws_client.receive_json()
assert response["success"]
assert response["result"] == {"disable_new_entities": False}
async def test_update_system_options(hass, hass_ws_client):
"""Test that we can update system options."""
assert await async_setup_component(hass, "config", {})
ws_client = await hass_ws_client(hass)
entry = MockConfigEntry(domain="demo")
entry.add_to_hass(hass)
await ws_client.send_json(
{
"id": 5,
"type": "config_entries/system_options/update",
"entry_id": entry.entry_id,
"disable_new_entities": True,
}
)
response = await ws_client.receive_json()
assert response["success"]
assert response["result"]["disable_new_entities"]
assert entry.system_options.disable_new_entities

View File

@ -59,7 +59,8 @@ async def setup_gateway(hass, data, allow_clip_sensor=True):
ENTRY_CONFIG, ENTRY_CONFIG,
"test", "test",
config_entries.CONN_CLASS_LOCAL_PUSH, config_entries.CONN_CLASS_LOCAL_PUSH,
ENTRY_OPTIONS, system_options={},
options=ENTRY_OPTIONS,
) )
gateway = deconz.DeconzGateway(hass, config_entry) gateway = deconz.DeconzGateway(hass, config_entry)
gateway.api = DeconzSession(loop, session, **config_entry.data) gateway.api = DeconzSession(loop, session, **config_entry.data)

View File

@ -71,7 +71,8 @@ async def setup_gateway(hass, data, allow_clip_sensor=True):
ENTRY_CONFIG, ENTRY_CONFIG,
"test", "test",
config_entries.CONN_CLASS_LOCAL_PUSH, config_entries.CONN_CLASS_LOCAL_PUSH,
ENTRY_OPTIONS, system_options={},
options=ENTRY_OPTIONS,
) )
gateway = deconz.DeconzGateway(hass, config_entry) gateway = deconz.DeconzGateway(hass, config_entry)
gateway.api = DeconzSession(hass.loop, session, **config_entry.data) gateway.api = DeconzSession(hass.loop, session, **config_entry.data)

View File

@ -63,6 +63,7 @@ async def setup_gateway(hass, data):
ENTRY_CONFIG, ENTRY_CONFIG,
"test", "test",
config_entries.CONN_CLASS_LOCAL_PUSH, config_entries.CONN_CLASS_LOCAL_PUSH,
system_options={},
) )
gateway = deconz.DeconzGateway(hass, config_entry) gateway = deconz.DeconzGateway(hass, config_entry)
gateway.api = DeconzSession(loop, session, **config_entry.data) gateway.api = DeconzSession(loop, session, **config_entry.data)

View File

@ -90,7 +90,8 @@ async def setup_gateway(hass, data, allow_deconz_groups=True):
ENTRY_CONFIG, ENTRY_CONFIG,
"test", "test",
config_entries.CONN_CLASS_LOCAL_PUSH, config_entries.CONN_CLASS_LOCAL_PUSH,
ENTRY_OPTIONS, system_options={},
options=ENTRY_OPTIONS,
) )
gateway = deconz.DeconzGateway(hass, config_entry) gateway = deconz.DeconzGateway(hass, config_entry)
gateway.api = DeconzSession(loop, session, **config_entry.data) gateway.api = DeconzSession(loop, session, **config_entry.data)

View File

@ -46,6 +46,7 @@ async def setup_gateway(hass, data):
ENTRY_CONFIG, ENTRY_CONFIG,
"test", "test",
config_entries.CONN_CLASS_LOCAL_PUSH, config_entries.CONN_CLASS_LOCAL_PUSH,
system_options={},
) )
gateway = deconz.DeconzGateway(hass, config_entry) gateway = deconz.DeconzGateway(hass, config_entry)
gateway.api = DeconzSession(loop, session, **config_entry.data) gateway.api = DeconzSession(loop, session, **config_entry.data)

View File

@ -103,7 +103,8 @@ async def setup_gateway(hass, data, allow_clip_sensor=True):
ENTRY_CONFIG, ENTRY_CONFIG,
"test", "test",
config_entries.CONN_CLASS_LOCAL_PUSH, config_entries.CONN_CLASS_LOCAL_PUSH,
ENTRY_OPTIONS, system_options={},
options=ENTRY_OPTIONS,
) )
gateway = deconz.DeconzGateway(hass, config_entry) gateway = deconz.DeconzGateway(hass, config_entry)
gateway.api = DeconzSession(loop, session, **config_entry.data) gateway.api = DeconzSession(loop, session, **config_entry.data)

View File

@ -67,6 +67,7 @@ async def setup_gateway(hass, data):
ENTRY_CONFIG, ENTRY_CONFIG,
"test", "test",
config_entries.CONN_CLASS_LOCAL_PUSH, config_entries.CONN_CLASS_LOCAL_PUSH,
system_options={},
) )
gateway = deconz.DeconzGateway(hass, config_entry) gateway = deconz.DeconzGateway(hass, config_entry)
gateway.api = DeconzSession(loop, session, **config_entry.data) gateway.api = DeconzSession(loop, session, **config_entry.data)

View File

@ -97,6 +97,7 @@ async def test_storage_is_removed_on_config_entry_removal(hass, utcnow):
pairing_data, pairing_data,
"test", "test",
config_entries.CONN_CLASS_LOCAL_PUSH, config_entries.CONN_CLASS_LOCAL_PUSH,
system_options={},
) )
assert hkid in hass.data[ENTITY_MAP].storage_data assert hkid in hass.data[ENTITY_MAP].storage_data

View File

@ -221,6 +221,7 @@ async def setup_bridge(hass, mock_bridge):
{"host": "mock-host"}, {"host": "mock-host"},
"test", "test",
config_entries.CONN_CLASS_LOCAL_POLL, config_entries.CONN_CLASS_LOCAL_POLL,
system_options={},
) )
await hass.config_entries.async_forward_entry_setup(config_entry, "light") await hass.config_entries.async_forward_entry_setup(config_entry, "light")
# To flush out the service call to update the group # To flush out the service call to update the group

View File

@ -306,6 +306,7 @@ async def setup_bridge(hass, mock_bridge, hostname=None):
{"host": hostname}, {"host": hostname},
"test", "test",
config_entries.CONN_CLASS_LOCAL_POLL, config_entries.CONN_CLASS_LOCAL_POLL,
system_options={},
) )
await hass.config_entries.async_forward_entry_setup(config_entry, "binary_sensor") await hass.config_entries.async_forward_entry_setup(config_entry, "binary_sensor")
await hass.config_entries.async_forward_entry_setup(config_entry, "sensor") await hass.config_entries.async_forward_entry_setup(config_entry, "sensor")

View File

@ -142,7 +142,7 @@ async def test_config_flow_entry_migrate(hass):
"media_player", "media_player",
"ps4", "ps4",
MOCK_UNIQUE_ID, MOCK_UNIQUE_ID,
config_entry_id=MOCK_ENTRY_ID, config_entry=mock_entry,
device_id=MOCK_DEVICE_ID, device_id=MOCK_DEVICE_ID,
) )
assert len(mock_e_registry.entities) == 1 assert len(mock_e_registry.entities) == 1

View File

@ -335,7 +335,7 @@ async def test_device_info_is_assummed(hass):
mock_unique_id = ps4.format_unique_id(MOCK_CREDS, MOCK_HOST_ID) mock_unique_id = ps4.format_unique_id(MOCK_CREDS, MOCK_HOST_ID)
mock_e_registry = mock_registry(hass) mock_e_registry = mock_registry(hass)
mock_e_registry.async_get_or_create( mock_e_registry.async_get_or_create(
"media_player", DOMAIN, mock_unique_id, config_entry_id=MOCK_ENTRY_ID "media_player", DOMAIN, mock_unique_id, config_entry=MOCK_CONFIG
) )
mock_entity_id = mock_e_registry.async_get_entity_id( mock_entity_id = mock_e_registry.async_get_entity_id(
"media_player", DOMAIN, mock_unique_id "media_player", DOMAIN, mock_unique_id

View File

@ -56,6 +56,7 @@ async def setup_platform(hass, platform: str, *, devices=None, scenes=None):
{CONF_INSTALLED_APP_ID: str(uuid4())}, {CONF_INSTALLED_APP_ID: str(uuid4())},
SOURCE_USER, SOURCE_USER,
CONN_CLASS_CLOUD_PUSH, CONN_CLASS_CLOUD_PUSH,
system_options={},
) )
broker = DeviceBroker( broker = DeviceBroker(
hass, config_entry, Mock(), Mock(), devices or [], scenes or [] hass, config_entry, Mock(), Mock(), devices or [], scenes or []

View File

@ -145,6 +145,7 @@ async def setup_controller(hass, mock_controller):
"test", "test",
config_entries.CONN_CLASS_LOCAL_POLL, config_entries.CONN_CLASS_LOCAL_POLL,
entry_id=1, entry_id=1,
system_options={},
) )
mock_controller.config_entry = config_entry mock_controller.config_entry = config_entry
@ -235,20 +236,31 @@ async def test_restoring_client(hass, mock_controller):
mock_controller.mock_client_all_responses.append([CLIENT_1]) mock_controller.mock_client_all_responses.append([CLIENT_1])
mock_controller.unifi_config = {unifi.CONF_BLOCK_CLIENT: True} mock_controller.unifi_config = {unifi.CONF_BLOCK_CLIENT: True}
config_entry = config_entries.ConfigEntry(
1,
unifi.DOMAIN,
"Mock Title",
ENTRY_CONFIG,
"test",
config_entries.CONN_CLASS_LOCAL_POLL,
entry_id=1,
system_options={},
)
registry = await entity_registry.async_get_registry(hass) registry = await entity_registry.async_get_registry(hass)
registry.async_get_or_create( registry.async_get_or_create(
device_tracker.DOMAIN, device_tracker.DOMAIN,
unifi_dt.UNIFI_DOMAIN, unifi_dt.UNIFI_DOMAIN,
"{}-mock-site".format(CLIENT_1["mac"]), "{}-mock-site".format(CLIENT_1["mac"]),
suggested_object_id=CLIENT_1["hostname"], suggested_object_id=CLIENT_1["hostname"],
config_entry_id=1, config_entry=config_entry,
) )
registry.async_get_or_create( registry.async_get_or_create(
device_tracker.DOMAIN, device_tracker.DOMAIN,
unifi_dt.UNIFI_DOMAIN, unifi_dt.UNIFI_DOMAIN,
"{}-mock-site".format(CLIENT_2["mac"]), "{}-mock-site".format(CLIENT_2["mac"]),
suggested_object_id=CLIENT_2["hostname"], suggested_object_id=CLIENT_2["hostname"],
config_entry_id=1, config_entry=config_entry,
) )
await setup_controller(hass, mock_controller) await setup_controller(hass, mock_controller)

View File

@ -262,6 +262,7 @@ async def setup_controller(hass, mock_controller):
"test", "test",
config_entries.CONN_CLASS_LOCAL_POLL, config_entries.CONN_CLASS_LOCAL_POLL,
entry_id=1, entry_id=1,
system_options={},
) )
mock_controller.config_entry = config_entry mock_controller.config_entry = config_entry
@ -468,20 +469,31 @@ async def test_restoring_client(hass, mock_controller):
mock_controller.mock_client_all_responses.append([CLIENT_1]) mock_controller.mock_client_all_responses.append([CLIENT_1])
mock_controller.unifi_config = {unifi.CONF_BLOCK_CLIENT: ["random mac"]} mock_controller.unifi_config = {unifi.CONF_BLOCK_CLIENT: ["random mac"]}
config_entry = config_entries.ConfigEntry(
1,
unifi.DOMAIN,
"Mock Title",
ENTRY_CONFIG,
"test",
config_entries.CONN_CLASS_LOCAL_POLL,
entry_id=1,
system_options={},
)
registry = await entity_registry.async_get_registry(hass) registry = await entity_registry.async_get_registry(hass)
registry.async_get_or_create( registry.async_get_or_create(
switch.DOMAIN, switch.DOMAIN,
unifi.DOMAIN, unifi.DOMAIN,
"poe-{}".format(CLIENT_1["mac"]), "poe-{}".format(CLIENT_1["mac"]),
suggested_object_id=CLIENT_1["hostname"], suggested_object_id=CLIENT_1["hostname"],
config_entry_id=1, config_entry=config_entry,
) )
registry.async_get_or_create( registry.async_get_or_create(
switch.DOMAIN, switch.DOMAIN,
unifi.DOMAIN, unifi.DOMAIN,
"poe-{}".format(CLIENT_2["mac"]), "poe-{}".format(CLIENT_2["mac"]),
suggested_object_id=CLIENT_2["hostname"], suggested_object_id=CLIENT_2["hostname"],
config_entry_id=1, config_entry=config_entry,
) )
await setup_controller(hass, mock_controller) await setup_controller(hass, mock_controller)

View File

@ -14,7 +14,13 @@ from homeassistant.components.zha.core.store import async_get_registry
def config_entry_fixture(hass): def config_entry_fixture(hass):
"""Fixture representing a config entry.""" """Fixture representing a config entry."""
config_entry = config_entries.ConfigEntry( config_entry = config_entries.ConfigEntry(
1, DOMAIN, "Mock Title", {}, "test", config_entries.CONN_CLASS_LOCAL_PUSH 1,
DOMAIN,
"Mock Title",
{},
"test",
config_entries.CONN_CLASS_LOCAL_PUSH,
system_options={},
) )
return config_entry return config_entry

View File

@ -287,6 +287,7 @@ async def setup_ozw(hass, mock_openzwave):
{"usb_path": "mock-path", "network_key": "mock-key"}, {"usb_path": "mock-path", "network_key": "mock-key"},
"test", "test",
config_entries.CONN_CLASS_LOCAL_PUSH, config_entries.CONN_CLASS_LOCAL_PUSH,
system_options={},
) )
await hass.config_entries.async_forward_entry_setup(config_entry, "lock") await hass.config_entries.async_forward_entry_setup(config_entry, "lock")
await hass.async_block_till_done() await hass.async_block_till_done()

View File

@ -8,7 +8,7 @@ import pytest
from homeassistant.core import valid_entity_id, callback from homeassistant.core import valid_entity_id, callback
from homeassistant.helpers import entity_registry from homeassistant.helpers import entity_registry
from tests.common import mock_registry, flush_store from tests.common import MockConfigEntry, mock_registry, flush_store
YAML__OPEN_PATH = "homeassistant.util.yaml.loader.open" YAML__OPEN_PATH = "homeassistant.util.yaml.loader.open"
@ -88,9 +88,11 @@ def test_create_triggers_save(hass, registry):
async def test_loading_saving_data(hass, registry): async def test_loading_saving_data(hass, registry):
"""Test that we load/save data correctly.""" """Test that we load/save data correctly."""
mock_config = MockConfigEntry(domain="light")
orig_entry1 = registry.async_get_or_create("light", "hue", "1234") orig_entry1 = registry.async_get_or_create("light", "hue", "1234")
orig_entry2 = registry.async_get_or_create( orig_entry2 = registry.async_get_or_create(
"light", "hue", "5678", config_entry_id="mock-id" "light", "hue", "5678", config_entry=mock_config
) )
assert len(registry.entities) == 2 assert len(registry.entities) == 2
@ -104,7 +106,7 @@ async def test_loading_saving_data(hass, registry):
assert list(registry.entities) == list(registry2.entities) assert list(registry.entities) == list(registry2.entities)
new_entry1 = registry.async_get_or_create("light", "hue", "1234") new_entry1 = registry.async_get_or_create("light", "hue", "1234")
new_entry2 = registry.async_get_or_create( new_entry2 = registry.async_get_or_create(
"light", "hue", "5678", config_entry_id="mock-id" "light", "hue", "5678", config_entry=mock_config
) )
assert orig_entry1 == new_entry1 assert orig_entry1 == new_entry1
@ -198,11 +200,14 @@ def test_async_get_entity_id(registry):
async def test_updating_config_entry_id(hass, registry, update_events): async def test_updating_config_entry_id(hass, registry, update_events):
"""Test that we update config entry id in registry.""" """Test that we update config entry id in registry."""
mock_config_1 = MockConfigEntry(domain="light", entry_id="mock-id-1")
entry = registry.async_get_or_create( entry = registry.async_get_or_create(
"light", "hue", "5678", config_entry_id="mock-id-1" "light", "hue", "5678", config_entry=mock_config_1
) )
mock_config_2 = MockConfigEntry(domain="light", entry_id="mock-id-2")
entry2 = registry.async_get_or_create( entry2 = registry.async_get_or_create(
"light", "hue", "5678", config_entry_id="mock-id-2" "light", "hue", "5678", config_entry=mock_config_2
) )
assert entry.entity_id == entry2.entity_id assert entry.entity_id == entry2.entity_id
assert entry2.config_entry_id == "mock-id-2" assert entry2.config_entry_id == "mock-id-2"
@ -218,8 +223,10 @@ async def test_updating_config_entry_id(hass, registry, update_events):
async def test_removing_config_entry_id(hass, registry, update_events): async def test_removing_config_entry_id(hass, registry, update_events):
"""Test that we update config entry id in registry.""" """Test that we update config entry id in registry."""
mock_config = MockConfigEntry(domain="light", entry_id="mock-id-1")
entry = registry.async_get_or_create( entry = registry.async_get_or_create(
"light", "hue", "5678", config_entry_id="mock-id-1" "light", "hue", "5678", config_entry=mock_config
) )
assert entry.config_entry_id == "mock-id-1" assert entry.config_entry_id == "mock-id-1"
registry.async_clear_config_entry("mock-id-1") registry.async_clear_config_entry("mock-id-1")
@ -237,6 +244,8 @@ async def test_removing_config_entry_id(hass, registry, update_events):
async def test_migration(hass): async def test_migration(hass):
"""Test migration from old data to new.""" """Test migration from old data to new."""
mock_config = MockConfigEntry(domain="test-platform", entry_id="test-config-id")
old_conf = { old_conf = {
"light.kitchen": { "light.kitchen": {
"config_entry_id": "test-config-id", "config_entry_id": "test-config-id",
@ -256,7 +265,7 @@ async def test_migration(hass):
domain="light", domain="light",
platform="test-platform", platform="test-platform",
unique_id="test-unique", unique_id="test-unique",
config_entry_id="test-config-id", config_entry=mock_config,
) )
assert entry.name == "Test Name" assert entry.name == "Test Name"
assert entry.disabled_by == "hass" assert entry.disabled_by == "hass"
@ -326,8 +335,10 @@ async def test_loading_race_condition(hass):
async def test_update_entity_unique_id(registry): async def test_update_entity_unique_id(registry):
"""Test entity's unique_id is updated.""" """Test entity's unique_id is updated."""
mock_config = MockConfigEntry(domain="light", entry_id="mock-id-1")
entry = registry.async_get_or_create( entry = registry.async_get_or_create(
"light", "hue", "5678", config_entry_id="mock-id-1" "light", "hue", "5678", config_entry=mock_config
) )
new_unique_id = "1234" new_unique_id = "1234"
with patch.object(registry, "async_schedule_save") as mock_schedule_save: with patch.object(registry, "async_schedule_save") as mock_schedule_save:
@ -341,11 +352,12 @@ async def test_update_entity_unique_id(registry):
async def test_update_entity_unique_id_conflict(registry): async def test_update_entity_unique_id_conflict(registry):
"""Test migration raises when unique_id already in use.""" """Test migration raises when unique_id already in use."""
mock_config = MockConfigEntry(domain="light", entry_id="mock-id-1")
entry = registry.async_get_or_create( entry = registry.async_get_or_create(
"light", "hue", "5678", config_entry_id="mock-id-1" "light", "hue", "5678", config_entry=mock_config
) )
entry2 = registry.async_get_or_create( entry2 = registry.async_get_or_create(
"light", "hue", "1234", config_entry_id="mock-id-1" "light", "hue", "1234", config_entry=mock_config
) )
with patch.object( with patch.object(
registry, "async_schedule_save" registry, "async_schedule_save"
@ -356,8 +368,9 @@ async def test_update_entity_unique_id_conflict(registry):
async def test_update_entity(registry): async def test_update_entity(registry):
"""Test updating entity.""" """Test updating entity."""
mock_config = MockConfigEntry(domain="light", entry_id="mock-id-1")
entry = registry.async_get_or_create( entry = registry.async_get_or_create(
"light", "hue", "5678", config_entry_id="mock-id-1" "light", "hue", "5678", config_entry=mock_config
) )
for attr_name, new_value in ( for attr_name, new_value in (
@ -386,3 +399,21 @@ async def test_disabled_by(registry):
entry2 = registry.async_get_or_create("light", "hue", "1234") entry2 = registry.async_get_or_create("light", "hue", "1234")
assert entry2.disabled_by is None assert entry2.disabled_by is None
async def test_disabled_by_system_options(registry):
"""Test system options setting disabled_by."""
mock_config = MockConfigEntry(
domain="light",
entry_id="mock-id-1",
system_options={"disable_new_entities": True},
)
entry = registry.async_get_or_create(
"light", "hue", "AAAA", config_entry=mock_config
)
assert entry.disabled_by == "integration"
entry2 = registry.async_get_or_create(
"light", "hue", "BBBB", config_entry=mock_config, disabled_by="user"
)
assert entry2.disabled_by == "user"

View File

@ -596,6 +596,22 @@ async def test_updating_entry_data(manager):
assert entry.data == {"second": True} assert entry.data == {"second": True}
async def test_updating_entry_system_options(manager):
"""Test that we can update an entry data."""
entry = MockConfigEntry(
domain="test",
data={"first": True},
state=config_entries.ENTRY_STATE_SETUP_ERROR,
system_options={"disable_new_entities": True},
)
entry.add_to_manager(manager)
assert entry.system_options.disable_new_entities
entry.system_options.update(disable_new_entities=False)
assert not entry.system_options.disable_new_entities
async def test_update_entry_options_and_trigger_listener(hass, manager): async def test_update_entry_options_and_trigger_listener(hass, manager):
"""Test that we can update entry options and trigger listener.""" """Test that we can update entry options and trigger listener."""
entry = MockConfigEntry(domain="test", options={"first": True}) entry = MockConfigEntry(domain="test", options={"first": True})