Add default code to alarm_control_panel (#112540)

This commit is contained in:
G Johansson 2024-05-29 10:46:53 +02:00 committed by GitHub
parent 38da61a5ac
commit 6b7ff2bf44
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 680 additions and 74 deletions

View File

@ -21,7 +21,8 @@ from homeassistant.const import (
SERVICE_ALARM_DISARM, SERVICE_ALARM_DISARM,
SERVICE_ALARM_TRIGGER, SERVICE_ALARM_TRIGGER,
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ServiceValidationError
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.config_validation import make_entity_service_schema from homeassistant.helpers.config_validation import make_entity_service_schema
from homeassistant.helpers.deprecation import ( from homeassistant.helpers.deprecation import (
@ -55,6 +56,8 @@ _LOGGER: Final = logging.getLogger(__name__)
SCAN_INTERVAL: Final = timedelta(seconds=30) SCAN_INTERVAL: Final = timedelta(seconds=30)
ENTITY_ID_FORMAT: Final = DOMAIN + ".{}" ENTITY_ID_FORMAT: Final = DOMAIN + ".{}"
CONF_DEFAULT_CODE = "default_code"
ALARM_SERVICE_SCHEMA: Final = make_entity_service_schema( ALARM_SERVICE_SCHEMA: Final = make_entity_service_schema(
{vol.Optional(ATTR_CODE): cv.string} {vol.Optional(ATTR_CODE): cv.string}
) )
@ -74,36 +77,38 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
await component.async_setup(config) await component.async_setup(config)
component.async_register_entity_service( component.async_register_entity_service(
SERVICE_ALARM_DISARM, ALARM_SERVICE_SCHEMA, "async_alarm_disarm" SERVICE_ALARM_DISARM,
ALARM_SERVICE_SCHEMA,
"async_handle_alarm_disarm",
) )
component.async_register_entity_service( component.async_register_entity_service(
SERVICE_ALARM_ARM_HOME, SERVICE_ALARM_ARM_HOME,
ALARM_SERVICE_SCHEMA, ALARM_SERVICE_SCHEMA,
"async_alarm_arm_home", "async_handle_alarm_arm_home",
[AlarmControlPanelEntityFeature.ARM_HOME], [AlarmControlPanelEntityFeature.ARM_HOME],
) )
component.async_register_entity_service( component.async_register_entity_service(
SERVICE_ALARM_ARM_AWAY, SERVICE_ALARM_ARM_AWAY,
ALARM_SERVICE_SCHEMA, ALARM_SERVICE_SCHEMA,
"async_alarm_arm_away", "async_handle_alarm_arm_away",
[AlarmControlPanelEntityFeature.ARM_AWAY], [AlarmControlPanelEntityFeature.ARM_AWAY],
) )
component.async_register_entity_service( component.async_register_entity_service(
SERVICE_ALARM_ARM_NIGHT, SERVICE_ALARM_ARM_NIGHT,
ALARM_SERVICE_SCHEMA, ALARM_SERVICE_SCHEMA,
"async_alarm_arm_night", "async_handle_alarm_arm_night",
[AlarmControlPanelEntityFeature.ARM_NIGHT], [AlarmControlPanelEntityFeature.ARM_NIGHT],
) )
component.async_register_entity_service( component.async_register_entity_service(
SERVICE_ALARM_ARM_VACATION, SERVICE_ALARM_ARM_VACATION,
ALARM_SERVICE_SCHEMA, ALARM_SERVICE_SCHEMA,
"async_alarm_arm_vacation", "async_handle_alarm_arm_vacation",
[AlarmControlPanelEntityFeature.ARM_VACATION], [AlarmControlPanelEntityFeature.ARM_VACATION],
) )
component.async_register_entity_service( component.async_register_entity_service(
SERVICE_ALARM_ARM_CUSTOM_BYPASS, SERVICE_ALARM_ARM_CUSTOM_BYPASS,
ALARM_SERVICE_SCHEMA, ALARM_SERVICE_SCHEMA,
"async_alarm_arm_custom_bypass", "async_handle_alarm_arm_custom_bypass",
[AlarmControlPanelEntityFeature.ARM_CUSTOM_BYPASS], [AlarmControlPanelEntityFeature.ARM_CUSTOM_BYPASS],
) )
component.async_register_entity_service( component.async_register_entity_service(
@ -150,6 +155,21 @@ class AlarmControlPanelEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_A
_attr_supported_features: AlarmControlPanelEntityFeature = ( _attr_supported_features: AlarmControlPanelEntityFeature = (
AlarmControlPanelEntityFeature(0) AlarmControlPanelEntityFeature(0)
) )
_alarm_control_panel_option_default_code: str | None = None
@final
@callback
def code_or_default_code(self, code: str | None) -> str | None:
"""Return code to use for a service call.
If the passed in code is not None, it will be returned. Otherwise return the
default code, if set, or None if not set, is returned.
"""
if code:
# Return code provided by user
return code
# Fallback to default code or None if not set
return self._alarm_control_panel_option_default_code
@cached_property @cached_property
def code_format(self) -> CodeFormat | None: def code_format(self) -> CodeFormat | None:
@ -166,6 +186,26 @@ class AlarmControlPanelEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_A
"""Whether the code is required for arm actions.""" """Whether the code is required for arm actions."""
return self._attr_code_arm_required return self._attr_code_arm_required
@final
@callback
def check_code_arm_required(self, code: str | None) -> str | None:
"""Check if arm code is required, raise if no code is given."""
if not (_code := self.code_or_default_code(code)) and self.code_arm_required:
raise ServiceValidationError(
f"Arming requires a code but none was given for {self.entity_id}",
translation_domain=DOMAIN,
translation_key="code_arm_required",
translation_placeholders={
"entity_id": self.entity_id,
},
)
return _code
@final
async def async_handle_alarm_disarm(self, code: str | None = None) -> None:
"""Add default code and disarm."""
await self.async_alarm_disarm(self.code_or_default_code(code))
def alarm_disarm(self, code: str | None = None) -> None: def alarm_disarm(self, code: str | None = None) -> None:
"""Send disarm command.""" """Send disarm command."""
raise NotImplementedError raise NotImplementedError
@ -174,6 +214,11 @@ class AlarmControlPanelEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_A
"""Send disarm command.""" """Send disarm command."""
await self.hass.async_add_executor_job(self.alarm_disarm, code) await self.hass.async_add_executor_job(self.alarm_disarm, code)
@final
async def async_handle_alarm_arm_home(self, code: str | None = None) -> None:
"""Add default code and arm home."""
await self.async_alarm_arm_home(self.check_code_arm_required(code))
def alarm_arm_home(self, code: str | None = None) -> None: def alarm_arm_home(self, code: str | None = None) -> None:
"""Send arm home command.""" """Send arm home command."""
raise NotImplementedError raise NotImplementedError
@ -182,6 +227,11 @@ class AlarmControlPanelEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_A
"""Send arm home command.""" """Send arm home command."""
await self.hass.async_add_executor_job(self.alarm_arm_home, code) await self.hass.async_add_executor_job(self.alarm_arm_home, code)
@final
async def async_handle_alarm_arm_away(self, code: str | None = None) -> None:
"""Add default code and arm away."""
await self.async_alarm_arm_away(self.check_code_arm_required(code))
def alarm_arm_away(self, code: str | None = None) -> None: def alarm_arm_away(self, code: str | None = None) -> None:
"""Send arm away command.""" """Send arm away command."""
raise NotImplementedError raise NotImplementedError
@ -190,6 +240,11 @@ class AlarmControlPanelEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_A
"""Send arm away command.""" """Send arm away command."""
await self.hass.async_add_executor_job(self.alarm_arm_away, code) await self.hass.async_add_executor_job(self.alarm_arm_away, code)
@final
async def async_handle_alarm_arm_night(self, code: str | None = None) -> None:
"""Add default code and arm night."""
await self.async_alarm_arm_night(self.check_code_arm_required(code))
def alarm_arm_night(self, code: str | None = None) -> None: def alarm_arm_night(self, code: str | None = None) -> None:
"""Send arm night command.""" """Send arm night command."""
raise NotImplementedError raise NotImplementedError
@ -198,6 +253,11 @@ class AlarmControlPanelEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_A
"""Send arm night command.""" """Send arm night command."""
await self.hass.async_add_executor_job(self.alarm_arm_night, code) await self.hass.async_add_executor_job(self.alarm_arm_night, code)
@final
async def async_handle_alarm_arm_vacation(self, code: str | None = None) -> None:
"""Add default code and arm vacation."""
await self.async_alarm_arm_vacation(self.check_code_arm_required(code))
def alarm_arm_vacation(self, code: str | None = None) -> None: def alarm_arm_vacation(self, code: str | None = None) -> None:
"""Send arm vacation command.""" """Send arm vacation command."""
raise NotImplementedError raise NotImplementedError
@ -214,6 +274,13 @@ class AlarmControlPanelEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_A
"""Send alarm trigger command.""" """Send alarm trigger command."""
await self.hass.async_add_executor_job(self.alarm_trigger, code) await self.hass.async_add_executor_job(self.alarm_trigger, code)
@final
async def async_handle_alarm_arm_custom_bypass(
self, code: str | None = None
) -> None:
"""Add default code and arm custom bypass."""
await self.async_alarm_arm_custom_bypass(self.check_code_arm_required(code))
def alarm_arm_custom_bypass(self, code: str | None = None) -> None: def alarm_arm_custom_bypass(self, code: str | None = None) -> None:
"""Send arm custom bypass command.""" """Send arm custom bypass command."""
raise NotImplementedError raise NotImplementedError
@ -242,6 +309,33 @@ class AlarmControlPanelEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_A
ATTR_CODE_ARM_REQUIRED: self.code_arm_required, ATTR_CODE_ARM_REQUIRED: self.code_arm_required,
} }
async def async_internal_added_to_hass(self) -> None:
"""Call when the alarm control panel entity is added to hass."""
await super().async_internal_added_to_hass()
if not self.registry_entry:
return
self._async_read_entity_options()
@callback
def async_registry_entry_updated(self) -> None:
"""Run when the entity registry entry has been updated."""
self._async_read_entity_options()
@callback
def _async_read_entity_options(self) -> None:
"""Read entity options from entity registry.
Called when the entity registry entry has been updated and before the
alarm control panel is added to the state machine.
"""
assert self.registry_entry
if (alarm_options := self.registry_entry.options.get(DOMAIN)) and (
default_code := alarm_options.get(CONF_DEFAULT_CODE)
):
self._alarm_control_panel_option_default_code = default_code
return
self._alarm_control_panel_option_default_code = None
# As we import constants of the const module here, we need to add the following # As we import constants of the const module here, we need to add the following
# functions to check for deprecated constants again # functions to check for deprecated constants again

View File

@ -53,6 +53,7 @@ class CanaryAlarm(
| AlarmControlPanelEntityFeature.ARM_AWAY | AlarmControlPanelEntityFeature.ARM_AWAY
| AlarmControlPanelEntityFeature.ARM_NIGHT | AlarmControlPanelEntityFeature.ARM_NIGHT
) )
_attr_code_arm_required = False
def __init__( def __init__(
self, coordinator: CanaryDataUpdateCoordinator, location: Location self, coordinator: CanaryDataUpdateCoordinator, location: Location

View File

@ -30,7 +30,7 @@ async def async_setup_entry(
"""Set up the Demo config entry.""" """Set up the Demo config entry."""
async_add_entities( async_add_entities(
[ [
ManualAlarm( # type:ignore[no-untyped-call] DemoAlarm( # type:ignore[no-untyped-call]
hass, hass,
"Security", "Security",
"1234", "1234",
@ -74,3 +74,9 @@ async def async_setup_entry(
) )
] ]
) )
class DemoAlarm(ManualAlarm):
"""Demo Alarm Control Panel."""
_attr_unique_id = "demo_alarm_control_panel"

View File

@ -52,6 +52,8 @@ async def async_setup_entry(
class FreeboxAlarm(FreeboxHomeEntity, AlarmControlPanelEntity): class FreeboxAlarm(FreeboxHomeEntity, AlarmControlPanelEntity):
"""Representation of a Freebox alarm.""" """Representation of a Freebox alarm."""
_attr_code_arm_required = False
def __init__( def __init__(
self, hass: HomeAssistant, router: FreeboxRouter, node: dict[str, Any] self, hass: HomeAssistant, router: FreeboxRouter, node: dict[str, Any]
) -> None: ) -> None:

View File

@ -47,6 +47,7 @@ class HomematicipAlarmControlPanelEntity(AlarmControlPanelEntity):
AlarmControlPanelEntityFeature.ARM_HOME AlarmControlPanelEntityFeature.ARM_HOME
| AlarmControlPanelEntityFeature.ARM_AWAY | AlarmControlPanelEntityFeature.ARM_AWAY
) )
_attr_code_arm_required = False
def __init__(self, hap: HomematicipHAP) -> None: def __init__(self, hap: HomematicipHAP) -> None:
"""Initialize the alarm control panel.""" """Initialize the alarm control panel."""

View File

@ -74,6 +74,7 @@ class TotalConnectAlarm(TotalConnectLocationEntity, AlarmControlPanelEntity):
| AlarmControlPanelEntityFeature.ARM_AWAY | AlarmControlPanelEntityFeature.ARM_AWAY
| AlarmControlPanelEntityFeature.ARM_NIGHT | AlarmControlPanelEntityFeature.ARM_NIGHT
) )
_attr_code_arm_required = False
def __init__( def __init__(
self, self,

View File

@ -1,8 +1,33 @@
"""Fixturs for Alarm Control Panel tests.""" """Fixturs for Alarm Control Panel tests."""
from collections.abc import Generator
from unittest.mock import MagicMock
import pytest import pytest
from tests.components.alarm_control_panel.common import MockAlarm from homeassistant.components.alarm_control_panel import (
DOMAIN as ALARM_CONTROL_PANEL_DOMAIN,
AlarmControlPanelEntity,
AlarmControlPanelEntityFeature,
)
from homeassistant.components.alarm_control_panel.const import CodeFormat
from homeassistant.config_entries import ConfigEntry, ConfigFlow
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .common import MockAlarm
from tests.common import (
MockConfigEntry,
MockModule,
MockPlatform,
mock_config_flow,
mock_integration,
mock_platform,
)
TEST_DOMAIN = "test"
@pytest.fixture @pytest.fixture
@ -20,3 +45,157 @@ def mock_alarm_control_panel_entities() -> dict[str, MockAlarm]:
unique_id="unique_no_arm_code", unique_id="unique_no_arm_code",
), ),
} }
class MockAlarmControlPanel(AlarmControlPanelEntity):
"""Mocked alarm control entity."""
def __init__(
self,
supported_features: AlarmControlPanelEntityFeature = AlarmControlPanelEntityFeature(
0
),
code_format: CodeFormat | None = None,
code_arm_required: bool = True,
) -> None:
"""Initialize the alarm control."""
self.calls_disarm = MagicMock()
self.calls_arm_home = MagicMock()
self.calls_arm_away = MagicMock()
self.calls_arm_night = MagicMock()
self.calls_arm_vacation = MagicMock()
self.calls_trigger = MagicMock()
self.calls_arm_custom = MagicMock()
self._attr_code_format = code_format
self._attr_supported_features = supported_features
self._attr_code_arm_required = code_arm_required
self._attr_has_entity_name = True
self._attr_name = "test_alarm_control_panel"
self._attr_unique_id = "very_unique_alarm_control_panel_id"
super().__init__()
def alarm_disarm(self, code: str | None = None) -> None:
"""Mock alarm disarm calls."""
self.calls_disarm(code)
def alarm_arm_home(self, code: str | None = None) -> None:
"""Mock arm home calls."""
self.calls_arm_home(code)
def alarm_arm_away(self, code: str | None = None) -> None:
"""Mock arm away calls."""
self.calls_arm_away(code)
def alarm_arm_night(self, code: str | None = None) -> None:
"""Mock arm night calls."""
self.calls_arm_night(code)
def alarm_arm_vacation(self, code: str | None = None) -> None:
"""Mock arm vacation calls."""
self.calls_arm_vacation(code)
def alarm_trigger(self, code: str | None = None) -> None:
"""Mock trigger calls."""
self.calls_trigger(code)
def alarm_arm_custom_bypass(self, code: str | None = None) -> None:
"""Mock arm custom bypass calls."""
self.calls_arm_custom(code)
class MockFlow(ConfigFlow):
"""Test flow."""
@pytest.fixture(autouse=True)
def config_flow_fixture(hass: HomeAssistant) -> Generator[None, None, None]:
"""Mock config flow."""
mock_platform(hass, f"{TEST_DOMAIN}.config_flow")
with mock_config_flow(TEST_DOMAIN, MockFlow):
yield
@pytest.fixture
async def code_format() -> CodeFormat | None:
"""Return the code format for the test alarm control panel entity."""
return CodeFormat.NUMBER
@pytest.fixture
async def code_arm_required() -> bool:
"""Return if code required for arming."""
return True
@pytest.fixture(name="supported_features")
async def lock_supported_features() -> AlarmControlPanelEntityFeature:
"""Return the supported features for the test alarm control panel entity."""
return (
AlarmControlPanelEntityFeature.ARM_AWAY
| AlarmControlPanelEntityFeature.ARM_CUSTOM_BYPASS
| AlarmControlPanelEntityFeature.ARM_HOME
| AlarmControlPanelEntityFeature.ARM_NIGHT
| AlarmControlPanelEntityFeature.ARM_VACATION
| AlarmControlPanelEntityFeature.TRIGGER
)
@pytest.fixture(name="mock_alarm_control_panel_entity")
async def setup_lock_platform_test_entity(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
code_format: CodeFormat | None,
supported_features: AlarmControlPanelEntityFeature,
code_arm_required: bool,
) -> MagicMock:
"""Set up alarm control panel entity using an entity platform."""
async def async_setup_entry_init(
hass: HomeAssistant, config_entry: ConfigEntry
) -> bool:
"""Set up test config entry."""
await hass.config_entries.async_forward_entry_setup(
config_entry, ALARM_CONTROL_PANEL_DOMAIN
)
return True
MockPlatform(hass, f"{TEST_DOMAIN}.config_flow")
mock_integration(
hass,
MockModule(
TEST_DOMAIN,
async_setup_entry=async_setup_entry_init,
),
)
# Unnamed sensor without device class -> no name
entity = MockAlarmControlPanel(
supported_features=supported_features,
code_format=code_format,
code_arm_required=code_arm_required,
)
async def async_setup_entry_platform(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up test alarm control panel platform via config entry."""
async_add_entities([entity])
mock_platform(
hass,
f"{TEST_DOMAIN}.{ALARM_CONTROL_PANEL_DOMAIN}",
MockPlatform(async_setup_entry=async_setup_entry_platform),
)
config_entry = MockConfigEntry(domain=TEST_DOMAIN)
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
state = hass.states.get(entity.entity_id)
assert state is not None
return entity

View File

@ -1,14 +1,52 @@
"""Test for the alarm control panel const module.""" """Test for the alarm control panel const module."""
from types import ModuleType from types import ModuleType
from typing import Any
import pytest import pytest
from homeassistant.components import alarm_control_panel from homeassistant.components import alarm_control_panel
from homeassistant.components.alarm_control_panel.const import (
AlarmControlPanelEntityFeature,
CodeFormat,
)
from homeassistant.const import (
ATTR_CODE,
SERVICE_ALARM_ARM_AWAY,
SERVICE_ALARM_ARM_CUSTOM_BYPASS,
SERVICE_ALARM_ARM_HOME,
SERVICE_ALARM_ARM_NIGHT,
SERVICE_ALARM_ARM_VACATION,
SERVICE_ALARM_DISARM,
SERVICE_ALARM_TRIGGER,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.typing import UNDEFINED, UndefinedType
from .conftest import MockAlarmControlPanel
from tests.common import help_test_all, import_and_test_deprecated_constant_enum from tests.common import help_test_all, import_and_test_deprecated_constant_enum
async def help_test_async_alarm_control_panel_service(
hass: HomeAssistant,
entity_id: str,
service: str,
code: str | None | UndefinedType = UNDEFINED,
) -> None:
"""Help to lock a test lock."""
data: dict[str, Any] = {"entity_id": entity_id}
if code is not UNDEFINED:
data[ATTR_CODE] = code
await hass.services.async_call(
alarm_control_panel.DOMAIN, service, data, blocking=True
)
await hass.async_block_till_done()
@pytest.mark.parametrize( @pytest.mark.parametrize(
"module", "module",
[alarm_control_panel, alarm_control_panel.const], [alarm_control_panel, alarm_control_panel.const],
@ -77,3 +115,171 @@ def test_deprecated_supported_features_ints(caplog: pytest.LogCaptureFixture) ->
is alarm_control_panel.AlarmControlPanelEntityFeature(1) is alarm_control_panel.AlarmControlPanelEntityFeature(1)
) )
assert "is using deprecated supported features values" not in caplog.text assert "is using deprecated supported features values" not in caplog.text
async def test_set_mock_alarm_control_panel_options(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
mock_alarm_control_panel_entity: MockAlarmControlPanel,
) -> None:
"""Test mock attributes and default code stored in the registry."""
entity_registry.async_update_entity_options(
"alarm_control_panel.test_alarm_control_panel",
"alarm_control_panel",
{alarm_control_panel.CONF_DEFAULT_CODE: "1234"},
)
await hass.async_block_till_done()
assert (
mock_alarm_control_panel_entity._alarm_control_panel_option_default_code
== "1234"
)
state = hass.states.get(mock_alarm_control_panel_entity.entity_id)
assert state is not None
assert state.attributes["code_format"] == CodeFormat.NUMBER
assert (
state.attributes["supported_features"]
== AlarmControlPanelEntityFeature.ARM_AWAY
| AlarmControlPanelEntityFeature.ARM_CUSTOM_BYPASS
| AlarmControlPanelEntityFeature.ARM_HOME
| AlarmControlPanelEntityFeature.ARM_NIGHT
| AlarmControlPanelEntityFeature.ARM_VACATION
| AlarmControlPanelEntityFeature.TRIGGER
)
async def test_default_code_option_update(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
mock_alarm_control_panel_entity: MockAlarmControlPanel,
) -> None:
"""Test default code stored in the registry is updated."""
assert (
mock_alarm_control_panel_entity._alarm_control_panel_option_default_code is None
)
entity_registry.async_update_entity_options(
"alarm_control_panel.test_alarm_control_panel",
"alarm_control_panel",
{alarm_control_panel.CONF_DEFAULT_CODE: "4321"},
)
await hass.async_block_till_done()
assert (
mock_alarm_control_panel_entity._alarm_control_panel_option_default_code
== "4321"
)
@pytest.mark.parametrize(
("code_format", "supported_features"),
[(CodeFormat.TEXT, AlarmControlPanelEntityFeature.ARM_AWAY)],
)
async def test_alarm_control_panel_arm_with_code(
hass: HomeAssistant, mock_alarm_control_panel_entity: MockAlarmControlPanel
) -> None:
"""Test alarm control panel entity with open service."""
state = hass.states.get(mock_alarm_control_panel_entity.entity_id)
assert state.attributes["code_format"] == CodeFormat.TEXT
with pytest.raises(ServiceValidationError):
await help_test_async_alarm_control_panel_service(
hass, mock_alarm_control_panel_entity.entity_id, SERVICE_ALARM_ARM_AWAY
)
with pytest.raises(ServiceValidationError):
await help_test_async_alarm_control_panel_service(
hass,
mock_alarm_control_panel_entity.entity_id,
SERVICE_ALARM_ARM_AWAY,
code="",
)
await help_test_async_alarm_control_panel_service(
hass,
mock_alarm_control_panel_entity.entity_id,
SERVICE_ALARM_ARM_AWAY,
code="1234",
)
assert mock_alarm_control_panel_entity.calls_arm_away.call_count == 1
mock_alarm_control_panel_entity.calls_arm_away.assert_called_with("1234")
@pytest.mark.parametrize(
("code_format", "code_arm_required"),
[(CodeFormat.NUMBER, False)],
)
async def test_alarm_control_panel_with_no_code(
hass: HomeAssistant, mock_alarm_control_panel_entity: MockAlarmControlPanel
) -> None:
"""Test alarm control panel entity without code."""
await help_test_async_alarm_control_panel_service(
hass, mock_alarm_control_panel_entity.entity_id, SERVICE_ALARM_ARM_AWAY
)
mock_alarm_control_panel_entity.calls_arm_away.assert_called_with(None)
await help_test_async_alarm_control_panel_service(
hass, mock_alarm_control_panel_entity.entity_id, SERVICE_ALARM_ARM_CUSTOM_BYPASS
)
mock_alarm_control_panel_entity.calls_arm_custom.assert_called_with(None)
await help_test_async_alarm_control_panel_service(
hass, mock_alarm_control_panel_entity.entity_id, SERVICE_ALARM_ARM_HOME
)
mock_alarm_control_panel_entity.calls_arm_home.assert_called_with(None)
await help_test_async_alarm_control_panel_service(
hass, mock_alarm_control_panel_entity.entity_id, SERVICE_ALARM_ARM_NIGHT
)
mock_alarm_control_panel_entity.calls_arm_night.assert_called_with(None)
await help_test_async_alarm_control_panel_service(
hass, mock_alarm_control_panel_entity.entity_id, SERVICE_ALARM_ARM_VACATION
)
mock_alarm_control_panel_entity.calls_arm_vacation.assert_called_with(None)
await help_test_async_alarm_control_panel_service(
hass, mock_alarm_control_panel_entity.entity_id, SERVICE_ALARM_DISARM
)
mock_alarm_control_panel_entity.calls_disarm.assert_called_with(None)
await help_test_async_alarm_control_panel_service(
hass, mock_alarm_control_panel_entity.entity_id, SERVICE_ALARM_TRIGGER
)
mock_alarm_control_panel_entity.calls_trigger.assert_called_with(None)
@pytest.mark.parametrize(
("code_format", "code_arm_required"),
[(CodeFormat.NUMBER, True)],
)
async def test_alarm_control_panel_with_default_code(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
mock_alarm_control_panel_entity: MockAlarmControlPanel,
) -> None:
"""Test alarm control panel entity without code."""
entity_registry.async_update_entity_options(
"alarm_control_panel.test_alarm_control_panel",
"alarm_control_panel",
{alarm_control_panel.CONF_DEFAULT_CODE: "1234"},
)
await hass.async_block_till_done()
await help_test_async_alarm_control_panel_service(
hass, mock_alarm_control_panel_entity.entity_id, SERVICE_ALARM_ARM_AWAY
)
mock_alarm_control_panel_entity.calls_arm_away.assert_called_with("1234")
await help_test_async_alarm_control_panel_service(
hass, mock_alarm_control_panel_entity.entity_id, SERVICE_ALARM_ARM_CUSTOM_BYPASS
)
mock_alarm_control_panel_entity.calls_arm_custom.assert_called_with("1234")
await help_test_async_alarm_control_panel_service(
hass, mock_alarm_control_panel_entity.entity_id, SERVICE_ALARM_ARM_HOME
)
mock_alarm_control_panel_entity.calls_arm_home.assert_called_with("1234")
await help_test_async_alarm_control_panel_service(
hass, mock_alarm_control_panel_entity.entity_id, SERVICE_ALARM_ARM_NIGHT
)
mock_alarm_control_panel_entity.calls_arm_night.assert_called_with("1234")
await help_test_async_alarm_control_panel_service(
hass, mock_alarm_control_panel_entity.entity_id, SERVICE_ALARM_ARM_VACATION
)
mock_alarm_control_panel_entity.calls_arm_vacation.assert_called_with("1234")
await help_test_async_alarm_control_panel_service(
hass, mock_alarm_control_panel_entity.entity_id, SERVICE_ALARM_DISARM
)
mock_alarm_control_panel_entity.calls_disarm.assert_called_with("1234")

View File

@ -34,7 +34,7 @@ async def test_switch_change_alarm_state(hass: HomeAssistant) -> None:
await hass.services.async_call( await hass.services.async_call(
"alarm_control_panel", "alarm_control_panel",
"alarm_arm_home", "alarm_arm_home",
{"entity_id": "alarm_control_panel.testdevice"}, {"entity_id": "alarm_control_panel.testdevice", "code": "1234"},
blocking=True, blocking=True,
) )
helper.async_assert_service_values( helper.async_assert_service_values(
@ -47,7 +47,7 @@ async def test_switch_change_alarm_state(hass: HomeAssistant) -> None:
await hass.services.async_call( await hass.services.async_call(
"alarm_control_panel", "alarm_control_panel",
"alarm_arm_away", "alarm_arm_away",
{"entity_id": "alarm_control_panel.testdevice"}, {"entity_id": "alarm_control_panel.testdevice", "code": "1234"},
blocking=True, blocking=True,
) )
helper.async_assert_service_values( helper.async_assert_service_values(
@ -60,7 +60,7 @@ async def test_switch_change_alarm_state(hass: HomeAssistant) -> None:
await hass.services.async_call( await hass.services.async_call(
"alarm_control_panel", "alarm_control_panel",
"alarm_arm_night", "alarm_arm_night",
{"entity_id": "alarm_control_panel.testdevice"}, {"entity_id": "alarm_control_panel.testdevice", "code": "1234"},
blocking=True, blocking=True,
) )
helper.async_assert_service_values( helper.async_assert_service_values(
@ -73,7 +73,7 @@ async def test_switch_change_alarm_state(hass: HomeAssistant) -> None:
await hass.services.async_call( await hass.services.async_call(
"alarm_control_panel", "alarm_control_panel",
"alarm_disarm", "alarm_disarm",
{"entity_id": "alarm_control_panel.testdevice"}, {"entity_id": "alarm_control_panel.testdevice", "code": "1234"},
blocking=True, blocking=True,
) )
helper.async_assert_service_values( helper.async_assert_service_values(

View File

@ -315,7 +315,7 @@ async def test_with_specific_pending(
await hass.services.async_call( await hass.services.async_call(
alarm_control_panel.DOMAIN, alarm_control_panel.DOMAIN,
service, service,
{ATTR_ENTITY_ID: "alarm_control_panel.test"}, {ATTR_ENTITY_ID: "alarm_control_panel.test", ATTR_CODE: "1234"},
blocking=True, blocking=True,
) )

View File

@ -380,7 +380,7 @@ async def test_with_specific_pending(
await hass.services.async_call( await hass.services.async_call(
alarm_control_panel.DOMAIN, alarm_control_panel.DOMAIN,
service, service,
{ATTR_ENTITY_ID: "alarm_control_panel.test"}, {ATTR_ENTITY_ID: "alarm_control_panel.test", ATTR_CODE: "1234"},
blocking=True, blocking=True,
) )
@ -1442,7 +1442,7 @@ async def test_state_changes_are_published_to_mqtt(
mqtt_mock.async_publish.reset_mock() mqtt_mock.async_publish.reset_mock()
# Arm in home mode # Arm in home mode
await common.async_alarm_arm_home(hass) await common.async_alarm_arm_home(hass, "1234")
await hass.async_block_till_done() await hass.async_block_till_done()
mqtt_mock.async_publish.assert_called_once_with( mqtt_mock.async_publish.assert_called_once_with(
"alarm/state", STATE_ALARM_PENDING, 0, True "alarm/state", STATE_ALARM_PENDING, 0, True
@ -1462,7 +1462,7 @@ async def test_state_changes_are_published_to_mqtt(
mqtt_mock.async_publish.reset_mock() mqtt_mock.async_publish.reset_mock()
# Arm in away mode # Arm in away mode
await common.async_alarm_arm_away(hass) await common.async_alarm_arm_away(hass, "1234")
await hass.async_block_till_done() await hass.async_block_till_done()
mqtt_mock.async_publish.assert_called_once_with( mqtt_mock.async_publish.assert_called_once_with(
"alarm/state", STATE_ALARM_PENDING, 0, True "alarm/state", STATE_ALARM_PENDING, 0, True
@ -1482,7 +1482,7 @@ async def test_state_changes_are_published_to_mqtt(
mqtt_mock.async_publish.reset_mock() mqtt_mock.async_publish.reset_mock()
# Arm in night mode # Arm in night mode
await common.async_alarm_arm_night(hass) await common.async_alarm_arm_night(hass, "1234")
await hass.async_block_till_done() await hass.async_block_till_done()
mqtt_mock.async_publish.assert_called_once_with( mqtt_mock.async_publish.assert_called_once_with(
"alarm/state", STATE_ALARM_PENDING, 0, True "alarm/state", STATE_ALARM_PENDING, 0, True

View File

@ -1,5 +1,6 @@
"""The tests the MQTT alarm control panel component.""" """The tests the MQTT alarm control panel component."""
from contextlib import AbstractContextManager, contextmanager
import copy import copy
import json import json
from typing import Any from typing import Any
@ -37,7 +38,7 @@ from homeassistant.const import (
STATE_UNKNOWN, STATE_UNKNOWN,
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from .test_common import ( from .test_common import (
help_custom_config, help_custom_config,
@ -97,6 +98,17 @@ DEFAULT_CONFIG = {
} }
} }
DEFAULT_CONFIG_CODE_NOT_REQUIRED = {
mqtt.DOMAIN: {
alarm_control_panel.DOMAIN: {
"name": "test",
"state_topic": "alarm/state",
"command_topic": "alarm/command",
"code_arm_required": False,
}
}
}
DEFAULT_CONFIG_CODE = { DEFAULT_CONFIG_CODE = {
mqtt.DOMAIN: { mqtt.DOMAIN: {
alarm_control_panel.DOMAIN: { alarm_control_panel.DOMAIN: {
@ -134,6 +146,12 @@ DEFAULT_CONFIG_REMOTE_CODE_TEXT = {
} }
@contextmanager
def does_not_raise():
"""Do not raise error."""
yield
@pytest.mark.parametrize( @pytest.mark.parametrize(
("hass_config", "valid"), ("hass_config", "valid"),
[ [
@ -317,13 +335,17 @@ async def test_supported_features(
@pytest.mark.parametrize( @pytest.mark.parametrize(
("hass_config", "service", "payload"), ("hass_config", "service", "payload"),
[ [
(DEFAULT_CONFIG, SERVICE_ALARM_ARM_HOME, "ARM_HOME"), (DEFAULT_CONFIG_CODE_NOT_REQUIRED, SERVICE_ALARM_ARM_HOME, "ARM_HOME"),
(DEFAULT_CONFIG, SERVICE_ALARM_ARM_AWAY, "ARM_AWAY"), (DEFAULT_CONFIG_CODE_NOT_REQUIRED, SERVICE_ALARM_ARM_AWAY, "ARM_AWAY"),
(DEFAULT_CONFIG, SERVICE_ALARM_ARM_NIGHT, "ARM_NIGHT"), (DEFAULT_CONFIG_CODE_NOT_REQUIRED, SERVICE_ALARM_ARM_NIGHT, "ARM_NIGHT"),
(DEFAULT_CONFIG, SERVICE_ALARM_ARM_VACATION, "ARM_VACATION"), (DEFAULT_CONFIG_CODE_NOT_REQUIRED, SERVICE_ALARM_ARM_VACATION, "ARM_VACATION"),
(DEFAULT_CONFIG, SERVICE_ALARM_ARM_CUSTOM_BYPASS, "ARM_CUSTOM_BYPASS"), (
(DEFAULT_CONFIG, SERVICE_ALARM_DISARM, "DISARM"), DEFAULT_CONFIG_CODE_NOT_REQUIRED,
(DEFAULT_CONFIG, SERVICE_ALARM_TRIGGER, "TRIGGER"), SERVICE_ALARM_ARM_CUSTOM_BYPASS,
"ARM_CUSTOM_BYPASS",
),
(DEFAULT_CONFIG_CODE_NOT_REQUIRED, SERVICE_ALARM_DISARM, "DISARM"),
(DEFAULT_CONFIG_CODE_NOT_REQUIRED, SERVICE_ALARM_TRIGGER, "TRIGGER"),
], ],
) )
async def test_publish_mqtt_no_code( async def test_publish_mqtt_no_code(
@ -346,28 +368,55 @@ async def test_publish_mqtt_no_code(
@pytest.mark.parametrize( @pytest.mark.parametrize(
("hass_config", "service", "payload"), ("hass_config", "service", "payload", "raises"),
[ [
(DEFAULT_CONFIG_CODE, SERVICE_ALARM_ARM_HOME, "ARM_HOME"), (
(DEFAULT_CONFIG_CODE, SERVICE_ALARM_ARM_AWAY, "ARM_AWAY"), DEFAULT_CONFIG_CODE,
(DEFAULT_CONFIG_CODE, SERVICE_ALARM_ARM_NIGHT, "ARM_NIGHT"), SERVICE_ALARM_ARM_HOME,
(DEFAULT_CONFIG_CODE, SERVICE_ALARM_ARM_VACATION, "ARM_VACATION"), "ARM_HOME",
(DEFAULT_CONFIG_CODE, SERVICE_ALARM_ARM_CUSTOM_BYPASS, "ARM_CUSTOM_BYPASS"), pytest.raises(ServiceValidationError),
(DEFAULT_CONFIG_CODE, SERVICE_ALARM_DISARM, "DISARM"), ),
(DEFAULT_CONFIG_CODE, SERVICE_ALARM_TRIGGER, "TRIGGER"), (
DEFAULT_CONFIG_CODE,
SERVICE_ALARM_ARM_AWAY,
"ARM_AWAY",
pytest.raises(ServiceValidationError),
),
(
DEFAULT_CONFIG_CODE,
SERVICE_ALARM_ARM_NIGHT,
"ARM_NIGHT",
pytest.raises(ServiceValidationError),
),
(
DEFAULT_CONFIG_CODE,
SERVICE_ALARM_ARM_VACATION,
"ARM_VACATION",
pytest.raises(ServiceValidationError),
),
(
DEFAULT_CONFIG_CODE,
SERVICE_ALARM_ARM_CUSTOM_BYPASS,
"ARM_CUSTOM_BYPASS",
pytest.raises(ServiceValidationError),
),
(DEFAULT_CONFIG_CODE, SERVICE_ALARM_DISARM, "DISARM", does_not_raise()),
(DEFAULT_CONFIG_CODE, SERVICE_ALARM_TRIGGER, "TRIGGER", does_not_raise()),
], ],
) )
async def test_publish_mqtt_with_code( async def test_publish_mqtt_with_code(
hass: HomeAssistant, hass: HomeAssistant,
mqtt_mock_entry: MqttMockHAClientGenerator, mqtt_mock_entry: MqttMockHAClientGenerator,
service, service: str,
payload, payload: str,
raises: AbstractContextManager,
) -> None: ) -> None:
"""Test publishing of MQTT messages when code is configured.""" """Test publishing of MQTT messages when code is configured."""
mqtt_mock = await mqtt_mock_entry() mqtt_mock = await mqtt_mock_entry()
call_count = mqtt_mock.async_publish.call_count call_count = mqtt_mock.async_publish.call_count
# No code provided, should not publish # No code provided, should not publish
with raises:
await hass.services.async_call( await hass.services.async_call(
alarm_control_panel.DOMAIN, alarm_control_panel.DOMAIN,
service, service,
@ -396,32 +445,60 @@ async def test_publish_mqtt_with_code(
@pytest.mark.parametrize( @pytest.mark.parametrize(
("hass_config", "service", "payload"), ("hass_config", "service", "payload", "raises"),
[ [
(DEFAULT_CONFIG_REMOTE_CODE, SERVICE_ALARM_ARM_HOME, "ARM_HOME"), (
(DEFAULT_CONFIG_REMOTE_CODE, SERVICE_ALARM_ARM_AWAY, "ARM_AWAY"), DEFAULT_CONFIG_REMOTE_CODE,
(DEFAULT_CONFIG_REMOTE_CODE, SERVICE_ALARM_ARM_NIGHT, "ARM_NIGHT"), SERVICE_ALARM_ARM_HOME,
(DEFAULT_CONFIG_REMOTE_CODE, SERVICE_ALARM_ARM_VACATION, "ARM_VACATION"), "ARM_HOME",
pytest.raises(ServiceValidationError),
),
(
DEFAULT_CONFIG_REMOTE_CODE,
SERVICE_ALARM_ARM_AWAY,
"ARM_AWAY",
pytest.raises(ServiceValidationError),
),
(
DEFAULT_CONFIG_REMOTE_CODE,
SERVICE_ALARM_ARM_NIGHT,
"ARM_NIGHT",
pytest.raises(ServiceValidationError),
),
(
DEFAULT_CONFIG_REMOTE_CODE,
SERVICE_ALARM_ARM_VACATION,
"ARM_VACATION",
pytest.raises(ServiceValidationError),
),
( (
DEFAULT_CONFIG_REMOTE_CODE, DEFAULT_CONFIG_REMOTE_CODE,
SERVICE_ALARM_ARM_CUSTOM_BYPASS, SERVICE_ALARM_ARM_CUSTOM_BYPASS,
"ARM_CUSTOM_BYPASS", "ARM_CUSTOM_BYPASS",
pytest.raises(ServiceValidationError),
),
(DEFAULT_CONFIG_REMOTE_CODE, SERVICE_ALARM_DISARM, "DISARM", does_not_raise()),
(
DEFAULT_CONFIG_REMOTE_CODE,
SERVICE_ALARM_TRIGGER,
"TRIGGER",
does_not_raise(),
), ),
(DEFAULT_CONFIG_REMOTE_CODE, SERVICE_ALARM_DISARM, "DISARM"),
(DEFAULT_CONFIG_REMOTE_CODE, SERVICE_ALARM_TRIGGER, "TRIGGER"),
], ],
) )
async def test_publish_mqtt_with_remote_code( async def test_publish_mqtt_with_remote_code(
hass: HomeAssistant, hass: HomeAssistant,
mqtt_mock_entry: MqttMockHAClientGenerator, mqtt_mock_entry: MqttMockHAClientGenerator,
service, service: str,
payload, payload: str,
raises: AbstractContextManager,
) -> None: ) -> None:
"""Test publishing of MQTT messages when remode code is configured.""" """Test publishing of MQTT messages when remode code is configured."""
mqtt_mock = await mqtt_mock_entry() mqtt_mock = await mqtt_mock_entry()
call_count = mqtt_mock.async_publish.call_count call_count = mqtt_mock.async_publish.call_count
# No code provided, should not publish # No code provided, should not publish
with raises:
await hass.services.async_call( await hass.services.async_call(
alarm_control_panel.DOMAIN, alarm_control_panel.DOMAIN,
service, service,
@ -441,19 +518,50 @@ async def test_publish_mqtt_with_remote_code(
@pytest.mark.parametrize( @pytest.mark.parametrize(
("hass_config", "service", "payload"), ("hass_config", "service", "payload", "raises"),
[ [
(DEFAULT_CONFIG_REMOTE_CODE_TEXT, SERVICE_ALARM_ARM_HOME, "ARM_HOME"), (
(DEFAULT_CONFIG_REMOTE_CODE_TEXT, SERVICE_ALARM_ARM_AWAY, "ARM_AWAY"), DEFAULT_CONFIG_REMOTE_CODE_TEXT,
(DEFAULT_CONFIG_REMOTE_CODE_TEXT, SERVICE_ALARM_ARM_NIGHT, "ARM_NIGHT"), SERVICE_ALARM_ARM_HOME,
(DEFAULT_CONFIG_REMOTE_CODE_TEXT, SERVICE_ALARM_ARM_VACATION, "ARM_VACATION"), "ARM_HOME",
pytest.raises(ServiceValidationError),
),
(
DEFAULT_CONFIG_REMOTE_CODE_TEXT,
SERVICE_ALARM_ARM_AWAY,
"ARM_AWAY",
pytest.raises(ServiceValidationError),
),
(
DEFAULT_CONFIG_REMOTE_CODE_TEXT,
SERVICE_ALARM_ARM_NIGHT,
"ARM_NIGHT",
pytest.raises(ServiceValidationError),
),
(
DEFAULT_CONFIG_REMOTE_CODE_TEXT,
SERVICE_ALARM_ARM_VACATION,
"ARM_VACATION",
pytest.raises(ServiceValidationError),
),
( (
DEFAULT_CONFIG_REMOTE_CODE_TEXT, DEFAULT_CONFIG_REMOTE_CODE_TEXT,
SERVICE_ALARM_ARM_CUSTOM_BYPASS, SERVICE_ALARM_ARM_CUSTOM_BYPASS,
"ARM_CUSTOM_BYPASS", "ARM_CUSTOM_BYPASS",
pytest.raises(ServiceValidationError),
),
(
DEFAULT_CONFIG_REMOTE_CODE_TEXT,
SERVICE_ALARM_DISARM,
"DISARM",
does_not_raise(),
),
(
DEFAULT_CONFIG_REMOTE_CODE_TEXT,
SERVICE_ALARM_TRIGGER,
"TRIGGER",
does_not_raise(),
), ),
(DEFAULT_CONFIG_REMOTE_CODE_TEXT, SERVICE_ALARM_DISARM, "DISARM"),
(DEFAULT_CONFIG_REMOTE_CODE_TEXT, SERVICE_ALARM_TRIGGER, "TRIGGER"),
], ],
) )
async def test_publish_mqtt_with_remote_code_text( async def test_publish_mqtt_with_remote_code_text(
@ -461,12 +569,14 @@ async def test_publish_mqtt_with_remote_code_text(
mqtt_mock_entry: MqttMockHAClientGenerator, mqtt_mock_entry: MqttMockHAClientGenerator,
service: str, service: str,
payload: str, payload: str,
raises: AbstractContextManager,
) -> None: ) -> None:
"""Test publishing of MQTT messages when remote text code is configured.""" """Test publishing of MQTT messages when remote text code is configured."""
mqtt_mock = await mqtt_mock_entry() mqtt_mock = await mqtt_mock_entry()
call_count = mqtt_mock.async_publish.call_count call_count = mqtt_mock.async_publish.call_count
# No code provided, should not publish # No code provided, should not publish
with raises:
await hass.services.async_call( await hass.services.async_call(
alarm_control_panel.DOMAIN, alarm_control_panel.DOMAIN,
service, service,

View File

@ -154,7 +154,10 @@ async def test_optimistic_states(hass: HomeAssistant, start_ha) -> None:
("alarm_trigger", STATE_ALARM_TRIGGERED), ("alarm_trigger", STATE_ALARM_TRIGGERED),
]: ]:
await hass.services.async_call( await hass.services.async_call(
ALARM_DOMAIN, service, {"entity_id": TEMPLATE_NAME}, blocking=True ALARM_DOMAIN,
service,
{"entity_id": TEMPLATE_NAME, "code": "1234"},
blocking=True,
) )
await hass.async_block_till_done() await hass.async_block_till_done()
assert hass.states.get(TEMPLATE_NAME).state == set_state assert hass.states.get(TEMPLATE_NAME).state == set_state
@ -286,7 +289,10 @@ async def test_actions(
) -> None: ) -> None:
"""Test alarm actions.""" """Test alarm actions."""
await hass.services.async_call( await hass.services.async_call(
ALARM_DOMAIN, service, {"entity_id": TEMPLATE_NAME}, blocking=True ALARM_DOMAIN,
service,
{"entity_id": TEMPLATE_NAME, "code": "1234"},
blocking=True,
) )
await hass.async_block_till_done() await hass.async_block_till_done()
assert len(call_service_events) == 1 assert len(call_service_events) == 1

View File

@ -37,7 +37,7 @@
'attributes': ReadOnlyDict({ 'attributes': ReadOnlyDict({
'ac_loss': False, 'ac_loss': False,
'changed_by': None, 'changed_by': None,
'code_arm_required': True, 'code_arm_required': False,
'code_format': None, 'code_format': None,
'cover_tampered': False, 'cover_tampered': False,
'friendly_name': 'test', 'friendly_name': 'test',
@ -95,7 +95,7 @@
'attributes': ReadOnlyDict({ 'attributes': ReadOnlyDict({
'ac_loss': False, 'ac_loss': False,
'changed_by': None, 'changed_by': None,
'code_arm_required': True, 'code_arm_required': False,
'code_format': None, 'code_format': None,
'cover_tampered': False, 'cover_tampered': False,
'friendly_name': 'test Partition 2', 'friendly_name': 'test Partition 2',