1
0
mirror of https://github.com/home-assistant/core.git synced 2026-03-03 08:10:36 +00:00

TotalConnect major test updates (#139672)

Co-authored-by: Joostlek <joostlek@outlook.com>
This commit is contained in:
Austin Mroczek
2025-08-10 13:26:43 -07:00
committed by GitHub
parent 38e6a7c6d4
commit ab04e2c501
21 changed files with 6374 additions and 1531 deletions

View File

@@ -172,9 +172,9 @@ class TotalConnectZoneBinarySensor(TotalConnectZoneEntity, BinarySensorEntity):
super().__init__(coordinator, zone, location_id, entity_description.key)
self.entity_description = entity_description
self._attr_extra_state_attributes = {
"zone_id": zone.zoneid,
"zone_id": str(zone.zoneid),
"location_id": location_id,
"partition": zone.partition,
"partition": str(zone.partition),
}
@property

View File

@@ -105,11 +105,7 @@ class TotalConnectConfigFlow(ConfigFlow, domain=DOMAIN):
},
)
else:
# Force the loading of locations using I/O
number_locations = await self.hass.async_add_executor_job(
self.client.get_number_locations,
)
if number_locations < 1:
if self.client.get_number_locations() < 1:
return self.async_abort(reason="no_locations")
for location_id in self.client.locations:
self.usercodes[location_id] = None

View File

@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/totalconnect",
"iot_class": "cloud_polling",
"loggers": ["total_connect_client"],
"requirements": ["total-connect-client==2025.1.4"]
"requirements": ["total-connect-client==2025.5"]
}

2
requirements_all.txt generated
View File

@@ -2965,7 +2965,7 @@ tololib==1.2.2
toonapi==0.3.0
# homeassistant.components.totalconnect
total-connect-client==2025.1.4
total-connect-client==2025.5
# homeassistant.components.tplink_lte
tp-connected==0.0.4

View File

@@ -2442,7 +2442,7 @@ tololib==1.2.2
toonapi==0.3.0
# homeassistant.components.totalconnect
total-connect-client==2025.1.4
total-connect-client==2025.5
# homeassistant.components.tplink_omada
tplink-omada-client==1.4.4

View File

@@ -1 +1,13 @@
"""Tests for the totalconnect component."""
from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry
async def setup_integration(hass: HomeAssistant, config_entry: MockConfigEntry) -> None:
"""Fixture for setting up the component."""
config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()

View File

@@ -1,473 +0,0 @@
"""Common methods used across tests for TotalConnect."""
from typing import Any
from unittest.mock import patch
from total_connect_client import ArmingState, ResultCode, ZoneStatus, ZoneType
from homeassistant.components.totalconnect.const import (
AUTO_BYPASS,
CODE_REQUIRED,
CONF_USERCODES,
DOMAIN,
)
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component
from tests.common import MockConfigEntry
LOCATION_ID = 123456
DEVICE_INFO_BASIC_1 = {
"DeviceID": "987654",
"DeviceName": "test",
"DeviceClassID": 1,
"DeviceSerialNumber": "987654321ABC",
"DeviceFlags": "PromptForUserCode=0,PromptForInstallerCode=0,PromptForImportSecuritySettings=0,AllowUserSlotEditing=0,CalCapable=1,CanBeSentToPanel=0,CanArmNightStay=0,CanSupportMultiPartition=0,PartitionCount=0,MaxPartitionCount=0,OnBoardingSupport=0,PartitionAdded=0,DuplicateUserSyncStatus=0,PanelType=8,PanelVariant=1,BLEDisarmCapable=0,ArmHomeSupported=0,DuplicateUserCodeCheck=1,CanSupportRapid=0,IsKeypadSupported=1,WifiEnrollmentSupported=0,IsConnectedPanel=0,ArmNightInSceneSupported=0,BuiltInCameraSettingsSupported=0,ZWaveThermostatScheduleDisabled=0,MultipleAuthorityLevelSupported=0,VideoOnPanelSupported=0,EnableBLEMode=0,IsPanelWiFiResetSupported=0,IsCompetitorClearBypass=0,IsNotReadyStateSupported=0,isArmStatusWithoutExitDelayNotSupported=0",
"SecurityPanelTypeID": None,
"DeviceSerialText": None,
}
DEVICE_LIST = [DEVICE_INFO_BASIC_1]
LOCATION_INFO_BASIC_NORMAL = {
"LocationID": LOCATION_ID,
"LocationName": "test",
"SecurityDeviceID": "987654",
"PhotoURL": "http://www.example.com/some/path/to/file.jpg",
"LocationModuleFlags": "Security=1,Video=0,Automation=0,GPS=0,VideoPIR=0",
"DeviceList": {"DeviceInfoBasic": DEVICE_LIST},
}
LOCATIONS = {"LocationInfoBasic": [LOCATION_INFO_BASIC_NORMAL]}
MODULE_FLAGS = "Some=0,Fake=1,Flags=2"
USER = {
"UserID": "1234567",
"Username": "username",
"UserFeatureList": "Master=0,User Administration=0,Configuration Administration=0",
}
RESPONSE_SESSION_DETAILS = {
"ResultCode": ResultCode.SUCCESS.value,
"ResultData": "Success",
"SessionID": "12345",
"Locations": LOCATIONS,
"ModuleFlags": MODULE_FLAGS,
"UserInfo": USER,
}
PARTITION_DISARMED = {
"PartitionID": "1",
"ArmingState": ArmingState.DISARMED,
}
PARTITION_DISARMED2 = {
"PartitionID": "2",
"ArmingState": ArmingState.DISARMED,
}
PARTITION_ARMED_STAY = {
"PartitionID": "1",
"ArmingState": ArmingState.ARMED_STAY,
}
PARTITION_ARMED_STAY2 = {
"PartitionID": "2",
"ArmingState": ArmingState.DISARMED,
}
PARTITION_ARMED_AWAY = {
"PartitionID": "1",
"ArmingState": ArmingState.ARMED_AWAY,
}
PARTITION_ARMED_CUSTOM = {
"PartitionID": "1",
"ArmingState": ArmingState.ARMED_CUSTOM_BYPASS,
}
PARTITION_ARMED_NIGHT = {
"PartitionID": "1",
"ArmingState": ArmingState.ARMED_STAY_NIGHT,
}
PARTITION_ARMING = {
"PartitionID": "1",
"ArmingState": ArmingState.ARMING,
}
PARTITION_DISARMING = {
"PartitionID": "1",
"ArmingState": ArmingState.DISARMING,
}
PARTITION_TRIGGERED_POLICE = {
"PartitionID": "1",
"ArmingState": ArmingState.ALARMING,
}
PARTITION_TRIGGERED_FIRE = {
"PartitionID": "1",
"ArmingState": ArmingState.ALARMING_FIRE_SMOKE,
}
PARTITION_TRIGGERED_CARBON_MONOXIDE = {
"PartitionID": "1",
"ArmingState": ArmingState.ALARMING_CARBON_MONOXIDE,
}
PARTITION_UNKNOWN = {
"PartitionID": "1",
"ArmingState": "99999",
}
PARTITION_INFO_DISARMED = [PARTITION_DISARMED, PARTITION_DISARMED2]
PARTITION_INFO_ARMED_STAY = [PARTITION_ARMED_STAY, PARTITION_ARMED_STAY2]
PARTITION_INFO_ARMED_AWAY = [PARTITION_ARMED_AWAY]
PARTITION_INFO_ARMED_CUSTOM = [PARTITION_ARMED_CUSTOM]
PARTITION_INFO_ARMED_NIGHT = [PARTITION_ARMED_NIGHT]
PARTITION_INFO_ARMING = [PARTITION_ARMING]
PARTITION_INFO_DISARMING = [PARTITION_DISARMING]
PARTITION_INFO_TRIGGERED_POLICE = [PARTITION_TRIGGERED_POLICE]
PARTITION_INFO_TRIGGERED_FIRE = [PARTITION_TRIGGERED_FIRE]
PARTITION_INFO_TRIGGERED_CARBON_MONOXIDE = [PARTITION_TRIGGERED_CARBON_MONOXIDE]
PARTITION_INFO_UNKNOWN = [PARTITION_UNKNOWN]
PARTITIONS_DISARMED = {"PartitionInfo": PARTITION_INFO_DISARMED}
PARTITIONS_ARMED_STAY = {"PartitionInfo": PARTITION_INFO_ARMED_STAY}
PARTITIONS_ARMED_AWAY = {"PartitionInfo": PARTITION_INFO_ARMED_AWAY}
PARTITIONS_ARMED_CUSTOM = {"PartitionInfo": PARTITION_INFO_ARMED_CUSTOM}
PARTITIONS_ARMED_NIGHT = {"PartitionInfo": PARTITION_INFO_ARMED_NIGHT}
PARTITIONS_ARMING = {"PartitionInfo": PARTITION_INFO_ARMING}
PARTITIONS_DISARMING = {"PartitionInfo": PARTITION_INFO_DISARMING}
PARTITIONS_TRIGGERED_POLICE = {"PartitionInfo": PARTITION_INFO_TRIGGERED_POLICE}
PARTITIONS_TRIGGERED_FIRE = {"PartitionInfo": PARTITION_INFO_TRIGGERED_FIRE}
PARTITIONS_TRIGGERED_CARBON_MONOXIDE = {
"PartitionInfo": PARTITION_INFO_TRIGGERED_CARBON_MONOXIDE
}
PARTITIONS_UNKNOWN = {"PartitionInfo": PARTITION_INFO_UNKNOWN}
ZONE_NORMAL = {
"ZoneID": "1",
"ZoneDescription": "Security",
"ZoneStatus": ZoneStatus.FAULT,
"ZoneTypeId": ZoneType.SECURITY,
"PartitionId": "1",
"CanBeBypassed": 1,
}
ZONE_2 = {
"ZoneID": "2",
"ZoneDescription": "Fire",
"ZoneStatus": ZoneStatus.LOW_BATTERY,
"ZoneTypeId": ZoneType.FIRE_SMOKE,
"PartitionId": "1",
"CanBeBypassed": 1,
}
ZONE_3 = {
"ZoneID": "3",
"ZoneDescription": "Gas",
"ZoneStatus": ZoneStatus.TAMPER,
"ZoneTypeId": ZoneType.CARBON_MONOXIDE,
"PartitionId": "1",
"CanBeBypassed": 1,
}
ZONE_4 = {
"ZoneID": "4",
"ZoneDescription": "Motion",
"ZoneStatus": ZoneStatus.NORMAL,
"ZoneTypeId": ZoneType.INTERIOR_FOLLOWER,
"PartitionId": "1",
"CanBeBypassed": 1,
}
ZONE_5 = {
"ZoneID": "5",
"ZoneDescription": "Medical",
"ZoneStatus": ZoneStatus.NORMAL,
"ZoneTypeId": ZoneType.PROA7_MEDICAL,
"PartitionId": "1",
"CanBeBypassed": 0,
}
# 99 is an unknown ZoneType
ZONE_6 = {
"ZoneID": "6",
"ZoneDescription": "Unknown",
"ZoneStatus": ZoneStatus.NORMAL,
"ZoneTypeId": 99,
"PartitionId": "1",
"CanBeBypassed": 0,
}
ZONE_7 = {
"ZoneID": 7,
"ZoneDescription": "Temperature",
"ZoneStatus": ZoneStatus.NORMAL,
"ZoneTypeId": ZoneType.MONITOR,
"PartitionId": "1",
"CanBeBypassed": 0,
}
# ZoneType security that cannot be bypassed is a Button on the alarm panel
ZONE_8 = {
"ZoneID": 8,
"ZoneDescription": "Button",
"ZoneStatus": ZoneStatus.FAULT,
"ZoneTypeId": ZoneType.SECURITY,
"PartitionId": "1",
"CanBeBypassed": 0,
}
ZONE_INFO = [ZONE_NORMAL, ZONE_2, ZONE_3, ZONE_4, ZONE_5, ZONE_6, ZONE_7]
ZONES = {"ZoneInfo": ZONE_INFO}
METADATA_DISARMED = {
"Partitions": PARTITIONS_DISARMED,
"Zones": ZONES,
"PromptForImportSecuritySettings": False,
"IsInACLoss": False,
"IsCoverTampered": False,
"Bell1SupervisionFailure": False,
"Bell2SupervisionFailure": False,
"IsInLowBattery": False,
}
METADATA_ARMED_STAY = METADATA_DISARMED.copy()
METADATA_ARMED_STAY["Partitions"] = PARTITIONS_ARMED_STAY
METADATA_ARMED_AWAY = METADATA_DISARMED.copy()
METADATA_ARMED_AWAY["Partitions"] = PARTITIONS_ARMED_AWAY
METADATA_ARMED_CUSTOM = METADATA_DISARMED.copy()
METADATA_ARMED_CUSTOM["Partitions"] = PARTITIONS_ARMED_CUSTOM
METADATA_ARMED_NIGHT = METADATA_DISARMED.copy()
METADATA_ARMED_NIGHT["Partitions"] = PARTITIONS_ARMED_NIGHT
METADATA_ARMING = METADATA_DISARMED.copy()
METADATA_ARMING["Partitions"] = PARTITIONS_ARMING
METADATA_DISARMING = METADATA_DISARMED.copy()
METADATA_DISARMING["Partitions"] = PARTITIONS_DISARMING
METADATA_TRIGGERED_POLICE = METADATA_DISARMED.copy()
METADATA_TRIGGERED_POLICE["Partitions"] = PARTITIONS_TRIGGERED_POLICE
METADATA_TRIGGERED_FIRE = METADATA_DISARMED.copy()
METADATA_TRIGGERED_FIRE["Partitions"] = PARTITIONS_TRIGGERED_FIRE
METADATA_TRIGGERED_CARBON_MONOXIDE = METADATA_DISARMED.copy()
METADATA_TRIGGERED_CARBON_MONOXIDE["Partitions"] = PARTITIONS_TRIGGERED_CARBON_MONOXIDE
METADATA_UNKNOWN = METADATA_DISARMED.copy()
METADATA_UNKNOWN["Partitions"] = PARTITIONS_UNKNOWN
RESPONSE_DISARMED = {
"ResultCode": 0,
"PanelMetadataAndStatus": METADATA_DISARMED,
"ArmingState": ArmingState.DISARMED,
}
RESPONSE_ARMED_STAY = {
"ResultCode": 0,
"PanelMetadataAndStatus": METADATA_ARMED_STAY,
"ArmingState": ArmingState.ARMED_STAY,
}
RESPONSE_ARMED_AWAY = {
"ResultCode": 0,
"PanelMetadataAndStatus": METADATA_ARMED_AWAY,
"ArmingState": ArmingState.ARMED_AWAY,
}
RESPONSE_ARMED_CUSTOM = {
"ResultCode": 0,
"PanelMetadataAndStatus": METADATA_ARMED_CUSTOM,
"ArmingState": ArmingState.ARMED_CUSTOM_BYPASS,
}
RESPONSE_ARMED_NIGHT = {
"ResultCode": 0,
"PanelMetadataAndStatus": METADATA_ARMED_NIGHT,
"ArmingState": ArmingState.ARMED_STAY_NIGHT,
}
RESPONSE_ARMING = {
"ResultCode": 0,
"PanelMetadataAndStatus": METADATA_ARMING,
"ArmingState": ArmingState.ARMING,
}
RESPONSE_DISARMING = {
"ResultCode": 0,
"PanelMetadataAndStatus": METADATA_DISARMING,
"ArmingState": ArmingState.DISARMING,
}
RESPONSE_TRIGGERED_POLICE = {
"ResultCode": 0,
"PanelMetadataAndStatus": METADATA_TRIGGERED_POLICE,
"ArmingState": ArmingState.ALARMING,
}
RESPONSE_TRIGGERED_FIRE = {
"ResultCode": 0,
"PanelMetadataAndStatus": METADATA_TRIGGERED_FIRE,
"ArmingState": ArmingState.ALARMING_FIRE_SMOKE,
}
RESPONSE_TRIGGERED_CARBON_MONOXIDE = {
"ResultCode": 0,
"PanelMetadataAndStatus": METADATA_TRIGGERED_CARBON_MONOXIDE,
"ArmingState": ArmingState.ALARMING_CARBON_MONOXIDE,
}
RESPONSE_UNKNOWN = {
"ResultCode": 0,
"PanelMetadataAndStatus": METADATA_UNKNOWN,
"ArmingState": ArmingState.DISARMED,
}
RESPONSE_ARM_SUCCESS = {"ResultCode": ResultCode.ARM_SUCCESS.value}
RESPONSE_ARM_FAILURE = {"ResultCode": ResultCode.COMMAND_FAILED.value}
RESPONSE_DISARM_SUCCESS = {"ResultCode": ResultCode.DISARM_SUCCESS.value}
RESPONSE_DISARM_FAILURE = {
"ResultCode": ResultCode.COMMAND_FAILED.value,
"ResultData": "Command Failed",
}
RESPONSE_USER_CODE_INVALID = {
"ResultCode": ResultCode.USER_CODE_INVALID.value,
"ResultData": "testing user code invalid",
}
RESPONSE_SUCCESS = {"ResultCode": ResultCode.SUCCESS.value}
RESPONSE_ZONE_BYPASS_SUCCESS = {
"ResultCode": ResultCode.SUCCESS.value,
"ResultData": "None",
}
RESPONSE_ZONE_BYPASS_FAILURE = {
"ResultCode": ResultCode.FAILED_TO_BYPASS_ZONE.value,
"ResultData": "None",
}
USERNAME = "username@me.com"
PASSWORD = "password"
USERCODES = {LOCATION_ID: "7890"}
CONFIG_DATA = {
CONF_USERNAME: USERNAME,
CONF_PASSWORD: PASSWORD,
CONF_USERCODES: USERCODES,
}
CONFIG_DATA_NO_USERCODES = {CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}
OPTIONS_DATA = {AUTO_BYPASS: False, CODE_REQUIRED: False}
OPTIONS_DATA_CODE_REQUIRED = {AUTO_BYPASS: False, CODE_REQUIRED: True}
PARTITION_DETAILS_1 = {
"PartitionID": "1",
"ArmingState": ArmingState.DISARMED.value,
"PartitionName": "Test1",
}
PARTITION_DETAILS_2 = {
"PartitionID": "2",
"ArmingState": ArmingState.DISARMED.value,
"PartitionName": "Test2",
}
PARTITION_DETAILS = {"PartitionDetails": [PARTITION_DETAILS_1, PARTITION_DETAILS_2]}
RESPONSE_PARTITION_DETAILS = {
"ResultCode": ResultCode.SUCCESS.value,
"ResultData": "testing partition details",
"PartitionsInfoList": PARTITION_DETAILS,
}
ZONE_DETAILS_NORMAL = {
"PartitionId": "1",
"Batterylevel": "-1",
"Signalstrength": "-1",
"zoneAdditionalInfo": None,
"ZoneID": "1",
"ZoneStatus": ZoneStatus.NORMAL,
"ZoneTypeId": ZoneType.SECURITY,
"CanBeBypassed": 1,
"ZoneFlags": None,
}
ZONE_STATUS_INFO = [ZONE_DETAILS_NORMAL]
ZONE_DETAILS = {"ZoneStatusInfoWithPartitionId": ZONE_STATUS_INFO}
ZONE_DETAIL_STATUS = {"Zones": ZONE_DETAILS}
RESPONSE_GET_ZONE_DETAILS_SUCCESS = {
"ResultCode": 0,
"ResultData": "Success",
"ZoneStatus": ZONE_DETAIL_STATUS,
}
TOTALCONNECT_REQUEST = (
"homeassistant.components.totalconnect.TotalConnectClient.request"
)
TOTALCONNECT_GET_CONFIG = (
"homeassistant.components.totalconnect.TotalConnectClient._get_configuration"
)
TOTALCONNECT_REQUEST_TOKEN = (
"homeassistant.components.totalconnect.TotalConnectClient._request_token"
)
async def setup_platform(
hass: HomeAssistant, platform: Any, code_required: bool = False
) -> MockConfigEntry:
"""Set up the TotalConnect platform."""
# first set up a config entry and add it to hass
if code_required:
mock_entry = MockConfigEntry(
domain=DOMAIN, data=CONFIG_DATA, options=OPTIONS_DATA_CODE_REQUIRED
)
else:
mock_entry = MockConfigEntry(
domain=DOMAIN, data=CONFIG_DATA, options=OPTIONS_DATA
)
mock_entry.add_to_hass(hass)
responses = [
RESPONSE_SESSION_DETAILS,
RESPONSE_PARTITION_DETAILS,
RESPONSE_GET_ZONE_DETAILS_SUCCESS,
RESPONSE_DISARMED,
RESPONSE_DISARMED,
]
with (
patch("homeassistant.components.totalconnect.PLATFORMS", [platform]),
patch(
TOTALCONNECT_REQUEST,
side_effect=responses,
) as mock_request,
patch(TOTALCONNECT_GET_CONFIG, side_effect=None),
patch(TOTALCONNECT_REQUEST_TOKEN, side_effect=None),
):
assert await async_setup_component(hass, DOMAIN, {})
assert mock_request.call_count == 5
await hass.async_block_till_done()
return mock_entry
async def init_integration(hass: HomeAssistant) -> MockConfigEntry:
"""Set up the TotalConnect integration."""
# first set up a config entry and add it to hass
mock_entry = MockConfigEntry(domain=DOMAIN, data=CONFIG_DATA, options=OPTIONS_DATA)
mock_entry.add_to_hass(hass)
responses = [
RESPONSE_SESSION_DETAILS,
RESPONSE_PARTITION_DETAILS,
RESPONSE_GET_ZONE_DETAILS_SUCCESS,
RESPONSE_DISARMED,
RESPONSE_DISARMED,
]
with (
patch(
TOTALCONNECT_REQUEST,
side_effect=responses,
) as mock_request,
patch(TOTALCONNECT_GET_CONFIG, side_effect=None),
patch(TOTALCONNECT_REQUEST_TOKEN, side_effect=None),
):
await hass.config_entries.async_setup(mock_entry.entry_id)
assert mock_request.call_count == 5
await hass.async_block_till_done()
return mock_entry

