Improve Axis integration (#36205)

* Improve configuration

* Read new properties for basic device information
Improve tests by mocking less improving stability of tests

* Clean up in device tests

* Support new port management api

* Improve initializing data

* Bump dependency to v28
This commit is contained in:
Robert Svensson 2020-05-31 20:00:15 +02:00 committed by GitHub
parent 6ed68d8ced
commit 01d9366299
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 215 additions and 152 deletions

View File

@ -27,7 +27,7 @@ async def async_setup_entry(hass, config_entry):
# 0.104 introduced config entry unique id, this makes upgrading possible # 0.104 introduced config entry unique id, this makes upgrading possible
if config_entry.unique_id is None: if config_entry.unique_id is None:
hass.config_entries.async_update_entry( hass.config_entries.async_update_entry(
config_entry, unique_id=device.api.vapix.params.system_serialnumber config_entry, unique_id=device.api.vapix.serial_number
) )
hass.data[AXIS_DOMAIN][config_entry.unique_id] = device hass.data[AXIS_DOMAIN][config_entry.unique_id] = device

View File

@ -61,8 +61,7 @@ class AxisFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
password=user_input[CONF_PASSWORD], password=user_input[CONF_PASSWORD],
) )
serial_number = device.vapix.params.system_serialnumber await self.async_set_unique_id(device.vapix.serial_number)
await self.async_set_unique_id(serial_number)
self._abort_if_unique_id_configured( self._abort_if_unique_id_configured(
updates={ updates={
@ -76,8 +75,8 @@ class AxisFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
CONF_PORT: user_input[CONF_PORT], CONF_PORT: user_input[CONF_PORT],
CONF_USERNAME: user_input[CONF_USERNAME], CONF_USERNAME: user_input[CONF_USERNAME],
CONF_PASSWORD: user_input[CONF_PASSWORD], CONF_PASSWORD: user_input[CONF_PASSWORD],
CONF_MAC: serial_number, CONF_MAC: device.vapix.serial_number,
CONF_MODEL: device.vapix.params.prodnbr, CONF_MODEL: device.vapix.product_number,
} }
return await self._create_entry() return await self._create_entry()

View File

@ -4,6 +4,7 @@ import asyncio
import async_timeout import async_timeout
import axis import axis
from axis.configuration import Configuration
from axis.event_stream import OPERATION_INITIALIZED from axis.event_stream import OPERATION_INITIALIZED
from axis.mqtt import mqtt_json_to_event from axis.mqtt import mqtt_json_to_event
from axis.streammanager import SIGNAL_PLAYING, STATE_STOPPED from axis.streammanager import SIGNAL_PLAYING, STATE_STOPPED
@ -185,8 +186,8 @@ class AxisNetworkDevice:
LOGGER.error("Unknown error connecting with Axis device on %s", self.host) LOGGER.error("Unknown error connecting with Axis device on %s", self.host)
return False return False
self.fw_version = self.api.vapix.params.firmware_version self.fw_version = self.api.vapix.firmware_version
self.product_type = self.api.vapix.params.prodtype self.product_type = self.api.vapix.product_type
async def start_platforms(): async def start_platforms():
await asyncio.gather( await asyncio.gather(
@ -254,22 +255,12 @@ async def get_device(hass, host, port, username, password):
"""Create a Axis device.""" """Create a Axis device."""
device = axis.AxisDevice( device = axis.AxisDevice(
host=host, port=port, username=username, password=password, web_proto="http", Configuration(host, port=port, username=username, password=password)
) )
device.vapix.initialize_params(preload_data=False)
device.vapix.initialize_ports()
try: try:
with async_timeout.timeout(15): with async_timeout.timeout(15):
await hass.async_add_executor_job(device.vapix.initialize)
for vapix_call in (
device.vapix.initialize_api_discovery,
device.vapix.params.update_brand,
device.vapix.params.update_properties,
device.vapix.ports.update,
):
await hass.async_add_executor_job(vapix_call)
return device return device

View File

@ -3,7 +3,7 @@
"name": "Axis", "name": "Axis",
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/axis", "documentation": "https://www.home-assistant.io/integrations/axis",
"requirements": ["axis==27"], "requirements": ["axis==28"],
"zeroconf": ["_axis-video._tcp.local."], "zeroconf": ["_axis-video._tcp.local."],
"after_dependencies": ["mqtt"], "after_dependencies": ["mqtt"],
"codeowners": ["@Kane610"] "codeowners": ["@Kane610"]

View File

@ -1,7 +1,6 @@
"""Support for Axis switches.""" """Support for Axis switches."""
from axis.event_stream import CLASS_OUTPUT from axis.event_stream import CLASS_OUTPUT
from axis.port_cgi import ACTION_HIGH, ACTION_LOW
from homeassistant.components.switch import SwitchEntity from homeassistant.components.switch import SwitchEntity
from homeassistant.core import callback from homeassistant.core import callback
@ -39,13 +38,13 @@ class AxisSwitch(AxisEventBase, SwitchEntity):
async def async_turn_on(self, **kwargs): async def async_turn_on(self, **kwargs):
"""Turn on switch.""" """Turn on switch."""
await self.hass.async_add_executor_job( await self.hass.async_add_executor_job(
self.device.api.vapix.ports[self.event.id].action, ACTION_HIGH self.device.api.vapix.ports[self.event.id].close
) )
async def async_turn_off(self, **kwargs): async def async_turn_off(self, **kwargs):
"""Turn off switch.""" """Turn off switch."""
await self.hass.async_add_executor_job( await self.hass.async_add_executor_job(
self.device.api.vapix.ports[self.event.id].action, ACTION_LOW self.device.api.vapix.ports[self.event.id].open
) )
@property @property

View File

@ -306,7 +306,7 @@ avea==1.4
avri-api==0.1.7 avri-api==0.1.7
# homeassistant.components.axis # homeassistant.components.axis
axis==27 axis==28
# homeassistant.components.azure_event_hub # homeassistant.components.azure_event_hub
azure-eventhub==1.3.1 azure-eventhub==1.3.1

View File

@ -147,7 +147,7 @@ async-upnp-client==0.14.13
av==8.0.1 av==8.0.1
# homeassistant.components.axis # homeassistant.components.axis
axis==27 axis==28
# homeassistant.components.homekit # homeassistant.components.homekit
base36==0.1.1 base36==0.1.1

View File

@ -1,5 +1,4 @@
"""Test Axis config flow.""" """Test Axis config flow."""
from homeassistant.components import axis
from homeassistant.components.axis import config_flow from homeassistant.components.axis import config_flow
from homeassistant.components.axis.const import CONF_MODEL, DOMAIN as AXIS_DOMAIN from homeassistant.components.axis.const import CONF_MODEL, DOMAIN as AXIS_DOMAIN
from homeassistant.const import ( from homeassistant.const import (
@ -11,30 +10,12 @@ from homeassistant.const import (
CONF_USERNAME, CONF_USERNAME,
) )
from .test_device import MAC, MODEL, NAME, setup_axis_integration from .test_device import MAC, MODEL, NAME, setup_axis_integration, vapix_session_request
from tests.async_mock import Mock, patch from tests.async_mock import patch
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
def setup_mock_axis_device(mock_device):
"""Prepare mock axis device."""
def mock_constructor(host, username, password, port, web_proto):
"""Fake the controller constructor."""
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 = MAC
mock_device.vapix.params.prodnbr = "prodnbr"
mock_device.vapix.params.prodtype = "prodtype"
mock_device.vapix.params.firmware_version = "firmware_version"
async def test_flow_manual_configuration(hass): async def test_flow_manual_configuration(hass):
"""Test that config flow works.""" """Test that config flow works."""
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
@ -44,10 +25,7 @@ async def test_flow_manual_configuration(hass):
assert result["type"] == "form" assert result["type"] == "form"
assert result["step_id"] == "user" assert result["step_id"] == "user"
with patch("axis.AxisDevice") as mock_device: with patch("axis.vapix.session_request", new=vapix_session_request):
setup_mock_axis_device(mock_device)
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
user_input={ user_input={
@ -59,15 +37,15 @@ async def test_flow_manual_configuration(hass):
) )
assert result["type"] == "create_entry" assert result["type"] == "create_entry"
assert result["title"] == f"prodnbr - {MAC}" assert result["title"] == f"M1065-LW - {MAC}"
assert result["data"] == { assert result["data"] == {
CONF_HOST: "1.2.3.4", CONF_HOST: "1.2.3.4",
CONF_USERNAME: "user", CONF_USERNAME: "user",
CONF_PASSWORD: "pass", CONF_PASSWORD: "pass",
CONF_PORT: 80, CONF_PORT: 80,
CONF_MAC: MAC, CONF_MAC: MAC,
CONF_MODEL: "prodnbr", CONF_MODEL: "M1065-LW",
CONF_NAME: "prodnbr 0", CONF_NAME: "M1065-LW 0",
} }
@ -82,13 +60,7 @@ async def test_manual_configuration_update_configuration(hass):
assert result["type"] == "form" assert result["type"] == "form"
assert result["step_id"] == "user" assert result["step_id"] == "user"
mock_device = Mock() with patch("axis.vapix.session_request", new=vapix_session_request):
mock_device.vapix.params.system_serialnumber = MAC
with patch(
"homeassistant.components.axis.config_flow.get_device",
return_value=mock_device,
):
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
user_input={ user_input={
@ -115,13 +87,7 @@ async def test_flow_fails_already_configured(hass):
assert result["type"] == "form" assert result["type"] == "form"
assert result["step_id"] == "user" assert result["step_id"] == "user"
mock_device = Mock() with patch("axis.vapix.session_request", new=vapix_session_request):
mock_device.vapix.params.system_serialnumber = MAC
with patch(
"homeassistant.components.axis.config_flow.get_device",
return_value=mock_device,
):
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
user_input={ user_input={
@ -191,11 +157,11 @@ async def test_flow_fails_device_unavailable(hass):
async def test_flow_create_entry_multiple_existing_entries_of_same_model(hass): async def test_flow_create_entry_multiple_existing_entries_of_same_model(hass):
"""Test that create entry can generate a name with other entries.""" """Test that create entry can generate a name with other entries."""
entry = MockConfigEntry( entry = MockConfigEntry(
domain=AXIS_DOMAIN, data={CONF_NAME: "prodnbr 0", CONF_MODEL: "prodnbr"}, domain=AXIS_DOMAIN, data={CONF_NAME: "M1065-LW 0", CONF_MODEL: "M1065-LW"},
) )
entry.add_to_hass(hass) entry.add_to_hass(hass)
entry2 = MockConfigEntry( entry2 = MockConfigEntry(
domain=AXIS_DOMAIN, data={CONF_NAME: "prodnbr 1", CONF_MODEL: "prodnbr"}, domain=AXIS_DOMAIN, data={CONF_NAME: "M1065-LW 1", CONF_MODEL: "M1065-LW"},
) )
entry2.add_to_hass(hass) entry2.add_to_hass(hass)
@ -206,10 +172,7 @@ async def test_flow_create_entry_multiple_existing_entries_of_same_model(hass):
assert result["type"] == "form" assert result["type"] == "form"
assert result["step_id"] == "user" assert result["step_id"] == "user"
with patch("axis.AxisDevice") as mock_device: with patch("axis.vapix.session_request", new=vapix_session_request):
setup_mock_axis_device(mock_device)
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
user_input={ user_input={
@ -221,23 +184,22 @@ async def test_flow_create_entry_multiple_existing_entries_of_same_model(hass):
) )
assert result["type"] == "create_entry" assert result["type"] == "create_entry"
assert result["title"] == f"prodnbr - {MAC}" assert result["title"] == f"M1065-LW - {MAC}"
assert result["data"] == { assert result["data"] == {
CONF_HOST: "1.2.3.4", CONF_HOST: "1.2.3.4",
CONF_USERNAME: "user", CONF_USERNAME: "user",
CONF_PASSWORD: "pass", CONF_PASSWORD: "pass",
CONF_PORT: 80, CONF_PORT: 80,
CONF_MAC: MAC, CONF_MAC: MAC,
CONF_MODEL: "prodnbr", CONF_MODEL: "M1065-LW",
CONF_NAME: "prodnbr 2", CONF_NAME: "M1065-LW 2",
} }
assert result["data"][CONF_NAME] == "prodnbr 2" assert result["data"][CONF_NAME] == "M1065-LW 2"
async def test_zeroconf_flow(hass): async def test_zeroconf_flow(hass):
"""Test that zeroconf discovery for new devices work.""" """Test that zeroconf discovery for new devices work."""
with patch.object(axis.device, "get_device", return_value=Mock()):
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
AXIS_DOMAIN, AXIS_DOMAIN,
data={ data={
@ -252,10 +214,7 @@ async def test_zeroconf_flow(hass):
assert result["type"] == "form" assert result["type"] == "form"
assert result["step_id"] == "user" assert result["step_id"] == "user"
with patch("axis.AxisDevice") as mock_device: with patch("axis.vapix.session_request", new=vapix_session_request):
setup_mock_axis_device(mock_device)
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
user_input={ user_input={
@ -267,18 +226,18 @@ async def test_zeroconf_flow(hass):
) )
assert result["type"] == "create_entry" assert result["type"] == "create_entry"
assert result["title"] == f"prodnbr - {MAC}" assert result["title"] == f"M1065-LW - {MAC}"
assert result["data"] == { assert result["data"] == {
CONF_HOST: "1.2.3.4", CONF_HOST: "1.2.3.4",
CONF_USERNAME: "user", CONF_USERNAME: "user",
CONF_PASSWORD: "pass", CONF_PASSWORD: "pass",
CONF_PORT: 80, CONF_PORT: 80,
CONF_MAC: MAC, CONF_MAC: MAC,
CONF_MODEL: "prodnbr", CONF_MODEL: "M1065-LW",
CONF_NAME: "prodnbr 0", CONF_NAME: "M1065-LW 0",
} }
assert result["data"][CONF_NAME] == "prodnbr 0" assert result["data"][CONF_NAME] == "M1065-LW 0"
async def test_zeroconf_flow_already_configured(hass): async def test_zeroconf_flow_already_configured(hass):

View File

@ -1,9 +1,22 @@
"""Test Axis device.""" """Test Axis device."""
from copy import deepcopy from copy import deepcopy
import json
from unittest import mock from unittest import mock
import axis as axislib import axis as axislib
from axis.api_discovery import URL as API_DISCOVERY_URL
from axis.basic_device_info import URL as BASIC_DEVICE_INFO_URL
from axis.event_stream import OPERATION_INITIALIZED from axis.event_stream import OPERATION_INITIALIZED
from axis.mqtt import URL_CLIENT as MQTT_CLIENT_URL
from axis.param_cgi import (
BRAND as BRAND_URL,
INPUT as INPUT_URL,
IOPORT as IOPORT_URL,
OUTPUT as OUTPUT_URL,
PROPERTIES as PROPERTIES_URL,
STREAM_PROFILES as STREAM_PROFILES_URL,
)
from axis.port_management import URL as PORT_MANAGEMENT_URL
import pytest import pytest
from homeassistant import config_entries from homeassistant import config_entries
@ -47,7 +60,7 @@ ENTRY_CONFIG = {
CONF_NAME: NAME, CONF_NAME: NAME,
} }
DEFAULT_API_DISCOVERY = { API_DISCOVERY_RESPONSE = {
"method": "getApiList", "method": "getApiList",
"apiVersion": "1.0", "apiVersion": "1.0",
"data": { "data": {
@ -58,7 +71,58 @@ DEFAULT_API_DISCOVERY = {
}, },
} }
DEFAULT_BRAND = """root.Brand.Brand=AXIS API_DISCOVERY_BASIC_DEVICE_INFO = {
"id": "basic-device-info",
"version": "1.1",
"name": "Basic Device Information",
}
API_DISCOVERY_MQTT = {"id": "mqtt-client", "version": "1.0", "name": "MQTT Client API"}
API_DISCOVERY_PORT_MANAGEMENT = {
"id": "io-port-management",
"version": "1.0",
"name": "IO Port Management",
}
BASIC_DEVICE_INFO_RESPONSE = {
"apiVersion": "1.1",
"data": {
"propertyList": {
"ProdNbr": "M1065-LW",
"ProdType": "Network Camera",
"SerialNumber": "00408C12345",
"Version": "9.80.1",
}
},
}
MQTT_CLIENT_RESPONSE = {
"apiVersion": "1.0",
"context": "some context",
"method": "getClientStatus",
"data": {"status": {"state": "active", "connectionStatus": "Connected"}},
}
PORT_MANAGEMENT_RESPONSE = {
"apiVersion": "1.0",
"method": "getPorts",
"data": {
"numberOfPorts": 1,
"items": [
{
"port": "0",
"configurable": False,
"usage": "",
"name": "PIR sensor",
"direction": "input",
"state": "open",
"normalState": "open",
}
],
},
}
BRAND_RESPONSE = """root.Brand.Brand=AXIS
root.Brand.ProdFullName=AXIS M1065-LW Network Camera root.Brand.ProdFullName=AXIS M1065-LW Network Camera
root.Brand.ProdNbr=M1065-LW root.Brand.ProdNbr=M1065-LW
root.Brand.ProdShortName=AXIS M1065-LW root.Brand.ProdShortName=AXIS M1065-LW
@ -67,7 +131,7 @@ root.Brand.ProdVariant=
root.Brand.WebURL=http://www.axis.com root.Brand.WebURL=http://www.axis.com
""" """
DEFAULT_PORTS = """root.Input.NbrOfInputs=1 PORTS_RESPONSE = """root.Input.NbrOfInputs=1
root.IOPort.I0.Configurable=no root.IOPort.I0.Configurable=no
root.IOPort.I0.Direction=input root.IOPort.I0.Direction=input
root.IOPort.I0.Input.Name=PIR sensor root.IOPort.I0.Input.Name=PIR sensor
@ -75,7 +139,7 @@ root.IOPort.I0.Input.Trig=closed
root.Output.NbrOfOutputs=0 root.Output.NbrOfOutputs=0
""" """
DEFAULT_PROPERTIES = """root.Properties.API.HTTP.Version=3 PROPERTIES_RESPONSE = """root.Properties.API.HTTP.Version=3
root.Properties.API.Metadata.Metadata=yes root.Properties.API.Metadata.Metadata=yes
root.Properties.API.Metadata.Version=1.0 root.Properties.API.Metadata.Version=1.0
root.Properties.Firmware.BuildDate=Feb 15 2019 09:42 root.Properties.Firmware.BuildDate=Feb 15 2019 09:42
@ -89,15 +153,27 @@ root.Properties.System.SerialNumber=00408C12345
""" """
async def setup_axis_integration( def vapix_session_request(session, url, **kwargs):
hass, """Return data based on url."""
config=ENTRY_CONFIG, if API_DISCOVERY_URL in url:
options=ENTRY_OPTIONS, return json.dumps(API_DISCOVERY_RESPONSE)
api_discovery=DEFAULT_API_DISCOVERY, if BASIC_DEVICE_INFO_URL in url:
brand=DEFAULT_BRAND, return json.dumps(BASIC_DEVICE_INFO_RESPONSE)
ports=DEFAULT_PORTS, if MQTT_CLIENT_URL in url:
properties=DEFAULT_PROPERTIES, return json.dumps(MQTT_CLIENT_RESPONSE)
): if PORT_MANAGEMENT_URL in url:
return json.dumps(PORT_MANAGEMENT_RESPONSE)
if BRAND_URL in url:
return BRAND_RESPONSE
if IOPORT_URL in url or INPUT_URL in url or OUTPUT_URL in url:
return PORTS_RESPONSE
if PROPERTIES_URL in url:
return PROPERTIES_RESPONSE
if STREAM_PROFILES_URL in url:
return ""
async def setup_axis_integration(hass, config=ENTRY_CONFIG, options=ENTRY_OPTIONS):
"""Create the Axis device.""" """Create the Axis device."""
config_entry = MockConfigEntry( config_entry = MockConfigEntry(
domain=AXIS_DOMAIN, domain=AXIS_DOMAIN,
@ -109,25 +185,7 @@ async def setup_axis_integration(
) )
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
def mock_update_api_discovery(self): with patch("axis.vapix.session_request", new=vapix_session_request), patch(
self.process_raw(api_discovery)
def mock_update_brand(self):
self.process_raw(brand)
def mock_update_ports(self):
self.process_raw(ports)
def mock_update_properties(self):
self.process_raw(properties)
with patch(
"axis.api_discovery.ApiDiscovery.update", new=mock_update_api_discovery
), patch("axis.param_cgi.Brand.update_brand", new=mock_update_brand), patch(
"axis.param_cgi.Ports.update_ports", new=mock_update_ports
), patch(
"axis.param_cgi.Properties.update_properties", new=mock_update_properties
), patch(
"axis.rtsp.RTSPClient.start", return_value=True, "axis.rtsp.RTSPClient.start", return_value=True,
): ):
await hass.config_entries.async_setup(config_entry.entry_id) await hass.config_entries.async_setup(config_entry.entry_id)
@ -144,6 +202,11 @@ async def test_device_setup(hass):
) as forward_entry_setup: ) as forward_entry_setup:
device = await setup_axis_integration(hass) device = await setup_axis_integration(hass)
assert device.api.vapix.firmware_version == "9.10.1"
assert device.api.vapix.product_number == "M1065-LW"
assert device.api.vapix.product_type == "Network Camera"
assert device.api.vapix.serial_number == "00408C12345"
entry = device.config_entry entry = device.config_entry
assert len(forward_entry_setup.mock_calls) == 3 assert len(forward_entry_setup.mock_calls) == 3
@ -157,20 +220,29 @@ async def test_device_setup(hass):
assert device.serial == ENTRY_CONFIG[CONF_MAC] assert device.serial == ENTRY_CONFIG[CONF_MAC]
async def test_device_info(hass):
"""Verify other path of device information works."""
api_discovery = deepcopy(API_DISCOVERY_RESPONSE)
api_discovery["data"]["apiList"].append(API_DISCOVERY_BASIC_DEVICE_INFO)
with patch.dict(API_DISCOVERY_RESPONSE, api_discovery):
device = await setup_axis_integration(hass)
assert device.api.vapix.firmware_version == "9.80.1"
assert device.api.vapix.product_number == "M1065-LW"
assert device.api.vapix.product_type == "Network Camera"
assert device.api.vapix.serial_number == "00408C12345"
async def test_device_support_mqtt(hass): async def test_device_support_mqtt(hass):
"""Successful setup.""" """Successful setup."""
api_discovery = deepcopy(DEFAULT_API_DISCOVERY) api_discovery = deepcopy(API_DISCOVERY_RESPONSE)
api_discovery["data"]["apiList"].append( api_discovery["data"]["apiList"].append(API_DISCOVERY_MQTT)
{"id": "mqtt-client", "version": "1.0", "name": "MQTT Client API"}
)
get_client_status = {"data": {"status": {"state": "active"}}}
mock_mqtt = await async_mock_mqtt_component(hass) mock_mqtt = await async_mock_mqtt_component(hass)
with patch( with patch.dict(API_DISCOVERY_RESPONSE, api_discovery):
"axis.mqtt.MqttClient.get_client_status", return_value=get_client_status await setup_axis_integration(hass)
):
await setup_axis_integration(hass, api_discovery=api_discovery)
mock_mqtt.async_subscribe.assert_called_with(f"{MAC}/#", mock.ANY, 0, "utf-8") mock_mqtt.async_subscribe.assert_called_with(f"{MAC}/#", mock.ANY, 0, "utf-8")
@ -267,7 +339,7 @@ async def test_shutdown():
async def test_get_device_fails(hass): async def test_get_device_fails(hass):
"""Device unauthorized yields authentication required error.""" """Device unauthorized yields authentication required error."""
with patch( with patch(
"axis.api_discovery.ApiDiscovery.update", side_effect=axislib.Unauthorized "axis.vapix.session_request", side_effect=axislib.Unauthorized
), pytest.raises(axis.errors.AuthenticationRequired): ), pytest.raises(axis.errors.AuthenticationRequired):
await axis.device.get_device(hass, host="", port="", username="", password="") await axis.device.get_device(hass, host="", port="", username="", password="")
@ -275,7 +347,7 @@ async def test_get_device_fails(hass):
async def test_get_device_device_unavailable(hass): async def test_get_device_device_unavailable(hass):
"""Device unavailable yields cannot connect error.""" """Device unavailable yields cannot connect error."""
with patch( with patch(
"axis.api_discovery.ApiDiscovery.update", side_effect=axislib.RequestError "axis.vapix.session_request", side_effect=axislib.RequestError
), pytest.raises(axis.errors.CannotConnect): ), pytest.raises(axis.errors.CannotConnect):
await axis.device.get_device(hass, host="", port="", username="", password="") await axis.device.get_device(hass, host="", port="", username="", password="")
@ -283,6 +355,6 @@ async def test_get_device_device_unavailable(hass):
async def test_get_device_unknown_error(hass): async def test_get_device_unknown_error(hass):
"""Device yield unknown error.""" """Device yield unknown error."""
with patch( with patch(
"axis.api_discovery.ApiDiscovery.update", side_effect=axislib.AxisException "axis.vapix.session_request", side_effect=axislib.AxisException
), pytest.raises(axis.errors.AuthenticationRequired): ), pytest.raises(axis.errors.AuthenticationRequired):
await axis.device.get_device(hass, host="", port="", username="", password="") await axis.device.get_device(hass, host="", port="", username="", password="")

View File

@ -1,14 +1,19 @@
"""Axis switch platform tests.""" """Axis switch platform tests."""
from axis.port_cgi import ACTION_HIGH, ACTION_LOW from copy import deepcopy
from homeassistant.components.axis.const import DOMAIN as AXIS_DOMAIN from homeassistant.components.axis.const import DOMAIN as AXIS_DOMAIN
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
from .test_device import NAME, setup_axis_integration from .test_device import (
API_DISCOVERY_PORT_MANAGEMENT,
API_DISCOVERY_RESPONSE,
NAME,
setup_axis_integration,
)
from tests.async_mock import Mock, call as mock_call from tests.async_mock import Mock, patch
EVENTS = [ EVENTS = [
{ {
@ -46,8 +51,8 @@ async def test_no_switches(hass):
assert not hass.states.async_entity_ids(SWITCH_DOMAIN) assert not hass.states.async_entity_ids(SWITCH_DOMAIN)
async def test_switches(hass): async def test_switches_with_port_cgi(hass):
"""Test that switches are loaded properly.""" """Test that switches are loaded properly using port.cgi."""
device = await setup_axis_integration(hass) device = await setup_axis_integration(hass)
device.api.vapix.ports = {"0": Mock(), "1": Mock()} device.api.vapix.ports = {"0": Mock(), "1": Mock()}
@ -68,14 +73,13 @@ async def test_switches(hass):
assert relay_1.state == "on" assert relay_1.state == "on"
assert relay_1.name == f"{NAME} Relay 1" assert relay_1.name == f"{NAME} Relay 1"
device.api.vapix.ports["0"].action = Mock()
await hass.services.async_call( await hass.services.async_call(
SWITCH_DOMAIN, SWITCH_DOMAIN,
"turn_on", "turn_on",
{"entity_id": f"switch.{NAME}_doorbell"}, {"entity_id": f"switch.{NAME}_doorbell"},
blocking=True, blocking=True,
) )
device.api.vapix.ports["0"].close.assert_called_once()
await hass.services.async_call( await hass.services.async_call(
SWITCH_DOMAIN, SWITCH_DOMAIN,
@ -83,8 +87,47 @@ async def test_switches(hass):
{"entity_id": f"switch.{NAME}_doorbell"}, {"entity_id": f"switch.{NAME}_doorbell"},
blocking=True, blocking=True,
) )
device.api.vapix.ports["0"].open.assert_called_once()
assert device.api.vapix.ports["0"].action.call_args_list == [
mock_call(ACTION_HIGH), async def test_switches_with_port_management(hass):
mock_call(ACTION_LOW), """Test that switches are loaded properly using port management."""
] api_discovery = deepcopy(API_DISCOVERY_RESPONSE)
api_discovery["data"]["apiList"].append(API_DISCOVERY_PORT_MANAGEMENT)
with patch.dict(API_DISCOVERY_RESPONSE, api_discovery):
device = await setup_axis_integration(hass)
device.api.vapix.ports = {"0": Mock(), "1": Mock()}
device.api.vapix.ports["0"].name = "Doorbell"
device.api.vapix.ports["1"].name = ""
for event in EVENTS:
device.api.event.process_event(event)
await hass.async_block_till_done()
assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 2
relay_0 = hass.states.get(f"switch.{NAME}_doorbell")
assert relay_0.state == "off"
assert relay_0.name == f"{NAME} Doorbell"
relay_1 = hass.states.get(f"switch.{NAME}_relay_1")
assert relay_1.state == "on"
assert relay_1.name == f"{NAME} Relay 1"
await hass.services.async_call(
SWITCH_DOMAIN,
"turn_on",
{"entity_id": f"switch.{NAME}_doorbell"},
blocking=True,
)
device.api.vapix.ports["0"].close.assert_called_once()
await hass.services.async_call(
SWITCH_DOMAIN,
"turn_off",
{"entity_id": f"switch.{NAME}_doorbell"},
blocking=True,
)
device.api.vapix.ports["0"].open.assert_called_once()