2019-03-24 15:16:50 +00:00
""" Test Axis device. """
2020-01-02 23:02:59 +00:00
from copy import deepcopy
2020-05-25 21:13:34 +00:00
from unittest import mock
2019-03-24 15:16:50 +00:00
2020-01-02 23:02:59 +00:00
import axis as axislib
2020-05-31 18:00:15 +00:00
from axis . api_discovery import URL as API_DISCOVERY_URL
2020-09-06 21:26:06 +00:00
from axis . applications import URL_LIST as APPLICATIONS_URL
from axis . applications . vmd4 import URL as VMD4_URL
2020-05-31 18:00:15 +00:00
from axis . basic_device_info import URL as BASIC_DEVICE_INFO_URL
2020-05-14 08:49:27 +00:00
from axis . event_stream import OPERATION_INITIALIZED
2020-06-18 21:27:08 +00:00
from axis . light_control import URL as LIGHT_CONTROL_URL
2020-05-31 18:00:15 +00:00
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
2019-03-24 15:16:50 +00:00
import pytest
2020-01-02 23:02:59 +00:00
from homeassistant import config_entries
from homeassistant . components import axis
2020-05-14 08:49:27 +00:00
from homeassistant . components . axis . const import (
CONF_EVENTS ,
CONF_MODEL ,
DOMAIN as AXIS_DOMAIN ,
)
2020-05-25 21:13:34 +00:00
from homeassistant . components . binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
2020-05-14 08:49:27 +00:00
from homeassistant . const import (
CONF_HOST ,
CONF_MAC ,
CONF_NAME ,
CONF_PASSWORD ,
CONF_PORT ,
CONF_USERNAME ,
)
2019-03-24 15:16:50 +00:00
2020-10-20 07:31:04 +00:00
from tests . async_mock import AsyncMock , Mock , patch
2020-06-22 21:59:50 +00:00
from tests . common import MockConfigEntry , async_fire_mqtt_message
2020-01-03 12:27:14 +00:00
2020-01-02 23:02:59 +00:00
MAC = " 00408C12345 "
MODEL = " model "
NAME = " name "
2019-12-08 14:44:04 +00:00
2020-06-04 12:25:50 +00:00
ENTRY_OPTIONS = { CONF_EVENTS : True }
2019-03-24 15:16:50 +00:00
ENTRY_CONFIG = {
2020-05-14 08:49:27 +00:00
CONF_HOST : " 1.2.3.4 " ,
2020-06-01 16:45:38 +00:00
CONF_USERNAME : " root " ,
CONF_PASSWORD : " pass " ,
2020-05-14 08:49:27 +00:00
CONF_PORT : 80 ,
CONF_MAC : MAC ,
CONF_MODEL : MODEL ,
CONF_NAME : NAME ,
2019-03-24 15:16:50 +00:00
}
2020-05-31 18:00:15 +00:00
API_DISCOVERY_RESPONSE = {
2020-05-25 21:13:34 +00:00
" method " : " getApiList " ,
" apiVersion " : " 1.0 " ,
" data " : {
" apiList " : [
{ " id " : " api-discovery " , " version " : " 1.0 " , " name " : " API Discovery Service " } ,
{ " id " : " param-cgi " , " version " : " 1.0 " , " name " : " Legacy Parameter Handling " } ,
]
} ,
}
2020-05-31 18:00:15 +00:00
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 " ,
}
2020-09-06 21:26:06 +00:00
APPLICATIONS_LIST_RESPONSE = """ <reply result= " ok " >
< application Name = " vmd " NiceName = " AXIS Video Motion Detection " Vendor = " Axis Communications " Version = " 4.2-0 " ApplicationID = " 143440 " License = " None " Status = " Running " ConfigurationPage = " local/vmd/config.html " VendorHomePage = " http://www.axis.com " / >
< / reply > """
2020-05-31 18:00:15 +00:00
BASIC_DEVICE_INFO_RESPONSE = {
" apiVersion " : " 1.1 " ,
" data " : {
" propertyList " : {
" ProdNbr " : " M1065-LW " ,
" ProdType " : " Network Camera " ,
" SerialNumber " : " 00408C12345 " ,
" Version " : " 9.80.1 " ,
}
} ,
}
2020-06-18 21:27:08 +00:00
LIGHT_CONTROL_RESPONSE = {
" apiVersion " : " 1.1 " ,
" method " : " getLightInformation " ,
" data " : {
" items " : [
{
" lightID " : " led0 " ,
" lightType " : " IR " ,
" enabled " : True ,
" synchronizeDayNightMode " : True ,
" lightState " : False ,
" automaticIntensityMode " : False ,
" automaticAngleOfIlluminationMode " : False ,
" nrOfLEDs " : 1 ,
" error " : False ,
" errorInfo " : " " ,
}
]
} ,
}
2020-05-31 18:00:15 +00:00
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 " ,
}
] ,
} ,
}
2020-09-06 21:26:06 +00:00
VMD4_RESPONSE = {
" apiVersion " : " 1.4 " ,
" method " : " getConfiguration " ,
" context " : " Axis library " ,
" data " : {
" cameras " : [ { " id " : 1 , " rotation " : 0 , " active " : True } ] ,
" profiles " : [
{ " filters " : [ ] , " camera " : 1 , " triggers " : [ ] , " name " : " Profile 1 " , " uid " : 1 }
] ,
} ,
}
2020-05-31 18:00:15 +00:00
BRAND_RESPONSE = """ root.Brand.Brand=AXIS
2020-01-02 23:02:59 +00:00
root . Brand . ProdFullName = AXIS M1065 - LW Network Camera
root . Brand . ProdNbr = M1065 - LW
root . Brand . ProdShortName = AXIS M1065 - LW
root . Brand . ProdType = Network Camera
root . Brand . ProdVariant =
root . Brand . WebURL = http : / / www . axis . com
"""
2020-05-31 18:00:15 +00:00
PORTS_RESPONSE = """ root.Input.NbrOfInputs=1
2020-01-02 23:02:59 +00:00
root . IOPort . I0 . Configurable = no
root . IOPort . I0 . Direction = input
root . IOPort . I0 . Input . Name = PIR sensor
root . IOPort . I0 . Input . Trig = closed
root . Output . NbrOfOutputs = 0
"""
2020-05-31 18:00:15 +00:00
PROPERTIES_RESPONSE = """ root.Properties.API.HTTP.Version=3
2020-01-02 23:02:59 +00:00
root . Properties . API . Metadata . Metadata = yes
root . Properties . API . Metadata . Version = 1.0
2020-09-06 21:26:06 +00:00
root . Properties . EmbeddedDevelopment . Version = 2.16
2020-01-02 23:02:59 +00:00
root . Properties . Firmware . BuildDate = Feb 15 2019 09 : 42
root . Properties . Firmware . BuildNumber = 26
root . Properties . Firmware . Version = 9.10 .1
root . Properties . Image . Format = jpeg , mjpeg , h264
root . Properties . Image . NbrOfViews = 2
root . Properties . Image . Resolution = 1920 x1080 , 1280 x960 , 1280 x720 , 1024 x768 , 1024 x576 , 800 x600 , 640 x480 , 640 x360 , 352 x240 , 320 x240
root . Properties . Image . Rotation = 0 , 180
2020-01-03 12:27:14 +00:00
root . Properties . System . SerialNumber = 00408 C12345
2020-01-02 23:02:59 +00:00
"""
2020-06-01 16:45:38 +00:00
STREAM_PROFILES_RESPONSE = """ root.StreamProfile.MaxGroups=26
root . StreamProfile . S0 . Description = profile_1_description
root . StreamProfile . S0 . Name = profile_1
root . StreamProfile . S0 . Parameters = videocodec = h264
root . StreamProfile . S1 . Description = profile_2_description
root . StreamProfile . S1 . Name = profile_2
root . StreamProfile . S1 . Parameters = videocodec = h265
"""
2020-01-02 23:02:59 +00:00
2020-10-20 07:31:04 +00:00
async def vapix_request ( self , session , url , * * kwargs ) :
2020-05-31 18:00:15 +00:00
""" Return data based on url. """
if API_DISCOVERY_URL in url :
2020-10-19 18:01:25 +00:00
return API_DISCOVERY_RESPONSE
2020-09-06 21:26:06 +00:00
if APPLICATIONS_URL in url :
return APPLICATIONS_LIST_RESPONSE
2020-05-31 18:00:15 +00:00
if BASIC_DEVICE_INFO_URL in url :
2020-10-19 18:01:25 +00:00
return BASIC_DEVICE_INFO_RESPONSE
2020-06-18 21:27:08 +00:00
if LIGHT_CONTROL_URL in url :
2020-10-19 18:01:25 +00:00
return LIGHT_CONTROL_RESPONSE
2020-05-31 18:00:15 +00:00
if MQTT_CLIENT_URL in url :
2020-10-19 18:01:25 +00:00
return MQTT_CLIENT_RESPONSE
2020-05-31 18:00:15 +00:00
if PORT_MANAGEMENT_URL in url :
2020-10-19 18:01:25 +00:00
return PORT_MANAGEMENT_RESPONSE
2020-09-06 21:26:06 +00:00
if VMD4_URL in url :
2020-10-19 18:01:25 +00:00
return VMD4_RESPONSE
2020-05-31 18:00:15 +00:00
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 :
2020-06-01 16:45:38 +00:00
return STREAM_PROFILES_RESPONSE
2020-05-31 18:00:15 +00:00
async def setup_axis_integration ( hass , config = ENTRY_CONFIG , options = ENTRY_OPTIONS ) :
2020-01-02 23:02:59 +00:00
""" Create the Axis device. """
2020-01-03 12:27:14 +00:00
config_entry = MockConfigEntry (
2020-05-14 08:49:27 +00:00
domain = AXIS_DOMAIN ,
2020-01-02 23:02:59 +00:00
data = deepcopy ( config ) ,
connection_class = config_entries . CONN_CLASS_LOCAL_PUSH ,
options = deepcopy ( options ) ,
entry_id = " 1 " ,
2020-01-30 21:20:30 +00:00
version = 2 ,
2020-01-02 23:02:59 +00:00
)
2020-01-03 12:27:14 +00:00
config_entry . add_to_hass ( hass )
2020-10-19 18:01:25 +00:00
with patch ( " axis.vapix.Vapix.request " , new = vapix_request ) , patch (
2020-08-27 11:56:20 +00:00
" axis.rtsp.RTSPClient.start " ,
return_value = True ,
2020-01-02 23:02:59 +00:00
) :
2020-01-03 12:27:14 +00:00
await hass . config_entries . async_setup ( config_entry . entry_id )
2020-05-25 21:13:34 +00:00
await hass . async_block_till_done ( )
2019-03-24 15:16:50 +00:00
2020-05-15 21:56:09 +00:00
return hass . data [ AXIS_DOMAIN ] . get ( config_entry . unique_id )
2019-03-24 15:16:50 +00:00
2020-01-02 23:02:59 +00:00
async def test_device_setup ( hass ) :
2019-04-02 18:13:11 +00:00
""" Successful setup. """
2020-01-02 23:02:59 +00:00
with patch (
" homeassistant.config_entries.ConfigEntries.async_forward_entry_setup " ,
return_value = True ,
) as forward_entry_setup :
device = await setup_axis_integration ( hass )
2020-05-31 18:00:15 +00:00
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
2020-01-02 23:02:59 +00:00
2020-06-18 21:27:08 +00:00
assert len ( forward_entry_setup . mock_calls ) == 4
2020-05-15 21:56:09 +00:00
assert forward_entry_setup . mock_calls [ 0 ] [ 1 ] == ( entry , " binary_sensor " )
assert forward_entry_setup . mock_calls [ 1 ] [ 1 ] == ( entry , " camera " )
2020-06-18 21:27:08 +00:00
assert forward_entry_setup . mock_calls [ 2 ] [ 1 ] == ( entry , " light " )
assert forward_entry_setup . mock_calls [ 3 ] [ 1 ] == ( entry , " switch " )
2020-01-02 23:02:59 +00:00
2020-05-14 08:49:27 +00:00
assert device . host == ENTRY_CONFIG [ CONF_HOST ]
assert device . model == ENTRY_CONFIG [ CONF_MODEL ]
assert device . name == ENTRY_CONFIG [ CONF_NAME ]
assert device . serial == ENTRY_CONFIG [ CONF_MAC ]
2020-01-02 23:02:59 +00:00
2020-05-31 18:00:15 +00:00
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 "
2020-06-22 21:59:50 +00:00
async def test_device_support_mqtt ( hass , mqtt_mock ) :
2020-05-25 21:13:34 +00:00
""" Successful setup. """
2020-05-31 18:00:15 +00:00
api_discovery = deepcopy ( API_DISCOVERY_RESPONSE )
api_discovery [ " data " ] [ " apiList " ] . append ( API_DISCOVERY_MQTT )
2020-05-25 21:13:34 +00:00
2020-05-31 18:00:15 +00:00
with patch . dict ( API_DISCOVERY_RESPONSE , api_discovery ) :
await setup_axis_integration ( hass )
2020-05-25 21:13:34 +00:00
2020-06-22 21:59:50 +00:00
mqtt_mock . async_subscribe . assert_called_with ( f " { MAC } /# " , mock . ANY , 0 , " utf-8 " )
2020-05-25 21:13:34 +00:00
topic = f " { MAC } /event/tns:onvif/Device/tns:axis/Sensor/PIR/$source/sensor/0 "
message = b ' { " timestamp " : 1590258472044, " topic " : " onvif:Device/axis:Sensor/PIR " , " message " : { " source " : { " sensor " : " 0 " }, " key " : {} , " data " : { " state " : " 1 " }}} '
assert len ( hass . states . async_entity_ids ( BINARY_SENSOR_DOMAIN ) ) == 0
async_fire_mqtt_message ( hass , topic , message )
await hass . async_block_till_done ( )
assert len ( hass . states . async_entity_ids ( BINARY_SENSOR_DOMAIN ) ) == 1
pir = hass . states . get ( f " binary_sensor. { NAME } _pir_0 " )
assert pir . state == " on "
assert pir . name == f " { NAME } PIR 0 "
2020-01-02 23:02:59 +00:00
async def test_update_address ( hass ) :
""" Test update address works. """
device = await setup_axis_integration ( hass )
assert device . api . config . host == " 1.2.3.4 "
2020-10-19 18:01:25 +00:00
with patch ( " axis.vapix.Vapix.request " , new = vapix_request ) , patch (
2020-08-27 11:56:20 +00:00
" homeassistant.components.axis.async_setup_entry " ,
return_value = True ,
2020-08-08 18:23:56 +00:00
) as mock_setup_entry :
await hass . config_entries . flow . async_init (
AXIS_DOMAIN ,
data = {
" host " : " 2.3.4.5 " ,
" port " : 80 ,
" hostname " : " name " ,
" properties " : { " macaddress " : MAC } ,
} ,
context = { " source " : " zeroconf " } ,
)
await hass . async_block_till_done ( )
2019-04-02 18:13:11 +00:00
2020-01-02 23:02:59 +00:00
assert device . api . config . host == " 2.3.4.5 "
2020-08-08 18:23:56 +00:00
assert len ( mock_setup_entry . mock_calls ) == 1
2019-04-02 18:13:11 +00:00
2019-04-15 22:06:45 +00:00
async def test_device_unavailable ( hass ) :
""" Successful setup. """
2020-01-02 23:02:59 +00:00
device = await setup_axis_integration ( hass )
device . async_connection_status_callback ( status = False )
assert not device . available
2019-04-15 22:06:45 +00:00
async def test_device_reset ( hass ) :
""" Successfully reset device. """
2020-01-02 23:02:59 +00:00
device = await setup_axis_integration ( hass )
result = await device . async_reset ( )
assert result is True
2019-04-15 22:06:45 +00:00
2020-01-02 23:02:59 +00:00
async def test_device_not_accessible ( hass ) :
2019-03-24 15:16:50 +00:00
""" Failed setup schedules a retry of setup. """
2020-01-03 12:27:14 +00:00
with patch . object ( axis . device , " get_device " , side_effect = axis . errors . CannotConnect ) :
2020-01-02 23:02:59 +00:00
await setup_axis_integration ( hass )
2020-05-14 08:49:27 +00:00
assert hass . data [ AXIS_DOMAIN ] == { }
2019-03-24 15:16:50 +00:00
2020-01-02 23:02:59 +00:00
async def test_device_unknown_error ( hass ) :
2019-03-24 15:16:50 +00:00
""" Unknown errors are handled. """
2020-01-02 23:02:59 +00:00
with patch . object ( axis . device , " get_device " , side_effect = Exception ) :
await setup_axis_integration ( hass )
2020-05-14 08:49:27 +00:00
assert hass . data [ AXIS_DOMAIN ] == { }
2019-03-24 15:16:50 +00:00
async def test_new_event_sends_signal ( hass ) :
""" Make sure that new event send signal. """
entry = Mock ( )
entry . data = ENTRY_CONFIG
2020-01-02 23:02:59 +00:00
axis_device = axis . device . AxisNetworkDevice ( hass , entry )
2019-03-24 15:16:50 +00:00
2020-01-02 23:02:59 +00:00
with patch . object ( axis . device , " async_dispatcher_send " ) as mock_dispatch_send :
2020-05-14 08:49:27 +00:00
axis_device . async_event_callback ( action = OPERATION_INITIALIZED , event_id = " event " )
2019-03-24 15:16:50 +00:00
await hass . async_block_till_done ( )
assert len ( mock_dispatch_send . mock_calls ) == 1
assert len ( mock_dispatch_send . mock_calls [ 0 ] ) == 3
async def test_shutdown ( ) :
""" Successful shutdown. """
hass = Mock ( )
entry = Mock ( )
entry . data = ENTRY_CONFIG
2020-01-02 23:02:59 +00:00
axis_device = axis . device . AxisNetworkDevice ( hass , entry )
2019-03-24 15:16:50 +00:00
axis_device . api = Mock ( )
2020-10-20 07:31:04 +00:00
axis_device . api . vapix . close = AsyncMock ( )
2019-03-24 15:16:50 +00:00
2020-10-20 07:31:04 +00:00
await axis_device . shutdown ( None )
2019-03-24 15:16:50 +00:00
2020-05-25 21:13:34 +00:00
assert len ( axis_device . api . stream . stop . mock_calls ) == 1
2020-10-20 07:31:04 +00:00
assert len ( axis_device . api . vapix . close . mock_calls ) == 1
2019-03-24 15:16:50 +00:00
async def test_get_device_fails ( hass ) :
""" Device unauthorized yields authentication required error. """
2019-07-31 19:25:30 +00:00
with patch (
2020-10-19 18:01:25 +00:00
" axis.vapix.Vapix.request " , side_effect = axislib . Unauthorized
2020-01-02 23:02:59 +00:00
) , pytest . raises ( axis . errors . AuthenticationRequired ) :
2020-01-30 21:20:30 +00:00
await axis . device . get_device ( hass , host = " " , port = " " , username = " " , password = " " )
2019-03-24 15:16:50 +00:00
async def test_get_device_device_unavailable ( hass ) :
""" Device unavailable yields cannot connect error. """
2019-07-31 19:25:30 +00:00
with patch (
2020-10-19 18:01:25 +00:00
" axis.vapix.Vapix.request " , side_effect = axislib . RequestError
2020-01-02 23:02:59 +00:00
) , pytest . raises ( axis . errors . CannotConnect ) :
2020-01-30 21:20:30 +00:00
await axis . device . get_device ( hass , host = " " , port = " " , username = " " , password = " " )
2019-03-24 15:16:50 +00:00
async def test_get_device_unknown_error ( hass ) :
""" Device yield unknown error. """
2019-07-31 19:25:30 +00:00
with patch (
2020-10-19 18:01:25 +00:00
" axis.vapix.Vapix.request " , side_effect = axislib . AxisException
2020-01-02 23:02:59 +00:00
) , pytest . raises ( axis . errors . AuthenticationRequired ) :
2020-01-30 21:20:30 +00:00
await axis . device . get_device ( hass , host = " " , port = " " , username = " " , password = " " )