View File

@@ -0,0 +1,249 @@
"""Configure py.test."""
from collections.abc import Generator
from typing import Any
from unittest.mock import AsyncMock, patch
import pytest
from total_connect_client import ArmingState, TotalConnectClient
from total_connect_client.device import TotalConnectDevice
from total_connect_client.location import TotalConnectLocation
from total_connect_client.partition import TotalConnectPartition
from total_connect_client.user import TotalConnectUser
from total_connect_client.zone import TotalConnectZone, ZoneStatus, ZoneType
from homeassistant.components.totalconnect.const import (
AUTO_BYPASS,
CODE_REQUIRED,
CONF_USERCODES,
DOMAIN,
)
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from .const import CODE, LOCATION_ID, PASSWORD, USERCODES, USERNAME
from tests.common import (
MockConfigEntry,
load_json_array_fixture,
load_json_object_fixture,
)
def create_mock_zone(
identifier: int,
partition: str,
description: str,
status: ZoneStatus,
zone_type_id: int,
can_be_bypassed: bool,
battery_level: int,
signal_strength: int,
sensor_serial_number: str | None,
loop_number: int | None,
response_type: str | None,
alarm_report_state: str | None,
supervision_type: str | None,
chime_state: str | None,
device_type: str | None,
) -> AsyncMock:
"""Create a mock TotalConnectZone."""
zone = AsyncMock(spec=TotalConnectZone, autospec=True)
zone.zoneid = identifier
zone.partition = partition
zone.description = description
zone.status = status
zone.zone_type_id = zone_type_id
zone.can_be_bypassed = can_be_bypassed
zone.battery_level = battery_level
zone.signal_strength = signal_strength
zone.sensor_serial_number = sensor_serial_number
zone.loop_number = loop_number
zone.response_type = response_type
zone.alarm_report_state = alarm_report_state
zone.supervision_type = supervision_type
zone.chime_state = chime_state
zone.device_type = device_type
zone.is_type_security.return_value = zone_type_id in (
ZoneType.SECURITY,
ZoneType.ENTRY_EXIT1,
ZoneType.ENTRY_EXIT2,
ZoneType.PERIMETER,
ZoneType.INTERIOR_FOLLOWER,
ZoneType.TROUBLE_ALARM,
ZoneType.SILENT_24HR,
ZoneType.AUDIBLE_24HR,
ZoneType.INTERIOR_DELAY,
ZoneType.LYRIC_LOCAL_ALARM,
ZoneType.PROA7_GARAGE_MONITOR,
)
zone.is_type_button.return_value = (
zone.is_type_security.return_value and not can_be_bypassed
) or zone_type_id in (
ZoneType.PROA7_MEDICAL,
ZoneType.AUDIBLE_24HR,
ZoneType.SILENT_24HR,
ZoneType.RF_ARM_STAY,
ZoneType.RF_ARM_AWAY,
ZoneType.RF_DISARM,
)
return zone
def create_mock_zone_from_dict(
zone_data: dict[str, Any],
) -> AsyncMock:
"""Create a mock TotalConnectZone from a dictionary."""
return create_mock_zone(
zone_data["ZoneID"],
zone_data["PartitionId"],
zone_data["ZoneDescription"],
ZoneStatus(zone_data["ZoneStatus"]),
zone_data["ZoneTypeId"],
zone_data["CanBeBypassed"],
zone_data.get("Batterylevel"),
zone_data.get("Signalstrength"),
(zone_data["zoneAdditionalInfo"] or {}).get("SensorSerialNumber"),
(zone_data["zoneAdditionalInfo"] or {}).get("LoopNumber"),
(zone_data["zoneAdditionalInfo"] or {}).get("ResponseType"),
(zone_data["zoneAdditionalInfo"] or {}).get("AlarmReportState"),
(zone_data["zoneAdditionalInfo"] or {}).get("ZoneSupervisionType"),
(zone_data["zoneAdditionalInfo"] or {}).get("ChimeState"),
(zone_data["zoneAdditionalInfo"] or {}).get("DeviceType"),
)
@pytest.fixture
def mock_partition() -> TotalConnectPartition:
"""Create a mock TotalConnectPartition."""
partition = AsyncMock(spec=TotalConnectPartition, autospec=True)
partition.partitionid = 1
partition.name = "Test1"
partition.is_stay_armed = False
partition.is_fire_armed = False
partition.is_fire_enabled = False
partition.is_common_armed = False
partition.is_common_enabled = False
partition.is_locked = False
partition.is_new_partition = False
partition.is_night_stay_enabled = 0
partition.exit_delay_timer = 0
partition.arming_state = ArmingState.DISARMED
return partition
@pytest.fixture
def mock_partition_2() -> TotalConnectPartition:
"""Create a mock TotalConnectPartition."""
partition = AsyncMock(spec=TotalConnectPartition, autospec=True)
partition.partitionid = 2
partition.name = "Test2"
partition.is_stay_armed = False
partition.is_fire_armed = False
partition.is_fire_enabled = False
partition.is_common_armed = False
partition.is_common_enabled = False
partition.is_locked = False
partition.is_new_partition = False
partition.is_night_stay_enabled = 0
partition.exit_delay_timer = 0
partition.arming_state = ArmingState.DISARMED
return partition
@pytest.fixture
def mock_location(
mock_partition: AsyncMock, mock_partition_2: AsyncMock
) -> TotalConnectLocation:
"""Create a mock TotalConnectLocation."""
location = AsyncMock(spec=TotalConnectLocation, autospec=True)
location.location_id = LOCATION_ID
location.location_name = "Test Location"
location.security_device_id = 7654321
location.set_usercode.return_value = True
location.partitions = {1: mock_partition, 2: mock_partition_2}
location.devices = {
7654321: TotalConnectDevice(load_json_object_fixture("device_1.json", DOMAIN))
}
location.zones = {
z["ZoneID"]: create_mock_zone_from_dict(z)
for z in load_json_array_fixture("zones.json", DOMAIN)
}
location.is_low_battery.return_value = False
location.is_cover_tampered.return_value = False
location.is_ac_loss.return_value = False
location.arming_state = ArmingState.DISARMED
location._module_flags = {
"can_bypass_zones": True,
"can_clear_bypass": True,
"can_set_usercodes": True,
}
location.ac_loss = False
location.low_battery = False
location.auto_bypass_low_battery = False
location.cover_tampered = False
return location
@pytest.fixture
def mock_client(mock_location: TotalConnectLocation) -> Generator[TotalConnectClient]:
"""Mock a TotalConnectClient for testing."""
with (
patch(
"homeassistant.components.totalconnect.config_flow.TotalConnectClient",
autospec=True,
) as mock_client,
patch(
"homeassistant.components.totalconnect.TotalConnectClient", new=mock_client
),
):
client = mock_client.return_value
client.get_number_locations.return_value = 1
client.locations = {mock_location.location_id: mock_location}
client.usercodes = {mock_location.location_id: CODE}
client.auto_bypass_low_battery = False
client._module_flags = {}
client.retry_delay = 0
client._invalid_credentials = False
user_mock = AsyncMock(spec=TotalConnectUser, autospec=True)
user_mock._master_user = True
user_mock._user_admin = True
user_mock._config_admin = True
user_mock.security_problem.return_value = False
user_mock._features = {
"can_set_usercodes": True,
"can_bypass_zones": True,
"can_clear_bypass": True,
}
setattr(client, "_user", user_mock)
yield client
@pytest.fixture
def code_required() -> bool:
"""Return whether a code is required."""
return False
@pytest.fixture
def mock_config_entry(code_required: bool) -> MockConfigEntry:
"""Create a mock config entry for testing."""
return MockConfigEntry(
domain=DOMAIN,
data={
CONF_USERNAME: USERNAME,
CONF_PASSWORD: PASSWORD,
CONF_USERCODES: USERCODES,
},
options={AUTO_BYPASS: False, CODE_REQUIRED: code_required},
unique_id=USERNAME,
)
@pytest.fixture
def mock_setup_entry() -> Generator[AsyncMock]:
"""Mock the setup entry for TotalConnect."""
with patch(
"homeassistant.components.totalconnect.async_setup_entry",
return_value=True,
) as mock_setup:
yield mock_setup

View File

@@ -0,0 +1,8 @@
"""Constants for the Total Connect tests."""
LOCATION_ID = 1234567
CODE = "7890"
USERNAME = "username@me.com"
PASSWORD = "password"
USERCODES = {LOCATION_ID: "7890"}

View File

@@ -0,0 +1,12 @@
{
"DeviceID": 7654321,
"DeviceName": "test",
"DeviceClassID": 1,
"DeviceSerialNumber": "1234567890AB",
"DeviceFlags": "PromptForUserCode=0,PromptForInstallerCode=0,PromptForImportSecuritySettings=0,AllowUserSlotEditing=0,CalCapable=1,CanBeSentToPanel=1,CanArmNightStay=0,CanSupportMultiPartition=0,PartitionCount=0,MaxPartitionCount=4,OnBoardingSupport=0,PartitionAdded=0,DuplicateUserSyncStatus=0,PanelType=12,PanelVariant=1,BLEDisarmCapable=0,ArmHomeSupported=1,DuplicateUserCodeCheck=1,CanSupportRapid=0,IsKeypadSupported=0,WifiEnrollmentSupported=1,IsConnectedPanel=1,ArmNightInSceneSupported=1,BuiltInCameraSettingsSupported=0,ZWaveThermostatScheduleDisabled=0,MultipleAuthorityLevelSupported=1,VideoOnPanelSupported=1,EnableBLEMode=0,IsPanelWiFiResetSupported=0,IsCompetitorClearBypass=0,IsNotReadyStateSupported=0,isArmStatusWithoutExitDelayNotSupported=0,UserCodeLength=4,UserCodeLengthChanged=0,DoubleDisarmRequired=0,TMSCloudSupported=0,IsAVCEnabled=0",
"SecurityPanelTypeID": 12,
"DeviceSerialText": null,
"DeviceType": null,
"DeviceVariants": null,
"RestrictedPanel": 0
}

View File

