Axis - Remove manual configuration and legacy config file import (#30365)
* Remove manual configuration and legacy config file import * Remove unused imports in tests after rebase
This commit is contained in:
parent
6387a50697
commit
bb55606d29
4 changed files with 24 additions and 233 deletions
|
@ -1,43 +1,18 @@
|
||||||
"""Support for Axis devices."""
|
"""Support for Axis devices."""
|
||||||
|
|
||||||
import voluptuous as vol
|
|
||||||
|
|
||||||
from homeassistant import config_entries
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_DEVICE,
|
CONF_DEVICE,
|
||||||
CONF_MAC,
|
CONF_MAC,
|
||||||
CONF_NAME,
|
|
||||||
CONF_TRIGGER_TIME,
|
CONF_TRIGGER_TIME,
|
||||||
EVENT_HOMEASSISTANT_STOP,
|
EVENT_HOMEASSISTANT_STOP,
|
||||||
)
|
)
|
||||||
from homeassistant.helpers import config_validation as cv
|
|
||||||
|
|
||||||
from .config_flow import DEVICE_SCHEMA
|
|
||||||
from .const import CONF_CAMERA, CONF_EVENTS, DEFAULT_TRIGGER_TIME, DOMAIN
|
from .const import CONF_CAMERA, CONF_EVENTS, DEFAULT_TRIGGER_TIME, DOMAIN
|
||||||
from .device import AxisNetworkDevice, get_device
|
from .device import AxisNetworkDevice, get_device
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema(
|
|
||||||
{DOMAIN: cv.schema_with_slug_keys(DEVICE_SCHEMA)}, extra=vol.ALLOW_EXTRA
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def async_setup(hass, config):
|
async def async_setup(hass, config):
|
||||||
"""Set up for Axis devices."""
|
"""Old way to set up Axis devices."""
|
||||||
if not hass.config_entries.async_entries(DOMAIN) and DOMAIN in config:
|
|
||||||
|
|
||||||
for device_name, device_config in config[DOMAIN].items():
|
|
||||||
|
|
||||||
if CONF_NAME not in device_config:
|
|
||||||
device_config[CONF_NAME] = device_name
|
|
||||||
|
|
||||||
hass.async_create_task(
|
|
||||||
hass.config_entries.flow.async_init(
|
|
||||||
DOMAIN,
|
|
||||||
context={"source": config_entries.SOURCE_IMPORT},
|
|
||||||
data=device_config,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,6 @@ from homeassistant.const import (
|
||||||
)
|
)
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.helpers import config_validation as cv
|
from homeassistant.helpers import config_validation as cv
|
||||||
from homeassistant.util.json import load_json
|
|
||||||
|
|
||||||
from .const import CONF_MODEL, DOMAIN
|
from .const import CONF_MODEL, DOMAIN
|
||||||
from .device import get_device
|
from .device import get_device
|
||||||
|
@ -107,16 +106,12 @@ class AxisFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
except CannotConnect:
|
except CannotConnect:
|
||||||
errors["base"] = "device_unavailable"
|
errors["base"] = "device_unavailable"
|
||||||
|
|
||||||
data = (
|
data = self.discovery_schema or {
|
||||||
self.import_schema
|
vol.Required(CONF_HOST): str,
|
||||||
or self.discovery_schema
|
vol.Required(CONF_USERNAME): str,
|
||||||
or {
|
vol.Required(CONF_PASSWORD): str,
|
||||||
vol.Required(CONF_HOST): str,
|
vol.Required(CONF_PORT, default=DEFAULT_PORT): int,
|
||||||
vol.Required(CONF_USERNAME): str,
|
}
|
||||||
vol.Required(CONF_PASSWORD): str,
|
|
||||||
vol.Required(CONF_PORT, default=DEFAULT_PORT): int,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
return self.async_show_form(
|
return self.async_show_form(
|
||||||
step_id="user",
|
step_id="user",
|
||||||
|
@ -130,18 +125,17 @@ class AxisFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
|
|
||||||
Generate a name to be used as a prefix for device entities.
|
Generate a name to be used as a prefix for device entities.
|
||||||
"""
|
"""
|
||||||
if self.name is None:
|
same_model = [
|
||||||
same_model = [
|
entry.data[CONF_NAME]
|
||||||
entry.data[CONF_NAME]
|
for entry in self.hass.config_entries.async_entries(DOMAIN)
|
||||||
for entry in self.hass.config_entries.async_entries(DOMAIN)
|
if entry.data[CONF_MODEL] == self.model
|
||||||
if entry.data[CONF_MODEL] == self.model
|
]
|
||||||
]
|
|
||||||
|
|
||||||
self.name = f"{self.model}"
|
self.name = f"{self.model}"
|
||||||
for idx in range(len(same_model) + 1):
|
for idx in range(len(same_model) + 1):
|
||||||
self.name = f"{self.model} {idx}"
|
self.name = f"{self.model} {idx}"
|
||||||
if self.name not in same_model:
|
if self.name not in same_model:
|
||||||
break
|
break
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
CONF_DEVICE: self.device_config,
|
CONF_DEVICE: self.device_config,
|
||||||
|
@ -187,53 +181,17 @@ class AxisFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
await self._update_entry(entry, discovery_info[CONF_HOST])
|
await self._update_entry(entry, discovery_info[CONF_HOST])
|
||||||
return self.async_abort(reason="already_configured")
|
return self.async_abort(reason="already_configured")
|
||||||
|
|
||||||
config_file = await self.hass.async_add_executor_job(
|
|
||||||
load_json, self.hass.config.path(CONFIG_FILE)
|
|
||||||
)
|
|
||||||
|
|
||||||
# pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167
|
# pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167
|
||||||
self.context["title_placeholders"] = {
|
self.context["title_placeholders"] = {
|
||||||
"name": discovery_info["hostname"][:-7],
|
"name": discovery_info["hostname"][:-7],
|
||||||
"host": discovery_info[CONF_HOST],
|
"host": discovery_info[CONF_HOST],
|
||||||
}
|
}
|
||||||
|
|
||||||
if serialnumber not in config_file:
|
self.discovery_schema = {
|
||||||
self.discovery_schema = {
|
vol.Required(CONF_HOST, default=discovery_info[CONF_HOST]): str,
|
||||||
vol.Required(CONF_HOST, default=discovery_info[CONF_HOST]): str,
|
vol.Required(CONF_USERNAME): str,
|
||||||
vol.Required(CONF_USERNAME): str,
|
vol.Required(CONF_PASSWORD): str,
|
||||||
vol.Required(CONF_PASSWORD): str,
|
vol.Required(CONF_PORT, default=discovery_info[CONF_PORT]): int,
|
||||||
vol.Required(CONF_PORT, default=discovery_info[CONF_PORT]): int,
|
|
||||||
}
|
|
||||||
|
|
||||||
return await self.async_step_user()
|
|
||||||
|
|
||||||
try:
|
|
||||||
device_config = DEVICE_SCHEMA(config_file[serialnumber])
|
|
||||||
device_config[CONF_HOST] = discovery_info[CONF_HOST]
|
|
||||||
|
|
||||||
if CONF_NAME not in device_config:
|
|
||||||
device_config[CONF_NAME] = discovery_info["hostname"]
|
|
||||||
|
|
||||||
except vol.Invalid:
|
|
||||||
return self.async_abort(reason="bad_config_file")
|
|
||||||
|
|
||||||
return await self.async_step_import(device_config)
|
|
||||||
|
|
||||||
async def async_step_import(self, import_config):
|
|
||||||
"""Import a Axis device as a config entry.
|
|
||||||
|
|
||||||
This flow is triggered by `async_setup` for configured devices.
|
|
||||||
This flow is also triggered by `async_step_discovery`.
|
|
||||||
|
|
||||||
This will execute for any Axis device that contains a complete
|
|
||||||
configuration.
|
|
||||||
"""
|
|
||||||
self.name = import_config[CONF_NAME]
|
|
||||||
|
|
||||||
self.import_schema = {
|
|
||||||
vol.Required(CONF_HOST, default=import_config[CONF_HOST]): str,
|
|
||||||
vol.Required(CONF_USERNAME, default=import_config[CONF_USERNAME]): str,
|
|
||||||
vol.Required(CONF_PASSWORD, default=import_config[CONF_PASSWORD]): str,
|
|
||||||
vol.Required(CONF_PORT, default=import_config[CONF_PORT]): int,
|
|
||||||
}
|
}
|
||||||
return await self.async_step_user(user_input=import_config)
|
|
||||||
|
return await self.async_step_user()
|
||||||
|
|
|
@ -196,52 +196,6 @@ async def test_zeroconf_flow(hass):
|
||||||
assert result["step_id"] == "user"
|
assert result["step_id"] == "user"
|
||||||
|
|
||||||
|
|
||||||
async def test_zeroconf_flow_known_device(hass):
|
|
||||||
"""Test that zeroconf discovery for known devices work.
|
|
||||||
|
|
||||||
This is legacy support from devices registered with configurator.
|
|
||||||
"""
|
|
||||||
with patch(
|
|
||||||
"homeassistant.components.axis.config_flow.load_json",
|
|
||||||
return_value={
|
|
||||||
"00408C12345": {
|
|
||||||
config_flow.CONF_HOST: "2.3.4.5",
|
|
||||||
config_flow.CONF_USERNAME: "user",
|
|
||||||
config_flow.CONF_PASSWORD: "pass",
|
|
||||||
config_flow.CONF_PORT: 80,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
), patch("axis.AxisDevice") as mock_device:
|
|
||||||
|
|
||||||
def mock_constructor(loop, host, username, password, port, web_proto):
|
|
||||||
"""Fake the controller constructor."""
|
|
||||||
mock_device.loop = loop
|
|
||||||
mock_device.host = host
|
|
||||||
mock_device.username = username
|
|
||||||
mock_device.password = password
|
|
||||||
mock_device.port = port
|
|
||||||
return mock_device
|
|
||||||
|
|
||||||
mock_device.side_effect = mock_constructor
|
|
||||||
mock_device.vapix.params.system_serialnumber = "serialnumber"
|
|
||||||
mock_device.vapix.params.prodnbr = "prodnbr"
|
|
||||||
mock_device.vapix.params.prodtype = "prodtype"
|
|
||||||
mock_device.vapix.params.firmware_version = "firmware_version"
|
|
||||||
|
|
||||||
result = await hass.config_entries.flow.async_init(
|
|
||||||
config_flow.DOMAIN,
|
|
||||||
data={
|
|
||||||
config_flow.CONF_HOST: "1.2.3.4",
|
|
||||||
config_flow.CONF_PORT: 80,
|
|
||||||
"hostname": "name",
|
|
||||||
"properties": {"macaddress": "00408C12345"},
|
|
||||||
},
|
|
||||||
context={"source": "zeroconf"},
|
|
||||||
)
|
|
||||||
|
|
||||||
assert result["type"] == "create_entry"
|
|
||||||
|
|
||||||
|
|
||||||
async def test_zeroconf_flow_already_configured(hass):
|
async def test_zeroconf_flow_already_configured(hass):
|
||||||
"""Test that zeroconf doesn't setup already configured devices."""
|
"""Test that zeroconf doesn't setup already configured devices."""
|
||||||
entry = MockConfigEntry(
|
entry = MockConfigEntry(
|
||||||
|
@ -298,79 +252,3 @@ async def test_zeroconf_flow_ignore_link_local_address(hass):
|
||||||
|
|
||||||
assert result["type"] == "abort"
|
assert result["type"] == "abort"
|
||||||
assert result["reason"] == "link_local_address"
|
assert result["reason"] == "link_local_address"
|
||||||
|
|
||||||
|
|
||||||
async def test_zeroconf_flow_bad_config_file(hass):
|
|
||||||
"""Test that zeroconf discovery with bad config files abort."""
|
|
||||||
with patch(
|
|
||||||
"homeassistant.components.axis.config_flow.load_json",
|
|
||||||
return_value={
|
|
||||||
"00408C12345": {
|
|
||||||
config_flow.CONF_HOST: "2.3.4.5",
|
|
||||||
config_flow.CONF_USERNAME: "user",
|
|
||||||
config_flow.CONF_PASSWORD: "pass",
|
|
||||||
config_flow.CONF_PORT: 80,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
), patch(
|
|
||||||
"homeassistant.components.axis.config_flow.DEVICE_SCHEMA",
|
|
||||||
side_effect=config_flow.vol.Invalid(""),
|
|
||||||
):
|
|
||||||
result = await hass.config_entries.flow.async_init(
|
|
||||||
config_flow.DOMAIN,
|
|
||||||
data={
|
|
||||||
config_flow.CONF_HOST: "1.2.3.4",
|
|
||||||
"hostname": "name",
|
|
||||||
"properties": {"macaddress": "00408C12345"},
|
|
||||||
},
|
|
||||||
context={"source": "zeroconf"},
|
|
||||||
)
|
|
||||||
|
|
||||||
assert result["type"] == "abort"
|
|
||||||
assert result["reason"] == "bad_config_file"
|
|
||||||
|
|
||||||
|
|
||||||
async def test_import_flow_works(hass):
|
|
||||||
"""Test that import flow works."""
|
|
||||||
with patch("axis.AxisDevice") as mock_device:
|
|
||||||
|
|
||||||
def mock_constructor(loop, host, username, password, port, web_proto):
|
|
||||||
"""Fake the controller constructor."""
|
|
||||||
mock_device.loop = loop
|
|
||||||
mock_device.host = host
|
|
||||||
mock_device.username = username
|
|
||||||
mock_device.password = password
|
|
||||||
mock_device.port = port
|
|
||||||
return mock_device
|
|
||||||
|
|
||||||
mock_device.side_effect = mock_constructor
|
|
||||||
mock_device.vapix.params.system_serialnumber = "serialnumber"
|
|
||||||
mock_device.vapix.params.prodnbr = "prodnbr"
|
|
||||||
mock_device.vapix.params.prodtype = "prodtype"
|
|
||||||
mock_device.vapix.params.firmware_version = "firmware_version"
|
|
||||||
|
|
||||||
result = await hass.config_entries.flow.async_init(
|
|
||||||
config_flow.DOMAIN,
|
|
||||||
data={
|
|
||||||
config_flow.CONF_HOST: "1.2.3.4",
|
|
||||||
config_flow.CONF_USERNAME: "user",
|
|
||||||
config_flow.CONF_PASSWORD: "pass",
|
|
||||||
config_flow.CONF_PORT: 80,
|
|
||||||
config_flow.CONF_NAME: "name",
|
|
||||||
},
|
|
||||||
context={"source": "import"},
|
|
||||||
)
|
|
||||||
|
|
||||||
assert result["type"] == "create_entry"
|
|
||||||
assert result["title"] == "{} - {}".format("prodnbr", "serialnumber")
|
|
||||||
assert result["data"] == {
|
|
||||||
axis.CONF_DEVICE: {
|
|
||||||
config_flow.CONF_HOST: "1.2.3.4",
|
|
||||||
config_flow.CONF_USERNAME: "user",
|
|
||||||
config_flow.CONF_PASSWORD: "pass",
|
|
||||||
config_flow.CONF_PORT: 80,
|
|
||||||
},
|
|
||||||
config_flow.CONF_MAC: "serialnumber",
|
|
||||||
config_flow.CONF_MODEL: "prodnbr",
|
|
||||||
config_flow.CONF_NAME: "name",
|
|
||||||
}
|
|
||||||
|
|
|
@ -7,26 +7,6 @@ from homeassistant.setup import async_setup_component
|
||||||
from tests.common import MockConfigEntry, mock_coro
|
from tests.common import MockConfigEntry, mock_coro
|
||||||
|
|
||||||
|
|
||||||
async def test_setup(hass):
|
|
||||||
"""Test configured options for a device are loaded via config entry."""
|
|
||||||
with patch.object(hass.config_entries, "flow") as mock_config_flow:
|
|
||||||
|
|
||||||
assert await async_setup_component(
|
|
||||||
hass,
|
|
||||||
axis.DOMAIN,
|
|
||||||
{
|
|
||||||
axis.DOMAIN: {
|
|
||||||
"device_name": {
|
|
||||||
axis.config_flow.CONF_HOST: "1.2.3.4",
|
|
||||||
axis.config_flow.CONF_PORT: 80,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
assert len(mock_config_flow.mock_calls) == 1
|
|
||||||
|
|
||||||
|
|
||||||
async def test_setup_device_already_configured(hass):
|
async def test_setup_device_already_configured(hass):
|
||||||
"""Test already configured device does not configure a second."""
|
"""Test already configured device does not configure a second."""
|
||||||
with patch.object(hass, "config_entries") as mock_config_entries:
|
with patch.object(hass, "config_entries") as mock_config_entries:
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue