From fc7e4b76644ecf3838a37752a6810bfeb4e33e7a Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Wed, 8 Feb 2023 11:20:27 +0100 Subject: [PATCH] Add type hints on common MQTT tests (#87299) * Add hints for hass and return type * Fix MqttMockType add apply hints to test_common * Add type hints for other arguments * Merge changes * Add missed annotation * Make tests types private and move to top --- tests/components/mqtt/test_common.py | 583 ++++++++++++++++----------- 1 file changed, 346 insertions(+), 237 deletions(-) diff --git a/tests/components/mqtt/test_common.py b/tests/components/mqtt/test_common.py index 654fb2009ca..5ad371c0da2 100644 --- a/tests/components/mqtt/test_common.py +++ b/tests/components/mqtt/test_common.py @@ -1,9 +1,13 @@ """Common test objects.""" +from contextlib import suppress import copy from datetime import datetime import json +from pathlib import Path +from typing import Any from unittest.mock import ANY, MagicMock, patch +import pytest import yaml from homeassistant import config as hass_config @@ -22,9 +26,11 @@ from homeassistant.core import HomeAssistant from homeassistant.generated.mqtt import MQTT from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.dispatcher import async_dispatcher_send +from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.setup import async_setup_component from tests.common import MockConfigEntry, async_fire_mqtt_message, mock_registry +from tests.typing import MqttMockHAClientGenerator DEFAULT_CONFIG_DEVICE_INFO_ID = { "identifiers": ["helloworld"], @@ -52,29 +58,39 @@ _SENTINEL = object() DISCOVERY_COUNT = len(MQTT) +_MqttMessageType = list[tuple[str, str]] +_AttributesType = list[tuple[str, Any]] +_StateDataType = list[tuple[_MqttMessageType, str | None, _AttributesType | None]] + async def help_test_availability_when_connection_lost( - hass, mqtt_mock_entry_with_yaml_config, domain, config -): + hass: HomeAssistant, + mqtt_mock_entry_with_yaml_config: MqttMockHAClientGenerator, + domain: str, + config: ConfigType, +) -> None: """Test availability after MQTT disconnection.""" assert await async_setup_component(hass, mqtt.DOMAIN, config) await hass.async_block_till_done() mqtt_mock = await mqtt_mock_entry_with_yaml_config() state = hass.states.get(f"{domain}.test") - assert state.state != STATE_UNAVAILABLE + assert state and state.state != STATE_UNAVAILABLE mqtt_mock.connected = False async_dispatcher_send(hass, MQTT_DISCONNECTED) await hass.async_block_till_done() state = hass.states.get(f"{domain}.test") - assert state.state == STATE_UNAVAILABLE + assert state and state.state == STATE_UNAVAILABLE async def help_test_availability_without_topic( - hass, mqtt_mock_entry_with_yaml_config, domain, config -): + hass: HomeAssistant, + mqtt_mock_entry_with_yaml_config: MqttMockHAClientGenerator, + domain: str, + config: ConfigType, +) -> None: """Test availability without defined availability topic.""" assert "availability_topic" not in config[mqtt.DOMAIN][domain] assert await async_setup_component(hass, mqtt.DOMAIN, config) @@ -82,18 +98,18 @@ async def help_test_availability_without_topic( await mqtt_mock_entry_with_yaml_config() state = hass.states.get(f"{domain}.test") - assert state.state != STATE_UNAVAILABLE + assert state and state.state != STATE_UNAVAILABLE async def help_test_default_availability_payload( - hass, - mqtt_mock_entry_with_yaml_config, - domain, - config, - no_assumed_state=False, - state_topic=None, - state_message=None, -): + hass: HomeAssistant, + mqtt_mock_entry_with_yaml_config: MqttMockHAClientGenerator, + domain: str, + config: ConfigType, + no_assumed_state: bool = False, + state_topic: str | None = None, + state_message: str | None = None, +) -> None: """Test availability by default payload with defined topic. This is a test helper for the MqttAvailability mixin. @@ -110,41 +126,41 @@ async def help_test_default_availability_payload( await mqtt_mock_entry_with_yaml_config() state = hass.states.get(f"{domain}.test") - assert state.state == STATE_UNAVAILABLE + assert state and state.state == STATE_UNAVAILABLE async_fire_mqtt_message(hass, "availability-topic", "online") state = hass.states.get(f"{domain}.test") - assert state.state != STATE_UNAVAILABLE + assert state and state.state != STATE_UNAVAILABLE if no_assumed_state: assert not state.attributes.get(ATTR_ASSUMED_STATE) async_fire_mqtt_message(hass, "availability-topic", "offline") state = hass.states.get(f"{domain}.test") - assert state.state == STATE_UNAVAILABLE + assert state and state.state == STATE_UNAVAILABLE - if state_topic: + if state_topic is not None and state_message is not None: async_fire_mqtt_message(hass, state_topic, state_message) state = hass.states.get(f"{domain}.test") - assert state.state == STATE_UNAVAILABLE + assert state and state.state == STATE_UNAVAILABLE async_fire_mqtt_message(hass, "availability-topic", "online") state = hass.states.get(f"{domain}.test") - assert state.state != STATE_UNAVAILABLE + assert state and state.state != STATE_UNAVAILABLE async def help_test_default_availability_list_payload( - hass, - mqtt_mock_entry_with_yaml_config, - domain, - config, - no_assumed_state=False, - state_topic=None, - state_message=None, -): + hass: HomeAssistant, + mqtt_mock_entry_with_yaml_config: MqttMockHAClientGenerator, + domain: str, + config: ConfigType, + no_assumed_state: bool = False, + state_topic: str | None = None, + state_message: str | None = None, +) -> None: """Test availability by default payload with defined topic. This is a test helper for the MqttAvailability mixin. @@ -164,53 +180,53 @@ async def help_test_default_availability_list_payload( await mqtt_mock_entry_with_yaml_config() state = hass.states.get(f"{domain}.test") - assert state.state == STATE_UNAVAILABLE + assert state and state.state == STATE_UNAVAILABLE async_fire_mqtt_message(hass, "availability-topic1", "online") state = hass.states.get(f"{domain}.test") - assert state.state != STATE_UNAVAILABLE + assert state and state.state != STATE_UNAVAILABLE if no_assumed_state: assert not state.attributes.get(ATTR_ASSUMED_STATE) async_fire_mqtt_message(hass, "availability-topic1", "offline") state = hass.states.get(f"{domain}.test") - assert state.state == STATE_UNAVAILABLE + assert state and state.state == STATE_UNAVAILABLE async_fire_mqtt_message(hass, "availability-topic2", "online") state = hass.states.get(f"{domain}.test") - assert state.state != STATE_UNAVAILABLE + assert state and state.state != STATE_UNAVAILABLE if no_assumed_state: assert not state.attributes.get(ATTR_ASSUMED_STATE) async_fire_mqtt_message(hass, "availability-topic2", "offline") state = hass.states.get(f"{domain}.test") - assert state.state == STATE_UNAVAILABLE + assert state and state.state == STATE_UNAVAILABLE - if state_topic: + if state_topic is not None and state_message is not None: async_fire_mqtt_message(hass, state_topic, state_message) state = hass.states.get(f"{domain}.test") - assert state.state == STATE_UNAVAILABLE + assert state and state.state == STATE_UNAVAILABLE async_fire_mqtt_message(hass, "availability-topic1", "online") state = hass.states.get(f"{domain}.test") - assert state.state != STATE_UNAVAILABLE + assert state and state.state != STATE_UNAVAILABLE async def help_test_default_availability_list_payload_all( - hass, - mqtt_mock_entry_with_yaml_config, - domain, - config, - no_assumed_state=False, - state_topic=None, - state_message=None, -): + hass: HomeAssistant, + mqtt_mock_entry_with_yaml_config: MqttMockHAClientGenerator, + domain: str, + config: ConfigType, + no_assumed_state: bool = False, + state_topic: str | None = None, + state_message: str | None = None, +) -> None: """Test availability by default payload with defined topic. This is a test helper for the MqttAvailability mixin. @@ -231,54 +247,54 @@ async def help_test_default_availability_list_payload_all( await mqtt_mock_entry_with_yaml_config() state = hass.states.get(f"{domain}.test") - assert state.state == STATE_UNAVAILABLE + assert state and state.state == STATE_UNAVAILABLE async_fire_mqtt_message(hass, "availability-topic1", "online") state = hass.states.get(f"{domain}.test") - assert state.state == STATE_UNAVAILABLE + assert state and state.state == STATE_UNAVAILABLE if no_assumed_state: assert not state.attributes.get(ATTR_ASSUMED_STATE) async_fire_mqtt_message(hass, "availability-topic2", "online") state = hass.states.get(f"{domain}.test") - assert state.state != STATE_UNAVAILABLE + assert state and state.state != STATE_UNAVAILABLE async_fire_mqtt_message(hass, "availability-topic2", "offline") state = hass.states.get(f"{domain}.test") - assert state.state == STATE_UNAVAILABLE + assert state and state.state == STATE_UNAVAILABLE if no_assumed_state: assert not state.attributes.get(ATTR_ASSUMED_STATE) async_fire_mqtt_message(hass, "availability-topic2", "online") state = hass.states.get(f"{domain}.test") - assert state.state != STATE_UNAVAILABLE + assert state and state.state != STATE_UNAVAILABLE async_fire_mqtt_message(hass, "availability-topic1", "offline") state = hass.states.get(f"{domain}.test") - assert state.state == STATE_UNAVAILABLE + assert state and state.state == STATE_UNAVAILABLE if no_assumed_state: assert not state.attributes.get(ATTR_ASSUMED_STATE) async_fire_mqtt_message(hass, "availability-topic1", "online") state = hass.states.get(f"{domain}.test") - assert state.state != STATE_UNAVAILABLE + assert state and state.state != STATE_UNAVAILABLE async def help_test_default_availability_list_payload_any( - hass, - mqtt_mock_entry_with_yaml_config, - domain, - config, - no_assumed_state=False, - state_topic=None, - state_message=None, -): + hass: HomeAssistant, + mqtt_mock_entry_with_yaml_config: MqttMockHAClientGenerator, + domain: str, + config: ConfigType, + no_assumed_state: bool = False, + state_topic: str | None = None, + state_message: str | None = None, +) -> None: """Test availability by default payload with defined topic. This is a test helper for the MqttAvailability mixin. @@ -299,49 +315,46 @@ async def help_test_default_availability_list_payload_any( await mqtt_mock_entry_with_yaml_config() state = hass.states.get(f"{domain}.test") - assert state.state == STATE_UNAVAILABLE + assert state and state.state == STATE_UNAVAILABLE async_fire_mqtt_message(hass, "availability-topic1", "online") state = hass.states.get(f"{domain}.test") - assert state.state != STATE_UNAVAILABLE + assert state and state.state != STATE_UNAVAILABLE if no_assumed_state: assert not state.attributes.get(ATTR_ASSUMED_STATE) async_fire_mqtt_message(hass, "availability-topic2", "online") state = hass.states.get(f"{domain}.test") - assert state.state != STATE_UNAVAILABLE + assert state and state.state != STATE_UNAVAILABLE async_fire_mqtt_message(hass, "availability-topic2", "offline") state = hass.states.get(f"{domain}.test") - assert state.state != STATE_UNAVAILABLE + assert state and state.state != STATE_UNAVAILABLE if no_assumed_state: assert not state.attributes.get(ATTR_ASSUMED_STATE) async_fire_mqtt_message(hass, "availability-topic1", "offline") state = hass.states.get(f"{domain}.test") - assert state.state == STATE_UNAVAILABLE + assert state and state.state == STATE_UNAVAILABLE async_fire_mqtt_message(hass, "availability-topic1", "online") state = hass.states.get(f"{domain}.test") - assert state.state != STATE_UNAVAILABLE + assert state and state.state != STATE_UNAVAILABLE if no_assumed_state: assert not state.attributes.get(ATTR_ASSUMED_STATE) async def help_test_default_availability_list_single( - hass, - caplog, - domain, - config, - no_assumed_state=False, - state_topic=None, - state_message=None, -): + hass: HomeAssistant, + caplog: pytest.LogCaptureFixture, + domain: str, + config: ConfigType, +) -> None: """Test availability list and availability_topic are mutually exclusive. This is a test helper for the MqttAvailability mixin. @@ -364,14 +377,14 @@ async def help_test_default_availability_list_single( async def help_test_custom_availability_payload( - hass, - mqtt_mock_entry_with_yaml_config, - domain, - config, - no_assumed_state=False, - state_topic=None, - state_message=None, -): + hass: HomeAssistant, + mqtt_mock_entry_with_yaml_config: MqttMockHAClientGenerator, + domain: str, + config: ConfigType, + no_assumed_state: bool = False, + state_topic: str | None = None, + state_message: str | None = None, +) -> None: """Test availability by custom payload with defined topic. This is a test helper for the MqttAvailability mixin. @@ -390,41 +403,38 @@ async def help_test_custom_availability_payload( await mqtt_mock_entry_with_yaml_config() state = hass.states.get(f"{domain}.test") - assert state.state == STATE_UNAVAILABLE + assert state and state.state == STATE_UNAVAILABLE async_fire_mqtt_message(hass, "availability-topic", "good") state = hass.states.get(f"{domain}.test") - assert state.state != STATE_UNAVAILABLE + assert state and state.state != STATE_UNAVAILABLE if no_assumed_state: assert not state.attributes.get(ATTR_ASSUMED_STATE) async_fire_mqtt_message(hass, "availability-topic", "nogood") state = hass.states.get(f"{domain}.test") - assert state.state == STATE_UNAVAILABLE + assert state and state.state == STATE_UNAVAILABLE - if state_topic: + if state_topic is not None and state_message is not None: async_fire_mqtt_message(hass, state_topic, state_message) state = hass.states.get(f"{domain}.test") - assert state.state == STATE_UNAVAILABLE + assert state and state.state == STATE_UNAVAILABLE async_fire_mqtt_message(hass, "availability-topic", "good") state = hass.states.get(f"{domain}.test") - assert state.state != STATE_UNAVAILABLE + assert state and state.state != STATE_UNAVAILABLE async def help_test_discovery_update_availability( - hass, - mqtt_mock_entry_no_yaml_config, - domain, - config, - no_assumed_state=False, - state_topic=None, - state_message=None, -): + hass: HomeAssistant, + mqtt_mock_entry_no_yaml_config: MqttMockHAClientGenerator, + domain: str, + config: ConfigType, +) -> None: """Test update of discovered MQTTAvailability. This is a test helper for the MQTTAvailability mixin. @@ -448,15 +458,15 @@ async def help_test_discovery_update_availability( await hass.async_block_till_done() state = hass.states.get(f"{domain}.test") - assert state.state == STATE_UNAVAILABLE + assert state and state.state == STATE_UNAVAILABLE async_fire_mqtt_message(hass, "availability-topic1", "online") state = hass.states.get(f"{domain}.test") - assert state.state != STATE_UNAVAILABLE + assert state and state.state != STATE_UNAVAILABLE async_fire_mqtt_message(hass, "availability-topic1", "offline") state = hass.states.get(f"{domain}.test") - assert state.state == STATE_UNAVAILABLE + assert state and state.state == STATE_UNAVAILABLE # Change availability_topic async_fire_mqtt_message(hass, f"homeassistant/{domain}/bla/config", data2) @@ -465,17 +475,17 @@ async def help_test_discovery_update_availability( # Verify we are no longer subscribing to the old topic async_fire_mqtt_message(hass, "availability-topic1", "online") state = hass.states.get(f"{domain}.test") - assert state.state == STATE_UNAVAILABLE + assert state and state.state == STATE_UNAVAILABLE # Verify we are subscribing to the new topic async_fire_mqtt_message(hass, "availability-topic2", "online") state = hass.states.get(f"{domain}.test") - assert state.state != STATE_UNAVAILABLE + assert state and state.state != STATE_UNAVAILABLE # Verify we are subscribing to the new topic async_fire_mqtt_message(hass, "availability-topic3", "offline") state = hass.states.get(f"{domain}.test") - assert state.state == STATE_UNAVAILABLE + assert state and state.state == STATE_UNAVAILABLE # Change availability_topic async_fire_mqtt_message(hass, f"homeassistant/{domain}/bla/config", data3) @@ -484,22 +494,25 @@ async def help_test_discovery_update_availability( # Verify we are no longer subscribing to the old topic async_fire_mqtt_message(hass, "availability-topic2", "online") state = hass.states.get(f"{domain}.test") - assert state.state == STATE_UNAVAILABLE + assert state and state.state == STATE_UNAVAILABLE # Verify we are no longer subscribing to the old topic async_fire_mqtt_message(hass, "availability-topic3", "online") state = hass.states.get(f"{domain}.test") - assert state.state == STATE_UNAVAILABLE + assert state and state.state == STATE_UNAVAILABLE # Verify we are subscribing to the new topic async_fire_mqtt_message(hass, "availability-topic4", "online") state = hass.states.get(f"{domain}.test") - assert state.state != STATE_UNAVAILABLE + assert state and state.state != STATE_UNAVAILABLE async def help_test_setting_attribute_via_mqtt_json_message( - hass, mqtt_mock_entry_with_yaml_config, domain, config -): + hass: HomeAssistant, + mqtt_mock_entry_with_yaml_config: MqttMockHAClientGenerator, + domain: str, + config: ConfigType, +) -> None: """Test the setting of attribute via MQTT with JSON payload. This is a test helper for the MqttAttributes mixin. @@ -518,18 +531,22 @@ async def help_test_setting_attribute_via_mqtt_json_message( async_fire_mqtt_message(hass, "attr-topic", '{ "val": "100" }') state = hass.states.get(f"{domain}.test") - assert state.attributes.get("val") == "100" + assert state and state.attributes.get("val") == "100" async def help_test_setting_blocked_attribute_via_mqtt_json_message( - hass, mqtt_mock_entry_no_yaml_config, domain, config, extra_blocked_attributes -): + hass: HomeAssistant, + mqtt_mock_entry_no_yaml_config: MqttMockHAClientGenerator, + domain: str, + config: ConfigType, + extra_blocked_attributes: frozenset[str] | None, +) -> None: """Test the setting of blocked attribute via MQTT with JSON payload. This is a test helper for the MqttAttributes mixin. """ await mqtt_mock_entry_no_yaml_config() - extra_blocked_attributes = extra_blocked_attributes or [] + extra_blocked_attribute_list = list(extra_blocked_attributes or []) # Add JSON attributes settings to config config = copy.deepcopy(config) @@ -542,17 +559,20 @@ async def help_test_setting_blocked_attribute_via_mqtt_json_message( for attr in MQTT_ATTRIBUTES_BLOCKED: async_fire_mqtt_message(hass, "attr-topic", json.dumps({attr: val})) state = hass.states.get(f"{domain}.test") - assert state.attributes.get(attr) != val + assert state and state.attributes.get(attr) != val - for attr in extra_blocked_attributes: + for attr in extra_blocked_attribute_list: async_fire_mqtt_message(hass, "attr-topic", json.dumps({attr: val})) state = hass.states.get(f"{domain}.test") - assert state.attributes.get(attr) != val + assert state and state.attributes.get(attr) != val async def help_test_setting_attribute_with_template( - hass, mqtt_mock_entry_with_yaml_config, domain, config -): + hass: HomeAssistant, + mqtt_mock_entry_with_yaml_config: MqttMockHAClientGenerator, + domain: str, + config: ConfigType, +) -> None: """Test the setting of attribute via MQTT with JSON payload. This is a test helper for the MqttAttributes mixin. @@ -576,13 +596,18 @@ async def help_test_setting_attribute_with_template( ) state = hass.states.get(f"{domain}.test") + assert state is not None assert state.attributes.get("Arm") == 0 assert state.attributes.get("Time") == "22:18" async def help_test_update_with_json_attrs_not_dict( - hass, mqtt_mock_entry_with_yaml_config, caplog, domain, config -): + hass: HomeAssistant, + mqtt_mock_entry_with_yaml_config: MqttMockHAClientGenerator, + caplog: pytest.LogCaptureFixture, + domain: str, + config: ConfigType, +) -> None: """Test attributes get extracted from a JSON result. This is a test helper for the MqttAttributes mixin. @@ -601,13 +626,17 @@ async def help_test_update_with_json_attrs_not_dict( async_fire_mqtt_message(hass, "attr-topic", '[ "list", "of", "things"]') state = hass.states.get(f"{domain}.test") - assert state.attributes.get("val") is None + assert state and state.attributes.get("val") is None assert "JSON result was not a dictionary" in caplog.text async def help_test_update_with_json_attrs_bad_json( - hass, mqtt_mock_entry_with_yaml_config, caplog, domain, config -): + hass: HomeAssistant, + mqtt_mock_entry_with_yaml_config: MqttMockHAClientGenerator, + caplog: pytest.LogCaptureFixture, + domain: str, + config: ConfigType, +) -> None: """Test JSON validation of attributes. This is a test helper for the MqttAttributes mixin. @@ -626,13 +655,17 @@ async def help_test_update_with_json_attrs_bad_json( async_fire_mqtt_message(hass, "attr-topic", "This is not JSON") state = hass.states.get(f"{domain}.test") - assert state.attributes.get("val") is None + assert state and state.attributes.get("val") is None assert "Erroneous JSON: This is not JSON" in caplog.text async def help_test_discovery_update_attr( - hass, mqtt_mock_entry_no_yaml_config, caplog, domain, config -): + hass: HomeAssistant, + mqtt_mock_entry_no_yaml_config: MqttMockHAClientGenerator, + caplog: pytest.LogCaptureFixture, + domain: str, + config: ConfigType, +) -> None: """Test update of discovered MQTTAttributes. This is a test helper for the MqttAttributes mixin. @@ -650,7 +683,7 @@ async def help_test_discovery_update_attr( await hass.async_block_till_done() async_fire_mqtt_message(hass, "attr-topic1", '{ "val": "100" }') state = hass.states.get(f"{domain}.test") - assert state.attributes.get("val") == "100" + assert state and state.attributes.get("val") == "100" # Change json_attributes_topic async_fire_mqtt_message(hass, f"homeassistant/{domain}/bla/config", data2) @@ -659,15 +692,20 @@ async def help_test_discovery_update_attr( # Verify we are no longer subscribing to the old topic async_fire_mqtt_message(hass, "attr-topic1", '{ "val": "50" }') state = hass.states.get(f"{domain}.test") - assert state.attributes.get("val") == "100" + assert state and state.attributes.get("val") == "100" # Verify we are subscribing to the new topic async_fire_mqtt_message(hass, "attr-topic2", '{ "val": "75" }') state = hass.states.get(f"{domain}.test") - assert state.attributes.get("val") == "75" + assert state and state.attributes.get("val") == "75" -async def help_test_unique_id(hass, mqtt_mock_entry_with_yaml_config, domain, config): +async def help_test_unique_id( + hass: HomeAssistant, + mqtt_mock_entry_with_yaml_config: MqttMockHAClientGenerator, + domain: str, + config: ConfigType, +) -> None: """Test unique id option only creates one entity per unique_id.""" assert await async_setup_component(hass, mqtt.DOMAIN, config) await hass.async_block_till_done() @@ -676,8 +714,12 @@ async def help_test_unique_id(hass, mqtt_mock_entry_with_yaml_config, domain, co async def help_test_discovery_removal( - hass, mqtt_mock_entry_no_yaml_config, caplog, domain, data -): + hass: HomeAssistant, + mqtt_mock_entry_no_yaml_config: MqttMockHAClientGenerator, + caplog: pytest.LogCaptureFixture, + domain: str, + data: str, +) -> None: """Test removal of discovered component. This is a test helper for the MqttDiscoveryUpdate mixin. @@ -698,15 +740,15 @@ async def help_test_discovery_removal( async def help_test_discovery_update( - hass, - mqtt_mock_entry_no_yaml_config, + hass: HomeAssistant, + mqtt_mock_entry_no_yaml_config: MqttMockHAClientGenerator, caplog, domain, - discovery_config1, - discovery_config2, - state_data1=None, - state_data2=None, -): + discovery_config1: DiscoveryInfoType, + discovery_config2: DiscoveryInfoType, + state_data1: _StateDataType | None = None, + state_data2: _StateDataType | None = None, +) -> None: """Test update of discovered component. This is a test helper for the MqttDiscoveryUpdate mixin. @@ -732,6 +774,7 @@ async def help_test_discovery_update( for topic, data in mqtt_messages: async_fire_mqtt_message(hass, topic, data) state = hass.states.get(f"{domain}.beer") + assert state is not None if expected_state: assert state.state == expected_state if attributes: @@ -750,6 +793,7 @@ async def help_test_discovery_update( for topic, data in mqtt_messages: async_fire_mqtt_message(hass, topic, data) state = hass.states.get(f"{domain}.beer") + assert state is not None if expected_state: assert state.state == expected_state if attributes: @@ -761,8 +805,13 @@ async def help_test_discovery_update( async def help_test_discovery_update_unchanged( - hass, mqtt_mock_entry_no_yaml_config, caplog, domain, data1, discovery_update -): + hass: HomeAssistant, + mqtt_mock_entry_no_yaml_config: MqttMockHAClientGenerator, + caplog: pytest.LogCaptureFixture, + domain: str, + data1: str, + discovery_update: MagicMock, +) -> None: """Test update of discovered component without changes. This is a test helper for the MqttDiscoveryUpdate mixin. @@ -782,8 +831,13 @@ async def help_test_discovery_update_unchanged( async def help_test_discovery_broken( - hass, mqtt_mock_entry_no_yaml_config, caplog, domain, data1, data2 -): + hass: HomeAssistant, + mqtt_mock_entry_no_yaml_config: MqttMockHAClientGenerator, + caplog: pytest.LogCaptureFixture, + domain: str, + data1: str, + data2: str, +) -> None: """Test handling of bad discovery message.""" await mqtt_mock_entry_no_yaml_config() async_fire_mqtt_message(hass, f"homeassistant/{domain}/bla/config", data1) @@ -803,29 +857,29 @@ async def help_test_discovery_broken( async def help_test_encoding_subscribable_topics( - hass, - mqtt_mock_entry_with_yaml_config, - caplog, - domain, - config, - topic, - value, - attribute=None, - attribute_value=None, - init_payload=None, - skip_raw_test=False, -): + hass: HomeAssistant, + mqtt_mock_entry_with_yaml_config: MqttMockHAClientGenerator, + caplog: pytest.LogCaptureFixture, + domain: str, + config: ConfigType, + topic: str, + value: Any, + attribute: str | None = None, + attribute_value: Any = None, + init_payload: str | None = None, + skip_raw_test: bool = False, +) -> None: """Test handling of incoming encoded payload.""" async def _test_encoding( - hass, + hass: HomeAssistant, entity_id, topic, encoded_value, attribute, init_payload_topic, init_payload_value, - ): + ) -> Any: state = hass.states.get(entity_id) if init_payload_value: @@ -839,6 +893,7 @@ async def help_test_encoding_subscribable_topics( await hass.async_block_till_done() state = hass.states.get(entity_id) + assert state is not None if attribute: return state.attributes.get(attribute) @@ -920,7 +975,7 @@ async def help_test_encoding_subscribable_topics( if skip_raw_test: return - try: + with suppress(AttributeError, TypeError, ValueError): result = await _test_encoding( hass, f"{domain}.test3", @@ -931,13 +986,14 @@ async def help_test_encoding_subscribable_topics( init_payload_value_utf16, ) assert result != expected_result - except (AttributeError, TypeError, ValueError): - pass async def help_test_entity_device_info_with_identifier( - hass, mqtt_mock_entry_no_yaml_config, domain, config -): + hass: HomeAssistant, + mqtt_mock_entry_no_yaml_config: MqttMockHAClientGenerator, + domain: str, + config: ConfigType, +) -> None: """Test device registry integration. This is a test helper for the MqttDiscoveryUpdate mixin. @@ -967,8 +1023,11 @@ async def help_test_entity_device_info_with_identifier( async def help_test_entity_device_info_with_connection( - hass, mqtt_mock_entry_no_yaml_config, domain, config -): + hass: HomeAssistant, + mqtt_mock_entry_no_yaml_config: MqttMockHAClientGenerator, + domain: str, + config: ConfigType, +) -> None: """Test device registry integration. This is a test helper for the MqttDiscoveryUpdate mixin. @@ -1000,8 +1059,11 @@ async def help_test_entity_device_info_with_connection( async def help_test_entity_device_info_remove( - hass, mqtt_mock_entry_no_yaml_config, domain, config -): + hass: HomeAssistant, + mqtt_mock_entry_no_yaml_config: MqttMockHAClientGenerator, + domain: str, + config: ConfigType, +) -> None: """Test device registry remove.""" await mqtt_mock_entry_no_yaml_config() # Add device settings to config @@ -1029,8 +1091,11 @@ async def help_test_entity_device_info_remove( async def help_test_entity_device_info_update( - hass, mqtt_mock_entry_no_yaml_config, domain, config -): + hass: HomeAssistant, + mqtt_mock_entry_no_yaml_config: MqttMockHAClientGenerator, + domain: str, + config: ConfigType, +) -> None: """Test device registry update. This is a test helper for the MqttDiscoveryUpdate mixin. @@ -1062,8 +1127,12 @@ async def help_test_entity_device_info_update( async def help_test_entity_id_update_subscriptions( - hass, mqtt_mock_entry_with_yaml_config, domain, config, topics=None -): + hass: HomeAssistant, + mqtt_mock_entry_with_yaml_config: MqttMockHAClientGenerator, + domain: str, + config: ConfigType, + topics: list[str] | None = None, +) -> None: """Test MQTT subscriptions are managed when entity_id is updated.""" # Add unique_id to config config = copy.deepcopy(config) @@ -1105,8 +1174,12 @@ async def help_test_entity_id_update_subscriptions( async def help_test_entity_id_update_discovery_update( - hass, mqtt_mock_entry_no_yaml_config, domain, config, topic=None -): + hass: HomeAssistant, + mqtt_mock_entry_no_yaml_config: MqttMockHAClientGenerator, + domain: str, + config: ConfigType, + topic: str | None = None, +) -> None: """Test MQTT discovery update after entity_id is updated.""" # Add unique_id to config await mqtt_mock_entry_no_yaml_config() @@ -1126,11 +1199,11 @@ async def help_test_entity_id_update_discovery_update( async_fire_mqtt_message(hass, topic, "online") state = hass.states.get(f"{domain}.test") - assert state.state != STATE_UNAVAILABLE + assert state and state.state != STATE_UNAVAILABLE async_fire_mqtt_message(hass, topic, "offline") state = hass.states.get(f"{domain}.test") - assert state.state == STATE_UNAVAILABLE + assert state and state.state == STATE_UNAVAILABLE ent_registry.async_update_entity(f"{domain}.test", new_entity_id=f"{domain}.milk") await hass.async_block_till_done() @@ -1143,12 +1216,15 @@ async def help_test_entity_id_update_discovery_update( async_fire_mqtt_message(hass, f"{topic}_2", "online") state = hass.states.get(f"{domain}.milk") - assert state.state != STATE_UNAVAILABLE + assert state and state.state != STATE_UNAVAILABLE async def help_test_entity_debug_info( - hass, mqtt_mock_entry_no_yaml_config, domain, config -): + hass: HomeAssistant, + mqtt_mock_entry_no_yaml_config: MqttMockHAClientGenerator, + domain: str, + config: ConfigType, +) -> None: """Test debug_info. This is a test helper for MQTT debug_info. @@ -1185,8 +1261,11 @@ async def help_test_entity_debug_info( async def help_test_entity_debug_info_max_messages( - hass, mqtt_mock_entry_no_yaml_config, domain, config -): + hass: HomeAssistant, + mqtt_mock_entry_no_yaml_config: MqttMockHAClientGenerator, + domain: str, + config: ConfigType, +) -> None: """Test debug_info message overflow. This is a test helper for MQTT debug_info. @@ -1240,17 +1319,17 @@ async def help_test_entity_debug_info_max_messages( async def help_test_entity_debug_info_message( - hass, - mqtt_mock_entry_no_yaml_config, - domain, - config, - service, - command_topic=_SENTINEL, - command_payload=_SENTINEL, - state_topic=_SENTINEL, - state_payload=_SENTINEL, - service_parameters=None, -): + hass: HomeAssistant, + mqtt_mock_entry_no_yaml_config: MqttMockHAClientGenerator, + domain: str, + config: ConfigType, + service: str, + command_topic: str | None = None, + command_payload: str | None = None, + state_topic: str | object | None = _SENTINEL, + state_payload: str | None = None, + service_parameters: dict[str, Any] | None = None, +) -> None: """Test debug_info. This is a test helper for MQTT debug_info. @@ -1261,12 +1340,12 @@ async def help_test_entity_debug_info_message( config["device"] = copy.deepcopy(DEFAULT_CONFIG_DEVICE_INFO_ID) config["unique_id"] = "veryunique" - if command_topic is _SENTINEL: + if command_topic is None: # Add default topic to config config["command_topic"] = "command-topic" command_topic = "command-topic" - if command_payload is _SENTINEL: + if command_payload is None: command_payload = "ON" if state_topic is _SENTINEL: @@ -1274,7 +1353,7 @@ async def help_test_entity_debug_info_message( config["state_topic"] = "state-topic" state_topic = "state-topic" - if state_payload is _SENTINEL: + if state_payload is None: state_payload = "ON" registry = dr.async_get(hass) @@ -1298,7 +1377,7 @@ async def help_test_entity_debug_info_message( with patch("homeassistant.util.dt.utcnow") as dt_utcnow: dt_utcnow.return_value = start_dt - async_fire_mqtt_message(hass, state_topic, state_payload) + async_fire_mqtt_message(hass, str(state_topic), state_payload) debug_info_data = debug_info.info_for_device(hass, device.id) assert len(debug_info_data["entities"][0]["subscriptions"]) >= 1 @@ -1352,8 +1431,11 @@ async def help_test_entity_debug_info_message( async def help_test_entity_debug_info_remove( - hass, mqtt_mock_entry_no_yaml_config, domain, config -): + hass: HomeAssistant, + mqtt_mock_entry_no_yaml_config: MqttMockHAClientGenerator, + domain: str, + config: ConfigType, +) -> None: """Test debug_info. This is a test helper for MQTT debug_info. @@ -1399,8 +1481,11 @@ async def help_test_entity_debug_info_remove( async def help_test_entity_debug_info_update_entity_id( - hass, mqtt_mock_entry_no_yaml_config, domain, config -): + hass: HomeAssistant, + mqtt_mock_entry_no_yaml_config: MqttMockHAClientGenerator, + domain: str, + config: ConfigType, +) -> None: """Test debug_info. This is a test helper for MQTT debug_info. @@ -1457,8 +1542,11 @@ async def help_test_entity_debug_info_update_entity_id( async def help_test_entity_disabled_by_default( - hass, mqtt_mock_entry_no_yaml_config, domain, config -): + hass: HomeAssistant, + mqtt_mock_entry_no_yaml_config: MqttMockHAClientGenerator, + domain: str, + config: ConfigType, +) -> None: """Test device registry remove.""" await mqtt_mock_entry_no_yaml_config() # Add device settings to config @@ -1475,7 +1563,7 @@ async def help_test_entity_disabled_by_default( async_fire_mqtt_message(hass, f"homeassistant/{domain}/bla1/config", data) await hass.async_block_till_done() entity_id = ent_registry.async_get_entity_id(domain, mqtt.DOMAIN, "veryunique1") - assert not hass.states.get(entity_id) + assert entity_id is not None and hass.states.get(entity_id) is None assert dev_registry.async_get_device({("mqtt", "helloworld")}) # Discover an enabled entity, tied to the same device @@ -1485,7 +1573,7 @@ async def help_test_entity_disabled_by_default( async_fire_mqtt_message(hass, f"homeassistant/{domain}/bla2/config", data) await hass.async_block_till_done() entity_id = ent_registry.async_get_entity_id(domain, mqtt.DOMAIN, "veryunique2") - assert hass.states.get(entity_id) + assert entity_id is not None and hass.states.get(entity_id) is not None # Remove the enabled entity, both entities and the device should be removed async_fire_mqtt_message(hass, f"homeassistant/{domain}/bla2/config", "") @@ -1496,8 +1584,11 @@ async def help_test_entity_disabled_by_default( async def help_test_entity_category( - hass, mqtt_mock_entry_no_yaml_config, domain, config -): + hass: HomeAssistant, + mqtt_mock_entry_no_yaml_config: MqttMockHAClientGenerator, + domain: str, + config: ConfigType, +) -> None: """Test device registry remove.""" await mqtt_mock_entry_no_yaml_config() # Add device settings to config @@ -1513,9 +1604,9 @@ async def help_test_entity_category( async_fire_mqtt_message(hass, f"homeassistant/{domain}/{unique_id}/config", data) await hass.async_block_till_done() entity_id = ent_registry.async_get_entity_id(domain, mqtt.DOMAIN, unique_id) - assert hass.states.get(entity_id) + assert entity_id is not None and hass.states.get(entity_id) entry = ent_registry.async_get(entity_id) - assert entry.entity_category is None + assert entry is not None and entry.entity_category is None # Discover an entity with entity category set to "config" unique_id = "veryunique2" @@ -1525,9 +1616,9 @@ async def help_test_entity_category( async_fire_mqtt_message(hass, f"homeassistant/{domain}/{unique_id}/config", data) await hass.async_block_till_done() entity_id = ent_registry.async_get_entity_id(domain, mqtt.DOMAIN, unique_id) - assert hass.states.get(entity_id) + assert entity_id is not None and hass.states.get(entity_id) entry = ent_registry.async_get(entity_id) - assert entry.entity_category == "config" + assert entry is not None and entry.entity_category == "config" # Discover an entity with entity category set to "no_such_category" unique_id = "veryunique3" @@ -1540,22 +1631,22 @@ async def help_test_entity_category( async def help_test_publishing_with_custom_encoding( - hass, - mqtt_mock_entry_with_yaml_config, - caplog, - domain, - config, - service, - topic, - parameters, - payload, - template, - tpl_par="value", - tpl_output=None, -): + hass: HomeAssistant, + mqtt_mock_entry_with_yaml_config: MqttMockHAClientGenerator, + caplog: pytest.LogCaptureFixture, + domain: str, + config: ConfigType, + service: str, + topic: str, + parameters: dict[str, Any] | None, + payload: str, + template: str | None, + tpl_par: str = "value", + tpl_output: str | None = None, +) -> None: """Test a service with publishing MQTT payload with different encoding.""" # prepare config for tests - test_config = { + test_config: dict[str, dict[str, Any]] = { "test1": {"encoding": None, "cmd_tpl": False}, "test2": {"encoding": "utf-16", "cmd_tpl": False}, "test3": {"encoding": "", "cmd_tpl": False}, @@ -1565,7 +1656,7 @@ async def help_test_publishing_with_custom_encoding( setup_config = [] service_data = {} for test_id, test_data in test_config.items(): - test_config_setup = copy.deepcopy(config[mqtt.DOMAIN][domain]) + test_config_setup: dict[str, Any] = copy.copy(config[mqtt.DOMAIN][domain]) test_config_setup.update( { topic: f"cmd/{test_id}", @@ -1657,7 +1748,12 @@ async def help_test_publishing_with_custom_encoding( mqtt_mock.async_publish.reset_mock() -async def help_test_reload_with_config(hass, caplog, tmp_path, config): +async def help_test_reload_with_config( + hass: HomeAssistant, + caplog: pytest.LogCaptureFixture, + tmp_path: Path, + config: ConfigType, +) -> None: """Test reloading with supplied config.""" new_yaml_config_file = tmp_path / "configuration.yaml" new_yaml_config = yaml.dump(config) @@ -1674,7 +1770,9 @@ async def help_test_reload_with_config(hass, caplog, tmp_path, config): await hass.async_block_till_done() -async def help_test_entry_reload_with_new_config(hass, tmp_path, new_config): +async def help_test_entry_reload_with_new_config( + hass: HomeAssistant, tmp_path: Path, new_config: ConfigType +) -> None: """Test reloading with supplied config.""" mqtt_config_entry = hass.config_entries.async_entries(mqtt.DOMAIN)[0] assert mqtt_config_entry.state is ConfigEntryState.LOADED @@ -1694,8 +1792,13 @@ async def help_test_entry_reload_with_new_config(hass, tmp_path, new_config): async def help_test_reloadable( - hass, mqtt_mock_entry_with_yaml_config, caplog, tmp_path, domain, config -): + hass: HomeAssistant, + mqtt_mock_entry_with_yaml_config: MqttMockHAClientGenerator, + caplog: pytest.LogCaptureFixture, + tmp_path: Path, + domain: str, + config: ConfigType, +) -> None: """Test reloading an MQTT platform.""" config = copy.deepcopy(config[mqtt.DOMAIN][domain]) # Create and test an old config of 2 entities based on the config supplied @@ -1738,11 +1841,13 @@ async def help_test_reloadable( assert hass.states.get(f"{domain}.test_new_3") -async def help_test_setup_manual_entity_from_yaml(hass, config): +async def help_test_setup_manual_entity_from_yaml( + hass: HomeAssistant, config: ConfigType +) -> None: """Help to test setup from yaml through configuration entry.""" calls = MagicMock() - async def mock_reload(hass): + async def mock_reload(hass: HomeAssistant) -> None: """Mock reload.""" calls() @@ -1761,7 +1866,9 @@ async def help_test_setup_manual_entity_from_yaml(hass, config): calls.assert_called_once() -async def help_test_unload_config_entry(hass, tmp_path, newconfig): +async def help_test_unload_config_entry( + hass: HomeAssistant, tmp_path: Path, newconfig: ConfigType +) -> None: """Test unloading the MQTT config entry.""" mqtt_config_entry = hass.config_entries.async_entries(mqtt.DOMAIN)[0] assert mqtt_config_entry.state is ConfigEntryState.LOADED @@ -1771,20 +1878,22 @@ async def help_test_unload_config_entry(hass, tmp_path, newconfig): new_yaml_config_file.write_text(new_yaml_config) with patch.object(hass_config, "YAML_CONFIG_FILE", new_yaml_config_file): assert await hass.config_entries.async_unload(mqtt_config_entry.entry_id) - assert mqtt_config_entry.state is ConfigEntryState.NOT_LOADED + # work-a-round mypy bug https://github.com/python/mypy/issues/9005#issuecomment-1280985006 + updated_config_entry = mqtt_config_entry + assert updated_config_entry.state is ConfigEntryState.NOT_LOADED await hass.async_block_till_done() async def help_test_unload_config_entry_with_platform( - hass, - mqtt_mock_entry_with_yaml_config, - tmp_path, - domain, - config, -): + hass: HomeAssistant, + mqtt_mock_entry_with_yaml_config: MqttMockHAClientGenerator, + tmp_path: Path, + domain: str, + config: dict[str, dict[str, Any]], +) -> None: """Test unloading the MQTT config entry with a specific platform domain.""" # prepare setup through configuration.yaml - config_setup = copy.deepcopy(config) + config_setup: dict[str, dict[str, Any]] = copy.deepcopy(config) config_setup[mqtt.DOMAIN][domain]["name"] = "config_setup" config_name = config_setup assert await async_setup_component(hass, mqtt.DOMAIN, config_setup) @@ -1830,4 +1939,4 @@ async def help_test_discovery_setup( ) await hass.async_block_till_done() state = hass.states.get(f"{domain}.{name}") - assert state.state is not None + assert state and state.state is not None