@@ -0,0 +1,658 @@
[
{
"PartitionId": 1,
"Batterylevel": -1,
"Signalstrength": -1,
"zoneAdditionalInfo": {
"SensorSerialNumber": "020000",
"LoopNumber": 1,
"ResponseType": "1",
"AlarmReportState": 1,
"ZoneSupervisionType": 0,
"ChimeState": 1,
"DeviceType": 0
},
"CanBeBypassed": 1,
"ZoneFlags": null,
"ZoneID": 2,
"ZoneStatus": 0,
"IsBypassableZone": 0,
"IsSensingZone": 1,
"ZoneDescription": "Security",
"AlarmTriggerTime": null,
"AlarmTriggerTimeLocalized": "2024-12-11T09:00:13",
"ZoneTypeId": 1
},
{
"PartitionId": 1,
"Batterylevel": -1,
"Signalstrength": -1,
"zoneAdditionalInfo": {
"SensorSerialNumber": "030000",
"LoopNumber": 1,
"ResponseType": "4",
"AlarmReportState": 1,
"ZoneSupervisionType": 0,
"ChimeState": 0,
"DeviceType": 2
},
"CanBeBypassed": 1,
"ZoneFlags": null,
"ZoneID": 3,
"ZoneStatus": 0,
"IsBypassableZone": 0,
"IsSensingZone": 1,
"ZoneDescription": "Fire",
"AlarmTriggerTime": null,
"AlarmTriggerTimeLocalized": "2024-06-02T15:41:05",
"ZoneTypeId": 9
},
{
"PartitionId": 1,
"Batterylevel": -1,
"Signalstrength": -1,
"zoneAdditionalInfo": {
"SensorSerialNumber": "040000",
"LoopNumber": 1,
"ResponseType": "4",
"AlarmReportState": 1,
"ZoneSupervisionType": 0,
"ChimeState": 0,
"DeviceType": 2
},
"CanBeBypassed": 1,
"ZoneFlags": null,
"ZoneID": 4,
"ZoneStatus": 0,
"IsBypassableZone": 0,
"IsSensingZone": 1,
"ZoneDescription": "Gas",
"AlarmTriggerTime": null,
"AlarmTriggerTimeLocalized": "2024-12-11T09:00:13",
"ZoneTypeId": 14
},
{
"PartitionId": 1,
"Batterylevel": -1,
"Signalstrength": -1,
"zoneAdditionalInfo": {
"SensorSerialNumber": "050000",
"LoopNumber": 1,
"ResponseType": "4",
"AlarmReportState": 1,
"ZoneSupervisionType": 0,
"ChimeState": 0,
"DeviceType": 2
},
"CanBeBypassed": 1,
"ZoneFlags": null,
"ZoneID": 5,
"ZoneStatus": 0,
"IsBypassableZone": 0,
"IsSensingZone": 1,
"ZoneDescription": "Unknown",
"AlarmTriggerTime": null,
"AlarmTriggerTimeLocalized": "2024-06-02T15:40:59",
"ZoneTypeId": 99
},
{
"PartitionId": 1,
"Batterylevel": -1,
"Signalstrength": -1,
"zoneAdditionalInfo": {
"SensorSerialNumber": "060000",
"LoopNumber": 1,
"ResponseType": "1",
"AlarmReportState": 1,
"ZoneSupervisionType": 1,
"ChimeState": 1,
"DeviceType": 0
},
"CanBeBypassed": 1,
"ZoneFlags": null,
"ZoneID": 6,
"ZoneStatus": 0,
"IsBypassableZone": 0,
"IsSensingZone": 1,
"ZoneDescription": "Temperature",
"AlarmTriggerTime": null,
"AlarmTriggerTimeLocalized": null,
"ZoneTypeId": 12
},
{
"PartitionId": 1,
"Batterylevel": 5,
"Signalstrength": 2,
"zoneAdditionalInfo": {
"SensorSerialNumber": "070000000000000A",
"LoopNumber": 2,
"ResponseType": "53",
"AlarmReportState": 0,
"ZoneSupervisionType": 0,
"ChimeState": 1,
"DeviceType": 15
},
"CanBeBypassed": 1,
"ZoneFlags": null,
"ZoneID": 7,
"ZoneStatus": 0,
"IsBypassableZone": 0,
"IsSensingZone": 1,
"ZoneDescription": "Doorbell Other",
"AlarmTriggerTime": null,
"AlarmTriggerTimeLocalized": null,
"ZoneTypeId": 53
},
{
"PartitionId": 1,
"Batterylevel": -1,
"Signalstrength": -1,
"zoneAdditionalInfo": {
"SensorSerialNumber": "080000",
"LoopNumber": 1,
"ResponseType": "3",
"AlarmReportState": 1,
"ZoneSupervisionType": 0,
"ChimeState": 1,
"DeviceType": 0
},
"CanBeBypassed": 1,
"ZoneFlags": null,
"ZoneID": 8,
"ZoneStatus": 1,
"IsBypassableZone": 0,
"IsSensingZone": 1,
"ZoneDescription": "Office Side Door",
"AlarmTriggerTime": null,
"AlarmTriggerTimeLocalized": null,
"ZoneTypeId": 3
},
{
"PartitionId": 1,
"Batterylevel": -1,
"Signalstrength": -1,
"zoneAdditionalInfo": {
"SensorSerialNumber": "090000",
"LoopNumber": 1,
"ResponseType": "3",
"AlarmReportState": 1,
"ZoneSupervisionType": 0,
"ChimeState": 1,
"DeviceType": 0
},
"CanBeBypassed": 1,
"ZoneFlags": null,
"ZoneID": 9,
"ZoneStatus": 0,
"IsBypassableZone": 0,
"IsSensingZone": 1,
"ZoneDescription": "Office Back Door",
"AlarmTriggerTime": null,
"AlarmTriggerTimeLocalized": null,
"ZoneTypeId": 3
},
{
"PartitionId": 1,
"Batterylevel": -1,
"Signalstrength": -1,
"zoneAdditionalInfo": {
"SensorSerialNumber": "100000",
"LoopNumber": 1,
"ResponseType": "1",
"AlarmReportState": 1,
"ZoneSupervisionType": 0,
"ChimeState": 1,
"DeviceType": 0
},
"CanBeBypassed": 1,
"ZoneFlags": null,
"ZoneID": 10,
"ZoneStatus": 0,
"IsBypassableZone": 0,
"IsSensingZone": 1,
"ZoneDescription": "Master Bedroom Door",
"AlarmTriggerTime": null,
"AlarmTriggerTimeLocalized": "2024-06-02T15:40:57",
"ZoneTypeId": 1
},
{
"PartitionId": 1,
"Batterylevel": -1,
"Signalstrength": -1,
"zoneAdditionalInfo": {
"SensorSerialNumber": "120000",
"LoopNumber": 1,
"ResponseType": "3",
"AlarmReportState": 1,
"ZoneSupervisionType": 0,
"ChimeState": 1,
"DeviceType": 0
},
"CanBeBypassed": 1,
"ZoneFlags": null,
"ZoneID": 12,
"ZoneStatus": 0,
"IsBypassableZone": 0,
"IsSensingZone": 1,
"ZoneDescription": "Dining Room Two Door",
"AlarmTriggerTime": null,
"AlarmTriggerTimeLocalized": null,
"ZoneTypeId": 3
},
{
"PartitionId": 1,
"Batterylevel": -1,
"Signalstrength": -1,
"zoneAdditionalInfo": {
"SensorSerialNumber": "130000",
"LoopNumber": 1,
"ResponseType": "3",
"AlarmReportState": 1,
"ZoneSupervisionType": 0,
"ChimeState": 1,
"DeviceType": 0
},
"CanBeBypassed": 1,
"ZoneFlags": null,
"ZoneID": 13,
"ZoneStatus": 0,
"IsBypassableZone": 0,
"IsSensingZone": 1,
"ZoneDescription": "Patio Door",
"AlarmTriggerTime": null,
"AlarmTriggerTimeLocalized": null,
"ZoneTypeId": 3
},
{
"PartitionId": 1,
"Batterylevel": -1,
"Signalstrength": -1,
"zoneAdditionalInfo": {
"SensorSerialNumber": "140000",
"LoopNumber": 1,
"ResponseType": "3",
"AlarmReportState": 1,
"ZoneSupervisionType": 0,
"ChimeState": 1,
"DeviceType": 1
},
"CanBeBypassed": 1,
"ZoneFlags": null,
"ZoneID": 14,
"ZoneStatus": 0,
"IsBypassableZone": 0,
"IsSensingZone": 1,
"ZoneDescription": "Living Room Window",
"AlarmTriggerTime": null,
"AlarmTriggerTimeLocalized": null,
"ZoneTypeId": 3
},
{
"PartitionId": 1,
"Batterylevel": -1,
"Signalstrength": -1,
"zoneAdditionalInfo": {
"SensorSerialNumber": "150000",
"LoopNumber": 1,
"ResponseType": "3",
"AlarmReportState": 1,
"ZoneSupervisionType": 0,
"ChimeState": 1,
"DeviceType": 1
},
"CanBeBypassed": 1,
"ZoneFlags": null,
"ZoneID": 15,
"ZoneStatus": 0,
"IsBypassableZone": 0,
"IsSensingZone": 1,
"ZoneDescription": "Living Room Two Window",
"AlarmTriggerTime": null,
"AlarmTriggerTimeLocalized": null,
"ZoneTypeId": 3
},
{
"PartitionId": 1,
"Batterylevel": -1,
"Signalstrength": -1,
"zoneAdditionalInfo": {
"SensorSerialNumber": "160000",
"LoopNumber": 1,
"ResponseType": "9",
"AlarmReportState": 1,
"ZoneSupervisionType": 0,
"ChimeState": 0,
"DeviceType": 4
},
"CanBeBypassed": 0,
"ZoneFlags": null,
"ZoneID": 16,
"ZoneStatus": 0,
"IsBypassableZone": 0,
"IsSensingZone": 1,
"ZoneDescription": "Apartment SmokeDetector",
"AlarmTriggerTime": null,
"AlarmTriggerTimeLocalized": "2024-04-28T09:42:29",
"ZoneTypeId": 9
},
{
"PartitionId": 1,
"Batterylevel": -1,
"Signalstrength": -1,
"zoneAdditionalInfo": {
"SensorSerialNumber": "170000",
"LoopNumber": 1,
"ResponseType": "9",
"AlarmReportState": 1,
"ZoneSupervisionType": 0,
"ChimeState": 0,
"DeviceType": 4
},
"CanBeBypassed": 0,
"ZoneFlags": null,
"ZoneID": 17,
"ZoneStatus": 0,
"IsBypassableZone": 0,
"IsSensingZone": 1,
"ZoneDescription": "Upstairs Hallway SmokeDetector",
"AlarmTriggerTime": null,
"AlarmTriggerTimeLocalized": "2024-04-28T09:53:57",
"ZoneTypeId": 9
},
{
"PartitionId": 1,
"Batterylevel": -1,
"Signalstrength": -1,
"zoneAdditionalInfo": {
"SensorSerialNumber": "180000",
"LoopNumber": 1,
"ResponseType": "9",
"AlarmReportState": 1,
"ZoneSupervisionType": 0,
"ChimeState": 0,
"DeviceType": 4
},
"CanBeBypassed": 0,
"ZoneFlags": null,
"ZoneID": 18,
"ZoneStatus": 0,
"IsBypassableZone": 0,
"IsSensingZone": 1,
"ZoneDescription": "Downstairs Hallway SmokeDetector",
"AlarmTriggerTime": null,
"AlarmTriggerTimeLocalized": "2024-04-28T09:47:10",
"ZoneTypeId": 9
},
{
"PartitionId": 1,
"Batterylevel": -1,
"Signalstrength": -1,
"zoneAdditionalInfo": {
"SensorSerialNumber": "190000",
"LoopNumber": 1,
"ResponseType": "9",
"AlarmReportState": 1,
"ZoneSupervisionType": 0,
"ChimeState": 0,
"DeviceType": 4
},
"CanBeBypassed": 0,
"ZoneFlags": null,
"ZoneID": 19,
"ZoneStatus": 0,
"IsBypassableZone": 0,
"IsSensingZone": 1,
"ZoneDescription": "Kid Bedroom SmokeDetector",
"AlarmTriggerTime": null,
"AlarmTriggerTimeLocalized": "2024-04-28T09:49:07",
"ZoneTypeId": 9
},
{
"PartitionId": 1,
"Batterylevel": -1,
"Signalstrength": -1,
"zoneAdditionalInfo": {
"SensorSerialNumber": "200000",
"LoopNumber": 1,
"ResponseType": "9",
"AlarmReportState": 1,
"ZoneSupervisionType": 0,
"ChimeState": 0,
"DeviceType": 4
},
"CanBeBypassed": 0,
"ZoneFlags": null,
"ZoneID": 20,
"ZoneStatus": 0,
"IsBypassableZone": 0,
"IsSensingZone": 1,
"ZoneDescription": "Guest Bedroom SmokeDetector",
"AlarmTriggerTime": null,
"AlarmTriggerTimeLocalized": "2024-04-28T09:50:20",
"ZoneTypeId": 9
},
{
"PartitionId": 1,
"Batterylevel": -1,
"Signalstrength": -1,
"zoneAdditionalInfo": {
"SensorSerialNumber": "210000",
"LoopNumber": 1,
"ResponseType": "14",
"AlarmReportState": 1,
"ZoneSupervisionType": 0,
"ChimeState": 0,
"DeviceType": 6
},
"CanBeBypassed": 0,
"ZoneFlags": null,
"ZoneID": 21,
"ZoneStatus": 0,
"IsBypassableZone": 0,
"IsSensingZone": 1,
"ZoneDescription": "Apartment CarbonMonoxideDetecto",
"AlarmTriggerTime": null,
"AlarmTriggerTimeLocalized": "2024-04-28T09:41:18",
"ZoneTypeId": 14
},
{
"PartitionId": 1,
"Batterylevel": -1,
"Signalstrength": -1,
"zoneAdditionalInfo": {
"SensorSerialNumber": "220000",
"LoopNumber": 1,
"ResponseType": "14",
"AlarmReportState": 1,
"ZoneSupervisionType": 0,
"ChimeState": 0,
"DeviceType": 6
},
"CanBeBypassed": 0,
"ZoneFlags": null,
"ZoneID": 22,
"ZoneStatus": 0,
"IsBypassableZone": 0,
"IsSensingZone": 1,
"ZoneDescription": "Downstairs Hallway CarbonMonoxid",
"AlarmTriggerTime": null,
"AlarmTriggerTimeLocalized": "2024-04-28T09:45:39",
"ZoneTypeId": 14
},
{
"PartitionId": 1,
"Batterylevel": -1,
"Signalstrength": -1,
"zoneAdditionalInfo": {
"SensorSerialNumber": "230000",
"LoopNumber": 1,
"ResponseType": "14",
"AlarmReportState": 1,
"ZoneSupervisionType": 0,
"ChimeState": 0,
"DeviceType": 6
},
"CanBeBypassed": 0,
"ZoneFlags": null,
"ZoneID": 23,
"ZoneStatus": 0,
"IsBypassableZone": 0,
"IsSensingZone": 1,
"ZoneDescription": "Upstairs Hallway CarbonMonoxideD",
"AlarmTriggerTime": null,
"AlarmTriggerTimeLocalized": "2024-04-28T09:52:37",
"ZoneTypeId": 14
},
{
"PartitionId": 1,
"Batterylevel": -1,
"Signalstrength": -1,
"zoneAdditionalInfo": {
"SensorSerialNumber": "240000",
"LoopNumber": 1,
"ResponseType": "9",
"AlarmReportState": 1,
"ZoneSupervisionType": 0,
"ChimeState": 0,
"DeviceType": 4
},
"CanBeBypassed": 0,
"ZoneFlags": null,
"ZoneID": 24,
"ZoneStatus": 0,
"IsBypassableZone": 0,
"IsSensingZone": 1,
"ZoneDescription": "Master Bedroom SmokeDetector",
"AlarmTriggerTime": null,
"AlarmTriggerTimeLocalized": null,
"ZoneTypeId": 9
},
{
"PartitionId": 1,
"Batterylevel": 5,
"Signalstrength": 3,
"zoneAdditionalInfo": {
"SensorSerialNumber": "250000000000000A",
"LoopNumber": 1,
"ResponseType": "23",
"AlarmReportState": 1,
"ZoneSupervisionType": 0,
"ChimeState": 0,
"DeviceType": 15
},
"CanBeBypassed": 1,
"ZoneFlags": null,
"ZoneID": 25,
"ZoneStatus": 0,
"IsBypassableZone": 0,
"IsSensingZone": 1,
"ZoneDescription": "Garage Side Other",
"AlarmTriggerTime": null,
"AlarmTriggerTimeLocalized": "2024-12-15T15:14:39",
"ZoneTypeId": 23
},
{
"PartitionId": 1,
"Batterylevel": 5,
"Signalstrength": 5,
"zoneAdditionalInfo": {
"SensorSerialNumber": "260000000000000A",
"LoopNumber": 1,
"ResponseType": "1",
"AlarmReportState": 1,
"ZoneSupervisionType": 0,
"ChimeState": 1,
"DeviceType": 0
},
"CanBeBypassed": 1,
"ZoneFlags": null,
"ZoneID": 26,
"ZoneStatus": 0,
"IsBypassableZone": 0,
"IsSensingZone": 1,
"ZoneDescription": "Front Door Door",
"AlarmTriggerTime": null,
"AlarmTriggerTimeLocalized": null,
"ZoneTypeId": 1
},
{
"PartitionId": 1,
"Batterylevel": -1,
"Signalstrength": -1,
"zoneAdditionalInfo": null,
"CanBeBypassed": 0,
"ZoneFlags": null,
"ZoneID": 800,
"ZoneStatus": 0,
"IsBypassableZone": 0,
"IsSensingZone": 0,
"ZoneDescription": "Master Bedroom Keypad",
"AlarmTriggerTime": null,
"AlarmTriggerTimeLocalized": null,
"ZoneTypeId": 50
},
{
"PartitionId": 1,
"Batterylevel": -1,
"Signalstrength": -1,
"zoneAdditionalInfo": null,
"CanBeBypassed": 0,
"ZoneFlags": null,
"ZoneID": 1995,
"ZoneStatus": 0,
"IsBypassableZone": 0,
"IsSensingZone": 0,
"ZoneDescription": "Zone 995 Fire",
"AlarmTriggerTime": null,
"AlarmTriggerTimeLocalized": null,
"ZoneTypeId": 9
},
{
"PartitionId": 1,
"Batterylevel": -1,
"Signalstrength": -1,
"zoneAdditionalInfo": null,
"CanBeBypassed": 0,
"ZoneFlags": null,
"ZoneID": 1996,
"ZoneStatus": 0,
"IsBypassableZone": 0,
"IsSensingZone": 0,
"ZoneDescription": "Zone 996 Medical",
"AlarmTriggerTime": null,
"AlarmTriggerTimeLocalized": null,
"ZoneTypeId": 15
},
{
"PartitionId": 1,
"Batterylevel": -1,
"Signalstrength": -1,
"zoneAdditionalInfo": null,
"CanBeBypassed": 0,
"ZoneFlags": null,
"ZoneID": 1998,
"ZoneStatus": 0,
"IsBypassableZone": 0,
"IsSensingZone": 0,
"ZoneDescription": "Zone 998 Other",
"AlarmTriggerTime": null,
"AlarmTriggerTimeLocalized": null,
"ZoneTypeId": 6
},
{
"PartitionId": 1,
"Batterylevel": -1,
"Signalstrength": -1,
"zoneAdditionalInfo": null,
"CanBeBypassed": 0,
"ZoneFlags": null,
"ZoneID": 1999,
"ZoneStatus": 0,
"IsBypassableZone": 0,
"IsSensingZone": 0,
"ZoneDescription": "Zone 999 Police",
"AlarmTriggerTime": null,
"AlarmTriggerTimeLocalized": null,
"ZoneTypeId": 7
}
]

