Add google_assistant alarm_control_panel (#26249)
* add alarm_control_panel to google_assistant * add cancel arming option * raise error if requested state is same as current * rework executing command logic * Add tests * fixed tests * fixed level synonyms
This commit is contained in:
parent
4582b6e668
commit
d1adb28c6b
5 changed files with 478 additions and 3 deletions
|
@ -1,6 +1,6 @@
|
|||
"""Tests for the Google Assistant traits."""
|
||||
from unittest.mock import patch, Mock
|
||||
|
||||
import logging
|
||||
import pytest
|
||||
|
||||
from homeassistant.components import (
|
||||
|
@ -18,12 +18,16 @@ from homeassistant.components import (
|
|||
switch,
|
||||
vacuum,
|
||||
group,
|
||||
alarm_control_panel,
|
||||
)
|
||||
from homeassistant.components.climate import const as climate
|
||||
from homeassistant.components.google_assistant import trait, helpers, const, error
|
||||
from homeassistant.const import (
|
||||
STATE_ON,
|
||||
STATE_OFF,
|
||||
STATE_ALARM_ARMED_AWAY,
|
||||
STATE_ALARM_DISARMED,
|
||||
STATE_ALARM_PENDING,
|
||||
ATTR_ENTITY_ID,
|
||||
SERVICE_TURN_ON,
|
||||
SERVICE_TURN_OFF,
|
||||
|
@ -40,6 +44,7 @@ from homeassistant.util import color
|
|||
from tests.common import async_mock_service, mock_coro
|
||||
from . import BASIC_CONFIG, MockConfig
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
REQ_ID = "ff36a3cc-ec34-11e6-b1a0-64510650abcf"
|
||||
|
||||
|
@ -816,6 +821,336 @@ async def test_lock_unlock_unlock(hass):
|
|||
assert len(calls) == 2
|
||||
|
||||
|
||||
async def test_arm_disarm_arm_away(hass):
|
||||
"""Test ArmDisarm trait Arming support for alarm_control_panel domain."""
|
||||
assert helpers.get_google_type(alarm_control_panel.DOMAIN, None) is not None
|
||||
assert trait.ArmDisArmTrait.supported(alarm_control_panel.DOMAIN, 0, None)
|
||||
assert trait.ArmDisArmTrait.might_2fa(alarm_control_panel.DOMAIN, 0, None)
|
||||
|
||||
trt = trait.ArmDisArmTrait(
|
||||
hass,
|
||||
State(
|
||||
"alarm_control_panel.alarm",
|
||||
STATE_ALARM_ARMED_AWAY,
|
||||
{alarm_control_panel.ATTR_CODE_ARM_REQUIRED: True},
|
||||
),
|
||||
PIN_CONFIG,
|
||||
)
|
||||
assert trt.sync_attributes() == {
|
||||
"availableArmLevels": {
|
||||
"levels": [
|
||||
{
|
||||
"level_name": "armed_home",
|
||||
"level_values": [
|
||||
{"level_synonym": ["armed home", "home"], "lang": "en"}
|
||||
],
|
||||
},
|
||||
{
|
||||
"level_name": "armed_away",
|
||||
"level_values": [
|
||||
{"level_synonym": ["armed away", "away"], "lang": "en"}
|
||||
],
|
||||
},
|
||||
{
|
||||
"level_name": "armed_night",
|
||||
"level_values": [
|
||||
{"level_synonym": ["armed night", "night"], "lang": "en"}
|
||||
],
|
||||
},
|
||||
{
|
||||
"level_name": "armed_custom_bypass",
|
||||
"level_values": [
|
||||
{
|
||||
"level_synonym": ["armed custom bypass", "custom"],
|
||||
"lang": "en",
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
"level_name": "triggered",
|
||||
"level_values": [{"level_synonym": ["triggered"], "lang": "en"}],
|
||||
},
|
||||
],
|
||||
"ordered": False,
|
||||
}
|
||||
}
|
||||
|
||||
assert trt.query_attributes() == {
|
||||
"isArmed": True,
|
||||
"currentArmLevel": STATE_ALARM_ARMED_AWAY,
|
||||
}
|
||||
|
||||
assert trt.can_execute(
|
||||
trait.COMMAND_ARMDISARM, {"arm": True, "armLevel": STATE_ALARM_ARMED_AWAY}
|
||||
)
|
||||
|
||||
calls = async_mock_service(
|
||||
hass, alarm_control_panel.DOMAIN, alarm_control_panel.SERVICE_ALARM_ARM_AWAY
|
||||
)
|
||||
|
||||
# Test with no secure_pin configured
|
||||
|
||||
with pytest.raises(error.SmartHomeError) as err:
|
||||
trt = trait.ArmDisArmTrait(
|
||||
hass,
|
||||
State(
|
||||
"alarm_control_panel.alarm",
|
||||
STATE_ALARM_DISARMED,
|
||||
{alarm_control_panel.ATTR_CODE_ARM_REQUIRED: True},
|
||||
),
|
||||
BASIC_CONFIG,
|
||||
)
|
||||
await trt.execute(
|
||||
trait.COMMAND_ARMDISARM,
|
||||
BASIC_DATA,
|
||||
{"arm": True, "armLevel": STATE_ALARM_ARMED_AWAY},
|
||||
{},
|
||||
)
|
||||
assert len(calls) == 0
|
||||
assert err.value.code == const.ERR_CHALLENGE_NOT_SETUP
|
||||
|
||||
trt = trait.ArmDisArmTrait(
|
||||
hass,
|
||||
State(
|
||||
"alarm_control_panel.alarm",
|
||||
STATE_ALARM_DISARMED,
|
||||
{alarm_control_panel.ATTR_CODE_ARM_REQUIRED: True},
|
||||
),
|
||||
PIN_CONFIG,
|
||||
)
|
||||
# No challenge data
|
||||
with pytest.raises(error.ChallengeNeeded) as err:
|
||||
await trt.execute(
|
||||
trait.COMMAND_ARMDISARM,
|
||||
PIN_DATA,
|
||||
{"arm": True, "armLevel": STATE_ALARM_ARMED_AWAY},
|
||||
{},
|
||||
)
|
||||
assert len(calls) == 0
|
||||
assert err.value.code == const.ERR_CHALLENGE_NEEDED
|
||||
assert err.value.challenge_type == const.CHALLENGE_PIN_NEEDED
|
||||
|
||||
# invalid pin
|
||||
with pytest.raises(error.ChallengeNeeded) as err:
|
||||
await trt.execute(
|
||||
trait.COMMAND_ARMDISARM,
|
||||
PIN_DATA,
|
||||
{"arm": True, "armLevel": STATE_ALARM_ARMED_AWAY},
|
||||
{"pin": 9999},
|
||||
)
|
||||
assert len(calls) == 0
|
||||
assert err.value.code == const.ERR_CHALLENGE_NEEDED
|
||||
assert err.value.challenge_type == const.CHALLENGE_FAILED_PIN_NEEDED
|
||||
|
||||
# correct pin
|
||||
await trt.execute(
|
||||
trait.COMMAND_ARMDISARM,
|
||||
PIN_DATA,
|
||||
{"arm": True, "armLevel": STATE_ALARM_ARMED_AWAY},
|
||||
{"pin": "1234"},
|
||||
)
|
||||
|
||||
assert len(calls) == 1
|
||||
|
||||
# Test already armed
|
||||
with pytest.raises(error.SmartHomeError) as err:
|
||||
trt = trait.ArmDisArmTrait(
|
||||
hass,
|
||||
State(
|
||||
"alarm_control_panel.alarm",
|
||||
STATE_ALARM_ARMED_AWAY,
|
||||
{alarm_control_panel.ATTR_CODE_ARM_REQUIRED: True},
|
||||
),
|
||||
PIN_CONFIG,
|
||||
)
|
||||
await trt.execute(
|
||||
trait.COMMAND_ARMDISARM,
|
||||
PIN_DATA,
|
||||
{"arm": True, "armLevel": STATE_ALARM_ARMED_AWAY},
|
||||
{},
|
||||
)
|
||||
assert len(calls) == 1
|
||||
assert err.value.code == const.ERR_ALREADY_ARMED
|
||||
|
||||
# Test with code_arm_required False
|
||||
trt = trait.ArmDisArmTrait(
|
||||
hass,
|
||||
State(
|
||||
"alarm_control_panel.alarm",
|
||||
STATE_ALARM_DISARMED,
|
||||
{alarm_control_panel.ATTR_CODE_ARM_REQUIRED: False},
|
||||
),
|
||||
PIN_CONFIG,
|
||||
)
|
||||
await trt.execute(
|
||||
trait.COMMAND_ARMDISARM,
|
||||
PIN_DATA,
|
||||
{"arm": True, "armLevel": STATE_ALARM_ARMED_AWAY},
|
||||
{},
|
||||
)
|
||||
assert len(calls) == 2
|
||||
|
||||
|
||||
async def test_arm_disarm_disarm(hass):
|
||||
"""Test ArmDisarm trait Disarming support for alarm_control_panel domain."""
|
||||
assert helpers.get_google_type(alarm_control_panel.DOMAIN, None) is not None
|
||||
assert trait.ArmDisArmTrait.supported(alarm_control_panel.DOMAIN, 0, None)
|
||||
assert trait.ArmDisArmTrait.might_2fa(alarm_control_panel.DOMAIN, 0, None)
|
||||
|
||||
trt = trait.ArmDisArmTrait(
|
||||
hass,
|
||||
State(
|
||||
"alarm_control_panel.alarm",
|
||||
STATE_ALARM_DISARMED,
|
||||
{alarm_control_panel.ATTR_CODE_ARM_REQUIRED: True},
|
||||
),
|
||||
PIN_CONFIG,
|
||||
)
|
||||
assert trt.sync_attributes() == {
|
||||
"availableArmLevels": {
|
||||
"levels": [
|
||||
{
|
||||
"level_name": "armed_home",
|
||||
"level_values": [
|
||||
{"level_synonym": ["armed home", "home"], "lang": "en"}
|
||||
],
|
||||
},
|
||||
{
|
||||
"level_name": "armed_away",
|
||||
"level_values": [
|
||||
{"level_synonym": ["armed away", "away"], "lang": "en"}
|
||||
],
|
||||
},
|
||||
{
|
||||
"level_name": "armed_night",
|
||||
"level_values": [
|
||||
{"level_synonym": ["armed night", "night"], "lang": "en"}
|
||||
],
|
||||
},
|
||||
{
|
||||
"level_name": "armed_custom_bypass",
|
||||
"level_values": [
|
||||
{
|
||||
"level_synonym": ["armed custom bypass", "custom"],
|
||||
"lang": "en",
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
"level_name": "triggered",
|
||||
"level_values": [{"level_synonym": ["triggered"], "lang": "en"}],
|
||||
},
|
||||
],
|
||||
"ordered": False,
|
||||
}
|
||||
}
|
||||
|
||||
assert trt.query_attributes() == {"isArmed": False}
|
||||
|
||||
assert trt.can_execute(trait.COMMAND_ARMDISARM, {"arm": False})
|
||||
|
||||
calls = async_mock_service(
|
||||
hass, alarm_control_panel.DOMAIN, alarm_control_panel.SERVICE_ALARM_DISARM
|
||||
)
|
||||
|
||||
# Test without secure_pin configured
|
||||
with pytest.raises(error.SmartHomeError) as err:
|
||||
trt = trait.ArmDisArmTrait(
|
||||
hass,
|
||||
State(
|
||||
"alarm_control_panel.alarm",
|
||||
STATE_ALARM_ARMED_AWAY,
|
||||
{alarm_control_panel.ATTR_CODE_ARM_REQUIRED: True},
|
||||
),
|
||||
BASIC_CONFIG,
|
||||
)
|
||||
await trt.execute(trait.COMMAND_ARMDISARM, BASIC_DATA, {"arm": False}, {})
|
||||
|
||||
assert len(calls) == 0
|
||||
assert err.value.code == const.ERR_CHALLENGE_NOT_SETUP
|
||||
|
||||
trt = trait.ArmDisArmTrait(
|
||||
hass,
|
||||
State(
|
||||
"alarm_control_panel.alarm",
|
||||
STATE_ALARM_ARMED_AWAY,
|
||||
{alarm_control_panel.ATTR_CODE_ARM_REQUIRED: True},
|
||||
),
|
||||
PIN_CONFIG,
|
||||
)
|
||||
|
||||
# No challenge data
|
||||
with pytest.raises(error.ChallengeNeeded) as err:
|
||||
await trt.execute(trait.COMMAND_ARMDISARM, PIN_DATA, {"arm": False}, {})
|
||||
assert len(calls) == 0
|
||||
assert err.value.code == const.ERR_CHALLENGE_NEEDED
|
||||
assert err.value.challenge_type == const.CHALLENGE_PIN_NEEDED
|
||||
|
||||
# invalid pin
|
||||
with pytest.raises(error.ChallengeNeeded) as err:
|
||||
await trt.execute(
|
||||
trait.COMMAND_ARMDISARM, PIN_DATA, {"arm": False}, {"pin": 9999}
|
||||
)
|
||||
assert len(calls) == 0
|
||||
assert err.value.code == const.ERR_CHALLENGE_NEEDED
|
||||
assert err.value.challenge_type == const.CHALLENGE_FAILED_PIN_NEEDED
|
||||
|
||||
# correct pin
|
||||
await trt.execute(
|
||||
trait.COMMAND_ARMDISARM, PIN_DATA, {"arm": False}, {"pin": "1234"}
|
||||
)
|
||||
|
||||
assert len(calls) == 1
|
||||
|
||||
# Test already disarmed
|
||||
with pytest.raises(error.SmartHomeError) as err:
|
||||
trt = trait.ArmDisArmTrait(
|
||||
hass,
|
||||
State(
|
||||
"alarm_control_panel.alarm",
|
||||
STATE_ALARM_DISARMED,
|
||||
{alarm_control_panel.ATTR_CODE_ARM_REQUIRED: True},
|
||||
),
|
||||
PIN_CONFIG,
|
||||
)
|
||||
await trt.execute(trait.COMMAND_ARMDISARM, PIN_DATA, {"arm": False}, {})
|
||||
assert len(calls) == 1
|
||||
assert err.value.code == const.ERR_ALREADY_DISARMED
|
||||
|
||||
# Cancel arming after already armed will require pin
|
||||
with pytest.raises(error.SmartHomeError) as err:
|
||||
trt = trait.ArmDisArmTrait(
|
||||
hass,
|
||||
State(
|
||||
"alarm_control_panel.alarm",
|
||||
STATE_ALARM_ARMED_AWAY,
|
||||
{alarm_control_panel.ATTR_CODE_ARM_REQUIRED: False},
|
||||
),
|
||||
PIN_CONFIG,
|
||||
)
|
||||
await trt.execute(
|
||||
trait.COMMAND_ARMDISARM, PIN_DATA, {"arm": True, "cancel": True}, {}
|
||||
)
|
||||
assert len(calls) == 1
|
||||
assert err.value.code == const.ERR_CHALLENGE_NEEDED
|
||||
assert err.value.challenge_type == const.CHALLENGE_PIN_NEEDED
|
||||
|
||||
# Cancel arming while pending to arm doesn't require pin
|
||||
trt = trait.ArmDisArmTrait(
|
||||
hass,
|
||||
State(
|
||||
"alarm_control_panel.alarm",
|
||||
STATE_ALARM_PENDING,
|
||||
{alarm_control_panel.ATTR_CODE_ARM_REQUIRED: False},
|
||||
),
|
||||
PIN_CONFIG,
|
||||
)
|
||||
await trt.execute(
|
||||
trait.COMMAND_ARMDISARM, PIN_DATA, {"arm": True, "cancel": True}, {}
|
||||
)
|
||||
assert len(calls) == 2
|
||||
|
||||
|
||||
async def test_fan_speed(hass):
|
||||
"""Test FanSpeed trait speed control support for fan domain."""
|
||||
assert helpers.get_google_type(fan.DOMAIN, None) is not None
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue