From 08affed884e4e6d2b447ea19318d7634966ee18f Mon Sep 17 00:00:00 2001 From: Snuffy2 Date: Sun, 8 Dec 2024 21:38:00 -0500 Subject: [PATCH 1/4] Update pyproject.toml --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 57dc3527..a4c71d54 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -344,7 +344,7 @@ log_level = "DEBUG" # Set the logging level to DEBUG asyncio_mode = "auto" asyncio_default_fixture_loop_scope = "function" timeout = 30 -addopts = "-vv --cov=custom_components/keymaster --cov-report=xml" +addopts = "-rA --cov=custom_components/keymaster --cov-report=xml" [tool.ruff] required-version = ">=0.8.0" From 890fa3606babf2e745c40dd16eb493de1e9764aa Mon Sep 17 00:00:00 2001 From: Snuffy2 Date: Sun, 8 Dec 2024 21:38:04 -0500 Subject: [PATCH 2/4] Update test_config_flow.py --- tests/test_config_flow.py | 275 ++++++++++++++++++++------------------ 1 file changed, 143 insertions(+), 132 deletions(-) diff --git a/tests/test_config_flow.py b/tests/test_config_flow.py index a36b1fb8..f469c5db 100644 --- a/tests/test_config_flow.py +++ b/tests/test_config_flow.py @@ -4,14 +4,21 @@ from unittest.mock import patch import pytest +from pytest_homeassistant_custom_component.common import MockConfigEntry -from custom_components.keymaster.config_flow import _get_entities # noqa: PLC2701 +from custom_components.keymaster.config_flow import ( + KeymasterFlowHandler, + _get_entities, # noqa: PLC2701 + _get_schema, # noqa: PLC2701 +) from custom_components.keymaster.const import DOMAIN from homeassistant import config_entries from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN from homeassistant.components.lock.const import LockState from homeassistant.data_entry_flow import FlowResultType +from .const import CONFIG_DATA + KWIKSET_910_LOCK_ENTITY = "lock.smart_code_with_home_connect_technology" _LOGGER: logging.Logger = logging.getLogger(__name__) @@ -75,137 +82,141 @@ async def test_form(test_user_input, title, final_config_flow_data, hass, mock_g assert len(mock_setup_entry.mock_calls) == 1 -# @pytest.mark.parametrize( -# ("input_1", "title", "data"), -# [ -# ( -# { -# "alarm_level_or_user_code_entity_id": "sensor.kwikset_touchpad_electronic_deadbolt_alarm_level_frontdoor", -# "alarm_type_or_access_control_entity_id": "sensor.kwikset_touchpad_electronic_deadbolt_alarm_type_frontdoor", -# "lock_entity_id": "lock.kwikset_touchpad_electronic_deadbolt_frontdoor", -# "lockname": "sidedoor", -# "sensorname": "binary_sensor.frontdoor", -# "slots": 4, -# "start_from": 1, -# "parent": "(none)", -# }, -# "frontdoor", -# { -# "alarm_level_or_user_code_entity_id": "sensor.kwikset_touchpad_electronic_deadbolt_alarm_level_frontdoor", -# "alarm_type_or_access_control_entity_id": "sensor.kwikset_touchpad_electronic_deadbolt_alarm_type_frontdoor", -# "lock_entity_id": "lock.kwikset_touchpad_electronic_deadbolt_frontdoor", -# "lockname": "sidedoor", -# "sensorname": "binary_sensor.frontdoor", -# "slots": 4, -# "start_from": 1, -# "hide_pins": False, -# "parent": None, -# } -# ) -# ] -# ) -# async def test_options_flow(input_1, title, data, hass, mock_get_entities): -# """Test config flow options.""" -# _LOGGER.error(_get_schema(hass, CONFIG_DATA, KeymasterFlowHandler.DEFAULTS)) -# entry = MockConfigEntry( -# domain=DOMAIN, -# title="frontdoor", -# data=_get_schema(hass, CONFIG_DATA, KeymasterFlowHandler.DEFAULTS)(CONFIG_DATA), -# version=3, -# ) - -# entry.add_to_hass(hass) -# assert await hass.config_entries.async_setup(entry.entry_id) -# await hass.async_block_till_done() - -# result = await hass.config_entries.options.async_init(entry.entry_id) - -# assert result["type"] == "form" -# assert result["step_id"] == "init" -# assert result["errors"] == {} - -# with patch( -# "custom_components.keymaster.async_setup_entry", -# return_value=True, -# ): - -# result2 = await hass.config_entries.options.async_configure( -# result["flow_id"], input_1 -# ) -# assert result2["type"] == "create_entry" - -# await hass.async_block_till_done() -# assert entry.data.copy() == data - - -# @pytest.mark.parametrize( -# "input_1,title,data", -# [ -# ( -# { -# "alarm_level_or_user_code_entity_id": "sensor.kwikset_touchpad_electronic_deadbolt_alarm_level_frontdoor", -# "alarm_type_or_access_control_entity_id": "sensor.kwikset_touchpad_electronic_deadbolt_alarm_type_frontdoor", -# "lock_entity_id": "lock.kwikset_touchpad_electronic_deadbolt_frontdoor", -# "lockname": "sidedoor", -# "sensorname": "binary_sensor.frontdoor", -# "slots": 4, -# "start_from": 1, -# "parent": "(none)", -# }, -# "frontdoor", -# { -# "alarm_level_or_user_code_entity_id": "sensor.kwikset_touchpad_electronic_deadbolt_alarm_level_frontdoor", -# "alarm_type_or_access_control_entity_id": "sensor.kwikset_touchpad_electronic_deadbolt_alarm_type_frontdoor", -# "lock_entity_id": "lock.kwikset_touchpad_electronic_deadbolt_frontdoor", -# "lockname": "sidedoor", -# "sensorname": "binary_sensor.frontdoor", -# "slots": 4, -# "start_from": 1, -# "hide_pins": False, -# "parent": None, -# }, -# ), -# ], -# ) -# async def test_options_flow_with_zwavejs( -# input_1, title, data, hass, mock_get_entities, client, lock_kwikset_910, integration -# ): -# """Test config flow options.""" - -# # Load ZwaveJS -# node = lock_kwikset_910 -# state = hass.states.get(KWIKSET_910_LOCK_ENTITY) - -# _LOGGER.error(_get_schema(hass, CONFIG_DATA, KeymasterFlowHandler.DEFAULTS)) -# entry = MockConfigEntry( -# domain=DOMAIN, -# title="frontdoor", -# data=_get_schema(hass, CONFIG_DATA, KeymasterFlowHandler.DEFAULTS)(CONFIG_DATA), -# version=3, -# ) - -# entry.add_to_hass(hass) -# assert await hass.config_entries.async_setup(entry.entry_id) -# await hass.async_block_till_done() - -# result = await hass.config_entries.options.async_init(entry.entry_id) - -# assert result["type"] == "form" -# assert result["step_id"] == "init" -# assert result["errors"] == {} - -# with patch( -# "custom_components.keymaster.async_setup_entry", -# return_value=True, -# ): - -# result2 = await hass.config_entries.options.async_configure( -# result["flow_id"], input_1 -# ) -# assert result2["type"] == "create_entry" - -# await hass.async_block_till_done() -# assert entry.data.copy() == data +@pytest.mark.parametrize( + ("test_user_input", "title", "final_config_flow_data"), + [ + ( + { + "alarm_level_or_user_code_entity_id": "sensor.kwikset_touchpad_electronic_deadbolt_alarm_level_frontdoor", + "alarm_type_or_access_control_entity_id": "sensor.kwikset_touchpad_electronic_deadbolt_alarm_type_frontdoor", + "lock_entity_id": "lock.kwikset_touchpad_electronic_deadbolt_frontdoor", + "lockname": "sidedoor", + "sensorname": "binary_sensor.frontdoor", + "slots": 4, + "start_from": 1, + "parent": "(none)", + "notify_script": "script.keymaster_frontdoor_manual_notify", + }, + "frontdoor", + { + "alarm_level_or_user_code_entity_id": "sensor.kwikset_touchpad_electronic_deadbolt_alarm_level_frontdoor", + "alarm_type_or_access_control_entity_id": "sensor.kwikset_touchpad_electronic_deadbolt_alarm_type_frontdoor", + "lock_entity_id": "lock.kwikset_touchpad_electronic_deadbolt_frontdoor", + "lockname": "sidedoor", + "sensorname": "binary_sensor.frontdoor", + "slots": 4, + "start_from": 1, + "hide_pins": False, + "parent": None, + "notify_script": "script.keymaster_frontdoor_manual_notify", + } + ) + ] +) +async def test_options_flow(test_user_input, title, final_config_flow_data, hass, mock_get_entities): + """Test config flow options.""" + _LOGGER.error(_get_schema(hass, CONFIG_DATA, KeymasterFlowHandler.DEFAULTS)) + entry = MockConfigEntry( + domain=DOMAIN, + title="frontdoor", + data=_get_schema(hass, CONFIG_DATA, KeymasterFlowHandler.DEFAULTS)(CONFIG_DATA), + version=3, + ) + + entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + result = await hass.config_entries.options.async_init(entry.entry_id) + + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == "init" + assert result["errors"] == {} + + with patch( + "custom_components.keymaster.async_setup_entry", + return_value=True, + ): + + result2 = await hass.config_entries.options.async_configure( + result["flow_id"], test_user_input + ) + assert result2["type"] is FlowResultType.CREATE_ENTRY + + await hass.async_block_till_done() + assert entry.data.copy() == final_config_flow_data + + +@pytest.mark.parametrize( + ("test_user_input", "title", "final_config_flow_data"), + [ + ( + { + "alarm_level_or_user_code_entity_id": "sensor.kwikset_touchpad_electronic_deadbolt_alarm_level_frontdoor", + "alarm_type_or_access_control_entity_id": "sensor.kwikset_touchpad_electronic_deadbolt_alarm_type_frontdoor", + "lock_entity_id": "lock.kwikset_touchpad_electronic_deadbolt_frontdoor", + "lockname": "sidedoor", + "sensorname": "binary_sensor.frontdoor", + "slots": 4, + "start_from": 1, + "parent": "(none)", + "notify_script": "script.keymaster_frontdoor_manual_notify", + }, + "frontdoor", + { + "alarm_level_or_user_code_entity_id": "sensor.kwikset_touchpad_electronic_deadbolt_alarm_level_frontdoor", + "alarm_type_or_access_control_entity_id": "sensor.kwikset_touchpad_electronic_deadbolt_alarm_type_frontdoor", + "lock_entity_id": "lock.kwikset_touchpad_electronic_deadbolt_frontdoor", + "lockname": "sidedoor", + "sensorname": "binary_sensor.frontdoor", + "slots": 4, + "start_from": 1, + "hide_pins": False, + "parent": None, + "notify_script": "script.keymaster_frontdoor_manual_notify", + }, + ), + ], +) +async def test_options_flow_with_zwavejs( + test_user_input, title, final_config_flow_data, hass, mock_get_entities, client, lock_kwikset_910, integration +): + """Test config flow options.""" + + # Load ZwaveJS + # node = lock_kwikset_910 + # state = hass.states.get(KWIKSET_910_LOCK_ENTITY) + + _LOGGER.error(_get_schema(hass, CONFIG_DATA, KeymasterFlowHandler.DEFAULTS)) + entry = MockConfigEntry( + domain=DOMAIN, + title="frontdoor", + data=_get_schema(hass, CONFIG_DATA, KeymasterFlowHandler.DEFAULTS)(CONFIG_DATA), + version=3, + ) + + entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + result = await hass.config_entries.options.async_init(entry.entry_id) + + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == "init" + assert result["errors"] == {} + + with patch( + "custom_components.keymaster.async_setup_entry", + return_value=True, + ): + + result2 = await hass.config_entries.options.async_configure( + result["flow_id"], test_user_input + ) + assert result2["type"] is FlowResultType.CREATE_ENTRY + + await hass.async_block_till_done() + assert entry.data.copy() == final_config_flow_data async def test_get_entities(hass, lock_kwikset_910, client, integration): From 58e149ecece2a0d56dbed53fb64ae6005185fe8e Mon Sep 17 00:00:00 2001 From: Snuffy2 Date: Sun, 8 Dec 2024 22:16:10 -0500 Subject: [PATCH 3/4] Update common.py --- tests/common.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/common.py b/tests/common.py index 694ce7c4..c6047788 100644 --- a/tests/common.py +++ b/tests/common.py @@ -3,7 +3,7 @@ import asyncio from datetime import datetime import functools as ft -import os +from pathlib import Path import time from unittest.mock import patch @@ -15,9 +15,8 @@ def load_fixture(filename): """Load a fixture.""" - path = os.path.join(os.path.dirname(__file__), "json", filename) - with open(path, encoding="utf-8") as fptr: - return fptr.read() + path = Path(__file__).parent / "json" / filename + return path.read_text(encoding="utf-8") def async_capture_events(hass, event_name): From 8b7f1c003fa2d6249e3153ff95030063d141479c Mon Sep 17 00:00:00 2001 From: Snuffy2 Date: Sun, 8 Dec 2024 22:34:43 -0500 Subject: [PATCH 4/4] Update coordinator.py --- custom_components/keymaster/coordinator.py | 45 ++++++++++++---------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/custom_components/keymaster/coordinator.py b/custom_components/keymaster/coordinator.py index 76c48f52..71b3f00f 100644 --- a/custom_components/keymaster/coordinator.py +++ b/custom_components/keymaster/coordinator.py @@ -4,32 +4,23 @@ import asyncio import base64 -from collections.abc import Callable, Mapping -from dataclasses import fields, is_dataclass -from datetime import datetime, time as dt_time, timedelta import functools import json import logging import os +from collections.abc import Callable, Mapping +from dataclasses import fields, is_dataclass +from datetime import datetime +from datetime import time as dt_time +from datetime import timedelta from typing import Any, Type, Union, get_args, get_origin -from zwave_js_server.const.command_class.lock import ATTR_IN_USE, ATTR_USERCODE -from zwave_js_server.exceptions import BaseZwaveJSServerError, FailedZWaveCommand -from zwave_js_server.model.node import Node as ZwaveJSNode -from zwave_js_server.util.lock import ( - clear_usercode, - get_usercode_from_node, - get_usercodes, - set_usercode, -) - -from homeassistant.components.lock.const import DOMAIN as LOCK_DOMAIN, LockState +from homeassistant.components.lock.const import DOMAIN as LOCK_DOMAIN +from homeassistant.components.lock.const import LockState from homeassistant.components.zwave_js import ZWAVE_JS_NOTIFICATION_EVENT -from homeassistant.components.zwave_js.const import ( - ATTR_PARAMETERS, - DATA_CLIENT as ZWAVE_JS_DATA_CLIENT, - DOMAIN as ZWAVE_JS_DOMAIN, -) +from homeassistant.components.zwave_js.const import ATTR_PARAMETERS +from homeassistant.components.zwave_js.const import DATA_CLIENT as ZWAVE_JS_DATA_CLIENT +from homeassistant.components.zwave_js.const import DOMAIN as ZWAVE_JS_DOMAIN from homeassistant.const import ( ATTR_DEVICE_ID, ATTR_ENTITY_ID, @@ -44,10 +35,21 @@ STATE_UNKNOWN, ) from homeassistant.core import CoreState, Event, EventStateChangedData, HomeAssistant -from homeassistant.helpers import device_registry as dr, entity_registry as er +from homeassistant.helpers import device_registry as dr +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.event import async_call_later, async_track_state_change_event from homeassistant.helpers.update_coordinator import DataUpdateCoordinator -from homeassistant.util import dt as dt_util, slugify +from homeassistant.util import dt as dt_util +from homeassistant.util import slugify +from zwave_js_server.const.command_class.lock import ATTR_IN_USE, ATTR_USERCODE +from zwave_js_server.exceptions import BaseZwaveJSServerError, FailedZWaveCommand +from zwave_js_server.model.node import Node as ZwaveJSNode +from zwave_js_server.util.lock import ( + clear_usercode, + get_usercode_from_node, + get_usercodes, + set_usercode, +) from .const import ( ACCESS_CONTROL, @@ -1054,6 +1056,7 @@ async def _update_door_and_lock_state( isinstance(kmlock.door_sensor_entity_id, str) and kmlock.door_sensor_entity_id and kmlock.door_sensor_entity_id != DEFAULT_DOOR_SENSOR + and self.hass.states.get(kmlock.door_sensor_entity_id) ): door_state: str = self.hass.states.get( kmlock.door_sensor_entity_id