View File

@@ -1,5 +1,5 @@
# serializer version: 1
# name: test_attributes[alarm_control_panel.test-entry]
# name: test_entities[alarm_control_panel.test-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
@@ -30,11 +30,11 @@
'suggested_object_id': None,
'supported_features': <AlarmControlPanelEntityFeature: 7>,
'translation_key': None,
'unique_id': '123456',
'unique_id': '1234567',
'unit_of_measurement': None,
})
# ---
# name: test_attributes[alarm_control_panel.test-state]
# name: test_entities[alarm_control_panel.test-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'changed_by': None,
@@ -51,7 +51,7 @@
'state': 'disarmed',
})
# ---
# name: test_attributes[alarm_control_panel.test_partition_2-entry]
# name: test_entities[alarm_control_panel.test_partition_2-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
@@ -82,11 +82,11 @@
'suggested_object_id': None,
'supported_features': <AlarmControlPanelEntityFeature: 7>,
'translation_key': 'partition',
'unique_id': '123456_2',
'unique_id': '1234567_2',
'unit_of_measurement': None,
})
# ---
# name: test_attributes[alarm_control_panel.test_partition_2-state]
# name: test_entities[alarm_control_panel.test_partition_2-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'changed_by': None,

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,100 @@
# serializer version: 1
# name: test_entity_registry[button.dining_room_two_door_bypass-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'button',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'button.dining_room_two_door_bypass',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Bypass',
'platform': 'totalconnect',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'bypass',
'unique_id': '1234567_12_bypass',
'unit_of_measurement': None,
})
# ---
# name: test_entity_registry[button.dining_room_two_door_bypass-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Dining Room Two Door Bypass',
}),
'context': <ANY>,
'entity_id': 'button.dining_room_two_door_bypass',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_entity_registry[button.doorbell_other_bypass-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'button',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'button.doorbell_other_bypass',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Bypass',
'platform': 'totalconnect',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'bypass',
'unique_id': '1234567_7_bypass',
'unit_of_measurement': None,
})
# ---
# name: test_entity_registry[button.doorbell_other_bypass-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Doorbell Other Bypass',
}),
'context': <ANY>,
'entity_id': 'button.doorbell_other_bypass',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_entity_registry[button.fire_bypass-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
@@ -30,7 +126,7 @@
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'bypass',
'unique_id': '123456_2_bypass',
'unique_id': '1234567_3_bypass',
'unit_of_measurement': None,
})
# ---
@@ -47,6 +143,102 @@
'state': 'unknown',
})
# ---
# name: test_entity_registry[button.front_door_door_bypass-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'button',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'button.front_door_door_bypass',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Bypass',
'platform': 'totalconnect',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'bypass',
'unique_id': '1234567_26_bypass',
'unit_of_measurement': None,
})
# ---
# name: test_entity_registry[button.front_door_door_bypass-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Front Door Door Bypass',
}),
'context': <ANY>,
'entity_id': 'button.front_door_door_bypass',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_entity_registry[button.garage_side_other_bypass-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'button',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'button.garage_side_other_bypass',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Bypass',
'platform': 'totalconnect',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'bypass',
'unique_id': '1234567_25_bypass',
'unit_of_measurement': None,
})
# ---
# name: test_entity_registry[button.garage_side_other_bypass-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Garage Side Other Bypass',
}),
'context': <ANY>,
'entity_id': 'button.garage_side_other_bypass',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_entity_registry[button.gas_bypass-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
@@ -78,7 +270,7 @@
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'bypass',
'unique_id': '123456_3_bypass',
'unique_id': '1234567_4_bypass',
'unit_of_measurement': None,
})
# ---
@@ -95,7 +287,7 @@
'state': 'unknown',
})
# ---
# name: test_entity_registry[button.motion_bypass-entry]
# name: test_entity_registry[button.living_room_two_window_bypass-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
@@ -108,7 +300,7 @@
'disabled_by': None,
'domain': 'button',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'button.motion_bypass',
'entity_id': 'button.living_room_two_window_bypass',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
@@ -126,17 +318,257 @@
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'bypass',
'unique_id': '123456_4_bypass',
'unique_id': '1234567_15_bypass',
'unit_of_measurement': None,
})
# ---
# name: test_entity_registry[button.motion_bypass-state]
# name: test_entity_registry[button.living_room_two_window_bypass-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Motion Bypass',
'friendly_name': 'Living Room Two Window Bypass',
}),
'context': <ANY>,
'entity_id': 'button.motion_bypass',
'entity_id': 'button.living_room_two_window_bypass',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_entity_registry[button.living_room_window_bypass-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'button',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'button.living_room_window_bypass',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Bypass',
'platform': 'totalconnect',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'bypass',
'unique_id': '1234567_14_bypass',
'unit_of_measurement': None,
})
# ---
# name: test_entity_registry[button.living_room_window_bypass-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Living Room Window Bypass',
}),
'context': <ANY>,
'entity_id': 'button.living_room_window_bypass',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_entity_registry[button.master_bedroom_door_bypass-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'button',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'button.master_bedroom_door_bypass',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Bypass',
'platform': 'totalconnect',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'bypass',
'unique_id': '1234567_10_bypass',
'unit_of_measurement': None,
})
# ---
# name: test_entity_registry[button.master_bedroom_door_bypass-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Master Bedroom Door Bypass',
}),
'context': <ANY>,
'entity_id': 'button.master_bedroom_door_bypass',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_entity_registry[button.office_back_door_bypass-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'button',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'button.office_back_door_bypass',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Bypass',
'platform': 'totalconnect',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'bypass',
'unique_id': '1234567_9_bypass',
'unit_of_measurement': None,
})
# ---
# name: test_entity_registry[button.office_back_door_bypass-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Office Back Door Bypass',
}),
'context': <ANY>,
'entity_id': 'button.office_back_door_bypass',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_entity_registry[button.office_side_door_bypass-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'button',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'button.office_side_door_bypass',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Bypass',
'platform': 'totalconnect',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'bypass',
'unique_id': '1234567_8_bypass',
'unit_of_measurement': None,
})
# ---
# name: test_entity_registry[button.office_side_door_bypass-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Office Side Door Bypass',
}),
'context': <ANY>,
'entity_id': 'button.office_side_door_bypass',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_entity_registry[button.patio_door_bypass-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'button',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'button.patio_door_bypass',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Bypass',
'platform': 'totalconnect',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'bypass',
'unique_id': '1234567_13_bypass',
'unit_of_measurement': None,
})
# ---
# name: test_entity_registry[button.patio_door_bypass-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Patio Door Bypass',
}),
'context': <ANY>,
'entity_id': 'button.patio_door_bypass',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
@@ -174,7 +606,7 @@
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'bypass',
'unique_id': '123456_1_bypass',
'unique_id': '1234567_2_bypass',
'unit_of_measurement': None,
})
# ---
@@ -191,6 +623,54 @@
'state': 'unknown',
})
# ---
# name: test_entity_registry[button.temperature_bypass-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'button',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'button.temperature_bypass',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Bypass',
'platform': 'totalconnect',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'bypass',
'unique_id': '1234567_6_bypass',
'unit_of_measurement': None,
})
# ---
# name: test_entity_registry[button.temperature_bypass-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Temperature Bypass',
}),
'context': <ANY>,
'entity_id': 'button.temperature_bypass',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_entity_registry[button.test_bypass_all-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
@@ -222,7 +702,7 @@
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'bypass_all',
'unique_id': '123456_bypass_all',
'unique_id': '1234567_bypass_all',
'unit_of_measurement': None,
})
# ---
@@ -270,7 +750,7 @@
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'clear_bypass',
'unique_id': '123456_clear_bypass',
'unique_id': '1234567_clear_bypass',
'unit_of_measurement': None,
})
# ---
@@ -287,3 +767,51 @@
'state': 'unknown',
})
# ---
# name: test_entity_registry[button.unknown_bypass-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'button',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'button.unknown_bypass',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Bypass',
'platform': 'totalconnect',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'bypass',
'unique_id': '1234567_5_bypass',
'unit_of_measurement': None,
})
# ---
# name: test_entity_registry[button.unknown_bypass-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Unknown Bypass',
}),
'context': <ANY>,
'entity_id': 'button.unknown_bypass',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---

View File

@@ -0,0 +1,619 @@
# serializer version: 1
# name: test_entry_diagnostics
dict({
'client': dict({
'auto_bypass_low_battery': False,
'invalid_credentials': False,
'module_flags': dict({
}),
'retry_delay': 0,
}),
'locations': list([
dict({
'ac_loss': False,
'arming_state': dict({
'__type': "<enum 'ArmingState'>",
'repr': '<ArmingState.DISARMED: 10200>',
}),
'auto_bypass_low_battery': False,
'cover_tampered': False,
'devices': list([
dict({
'class_id': 1,
'device_id': 7654321,
'flags': dict({
'AllowUserSlotEditing': '0',
'ArmHomeSupported': '1',
'ArmNightInSceneSupported': '1',
'BLEDisarmCapable': '0',
'BuiltInCameraSettingsSupported': '0',
'CalCapable': '1',
'CanArmNightStay': '0',
'CanBeSentToPanel': '1',
'CanSupportMultiPartition': '0',
'CanSupportRapid': '0',
'DoubleDisarmRequired': '0',
'DuplicateUserCodeCheck': '1',
'DuplicateUserSyncStatus': '0',
'EnableBLEMode': '0',
'IsAVCEnabled': '0',
'IsCompetitorClearBypass': '0',
'IsConnectedPanel': '1',
'IsKeypadSupported': '0',
'IsNotReadyStateSupported': '0',
'IsPanelWiFiResetSupported': '0',
'MaxPartitionCount': '4',
'MultipleAuthorityLevelSupported': '1',
'OnBoardingSupport': '0',
'PanelType': '12',
'PanelVariant': '1',
'PartitionAdded': '0',
'PartitionCount': '0',
'PromptForImportSecuritySettings': '0',
'PromptForInstallerCode': '0',
'PromptForUserCode': '0',
'TMSCloudSupported': '0',
'UserCodeLength': '4',
'UserCodeLengthChanged': '0',
'VideoOnPanelSupported': '1',
'WifiEnrollmentSupported': '1',
'ZWaveThermostatScheduleDisabled': '0',
'isArmStatusWithoutExitDelayNotSupported': '0',
}),
'name': 'test',
'security_panel_type_id': 12,
'serial_number': '**REDACTED**',
'serial_text': None,
}),
]),
'location_id': 1234567,
'low_battery': False,
'module_flags': dict({
'can_bypass_zones': True,
'can_clear_bypass': True,
'can_set_usercodes': True,
}),
'name': 'Test Location',
'partitions': list([
dict({
'arming_state': dict({
'__type': "<enum 'ArmingState'>",
'repr': '<ArmingState.DISARMED: 10200>',
}),
'exit_delay_timer': 0,
'is_common_enabled': False,
'is_fire_enabled': False,
'is_locked': False,
'is_new_partition': False,
'is_night_stay_enabled': 0,
'is_stay_armed': False,
'name': 'Test1',
'partition_id': 1,
}),
dict({
'arming_state': dict({
'__type': "<enum 'ArmingState'>",
'repr': '<ArmingState.DISARMED: 10200>',
}),
'exit_delay_timer': 0,
'is_common_enabled': False,
'is_fire_enabled': False,
'is_locked': False,
'is_new_partition': False,
'is_night_stay_enabled': 0,
'is_stay_armed': False,
'name': 'Test2',
'partition_id': 2,
}),
]),
'security_device_id': 7654321,
'zones': list([
dict({
'alarm_report_state': 1,
'battery_level': -1,
'can_be_bypassed': 1,
'chime_state': 1,
'description': 'Security',
'device_type': 0,
'loop_number': 1,
'partition': 1,
'response_type': '1',
'sensor_serial_number': '**REDACTED**',
'signal_strength': -1,
'status': 0,
'supervision_type': 0,
'zone_id': 2,
'zone_type_id': 1,
}),
dict({
'alarm_report_state': 1,
'battery_level': -1,
'can_be_bypassed': 1,
'chime_state': 0,
'description': 'Fire',
'device_type': 2,
'loop_number': 1,
'partition': 1,
'response_type': '4',
'sensor_serial_number': '**REDACTED**',
'signal_strength': -1,
'status': 0,
'supervision_type': 0,
'zone_id': 3,
'zone_type_id': 9,
}),
dict({
'alarm_report_state': 1,
'battery_level': -1,
'can_be_bypassed': 1,
'chime_state': 0,
'description': 'Gas',
'device_type': 2,
'loop_number': 1,
'partition': 1,
'response_type': '4',
'sensor_serial_number': '**REDACTED**',
'signal_strength': -1,
'status': 0,
'supervision_type': 0,
'zone_id': 4,
'zone_type_id': 14,
}),
dict({
'alarm_report_state': 1,
'battery_level': -1,
'can_be_bypassed': 1,
'chime_state': 0,
'description': 'Unknown',
'device_type': 2,
'loop_number': 1,
'partition': 1,
'response_type': '4',
'sensor_serial_number': '**REDACTED**',
'signal_strength': -1,
'status': 0,
'supervision_type': 0,
'zone_id': 5,
'zone_type_id': 99,
}),
dict({
'alarm_report_state': 1,
'battery_level': -1,
'can_be_bypassed': 1,
'chime_state': 1,
'description': 'Temperature',
'device_type': 0,
'loop_number': 1,
'partition': 1,
'response_type': '1',
'sensor_serial_number': '**REDACTED**',
'signal_strength': -1,
'status': 0,
'supervision_type': 1,
'zone_id': 6,
'zone_type_id': 12,
}),
dict({
'alarm_report_state': 0,
'battery_level': 5,
'can_be_bypassed': 1,
'chime_state': 1,
'description': 'Doorbell Other',
'device_type': 15,
'loop_number': 2,
'partition': 1,
'response_type': '53',
'sensor_serial_number': '**REDACTED**',
'signal_strength': 2,
'status': 0,
'supervision_type': 0,
'zone_id': 7,
'zone_type_id': 53,
}),
dict({
'alarm_report_state': 1,
'battery_level': -1,
'can_be_bypassed': 1,
'chime_state': 1,
'description': 'Office Side Door',
'device_type': 0,
'loop_number': 1,
'partition': 1,
'response_type': '3',
'sensor_serial_number': '**REDACTED**',
'signal_strength': -1,
'status': 1,
'supervision_type': 0,
'zone_id': 8,
'zone_type_id': 3,
}),
dict({
'alarm_report_state': 1,
'battery_level': -1,
'can_be_bypassed': 1,
'chime_state': 1,
'description': 'Office Back Door',
'device_type': 0,
'loop_number': 1,
'partition': 1,
'response_type': '3',
'sensor_serial_number': '**REDACTED**',
'signal_strength': -1,
'status': 0,
'supervision_type': 0,
'zone_id': 9,
'zone_type_id': 3,
}),
dict({
'alarm_report_state': 1,
'battery_level': -1,
'can_be_bypassed': 1,
'chime_state': 1,
'description': 'Master Bedroom Door',
'device_type': 0,
'loop_number': 1,
'partition': 1,
'response_type': '1',
'sensor_serial_number': '**REDACTED**',
'signal_strength': -1,
'status': 0,
'supervision_type': 0,
'zone_id': 10,
'zone_type_id': 1,
}),
dict({
'alarm_report_state': 1,
'battery_level': -1,
'can_be_bypassed': 1,
'chime_state': 1,
'description': 'Dining Room Two Door',
'device_type': 0,
'loop_number': 1,
'partition': 1,
'response_type': '3',
'sensor_serial_number': '**REDACTED**',
'signal_strength': -1,
'status': 0,
'supervision_type': 0,
'zone_id': 12,
'zone_type_id': 3,
}),
dict({
'alarm_report_state': 1,
'battery_level': -1,
'can_be_bypassed': 1,
'chime_state': 1,
'description': 'Patio Door',
'device_type': 0,
'loop_number': 1,
'partition': 1,
'response_type': '3',
'sensor_serial_number': '**REDACTED**',
'signal_strength': -1,
'status': 0,
'supervision_type': 0,
'zone_id': 13,
'zone_type_id': 3,
}),
dict({
'alarm_report_state': 1,
'battery_level': -1,
'can_be_bypassed': 1,
'chime_state': 1,
'description': 'Living Room Window',
'device_type': 1,
'loop_number': 1,
'partition': 1,
'response_type': '3',
'sensor_serial_number': '**REDACTED**',
'signal_strength': -1,
'status': 0,
'supervision_type': 0,
'zone_id': 14,
'zone_type_id': 3,
}),
dict({
'alarm_report_state': 1,
'battery_level': -1,
'can_be_bypassed': 1,
'chime_state': 1,
'description': 'Living Room Two Window',
'device_type': 1,
'loop_number': 1,
'partition': 1,
'response_type': '3',
'sensor_serial_number': '**REDACTED**',
'signal_strength': -1,
'status': 0,
'supervision_type': 0,
'zone_id': 15,
'zone_type_id': 3,
}),
dict({
'alarm_report_state': 1,
'battery_level': -1,
'can_be_bypassed': 0,
'chime_state': 0,
'description': 'Apartment SmokeDetector',
'device_type': 4,
'loop_number': 1,
'partition': 1,
'response_type': '9',
'sensor_serial_number': '**REDACTED**',
'signal_strength': -1,
'status': 0,
'supervision_type': 0,
'zone_id': 16,
'zone_type_id': 9,
}),
dict({
'alarm_report_state': 1,
'battery_level': -1,
'can_be_bypassed': 0,
'chime_state': 0,
'description': 'Upstairs Hallway SmokeDetector',
'device_type': 4,
'loop_number': 1,
'partition': 1,
'response_type': '9',
'sensor_serial_number': '**REDACTED**',
'signal_strength': -1,
'status': 0,
'supervision_type': 0,
'zone_id': 17,
'zone_type_id': 9,
}),
dict({
'alarm_report_state': 1,
'battery_level': -1,
'can_be_bypassed': 0,
'chime_state': 0,
'description': 'Downstairs Hallway SmokeDetector',
'device_type': 4,
'loop_number': 1,
'partition': 1,
'response_type': '9',
'sensor_serial_number': '**REDACTED**',
'signal_strength': -1,
'status': 0,
'supervision_type': 0,
'zone_id': 18,
'zone_type_id': 9,
}),
dict({
'alarm_report_state': 1,
'battery_level': -1,
'can_be_bypassed': 0,
'chime_state': 0,
'description': 'Kid Bedroom SmokeDetector',
'device_type': 4,
'loop_number': 1,
'partition': 1,
'response_type': '9',
'sensor_serial_number': '**REDACTED**',
'signal_strength': -1,
'status': 0,
'supervision_type': 0,
'zone_id': 19,
'zone_type_id': 9,
}),
dict({
'alarm_report_state': 1,
'battery_level': -1,
'can_be_bypassed': 0,
'chime_state': 0,
'description': 'Guest Bedroom SmokeDetector',
'device_type': 4,
'loop_number': 1,
'partition': 1,
'response_type': '9',
'sensor_serial_number': '**REDACTED**',
'signal_strength': -1,
'status': 0,
'supervision_type': 0,
'zone_id': 20,
'zone_type_id': 9,
}),
dict({
'alarm_report_state': 1,
'battery_level': -1,
'can_be_bypassed': 0,
'chime_state': 0,
'description': 'Apartment CarbonMonoxideDetecto',
'device_type': 6,
'loop_number': 1,
'partition': 1,
'response_type': '14',
'sensor_serial_number': '**REDACTED**',
'signal_strength': -1,
'status': 0,
'supervision_type': 0,
'zone_id': 21,
'zone_type_id': 14,
}),
dict({
'alarm_report_state': 1,
'battery_level': -1,
'can_be_bypassed': 0,
'chime_state': 0,
'description': 'Downstairs Hallway CarbonMonoxid',
'device_type': 6,
'loop_number': 1,
'partition': 1,
'response_type': '14',
'sensor_serial_number': '**REDACTED**',
'signal_strength': -1,
'status': 0,
'supervision_type': 0,
'zone_id': 22,
'zone_type_id': 14,
}),
dict({
'alarm_report_state': 1,
'battery_level': -1,
'can_be_bypassed': 0,
'chime_state': 0,
'description': 'Upstairs Hallway CarbonMonoxideD',
'device_type': 6,
'loop_number': 1,
'partition': 1,
'response_type': '14',
'sensor_serial_number': '**REDACTED**',
'signal_strength': -1,
'status': 0,
'supervision_type': 0,
'zone_id': 23,
'zone_type_id': 14,
}),
dict({
'alarm_report_state': 1,
'battery_level': -1,
'can_be_bypassed': 0,
'chime_state': 0,
'description': 'Master Bedroom SmokeDetector',
'device_type': 4,
'loop_number': 1,
'partition': 1,
'response_type': '9',
'sensor_serial_number': '**REDACTED**',
'signal_strength': -1,
'status': 0,
'supervision_type': 0,
'zone_id': 24,
'zone_type_id': 9,
}),
dict({
'alarm_report_state': 1,
'battery_level': 5,
'can_be_bypassed': 1,
'chime_state': 0,
'description': 'Garage Side Other',
'device_type': 15,
'loop_number': 1,
'partition': 1,
'response_type': '23',
'sensor_serial_number': '**REDACTED**',
'signal_strength': 3,
'status': 0,
'supervision_type': 0,
'zone_id': 25,
'zone_type_id': 23,
}),
dict({
'alarm_report_state': 1,
'battery_level': 5,
'can_be_bypassed': 1,
'chime_state': 1,
'description': 'Front Door Door',
'device_type': 0,
'loop_number': 1,
'partition': 1,
'response_type': '1',
'sensor_serial_number': '**REDACTED**',
'signal_strength': 5,
'status': 0,
'supervision_type': 0,
'zone_id': 26,
'zone_type_id': 1,
}),
dict({
'alarm_report_state': None,
'battery_level': -1,
'can_be_bypassed': 0,
'chime_state': None,
'description': 'Master Bedroom Keypad',
'device_type': None,
'loop_number': None,
'partition': 1,
'response_type': None,
'sensor_serial_number': None,
'signal_strength': -1,
'status': 0,
'supervision_type': None,
'zone_id': 800,
'zone_type_id': 50,
}),
dict({
'alarm_report_state': None,
'battery_level': -1,
'can_be_bypassed': 0,
'chime_state': None,
'description': 'Zone 995 Fire',
'device_type': None,
'loop_number': None,
'partition': 1,
'response_type': None,
'sensor_serial_number': None,
'signal_strength': -1,
'status': 0,
'supervision_type': None,
'zone_id': 1995,
'zone_type_id': 9,
}),
dict({
'alarm_report_state': None,
'battery_level': -1,
'can_be_bypassed': 0,
'chime_state': None,
'description': 'Zone 996 Medical',
'device_type': None,
'loop_number': None,
'partition': 1,
'response_type': None,
'sensor_serial_number': None,
'signal_strength': -1,
'status': 0,
'supervision_type': None,
'zone_id': 1996,
'zone_type_id': 15,
}),
dict({
'alarm_report_state': None,
'battery_level': -1,
'can_be_bypassed': 0,
'chime_state': None,
'description': 'Zone 998 Other',
'device_type': None,
'loop_number': None,
'partition': 1,
'response_type': None,
'sensor_serial_number': None,
'signal_strength': -1,
'status': 0,
'supervision_type': None,
'zone_id': 1998,
'zone_type_id': 6,
}),
dict({
'alarm_report_state': None,
'battery_level': -1,
'can_be_bypassed': 0,
'chime_state': None,
'description': 'Zone 999 Police',
'device_type': None,
'loop_number': None,
'partition': 1,
'response_type': None,
'sensor_serial_number': None,
'signal_strength': -1,
'status': 0,
'supervision_type': None,
'zone_id': 1999,
'zone_type_id': 7,
}),
]),
}),
]),
'user': dict({
'config_admin': True,
'features': dict({
'can_bypass_zones': True,
'can_clear_bypass': True,
'can_set_usercodes': True,
}),
'master': True,
'security_problem': False,
'user_admin': True,
}),
})
# ---

View File

@@ -1,19 +1,16 @@
"""Tests for the TotalConnect alarm control panel device."""
from datetime import timedelta
from unittest.mock import patch
from unittest.mock import AsyncMock, patch
from freezegun.api import FrozenDateTimeFactory
import pytest
from syrupy.assertion import SnapshotAssertion
from total_connect_client.exceptions import (
AuthenticationError,
ServiceUnavailable,
TotalConnectError,
)
from total_connect_client import ArmingState, ArmType
from total_connect_client.exceptions import BadResultCodeError, UsercodeInvalid
from homeassistant.components.alarm_control_panel import (
DOMAIN as ALARM_DOMAIN,
DOMAIN as ALARM_CONTROL_PANEL_DOMAIN,
AlarmControlPanelState,
)
from homeassistant.components.totalconnect.alarm_control_panel import (
@@ -21,593 +18,375 @@ from homeassistant.components.totalconnect.alarm_control_panel import (
SERVICE_ALARM_ARM_HOME_INSTANT,
)
from homeassistant.components.totalconnect.const import DOMAIN
from homeassistant.components.totalconnect.coordinator import SCAN_INTERVAL
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState
from homeassistant.const import (
ATTR_CODE,
ATTR_ENTITY_ID,
SERVICE_ALARM_ARM_AWAY,
SERVICE_ALARM_ARM_HOME,
SERVICE_ALARM_ARM_NIGHT,
SERVICE_ALARM_DISARM,
STATE_UNAVAILABLE,
Platform,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.entity_component import async_update_entity
from .common import (
LOCATION_ID,
RESPONSE_ARM_FAILURE,
RESPONSE_ARM_SUCCESS,
RESPONSE_ARMED_AWAY,
RESPONSE_ARMED_CUSTOM,
RESPONSE_ARMED_NIGHT,
RESPONSE_ARMED_STAY,
RESPONSE_ARMING,
RESPONSE_DISARM_FAILURE,
RESPONSE_DISARM_SUCCESS,
RESPONSE_DISARMED,
RESPONSE_DISARMING,
RESPONSE_SUCCESS,
RESPONSE_UNKNOWN,
RESPONSE_USER_CODE_INVALID,
TOTALCONNECT_REQUEST,
USERCODES,
setup_platform,
)
from . import setup_integration
from .const import CODE
from tests.common import async_fire_time_changed, snapshot_platform
from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform
ENTITY_ID = "alarm_control_panel.test"
ENTITY_ID_2 = "alarm_control_panel.test_partition_2"
CODE = "-1"
DATA = {ATTR_ENTITY_ID: ENTITY_ID}
DELAY = timedelta(seconds=10)
ARMING_HELPER = "homeassistant.components.totalconnect.alarm_control_panel.ArmingHelper"
async def test_attributes(
hass: HomeAssistant, entity_registry: er.EntityRegistry, snapshot: SnapshotAssertion
async def test_entities(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
mock_client: AsyncMock,
mock_config_entry: MockConfigEntry,
snapshot: SnapshotAssertion,
) -> None:
"""Test the alarm control panel attributes are correct."""
entry = await setup_platform(hass, ALARM_DOMAIN)
with patch(
"homeassistant.components.totalconnect.TotalConnectClient.request",
return_value=RESPONSE_DISARMED,
) as mock_request:
await async_update_entity(hass, ENTITY_ID)
await hass.async_block_till_done()
mock_request.assert_called_once()
"homeassistant.components.totalconnect.PLATFORMS",
[Platform.ALARM_CONTROL_PANEL],
):
await setup_integration(hass, mock_config_entry)
await snapshot_platform(hass, entity_registry, snapshot, entry.entry_id)
assert mock_request.call_count == 1
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
async def test_arm_home_success(
hass: HomeAssistant, freezer: FrozenDateTimeFactory
@pytest.mark.parametrize("code_required", [False, True])
@pytest.mark.parametrize(
("service", "arm_type"),
[
(SERVICE_ALARM_ARM_HOME, ArmType.STAY),
(SERVICE_ALARM_ARM_NIGHT, ArmType.STAY_NIGHT),
(SERVICE_ALARM_ARM_AWAY, ArmType.AWAY),
],
)
async def test_arming(
hass: HomeAssistant,
mock_client: AsyncMock,
mock_partition: AsyncMock,
mock_config_entry: MockConfigEntry,
service: str,
arm_type: ArmType,
) -> None:
"""Test arm home method success."""
responses = [RESPONSE_DISARMED, RESPONSE_ARM_SUCCESS, RESPONSE_ARMED_STAY]
await setup_platform(hass, ALARM_DOMAIN)
with patch(TOTALCONNECT_REQUEST, side_effect=responses) as mock_request:
await async_update_entity(hass, ENTITY_ID)
await hass.async_block_till_done()
assert hass.states.get(ENTITY_ID).state == AlarmControlPanelState.DISARMED
assert hass.states.get(ENTITY_ID_2).state == AlarmControlPanelState.DISARMED
assert mock_request.call_count == 1
"""Test arming method success."""
await setup_integration(hass, mock_config_entry)
await hass.services.async_call(
ALARM_DOMAIN, SERVICE_ALARM_ARM_HOME, DATA, blocking=True
)
assert mock_request.call_count == 2
entity_id = "alarm_control_panel.test"
assert hass.states.get(entity_id).state == AlarmControlPanelState.DISARMED
freezer.tick(DELAY)
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert mock_request.call_count == 3
assert hass.states.get(ENTITY_ID).state == AlarmControlPanelState.ARMED_HOME
# second partition should not be armed
assert hass.states.get(ENTITY_ID_2).state == AlarmControlPanelState.DISARMED
mock_partition.arming_state = ArmingState.ARMING
await hass.services.async_call(
ALARM_CONTROL_PANEL_DOMAIN,
service,
{ATTR_ENTITY_ID: entity_id, ATTR_CODE: CODE},
blocking=True,
)
assert mock_partition.arm.call_args[1] == {"arm_type": arm_type, "usercode": ""}
assert hass.states.get(entity_id).state == AlarmControlPanelState.ARMING
async def test_arm_home_failure(hass: HomeAssistant) -> None:
"""Test arm home method failure."""
responses = [RESPONSE_DISARMED, RESPONSE_ARM_FAILURE, RESPONSE_USER_CODE_INVALID]
await setup_platform(hass, ALARM_DOMAIN)
with patch(TOTALCONNECT_REQUEST, side_effect=responses) as mock_request:
await async_update_entity(hass, ENTITY_ID)
await hass.async_block_till_done()
assert hass.states.get(ENTITY_ID).state == AlarmControlPanelState.DISARMED
assert mock_request.call_count == 1
with pytest.raises(HomeAssistantError) as err:
await hass.services.async_call(
ALARM_DOMAIN, SERVICE_ALARM_ARM_HOME, DATA, blocking=True
)
await hass.async_block_till_done()
assert f"{err.value}" == "Failed to arm home test"
assert hass.states.get(ENTITY_ID).state == AlarmControlPanelState.DISARMED
assert mock_request.call_count == 2
# config entry usercode is invalid
with pytest.raises(HomeAssistantError) as err:
await hass.services.async_call(
ALARM_DOMAIN, SERVICE_ALARM_ARM_HOME, DATA, blocking=True
)
await hass.async_block_till_done()
assert f"{err.value}" == "Usercode is invalid, did not arm home"
assert hass.states.get(ENTITY_ID).state == AlarmControlPanelState.DISARMED
# should have started a re-auth flow
assert len(hass.config_entries.flow.async_progress_by_handler(DOMAIN)) == 1
assert mock_request.call_count == 3
async def test_arm_home_instant_success(
hass: HomeAssistant, freezer: FrozenDateTimeFactory
@pytest.mark.parametrize("code_required", [True])
@pytest.mark.parametrize(
("service", "arm_type"),
[
(SERVICE_ALARM_ARM_HOME, ArmType.STAY),
(SERVICE_ALARM_ARM_NIGHT, ArmType.STAY_NIGHT),
(SERVICE_ALARM_ARM_AWAY, ArmType.AWAY),
],
)
async def test_arming_invalid_usercode(
hass: HomeAssistant,
mock_client: AsyncMock,
mock_partition: AsyncMock,
mock_location: AsyncMock,
mock_config_entry: MockConfigEntry,
service: str,
arm_type: ArmType,
) -> None:
"""Test arm home instant method success."""
responses = [RESPONSE_DISARMED, RESPONSE_ARM_SUCCESS, RESPONSE_ARMED_STAY]
await setup_platform(hass, ALARM_DOMAIN)
with patch(TOTALCONNECT_REQUEST, side_effect=responses) as mock_request:
await async_update_entity(hass, ENTITY_ID)
await hass.async_block_till_done()
assert hass.states.get(ENTITY_ID).state == AlarmControlPanelState.DISARMED
assert hass.states.get(ENTITY_ID_2).state == AlarmControlPanelState.DISARMED
assert mock_request.call_count == 1
"""Test arming method with invalid usercode."""
await setup_integration(hass, mock_config_entry)
entity_id = "alarm_control_panel.test"
assert hass.states.get(entity_id).state == AlarmControlPanelState.DISARMED
assert mock_location.get_panel_meta_data.call_count == 1
mock_partition.arming_state = ArmingState.ARMING
with pytest.raises(ServiceValidationError, match="Incorrect code entered"):
await hass.services.async_call(
DOMAIN, SERVICE_ALARM_ARM_HOME_INSTANT, DATA, blocking=True
ALARM_CONTROL_PANEL_DOMAIN,
service,
{ATTR_ENTITY_ID: entity_id, ATTR_CODE: "invalid_code"},
blocking=True,
)
assert mock_request.call_count == 2
freezer.tick(DELAY)
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert mock_request.call_count == 3
assert hass.states.get(ENTITY_ID).state == AlarmControlPanelState.ARMED_HOME
assert mock_partition.arm.call_count == 0
assert mock_location.get_panel_meta_data.call_count == 1
async def test_arm_home_instant_failure(hass: HomeAssistant) -> None:
"""Test arm home instant method failure."""
responses = [RESPONSE_DISARMED, RESPONSE_ARM_FAILURE, RESPONSE_USER_CODE_INVALID]
await setup_platform(hass, ALARM_DOMAIN)
with patch(TOTALCONNECT_REQUEST, side_effect=responses) as mock_request:
await async_update_entity(hass, ENTITY_ID)
await hass.async_block_till_done()
assert hass.states.get(ENTITY_ID).state == AlarmControlPanelState.DISARMED
assert mock_request.call_count == 1
with pytest.raises(HomeAssistantError) as err:
await hass.services.async_call(
DOMAIN, SERVICE_ALARM_ARM_HOME_INSTANT, DATA, blocking=True
)
await hass.async_block_till_done()
assert f"{err.value}" == "Failed to arm home instant test"
assert hass.states.get(ENTITY_ID).state == AlarmControlPanelState.DISARMED
assert mock_request.call_count == 2
# usercode is invalid
with pytest.raises(HomeAssistantError) as err:
await hass.services.async_call(
DOMAIN, SERVICE_ALARM_ARM_HOME_INSTANT, DATA, blocking=True
)
await hass.async_block_till_done()
assert str(err.value) == "Usercode is invalid, did not arm home instant"
assert hass.states.get(ENTITY_ID).state == AlarmControlPanelState.DISARMED
# should have started a re-auth flow
assert len(hass.config_entries.flow.async_progress_by_handler(DOMAIN)) == 1
assert mock_request.call_count == 3
async def test_arm_away_instant_success(
hass: HomeAssistant, freezer: FrozenDateTimeFactory
@pytest.mark.parametrize("code_required", [False, True])
async def test_disarming(
hass: HomeAssistant,
mock_client: AsyncMock,
mock_partition: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test arm home instant method success."""
responses = [RESPONSE_DISARMED, RESPONSE_ARM_SUCCESS, RESPONSE_ARMED_AWAY]
await setup_platform(hass, ALARM_DOMAIN)
with patch(TOTALCONNECT_REQUEST, side_effect=responses) as mock_request:
await async_update_entity(hass, ENTITY_ID)
await hass.async_block_till_done()
assert hass.states.get(ENTITY_ID).state == AlarmControlPanelState.DISARMED
assert hass.states.get(ENTITY_ID_2).state == AlarmControlPanelState.DISARMED
assert mock_request.call_count == 1
"""Test disarming method success."""
await setup_integration(hass, mock_config_entry)
await hass.services.async_call(
DOMAIN, SERVICE_ALARM_ARM_AWAY_INSTANT, DATA, blocking=True
)
assert mock_request.call_count == 2
entity_id = "alarm_control_panel.test"
assert hass.states.get(entity_id).state == AlarmControlPanelState.DISARMED
freezer.tick(DELAY)
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert mock_request.call_count == 3
assert hass.states.get(ENTITY_ID).state == AlarmControlPanelState.ARMED_AWAY
mock_partition.arming_state = ArmingState.ARMING
await hass.services.async_call(
ALARM_CONTROL_PANEL_DOMAIN,
SERVICE_ALARM_DISARM,
{ATTR_ENTITY_ID: entity_id, ATTR_CODE: CODE},
blocking=True,
)
assert mock_partition.disarm.call_args[1] == {"usercode": ""}
assert hass.states.get(entity_id).state == AlarmControlPanelState.ARMING
async def test_arm_away_instant_failure(hass: HomeAssistant) -> None:
"""Test arm home instant method failure."""
responses = [RESPONSE_DISARMED, RESPONSE_ARM_FAILURE, RESPONSE_USER_CODE_INVALID]
await setup_platform(hass, ALARM_DOMAIN)
with patch(TOTALCONNECT_REQUEST, side_effect=responses) as mock_request:
await async_update_entity(hass, ENTITY_ID)
await hass.async_block_till_done()
assert hass.states.get(ENTITY_ID).state == AlarmControlPanelState.DISARMED
assert mock_request.call_count == 1
with pytest.raises(HomeAssistantError) as err:
await hass.services.async_call(
DOMAIN, SERVICE_ALARM_ARM_AWAY_INSTANT, DATA, blocking=True
)
await hass.async_block_till_done()
assert f"{err.value}" == "Failed to arm away instant test"
assert hass.states.get(ENTITY_ID).state == AlarmControlPanelState.DISARMED
assert mock_request.call_count == 2
# usercode is invalid
with pytest.raises(HomeAssistantError) as err:
await hass.services.async_call(
DOMAIN, SERVICE_ALARM_ARM_AWAY_INSTANT, DATA, blocking=True
)
await hass.async_block_till_done()
assert f"{err.value}" == "Usercode is invalid, did not arm away instant"
assert hass.states.get(ENTITY_ID).state == AlarmControlPanelState.DISARMED
# should have started a re-auth flow
assert len(hass.config_entries.flow.async_progress_by_handler(DOMAIN)) == 1
assert mock_request.call_count == 3
async def test_arm_away_success(
hass: HomeAssistant, freezer: FrozenDateTimeFactory
@pytest.mark.parametrize("code_required", [True])
async def test_disarming_invalid_usercode(
hass: HomeAssistant,
mock_client: AsyncMock,
mock_partition: AsyncMock,
mock_location: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test arm away method success."""
responses = [RESPONSE_DISARMED, RESPONSE_ARM_SUCCESS, RESPONSE_ARMED_AWAY]
await setup_platform(hass, ALARM_DOMAIN)
with patch(TOTALCONNECT_REQUEST, side_effect=responses) as mock_request:
await async_update_entity(hass, ENTITY_ID)
await hass.async_block_till_done()
assert hass.states.get(ENTITY_ID).state == AlarmControlPanelState.DISARMED
assert mock_request.call_count == 1
"""Test disarming method with invalid usercode."""
await setup_integration(hass, mock_config_entry)
entity_id = "alarm_control_panel.test"
assert hass.states.get(entity_id).state == AlarmControlPanelState.DISARMED
assert mock_location.get_panel_meta_data.call_count == 1
mock_partition.arming_state = ArmingState.ARMING
with pytest.raises(ServiceValidationError, match="Incorrect code entered"):
await hass.services.async_call(
ALARM_DOMAIN, SERVICE_ALARM_ARM_AWAY, DATA, blocking=True
ALARM_CONTROL_PANEL_DOMAIN,
SERVICE_ALARM_DISARM,
{ATTR_ENTITY_ID: entity_id, ATTR_CODE: "invalid_code"},
blocking=True,
)
assert mock_request.call_count == 2
freezer.tick(DELAY)
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert mock_request.call_count == 3
assert hass.states.get(ENTITY_ID).state == AlarmControlPanelState.ARMED_AWAY
assert mock_partition.disarm.call_count == 0
assert mock_location.get_panel_meta_data.call_count == 1
async def test_arm_away_failure(hass: HomeAssistant) -> None:
"""Test arm away method failure."""
responses = [RESPONSE_DISARMED, RESPONSE_ARM_FAILURE, RESPONSE_USER_CODE_INVALID]
await setup_platform(hass, ALARM_DOMAIN)
with patch(TOTALCONNECT_REQUEST, side_effect=responses) as mock_request:
await async_update_entity(hass, ENTITY_ID)
await hass.async_block_till_done()
assert hass.states.get(ENTITY_ID).state == AlarmControlPanelState.DISARMED
assert mock_request.call_count == 1
with pytest.raises(HomeAssistantError) as err:
await hass.services.async_call(
ALARM_DOMAIN, SERVICE_ALARM_ARM_AWAY, DATA, blocking=True
)
await hass.async_block_till_done()
assert f"{err.value}" == "Failed to arm away test"
assert hass.states.get(ENTITY_ID).state == AlarmControlPanelState.DISARMED
assert mock_request.call_count == 2
# usercode is invalid
with pytest.raises(HomeAssistantError) as err:
await hass.services.async_call(
ALARM_DOMAIN, SERVICE_ALARM_ARM_AWAY, DATA, blocking=True
)
await hass.async_block_till_done()
assert f"{err.value}" == "Usercode is invalid, did not arm away"
assert hass.states.get(ENTITY_ID).state == AlarmControlPanelState.DISARMED
# should have started a re-auth flow
assert len(hass.config_entries.flow.async_progress_by_handler(DOMAIN)) == 1
assert mock_request.call_count == 3
async def test_disarm_success(
hass: HomeAssistant, freezer: FrozenDateTimeFactory
@pytest.mark.parametrize(
("service", "arm_type"),
[
(SERVICE_ALARM_ARM_HOME_INSTANT, ArmType.STAY_INSTANT),
(SERVICE_ALARM_ARM_AWAY_INSTANT, ArmType.AWAY_INSTANT),
],
)
async def test_instant_arming(
hass: HomeAssistant,
mock_client: AsyncMock,
mock_partition: AsyncMock,
mock_config_entry: MockConfigEntry,
service: str,
arm_type: ArmType,
) -> None:
"""Test disarm method success."""
responses = [RESPONSE_ARMED_AWAY, RESPONSE_DISARM_SUCCESS, RESPONSE_DISARMED]
await setup_platform(hass, ALARM_DOMAIN)
with patch(TOTALCONNECT_REQUEST, side_effect=responses) as mock_request:
await async_update_entity(hass, ENTITY_ID)
await hass.async_block_till_done()
assert hass.states.get(ENTITY_ID).state == AlarmControlPanelState.ARMED_AWAY
assert mock_request.call_count == 1
"""Test instant arming method success."""
await setup_integration(hass, mock_config_entry)
await hass.services.async_call(
ALARM_DOMAIN, SERVICE_ALARM_DISARM, DATA, blocking=True
)
assert mock_request.call_count == 2
entity_id = "alarm_control_panel.test"
assert hass.states.get(entity_id).state == AlarmControlPanelState.DISARMED
freezer.tick(DELAY)
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert mock_request.call_count == 3
assert hass.states.get(ENTITY_ID).state == AlarmControlPanelState.DISARMED
mock_partition.arming_state = ArmingState.ARMING
await hass.services.async_call(
DOMAIN,
service,
{ATTR_ENTITY_ID: entity_id},
blocking=True,
)
assert mock_partition.arm.call_args[1] == {"arm_type": arm_type, "usercode": ""}
assert hass.states.get(entity_id).state == AlarmControlPanelState.ARMING
async def test_disarm_failure(hass: HomeAssistant) -> None:
"""Test disarm method failure."""
responses = [
RESPONSE_ARMED_AWAY,
RESPONSE_DISARM_FAILURE,
RESPONSE_USER_CODE_INVALID,
]
await setup_platform(hass, ALARM_DOMAIN)
with patch(TOTALCONNECT_REQUEST, side_effect=responses) as mock_request:
await async_update_entity(hass, ENTITY_ID)
await hass.async_block_till_done()
assert hass.states.get(ENTITY_ID).state == AlarmControlPanelState.ARMED_AWAY
assert mock_request.call_count == 1
with pytest.raises(HomeAssistantError) as err:
await hass.services.async_call(
ALARM_DOMAIN, SERVICE_ALARM_DISARM, DATA, blocking=True
)
await hass.async_block_till_done()
assert f"{err.value}" == "Failed to disarm test"
assert hass.states.get(ENTITY_ID).state == AlarmControlPanelState.ARMED_AWAY
assert mock_request.call_count == 2
# usercode is invalid
with pytest.raises(HomeAssistantError) as err:
await hass.services.async_call(
ALARM_DOMAIN, SERVICE_ALARM_DISARM, DATA, blocking=True
)
await hass.async_block_till_done()
assert f"{err.value}" == "Usercode is invalid, did not disarm"
assert hass.states.get(ENTITY_ID).state == AlarmControlPanelState.ARMED_AWAY
# should have started a re-auth flow
assert len(hass.config_entries.flow.async_progress_by_handler(DOMAIN)) == 1
assert mock_request.call_count == 3
async def test_disarm_code_required(
hass: HomeAssistant, freezer: FrozenDateTimeFactory
@pytest.mark.parametrize(
("exception", "suffix", "flows"),
[(UsercodeInvalid, "invalid_code", 1), (BadResultCodeError, "failed", 0)],
)
@pytest.mark.parametrize(
("service", "prefix"),
[
(SERVICE_ALARM_ARM_HOME, "arm_home"),
(SERVICE_ALARM_ARM_NIGHT, "arm_night"),
(SERVICE_ALARM_ARM_AWAY, "arm_away"),
],
)
async def test_arming_exceptions(
hass: HomeAssistant,
mock_client: AsyncMock,
mock_partition: AsyncMock,
mock_location: AsyncMock,
mock_config_entry: MockConfigEntry,
service: str,
prefix: str,
exception: Exception,
suffix: str,
flows: int,
) -> None:
"""Test disarm with code."""
responses = [RESPONSE_ARMED_AWAY, RESPONSE_DISARM_SUCCESS, RESPONSE_DISARMED]
await setup_platform(hass, ALARM_DOMAIN, code_required=True)
with patch(TOTALCONNECT_REQUEST, side_effect=responses) as mock_request:
await async_update_entity(hass, ENTITY_ID)
await hass.async_block_till_done()
assert hass.states.get(ENTITY_ID).state == AlarmControlPanelState.ARMED_AWAY
assert mock_request.call_count == 1
"""Test arming method exceptions."""
await setup_integration(hass, mock_config_entry)
# runtime user entered code is bad
DATA_WITH_CODE = DATA.copy()
DATA_WITH_CODE["code"] = "666"
with pytest.raises(ServiceValidationError, match="Incorrect code entered"):
await hass.services.async_call(
ALARM_DOMAIN, SERVICE_ALARM_DISARM, DATA_WITH_CODE, blocking=True
)
assert hass.states.get(ENTITY_ID).state == AlarmControlPanelState.ARMED_AWAY
# code check means the call to total_connect never happens
assert mock_request.call_count == 1
entity_id = "alarm_control_panel.test"
assert hass.states.get(entity_id).state == AlarmControlPanelState.DISARMED
assert mock_location.get_panel_meta_data.call_count == 1
# runtime user entered code that is in config
DATA_WITH_CODE["code"] = USERCODES[LOCATION_ID]
mock_partition.arm.side_effect = exception
mock_partition.arming_state = ArmingState.ARMING
with pytest.raises(HomeAssistantError) as exc:
await hass.services.async_call(
ALARM_DOMAIN, SERVICE_ALARM_DISARM, DATA_WITH_CODE, blocking=True
ALARM_CONTROL_PANEL_DOMAIN,
service,
{ATTR_ENTITY_ID: entity_id, ATTR_CODE: CODE},
blocking=True,
)
await hass.async_block_till_done()
assert mock_request.call_count == 2
assert mock_partition.arm.call_count == 1
freezer.tick(DELAY)
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert mock_request.call_count == 3
assert hass.states.get(ENTITY_ID).state == AlarmControlPanelState.DISARMED
assert exc.value.translation_key == f"{prefix}_{suffix}"
assert hass.states.get(entity_id).state == AlarmControlPanelState.DISARMED
assert mock_location.get_panel_meta_data.call_count == 1
assert len(hass.config_entries.flow.async_progress_by_handler(DOMAIN)) == flows
async def test_arm_night_success(
hass: HomeAssistant, freezer: FrozenDateTimeFactory
@pytest.mark.parametrize(
("exception", "suffix", "flows"),
[(UsercodeInvalid, "invalid_code", 1), (BadResultCodeError, "failed", 0)],
)
@pytest.mark.parametrize(
("service", "prefix"),
[
(SERVICE_ALARM_ARM_HOME_INSTANT, "arm_home_instant"),
(SERVICE_ALARM_ARM_AWAY_INSTANT, "arm_away_instant"),
],
)
async def test_instant_arming_exceptions(
hass: HomeAssistant,
mock_client: AsyncMock,
mock_partition: AsyncMock,
mock_location: AsyncMock,
mock_config_entry: MockConfigEntry,
service: str,
prefix: str,
exception: Exception,
suffix: str,
flows: int,
) -> None:
"""Test arm night method success."""
responses = [RESPONSE_DISARMED, RESPONSE_ARM_SUCCESS, RESPONSE_ARMED_NIGHT]
await setup_platform(hass, ALARM_DOMAIN)
with patch(TOTALCONNECT_REQUEST, side_effect=responses) as mock_request:
await async_update_entity(hass, ENTITY_ID)
await hass.async_block_till_done()
assert hass.states.get(ENTITY_ID).state == AlarmControlPanelState.DISARMED
assert mock_request.call_count == 1
"""Test arming method exceptions."""
await setup_integration(hass, mock_config_entry)
entity_id = "alarm_control_panel.test"
assert hass.states.get(entity_id).state == AlarmControlPanelState.DISARMED
assert mock_location.get_panel_meta_data.call_count == 1
mock_partition.arm.side_effect = exception
mock_partition.arming_state = ArmingState.ARMING
with pytest.raises(HomeAssistantError) as exc:
await hass.services.async_call(
ALARM_DOMAIN, SERVICE_ALARM_ARM_NIGHT, DATA, blocking=True
DOMAIN,
service,
{ATTR_ENTITY_ID: entity_id},
blocking=True,
)
assert mock_request.call_count == 2
assert mock_partition.arm.call_count == 1
freezer.tick(DELAY)
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert mock_request.call_count == 3
assert hass.states.get(ENTITY_ID).state == AlarmControlPanelState.ARMED_NIGHT
assert exc.value.translation_key == f"{prefix}_{suffix}"
assert hass.states.get(entity_id).state == AlarmControlPanelState.DISARMED
assert mock_location.get_panel_meta_data.call_count == 1
assert len(hass.config_entries.flow.async_progress_by_handler(DOMAIN)) == flows
async def test_arm_night_failure(hass: HomeAssistant) -> None:
"""Test arm night method failure."""
responses = [RESPONSE_DISARMED, RESPONSE_ARM_FAILURE, RESPONSE_USER_CODE_INVALID]
await setup_platform(hass, ALARM_DOMAIN)
with patch(TOTALCONNECT_REQUEST, side_effect=responses) as mock_request:
await async_update_entity(hass, ENTITY_ID)
await hass.async_block_till_done()
assert hass.states.get(ENTITY_ID).state == AlarmControlPanelState.DISARMED
assert mock_request.call_count == 1
with pytest.raises(HomeAssistantError) as err:
await hass.services.async_call(
ALARM_DOMAIN, SERVICE_ALARM_ARM_NIGHT, DATA, blocking=True
)
await hass.async_block_till_done()
assert f"{err.value}" == "Failed to arm night test"
assert hass.states.get(ENTITY_ID).state == AlarmControlPanelState.DISARMED
assert mock_request.call_count == 2
# usercode is invalid
with pytest.raises(HomeAssistantError) as err:
await hass.services.async_call(
ALARM_DOMAIN, SERVICE_ALARM_ARM_NIGHT, DATA, blocking=True
)
await hass.async_block_till_done()
assert f"{err.value}" == "Usercode is invalid, did not arm night"
assert hass.states.get(ENTITY_ID).state == AlarmControlPanelState.DISARMED
# should have started a re-auth flow
assert len(hass.config_entries.flow.async_progress_by_handler(DOMAIN)) == 1
assert mock_request.call_count == 3
async def test_arming(hass: HomeAssistant, freezer: FrozenDateTimeFactory) -> None:
"""Test arming."""
responses = [RESPONSE_DISARMED, RESPONSE_SUCCESS, RESPONSE_ARMING]
await setup_platform(hass, ALARM_DOMAIN)
with patch(TOTALCONNECT_REQUEST, side_effect=responses) as mock_request:
await async_update_entity(hass, ENTITY_ID)
await hass.async_block_till_done()
assert hass.states.get(ENTITY_ID).state == AlarmControlPanelState.DISARMED
assert mock_request.call_count == 1
await hass.services.async_call(
ALARM_DOMAIN, SERVICE_ALARM_ARM_NIGHT, DATA, blocking=True
)
assert mock_request.call_count == 2
freezer.tick(DELAY)
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert mock_request.call_count == 3
assert hass.states.get(ENTITY_ID).state == AlarmControlPanelState.ARMING
async def test_disarming(hass: HomeAssistant, freezer: FrozenDateTimeFactory) -> None:
"""Test disarming."""
responses = [RESPONSE_ARMED_AWAY, RESPONSE_SUCCESS, RESPONSE_DISARMING]
await setup_platform(hass, ALARM_DOMAIN)
with patch(TOTALCONNECT_REQUEST, side_effect=responses) as mock_request:
await async_update_entity(hass, ENTITY_ID)
await hass.async_block_till_done()
assert hass.states.get(ENTITY_ID).state == AlarmControlPanelState.ARMED_AWAY
assert mock_request.call_count == 1
await hass.services.async_call(
ALARM_DOMAIN, SERVICE_ALARM_DISARM, DATA, blocking=True
)
assert mock_request.call_count == 2
freezer.tick(DELAY)
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert mock_request.call_count == 3
assert hass.states.get(ENTITY_ID).state == AlarmControlPanelState.DISARMING
async def test_armed_custom(hass: HomeAssistant) -> None:
"""Test armed custom."""
responses = [RESPONSE_ARMED_CUSTOM]
await setup_platform(hass, ALARM_DOMAIN)
with patch(TOTALCONNECT_REQUEST, side_effect=responses) as mock_request:
await async_update_entity(hass, ENTITY_ID)
await hass.async_block_till_done()
assert (
hass.states.get(ENTITY_ID).state
== AlarmControlPanelState.ARMED_CUSTOM_BYPASS
)
assert mock_request.call_count == 1
async def test_unknown(hass: HomeAssistant) -> None:
"""Test unknown arm status."""
responses = [RESPONSE_UNKNOWN]
await setup_platform(hass, ALARM_DOMAIN)
with patch(TOTALCONNECT_REQUEST, side_effect=responses) as mock_request:
await async_update_entity(hass, ENTITY_ID)
await hass.async_block_till_done()
assert hass.states.get(ENTITY_ID).state == STATE_UNAVAILABLE
assert mock_request.call_count == 1
async def test_other_update_failures(
hass: HomeAssistant, freezer: FrozenDateTimeFactory
@pytest.mark.parametrize(
("arming_state", "state"),
[
(ArmingState.DISARMED, AlarmControlPanelState.DISARMED),
(ArmingState.DISARMED_BYPASS, AlarmControlPanelState.DISARMED),
(ArmingState.DISARMED_ZONE_FAULTED, AlarmControlPanelState.DISARMED),
(ArmingState.ARMED_STAY_NIGHT, AlarmControlPanelState.ARMED_NIGHT),
(ArmingState.ARMED_STAY_NIGHT_BYPASS_PROA7, AlarmControlPanelState.ARMED_NIGHT),
(
ArmingState.ARMED_STAY_NIGHT_INSTANT_PROA7,
AlarmControlPanelState.ARMED_NIGHT,
),
(
ArmingState.ARMED_STAY_NIGHT_INSTANT_BYPASS_PROA7,
AlarmControlPanelState.ARMED_NIGHT,
),
(ArmingState.ARMED_STAY, AlarmControlPanelState.ARMED_HOME),
(ArmingState.ARMED_STAY_PROA7, AlarmControlPanelState.ARMED_HOME),
(ArmingState.ARMED_STAY_BYPASS, AlarmControlPanelState.ARMED_HOME),
(ArmingState.ARMED_STAY_BYPASS_PROA7, AlarmControlPanelState.ARMED_HOME),
(ArmingState.ARMED_STAY_INSTANT, AlarmControlPanelState.ARMED_HOME),
(ArmingState.ARMED_STAY_INSTANT_PROA7, AlarmControlPanelState.ARMED_HOME),
(ArmingState.ARMED_STAY_INSTANT_BYPASS, AlarmControlPanelState.ARMED_HOME),
(
ArmingState.ARMED_STAY_INSTANT_BYPASS_PROA7,
AlarmControlPanelState.ARMED_HOME,
),
(ArmingState.ARMED_STAY_OTHER, AlarmControlPanelState.ARMED_HOME),
(ArmingState.ARMED_AWAY, AlarmControlPanelState.ARMED_AWAY),
(ArmingState.ARMED_AWAY_BYPASS, AlarmControlPanelState.ARMED_AWAY),
(ArmingState.ARMED_AWAY_INSTANT, AlarmControlPanelState.ARMED_AWAY),
(ArmingState.ARMED_AWAY_INSTANT_BYPASS, AlarmControlPanelState.ARMED_AWAY),
(ArmingState.ARMED_CUSTOM_BYPASS, AlarmControlPanelState.ARMED_CUSTOM_BYPASS),
(ArmingState.ARMING, AlarmControlPanelState.ARMING),
(ArmingState.DISARMING, AlarmControlPanelState.DISARMING),
(ArmingState.ALARMING, AlarmControlPanelState.TRIGGERED),
(ArmingState.ALARMING_FIRE_SMOKE, AlarmControlPanelState.TRIGGERED),
(ArmingState.ALARMING_CARBON_MONOXIDE, AlarmControlPanelState.TRIGGERED),
(ArmingState.ALARMING_CARBON_MONOXIDE_PROA7, AlarmControlPanelState.TRIGGERED),
],
)
async def test_arming_state(
hass: HomeAssistant,
mock_client: AsyncMock,
mock_partition: AsyncMock,
mock_location: AsyncMock,
mock_config_entry: MockConfigEntry,
arming_state: ArmingState,
state: AlarmControlPanelState,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test other failures seen during updates."""
responses = [
RESPONSE_DISARMED,
ServiceUnavailable,
RESPONSE_DISARMED,
TotalConnectError,
RESPONSE_DISARMED,
ValueError,
]
await setup_platform(hass, ALARM_DOMAIN)
with patch(TOTALCONNECT_REQUEST, side_effect=responses) as mock_request:
# first things work as planned
await async_update_entity(hass, ENTITY_ID)
await hass.async_block_till_done()
assert hass.states.get(ENTITY_ID).state == AlarmControlPanelState.DISARMED
assert mock_request.call_count == 1
"""Test arming state transitions."""
await setup_integration(hass, mock_config_entry)
# then an error: ServiceUnavailable --> UpdateFailed
freezer.tick(SCAN_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done(wait_background_tasks=True)
assert hass.states.get(ENTITY_ID).state == STATE_UNAVAILABLE
assert mock_request.call_count == 2
entity_id = "alarm_control_panel.test"
assert hass.states.get(entity_id).state == AlarmControlPanelState.DISARMED
# works again
freezer.tick(SCAN_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done(wait_background_tasks=True)
assert hass.states.get(ENTITY_ID).state == AlarmControlPanelState.DISARMED
assert mock_request.call_count == 3
mock_partition.arming_state = arming_state
# then an error: TotalConnectError --> UpdateFailed
freezer.tick(SCAN_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done(wait_background_tasks=True)
assert hass.states.get(ENTITY_ID).state == STATE_UNAVAILABLE
assert mock_request.call_count == 4
freezer.tick(timedelta(seconds=30))
async_fire_time_changed(hass)
await hass.async_block_till_done(wait_background_tasks=True)
# works again
freezer.tick(SCAN_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done(wait_background_tasks=True)
assert hass.states.get(ENTITY_ID).state == AlarmControlPanelState.DISARMED
assert mock_request.call_count == 5
# unknown TotalConnect status via ValueError
freezer.tick(SCAN_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done(wait_background_tasks=True)
assert hass.states.get(ENTITY_ID).state == STATE_UNAVAILABLE
assert mock_request.call_count == 6
async def test_authentication_error(hass: HomeAssistant) -> None:
"""Test other failures seen during updates."""
entry = await setup_platform(hass, ALARM_DOMAIN)
with patch(TOTALCONNECT_REQUEST, side_effect=AuthenticationError):
await async_update_entity(hass, ENTITY_ID)
await hass.async_block_till_done()
assert entry.state is ConfigEntryState.LOADED
flows = hass.config_entries.flow.async_progress()
assert len(flows) == 1
flow = flows[0]
assert flow.get("step_id") == "reauth_confirm"
assert flow.get("handler") == DOMAIN
assert "context" in flow
assert flow["context"].get("source") == SOURCE_REAUTH
assert flow["context"].get("entry_id") == entry.entry_id
assert hass.states.get(entity_id).state == state

View File

@@ -1,91 +1,29 @@
"""Tests for the TotalConnect binary sensor."""
from unittest.mock import patch
from unittest.mock import AsyncMock, patch
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.binary_sensor import (
DOMAIN as BINARY_SENSOR,
BinarySensorDeviceClass,
)
from homeassistant.const import ATTR_FRIENDLY_NAME, STATE_OFF, STATE_ON
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from .common import RESPONSE_DISARMED, ZONE_NORMAL, setup_platform
from . import setup_integration
from tests.common import snapshot_platform
ZONE_ENTITY_ID = "binary_sensor.security"
ZONE_LOW_BATTERY_ID = "binary_sensor.security_battery"
ZONE_TAMPER_ID = "binary_sensor.security_tamper"
PANEL_BATTERY_ID = "binary_sensor.test_battery"
PANEL_TAMPER_ID = "binary_sensor.test_tamper"
PANEL_POWER_ID = "binary_sensor.test_power"
from tests.common import MockConfigEntry, snapshot_platform
async def test_entity_registry(
hass: HomeAssistant, entity_registry: er.EntityRegistry, snapshot: SnapshotAssertion
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
mock_client: AsyncMock,
mock_config_entry: MockConfigEntry,
snapshot: SnapshotAssertion,
) -> None:
"""Test the binary sensor is registered in entity registry."""
entry = await setup_platform(hass, BINARY_SENSOR)
await snapshot_platform(hass, entity_registry, snapshot, entry.entry_id)
async def test_state_and_attributes(hass: HomeAssistant) -> None:
"""Test the binary sensor attributes are correct."""
"""Test the alarm control panel attributes are correct."""
with patch(
"homeassistant.components.totalconnect.TotalConnectClient.request",
return_value=RESPONSE_DISARMED,
"homeassistant.components.totalconnect.PLATFORMS", [Platform.BINARY_SENSOR]
):
await setup_platform(hass, BINARY_SENSOR)
await setup_integration(hass, mock_config_entry)
state = hass.states.get(ZONE_ENTITY_ID)
assert state.state == STATE_ON
assert (
state.attributes.get(ATTR_FRIENDLY_NAME) == ZONE_NORMAL["ZoneDescription"]
)
assert state.attributes.get("device_class") == BinarySensorDeviceClass.DOOR
state = hass.states.get(f"{ZONE_ENTITY_ID}_battery")
assert state.state == STATE_OFF
state = hass.states.get(f"{ZONE_ENTITY_ID}_tamper")
assert state.state == STATE_OFF
# Zone 2 is fire with low battery
state = hass.states.get("binary_sensor.fire")
assert state.state == STATE_OFF
assert state.attributes.get("device_class") == BinarySensorDeviceClass.SMOKE
state = hass.states.get("binary_sensor.fire_battery")
assert state.state == STATE_ON
state = hass.states.get("binary_sensor.fire_tamper")
assert state.state == STATE_OFF
# Zone 3 is gas with tamper
state = hass.states.get("binary_sensor.gas")
assert state.state == STATE_OFF
assert state.attributes.get("device_class") == BinarySensorDeviceClass.GAS
state = hass.states.get("binary_sensor.gas_battery")
assert state.state == STATE_OFF
state = hass.states.get("binary_sensor.gas_tamper")
assert state.state == STATE_ON
# Zone 6 is unknown type, assume it is a security (door) sensor
state = hass.states.get("binary_sensor.unknown")
assert state.state == STATE_OFF
assert state.attributes.get("device_class") == BinarySensorDeviceClass.DOOR
state = hass.states.get("binary_sensor.unknown_battery")
assert state.state == STATE_OFF
state = hass.states.get("binary_sensor.unknown_tamper")
assert state.state == STATE_OFF
# Zone 7 is temperature
state = hass.states.get("binary_sensor.temperature")
assert state.state == STATE_OFF
assert state.attributes.get("device_class") == BinarySensorDeviceClass.PROBLEM
state = hass.states.get("binary_sensor.temperature_battery")
assert state.state == STATE_OFF
state = hass.states.get("binary_sensor.temperature_tamper")
assert state.state == STATE_OFF
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)

View File

@@ -1,84 +1,64 @@
"""Tests for the TotalConnect buttons."""
from unittest.mock import patch
from unittest.mock import AsyncMock, patch
import pytest
from syrupy.assertion import SnapshotAssertion
from total_connect_client.exceptions import FailedToBypassZone
from homeassistant.components.button import DOMAIN as BUTTON, SERVICE_PRESS
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN, SERVICE_PRESS
from homeassistant.const import ATTR_ENTITY_ID, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from .common import setup_platform
from . import setup_integration
from tests.common import snapshot_platform
ZONE_BYPASS_ID = "button.security_bypass"
PANEL_CLEAR_ID = "button.test_clear_bypass"
PANEL_BYPASS_ID = "button.test_bypass_all"
from tests.common import MockConfigEntry, snapshot_platform
async def test_entity_registry(
hass: HomeAssistant, entity_registry: er.EntityRegistry, snapshot: SnapshotAssertion
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
mock_client: AsyncMock,
mock_config_entry: MockConfigEntry,
snapshot: SnapshotAssertion,
) -> None:
"""Test the button is registered in entity registry."""
entry = await setup_platform(hass, BUTTON)
with patch("homeassistant.components.totalconnect.PLATFORMS", [Platform.BUTTON]):
await setup_integration(hass, mock_config_entry)
await snapshot_platform(hass, entity_registry, snapshot, entry.entry_id)
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
@pytest.mark.parametrize(
("entity_id", "tcc_request"),
[
(ZONE_BYPASS_ID, "total_connect_client.zone.TotalConnectZone.bypass"),
(
PANEL_BYPASS_ID,
"total_connect_client.location.TotalConnectLocation.zone_bypass_all",
),
],
)
async def test_bypass_button(
hass: HomeAssistant, entity_id: str, tcc_request: str
hass: HomeAssistant,
mock_client: AsyncMock,
mock_config_entry: MockConfigEntry,
mock_location: AsyncMock,
) -> None:
"""Test pushing a bypass button."""
responses = [FailedToBypassZone, None]
await setup_platform(hass, BUTTON)
with patch(tcc_request, side_effect=responses) as mock_request:
# try to bypass, but fails
with pytest.raises(FailedToBypassZone):
await hass.services.async_call(
domain=BUTTON,
service=SERVICE_PRESS,
service_data={ATTR_ENTITY_ID: entity_id},
blocking=True,
)
assert mock_request.call_count == 1
# try to bypass, works this time
await hass.services.async_call(
domain=BUTTON,
service=SERVICE_PRESS,
service_data={ATTR_ENTITY_ID: entity_id},
blocking=True,
)
assert mock_request.call_count == 2
async def test_clear_button(hass: HomeAssistant) -> None:
"""Test pushing the clear bypass button."""
data = {ATTR_ENTITY_ID: PANEL_CLEAR_ID}
await setup_platform(hass, BUTTON)
TOTALCONNECT_REQUEST = (
"total_connect_client.location.TotalConnectLocation.clear_bypass"
await setup_integration(hass, mock_config_entry)
await hass.services.async_call(
BUTTON_DOMAIN,
SERVICE_PRESS,
{ATTR_ENTITY_ID: "button.security_bypass"},
blocking=True,
)
with patch(TOTALCONNECT_REQUEST) as mock_request:
await hass.services.async_call(
domain=BUTTON,
service=SERVICE_PRESS,
service_data=data,
blocking=True,
)
assert mock_request.call_count == 1
assert mock_location.zones[2].bypass.call_count == 1
async def test_clear_button(
hass: HomeAssistant,
mock_client: AsyncMock,
mock_config_entry: MockConfigEntry,
mock_location: AsyncMock,
) -> None:
"""Test pushing the clear bypass button."""
await setup_integration(hass, mock_config_entry)
await hass.services.async_call(
BUTTON_DOMAIN,
SERVICE_PRESS,
{ATTR_ENTITY_ID: "button.test_clear_bypass"},
blocking=True,
)
assert mock_location.clear_bypass.call_count == 1

View File

@@ -1,6 +1,6 @@
"""Tests for the TotalConnect config flow."""
from unittest.mock import patch
from unittest.mock import AsyncMock, patch
from total_connect_client.exceptions import AuthenticationError
@@ -11,217 +11,235 @@ from homeassistant.components.totalconnect.const import (
DOMAIN,
)
from homeassistant.config_entries import SOURCE_USER
from homeassistant.const import CONF_PASSWORD
from homeassistant.const import CONF_LOCATION, CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from .common import (
CONFIG_DATA,
CONFIG_DATA_NO_USERCODES,
RESPONSE_DISARMED,
RESPONSE_GET_ZONE_DETAILS_SUCCESS,
RESPONSE_PARTITION_DETAILS,
RESPONSE_SESSION_DETAILS,
RESPONSE_SUCCESS,
RESPONSE_USER_CODE_INVALID,
TOTALCONNECT_GET_CONFIG,
TOTALCONNECT_REQUEST,
TOTALCONNECT_REQUEST_TOKEN,
USERNAME,
init_integration,
)
from . import setup_integration
from .const import LOCATION_ID, PASSWORD, USERNAME
from tests.common import MockConfigEntry
async def test_user(hass: HomeAssistant) -> None:
async def test_full_flow(
hass: HomeAssistant, mock_setup_entry: AsyncMock, mock_client: AsyncMock
) -> None:
"""Test user step."""
# user starts with no data entered, so show the user form
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_USER},
data=None,
DOMAIN, context={"source": SOURCE_USER}
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user"
result = await hass.config_entries.flow.async_configure(
result["flow_id"], {CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}
)
async def test_user_show_locations(hass: HomeAssistant) -> None:
"""Test user locations form."""
# user/pass provided, so check if valid then ask for usercodes on locations form
responses = [
RESPONSE_SESSION_DETAILS,
RESPONSE_PARTITION_DETAILS,
RESPONSE_GET_ZONE_DETAILS_SUCCESS,
RESPONSE_DISARMED,
RESPONSE_USER_CODE_INVALID,
RESPONSE_SUCCESS,
]
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "locations"
with (
patch(
TOTALCONNECT_REQUEST,
side_effect=responses,
) as mock_request,
patch(TOTALCONNECT_GET_CONFIG, side_effect=None),
patch(TOTALCONNECT_REQUEST_TOKEN, side_effect=None),
patch(
"homeassistant.components.totalconnect.async_setup_entry", return_value=True
),
):
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_USER},
data=CONFIG_DATA_NO_USERCODES,
result = await hass.config_entries.flow.async_configure(
result["flow_id"], {CONF_USERCODES: "7890"}
)
assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["data"] == {
CONF_PASSWORD: PASSWORD,
CONF_USERNAME: USERNAME,
CONF_USERCODES: {LOCATION_ID: "7890"},
}
assert result["title"] == "Total Connect"
assert result["options"] == {}
assert result["result"].unique_id == USERNAME
async def test_login_errors(
hass: HomeAssistant, mock_setup_entry: AsyncMock, mock_client: AsyncMock
) -> None:
"""Test login errors."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user"
with patch(
"homeassistant.components.totalconnect.config_flow.TotalConnectClient",
) as client:
client.side_effect = AuthenticationError()
result = await hass.config_entries.flow.async_configure(
result["flow_id"], {CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}
)
# first it should show the locations form
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "locations"
# client should have sent four requests for init
assert mock_request.call_count == 4
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user"
assert result["errors"] == {"base": "invalid_auth"}
# user enters an invalid usercode
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={CONF_USERCODES: "bad"},
)
assert result2["type"] is FlowResultType.FORM
assert result2["step_id"] == "locations"
# client should have sent 5th request to validate usercode
assert mock_request.call_count == 5
result = await hass.config_entries.flow.async_configure(
result["flow_id"], {CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}
)
# user enters a valid usercode
result3 = await hass.config_entries.flow.async_configure(
result2["flow_id"],
user_input={CONF_USERCODES: "7890"},
)
assert result3["type"] is FlowResultType.CREATE_ENTRY
# client should have sent another request to validate usercode
assert mock_request.call_count == 6
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "locations"
result = await hass.config_entries.flow.async_configure(
result["flow_id"], {CONF_USERCODES: "7890"}
)
assert result["type"] is FlowResultType.CREATE_ENTRY
async def test_abort_if_already_setup(hass: HomeAssistant) -> None:
async def test_usercode_errors(
hass: HomeAssistant,
mock_setup_entry: AsyncMock,
mock_client: AsyncMock,
mock_location: AsyncMock,
) -> None:
"""Test user step with usercode errors."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user"
result = await hass.config_entries.flow.async_configure(
result["flow_id"], {CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "locations"
mock_location.set_usercode.return_value = False
result = await hass.config_entries.flow.async_configure(
result["flow_id"], {CONF_USERCODES: "7890"}
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "locations"
assert result["errors"] == {CONF_LOCATION: "usercode"}
mock_location.set_usercode.return_value = True
result = await hass.config_entries.flow.async_configure(
result["flow_id"], {CONF_USERCODES: "7890"}
)
assert result["type"] is FlowResultType.CREATE_ENTRY
async def test_no_locations(
hass: HomeAssistant,
mock_setup_entry: AsyncMock,
mock_client: AsyncMock,
mock_location: AsyncMock,
) -> None:
"""Test no locations found."""
mock_client.get_number_locations.return_value = 0
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user"
result = await hass.config_entries.flow.async_configure(
result["flow_id"], {CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "no_locations"
async def test_abort_if_already_setup(
hass: HomeAssistant, mock_config_entry: MockConfigEntry
) -> None:
"""Test abort if the account is already setup."""
MockConfigEntry(
domain=DOMAIN,
data=CONFIG_DATA,
unique_id=USERNAME,
).add_to_hass(hass)
mock_config_entry.add_to_hass(hass)
# Should fail, same USERNAME (flow)
with patch("homeassistant.components.totalconnect.config_flow.TotalConnectClient"):
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_USER},
data=CONFIG_DATA,
)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user"
result = await hass.config_entries.flow.async_configure(
result["flow_id"], {CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "already_configured"
async def test_login_failed(hass: HomeAssistant) -> None:
"""Test when we have errors during login."""
with patch(
"homeassistant.components.totalconnect.config_flow.TotalConnectClient"
) as client_mock:
client_mock.side_effect = AuthenticationError()
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_USER},
data=CONFIG_DATA,
)
async def test_reauth(
hass: HomeAssistant,
mock_setup_entry: AsyncMock,
mock_client: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test login errors."""
mock_config_entry.add_to_hass(hass)
result = await mock_config_entry.start_reauth_flow(hass)
assert result["type"] is FlowResultType.FORM
assert result["errors"] == {"base": "invalid_auth"}
async def test_reauth(hass: HomeAssistant) -> None:
"""Test reauth."""
entry = MockConfigEntry(
domain=DOMAIN,
data=CONFIG_DATA,
unique_id=USERNAME,
)
entry.add_to_hass(hass)
result = await entry.start_reauth_flow(hass)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "reauth_confirm"
with (
patch(
"homeassistant.components.totalconnect.config_flow.TotalConnectClient"
) as client_mock,
patch(
"homeassistant.components.totalconnect.async_setup_entry", return_value=True
),
):
# first test with an invalid password
client_mock.side_effect = AuthenticationError()
result = await hass.config_entries.flow.async_configure(
result["flow_id"], {CONF_PASSWORD: "abc"}
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "reauth_successful"
assert mock_config_entry.data[CONF_PASSWORD] == "abc"
async def test_reauth_errors(
hass: HomeAssistant,
mock_setup_entry: AsyncMock,
mock_client: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test login errors."""
mock_config_entry.add_to_hass(hass)
result = await mock_config_entry.start_reauth_flow(hass)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "reauth_confirm"
with patch(
"homeassistant.components.totalconnect.config_flow.TotalConnectClient",
) as client:
client.side_effect = AuthenticationError()
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input={CONF_PASSWORD: "password"}
result["flow_id"], {CONF_PASSWORD: PASSWORD}
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "reauth_confirm"
assert result["errors"] == {"base": "invalid_auth"}
# now test with the password valid
client_mock.side_effect = None
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "reauth_confirm"
assert result["errors"] == {"base": "invalid_auth"}
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input={CONF_PASSWORD: "password"}
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "reauth_successful"
await hass.async_block_till_done()
result = await hass.config_entries.flow.async_configure(
result["flow_id"], {CONF_PASSWORD: PASSWORD}
)
assert len(hass.config_entries.async_entries()) == 1
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "reauth_successful"
async def test_no_locations(hass: HomeAssistant) -> None:
"""Test with no user locations."""
responses = [
RESPONSE_SESSION_DETAILS,
RESPONSE_PARTITION_DETAILS,
RESPONSE_GET_ZONE_DETAILS_SUCCESS,
RESPONSE_DISARMED,
]
with (
patch(
TOTALCONNECT_REQUEST,
side_effect=responses,
) as mock_request,
patch(TOTALCONNECT_GET_CONFIG, side_effect=None),
patch(TOTALCONNECT_REQUEST_TOKEN, side_effect=None),
patch(
"homeassistant.components.totalconnect.async_setup_entry", return_value=True
),
patch(
"homeassistant.components.totalconnect.TotalConnectClient.get_number_locations",
return_value=0,
),
):
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_USER},
data=CONFIG_DATA_NO_USERCODES,
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "no_locations"
await hass.async_block_till_done()
assert mock_request.call_count == 1
async def test_options_flow(hass: HomeAssistant) -> None:
async def test_options_flow(
hass: HomeAssistant,
mock_setup_entry: AsyncMock,
mock_client: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test config flow options."""
config_entry = await init_integration(hass)
result = await hass.config_entries.options.async_init(config_entry.entry_id)
await setup_integration(hass, mock_config_entry)
result = await hass.config_entries.options.async_init(mock_config_entry.entry_id)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "init"
@@ -231,8 +249,4 @@ async def test_options_flow(hass: HomeAssistant) -> None:
)
assert result["type"] is FlowResultType.CREATE_ENTRY
assert config_entry.options == {AUTO_BYPASS: True, CODE_REQUIRED: False}
await hass.async_block_till_done()
assert await hass.config_entries.async_unload(config_entry.entry_id)
await hass.async_block_till_done()
assert mock_config_entry.options == {AUTO_BYPASS: True, CODE_REQUIRED: False}

View File

@@ -1,36 +1,29 @@
"""Test TotalConnect diagnostics."""
from homeassistant.components.diagnostics import REDACTED
from unittest.mock import AsyncMock
from syrupy.assertion import SnapshotAssertion
from homeassistant.core import HomeAssistant
from .common import LOCATION_ID, init_integration
from . import setup_integration
from tests.common import MockConfigEntry
from tests.components.diagnostics import get_diagnostics_for_config_entry
from tests.typing import ClientSessionGenerator
async def test_entry_diagnostics(
hass: HomeAssistant, hass_client: ClientSessionGenerator
hass: HomeAssistant,
mock_client: AsyncMock,
hass_client: ClientSessionGenerator,
snapshot: SnapshotAssertion,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test config entry diagnostics."""
entry = await init_integration(hass)
await setup_integration(hass, mock_config_entry)
result = await get_diagnostics_for_config_entry(hass, hass_client, entry)
client = result["client"]
assert client["invalid_credentials"] is False
user = result["user"]
assert user["master"] is False
location = result["locations"][0]
assert location["location_id"] == LOCATION_ID
device = location["devices"][0]
assert device["serial_number"] == REDACTED
partition = location["partitions"][0]
assert partition["name"] == "Test1"
zone = location["zones"][0]
assert zone["zone_id"] == "1"
assert (
await get_diagnostics_for_config_entry(hass, hass_client, mock_config_entry)
== snapshot
)

View File

@@ -4,29 +4,23 @@ from unittest.mock import patch
from total_connect_client.exceptions import AuthenticationError
from homeassistant.components.totalconnect.const import DOMAIN
from homeassistant.config_entries import ConfigEntryState
from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component
from .common import CONFIG_DATA
from . import setup_integration
from tests.common import MockConfigEntry
async def test_reauth_started(hass: HomeAssistant) -> None:
async def test_reauth_start(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test that reauth is started when we have login errors."""
mock_entry = MockConfigEntry(
domain=DOMAIN,
data=CONFIG_DATA,
)
mock_entry.add_to_hass(hass)
with patch(
"homeassistant.components.totalconnect.TotalConnectClient",
) as mock_client:
mock_client.side_effect = AuthenticationError()
assert await async_setup_component(hass, DOMAIN, {})
await hass.async_block_till_done()
await setup_integration(hass, mock_config_entry)
assert mock_entry.state is ConfigEntryState.SETUP_ERROR
assert mock_config_entry.state is ConfigEntryState.SETUP_ERROR