Mqtt prepare test base part1 (#89796)

* Refactor test_reloadable

* Refactor test_disabling_and_enabling_entry

* optimize test_unload_config_entry

* Cleanup help_test_unload_config_entry

* cleanup test_unload_entry

* Update test tls_version

* More tests to entry only

* Add validate and hassconfig patch

* Revert fixture changes patch_hass_config

* Follow up comments
This commit is contained in:
Jan Bouwhuis
2023-03-16 15:57:01 +01:00
committed by GitHub
parent 886c2635ad
commit c81a38effb
28 changed files with 389 additions and 516 deletions

View File

@@ -14,7 +14,7 @@ import pytest
import voluptuous as vol
import yaml
from homeassistant import config as hass_config
from homeassistant import config as module_hass_config
from homeassistant.components import mqtt
from homeassistant.components.mqtt import CONFIG_SCHEMA, debug_info
from homeassistant.components.mqtt.client import EnsureJobAfterCooldown
@@ -25,6 +25,7 @@ from homeassistant.const import (
ATTR_ASSUMED_STATE,
EVENT_HOMEASSISTANT_STARTED,
EVENT_HOMEASSISTANT_STOP,
SERVICE_RELOAD,
Platform,
UnitOfTemperature,
)
@@ -41,7 +42,6 @@ from homeassistant.util.dt import utcnow
from .test_common import (
help_test_entry_reload_with_new_config,
help_test_reload_with_config,
help_test_setup_manual_entity_from_yaml,
)
@@ -121,20 +121,6 @@ def record_calls(calls: list[ReceiveMessage]) -> MessageCallbackType:
return record_calls
@pytest.fixture
def empty_mqtt_config(
hass: HomeAssistant, tmp_path: Path
) -> Generator[Path, None, None]:
"""Fixture to provide an empty config from yaml."""
new_yaml_config_file = tmp_path / "configuration.yaml"
new_yaml_config_file.write_text("")
with patch.object(
hass_config, "YAML_CONFIG_FILE", new_yaml_config_file
) as empty_config:
yield empty_config
async def test_mqtt_connects_on_home_assistant_mqtt_setup(
hass: HomeAssistant,
mqtt_client_mock: MqttMockPahoClient,
@@ -303,8 +289,21 @@ async def test_command_template_value(hass: HomeAssistant) -> None:
@patch("homeassistant.components.mqtt.PLATFORMS", [Platform.SELECT])
@pytest.mark.parametrize(
"config",
[
{
"command_topic": "test/select",
"name": "Test Select",
"options": ["milk", "beer"],
"command_template": '{"option": "{{ value }}", "entity_id": "{{ entity_id }}", "name": "{{ name }}", "this_object_state": "{{ this.state }}"}',
}
],
)
async def test_command_template_variables(
hass: HomeAssistant, mqtt_mock_entry_with_yaml_config: MqttMockHAClientGenerator
hass: HomeAssistant,
mqtt_mock_entry_no_yaml_config: MqttMockHAClientGenerator,
config: ConfigType,
) -> None:
"""Test the rendering of entity variables."""
topic = "test/select"
@@ -312,22 +311,10 @@ async def test_command_template_variables(
fake_state = ha.State("select.test_select", "milk")
mock_restore_cache(hass, (fake_state,))
assert await async_setup_component(
hass,
mqtt.DOMAIN,
{
mqtt.DOMAIN: {
"select": {
"command_topic": topic,
"name": "Test Select",
"options": ["milk", "beer"],
"command_template": '{"option": "{{ value }}", "entity_id": "{{ entity_id }}", "name": "{{ name }}", "this_object_state": "{{ this.state }}"}',
}
}
},
)
mqtt_mock = await mqtt_mock_entry_no_yaml_config()
await hass.async_block_till_done()
async_fire_mqtt_message(hass, "homeassistant/select/bla/config", json.dumps(config))
await hass.async_block_till_done()
mqtt_mock = await mqtt_mock_entry_with_yaml_config()
state = hass.states.get("select.test_select")
assert state and state.state == "milk"
@@ -1694,7 +1681,6 @@ async def test_initial_setup_logs_error(
hass: HomeAssistant,
caplog: pytest.LogCaptureFixture,
mqtt_client_mock: MqttMockPahoClient,
empty_mqtt_config: Path,
) -> None:
"""Test for setup failure if initial client connection fails."""
entry = MockConfigEntry(domain=mqtt.DOMAIN, data={mqtt.CONF_BROKER: "test-broker"})
@@ -1838,7 +1824,7 @@ async def test_setup_override_configuration(
new_yaml_config_file.write_text(new_yaml_config)
assert new_yaml_config_file.read_text() == new_yaml_config
with patch.object(hass_config, "YAML_CONFIG_FILE", new_yaml_config_file):
with patch.object(module_hass_config, "YAML_CONFIG_FILE", new_yaml_config_file):
# Mock config entry
entry = MockConfigEntry(
domain=mqtt.DOMAIN,
@@ -1914,26 +1900,43 @@ async def test_setup_manual_mqtt_empty_platform(
@patch("homeassistant.components.mqtt.PLATFORMS", [])
@pytest.mark.parametrize(
("mqtt_config_entry_data", "protocol"),
[
(
{
mqtt.CONF_BROKER: "mock-broker",
mqtt.CONF_PROTOCOL: "3.1",
},
3,
),
(
{
mqtt.CONF_BROKER: "mock-broker",
mqtt.CONF_PROTOCOL: "3.1.1",
},
4,
),
(
{
mqtt.CONF_BROKER: "mock-broker",
mqtt.CONF_PROTOCOL: "5",
},
5,
),
],
)
async def test_setup_mqtt_client_protocol(
hass: HomeAssistant, mqtt_mock_entry_with_yaml_config: MqttMockHAClientGenerator
hass: HomeAssistant,
mqtt_mock_entry_no_yaml_config: MqttMockHAClientGenerator,
protocol: int,
) -> None:
"""Test MQTT client protocol setup."""
with patch("paho.mqtt.client.Client") as mock_client:
assert await async_setup_component(
hass,
mqtt.DOMAIN,
{
mqtt.DOMAIN: {
mqtt.config_integration.CONF_PROTOCOL: "3.1",
}
},
)
mock_client.on_connect(return_value=0)
await hass.async_block_till_done()
await mqtt_mock_entry_with_yaml_config()
await mqtt_mock_entry_no_yaml_config()
# check if protocol setup was correctly
assert mock_client.call_args[1]["protocol"] == 3
# check if protocol setup was correctly
assert mock_client.call_args[1]["protocol"] == protocol
@patch("homeassistant.components.mqtt.client.TIMEOUT_ACK", 0.2)
@@ -1999,18 +2002,20 @@ async def test_setup_raises_config_entry_not_ready_if_no_connect_broker(
@pytest.mark.parametrize(
("config", "insecure_param"),
("mqtt_config_entry_data", "insecure_param"),
[
({"certificate": "auto"}, "not set"),
({"certificate": "auto", "tls_insecure": False}, False),
({"certificate": "auto", "tls_insecure": True}, True),
({"broker": "test-broker", "certificate": "auto"}, "not set"),
(
{"broker": "test-broker", "certificate": "auto", "tls_insecure": False},
False,
),
({"broker": "test-broker", "certificate": "auto", "tls_insecure": True}, True),
],
)
@patch("homeassistant.components.mqtt.PLATFORMS", [])
async def test_setup_uses_certificate_on_certificate_set_to_auto_and_insecure(
hass: HomeAssistant,
mqtt_mock_entry_with_yaml_config: MqttMockHAClientGenerator,
config: ConfigType,
mqtt_mock_entry_no_yaml_config: MqttMockHAClientGenerator,
insecure_param: bool | str,
) -> None:
"""Test setup uses bundled certs when certificate is set to auto and insecure."""
@@ -2028,48 +2033,41 @@ async def test_setup_uses_certificate_on_certificate_set_to_auto_and_insecure(
with patch("paho.mqtt.client.Client") as mock_client:
mock_client().tls_set = mock_tls_set
mock_client().tls_insecure_set = mock_tls_insecure_set
assert await async_setup_component(
hass,
mqtt.DOMAIN,
{mqtt.DOMAIN: config},
)
await mqtt_mock_entry_no_yaml_config()
await hass.async_block_till_done()
await mqtt_mock_entry_with_yaml_config()
assert calls
assert calls
import certifi
import certifi
expected_certificate = certifi.where()
assert calls[0][0] == expected_certificate
expected_certificate = certifi.where()
assert calls[0][0] == expected_certificate
# test if insecure is set
assert insecure_check["insecure"] == insecure_param
# test if insecure is set
assert insecure_check["insecure"] == insecure_param
@pytest.mark.parametrize(
"mqtt_config_entry_data",
[
{
mqtt.CONF_BROKER: "mock-broker",
mqtt.CONF_CERTIFICATE: "auto",
}
],
)
async def test_tls_version(
hass: HomeAssistant, mqtt_mock_entry_with_yaml_config: MqttMockHAClientGenerator
hass: HomeAssistant,
mqtt_client_mock: MqttMockPahoClient,
mqtt_mock_entry_no_yaml_config: MqttMockHAClientGenerator,
) -> None:
"""Test setup defaults for tls."""
calls = []
def mock_tls_set(
certificate, certfile=None, keyfile=None, tls_version=None
) -> None:
calls.append((certificate, certfile, keyfile, tls_version))
with patch("paho.mqtt.client.Client") as mock_client:
mock_client().tls_set = mock_tls_set
assert await async_setup_component(
hass,
mqtt.DOMAIN,
{mqtt.DOMAIN: {"certificate": "auto"}},
)
await hass.async_block_till_done()
await mqtt_mock_entry_with_yaml_config()
assert calls
assert calls[0][3] == ssl.PROTOCOL_TLS_CLIENT
await mqtt_mock_entry_no_yaml_config()
await hass.async_block_till_done()
assert (
mqtt_client_mock.tls_set.mock_calls[0][2]["tls_version"]
== ssl.PROTOCOL_TLS_CLIENT
)
@pytest.mark.parametrize(
@@ -3258,7 +3256,6 @@ async def test_unload_config_entry(
hass: HomeAssistant,
mqtt_mock: MqttMockHAClient,
mqtt_client_mock: MqttMockPahoClient,
tmp_path: Path,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test unloading the MQTT entry."""
@@ -3272,15 +3269,11 @@ async def test_unload_config_entry(
mqtt_client_mock.reset_mock()
mqtt.publish(hass, "just_in_time", "published", qos=0, retain=False)
new_yaml_config_file = tmp_path / "configuration.yaml"
new_yaml_config = yaml.dump({})
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)
new_mqtt_config_entry = mqtt_config_entry
mqtt_client_mock.publish.assert_any_call("just_in_time", "published", 0, False)
assert new_mqtt_config_entry.state is ConfigEntryState.NOT_LOADED
await hass.async_block_till_done()
assert await hass.config_entries.async_unload(mqtt_config_entry.entry_id)
new_mqtt_config_entry = mqtt_config_entry
mqtt_client_mock.publish.assert_any_call("just_in_time", "published", 0, False)
assert new_mqtt_config_entry.state is ConfigEntryState.NOT_LOADED
await hass.async_block_till_done()
assert not hass.services.has_service(mqtt.DOMAIN, "dump")
assert not hass.services.has_service(mqtt.DOMAIN, "publish")
assert "No ACK from MQTT server" not in caplog.text
@@ -3318,131 +3311,108 @@ async def test_publish_or_subscribe_without_valid_config_entry(
@patch("homeassistant.components.mqtt.PLATFORMS", [Platform.LIGHT])
async def test_reload_entry_with_new_config(
hass: HomeAssistant, tmp_path: Path
@pytest.mark.parametrize(
"hass_config",
[
{
"mqtt": {
"light": [
{"name": "test_new_modern", "command_topic": "test-topic_new"}
]
}
}
],
)
async def test_disabling_and_enabling_entry(
hass: HomeAssistant,
mqtt_mock_entry_no_yaml_config: MqttMockHAClientGenerator,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test reloading the config entry with a new yaml config."""
config_old = {
"mqtt": {"light": [{"name": "test_old1", "command_topic": "test-topic_old"}]}
}
config_yaml_new = {
"mqtt": {
"light": [{"name": "test_new_modern", "command_topic": "test-topic_new"}]
},
}
await help_test_setup_manual_entity_from_yaml(hass, config_old)
assert hass.states.get("light.test_old1") is not None
"""Test disabling and enabling the config entry."""
await mqtt_mock_entry_no_yaml_config()
entry = hass.config_entries.async_entries(mqtt.DOMAIN)[0]
assert entry.state is ConfigEntryState.LOADED
# Late discovery of a light
config = '{"name": "abc", "command_topic": "test-topic"}'
async_fire_mqtt_message(hass, "homeassistant/light/abc/config", config)
# Disable MQTT config entry
await hass.config_entries.async_set_disabled_by(
entry.entry_id, ConfigEntryDisabler.USER
)
await hass.async_block_till_done()
await hass.async_block_till_done()
assert (
"MQTT integration is disabled, skipping setup of discovered item MQTT light"
in caplog.text
)
new_mqtt_config_entry = entry
assert new_mqtt_config_entry.state is ConfigEntryState.NOT_LOADED
# Enable the entry again
await hass.config_entries.async_set_disabled_by(entry.entry_id, None)
await hass.async_block_till_done()
await hass.async_block_till_done()
new_mqtt_config_entry = entry
assert new_mqtt_config_entry.state is ConfigEntryState.LOADED
await help_test_entry_reload_with_new_config(hass, tmp_path, config_yaml_new)
assert hass.states.get("light.test_old1") is None
assert hass.states.get("light.test_new_modern") is not None
@patch("homeassistant.components.mqtt.PLATFORMS", [Platform.LIGHT])
async def test_disabling_and_enabling_entry(
hass: HomeAssistant, tmp_path: Path
) -> None:
"""Test disabling and enabling the config entry."""
config_old = {
"mqtt": {"light": [{"name": "test_old1", "command_topic": "test-topic_old"}]}
}
config_yaml_new = {
"mqtt": {
"light": [{"name": "test_new_modern", "command_topic": "test-topic_new"}]
},
}
await help_test_setup_manual_entity_from_yaml(hass, config_old)
assert hass.states.get("light.test_old1") is not None
mqtt_config_entry = hass.config_entries.async_entries(mqtt.DOMAIN)[0]
assert mqtt_config_entry.state is ConfigEntryState.LOADED
new_yaml_config_file = tmp_path / "configuration.yaml"
new_yaml_config = yaml.dump(config_yaml_new)
new_yaml_config_file.write_text(new_yaml_config)
assert new_yaml_config_file.read_text() == new_yaml_config
with patch.object(hass_config, "YAML_CONFIG_FILE", new_yaml_config_file), patch(
"paho.mqtt.client.Client"
) as mock_client:
mock_client().connect = lambda *args: 0
# Late discovery of a light
config = '{"name": "abc", "command_topic": "test-topic"}'
async_fire_mqtt_message(hass, "homeassistant/light/abc/config", config)
# Disable MQTT config entry
await hass.config_entries.async_set_disabled_by(
mqtt_config_entry.entry_id, ConfigEntryDisabler.USER
)
await hass.async_block_till_done()
await hass.async_block_till_done()
new_mqtt_config_entry = mqtt_config_entry
assert new_mqtt_config_entry.state is ConfigEntryState.NOT_LOADED
assert hass.states.get("light.test_old1") is None
# Enable the entry again
await hass.config_entries.async_set_disabled_by(
mqtt_config_entry.entry_id, None
)
await hass.async_block_till_done()
await hass.async_block_till_done()
new_mqtt_config_entry = mqtt_config_entry
assert new_mqtt_config_entry.state is ConfigEntryState.LOADED
assert hass.states.get("light.test_old1") is None
assert hass.states.get("light.test_new_modern") is not None
@patch("homeassistant.components.mqtt.PLATFORMS", [Platform.LIGHT])
@pytest.mark.parametrize(
("config", "unique"),
("hass_config", "unique"),
[
(
[
{
"name": "test1",
"unique_id": "very_not_unique_deadbeef",
"command_topic": "test-topic_unique",
},
{
"name": "test2",
"unique_id": "very_not_unique_deadbeef",
"command_topic": "test-topic_unique",
},
],
{
mqtt.DOMAIN: {
"light": [
{
"name": "test1",
"unique_id": "very_not_unique_deadbeef",
"command_topic": "test-topic_unique",
},
{
"name": "test2",
"unique_id": "very_not_unique_deadbeef",
"command_topic": "test-topic_unique",
},
]
}
},
False,
),
(
[
{
"name": "test1",
"unique_id": "very_unique_deadbeef1",
"command_topic": "test-topic_unique",
},
{
"name": "test2",
"unique_id": "very_unique_deadbeef2",
"command_topic": "test-topic_unique",
},
],
{
mqtt.DOMAIN: {
"light": [
{
"name": "test1",
"unique_id": "very_unique_deadbeef1",
"command_topic": "test-topic_unique",
},
{
"name": "test2",
"unique_id": "very_unique_deadbeef2",
"command_topic": "test-topic_unique",
},
]
}
},
True,
),
],
)
async def test_setup_manual_items_with_unique_ids(
hass: HomeAssistant,
tmp_path: Path,
caplog: pytest.LogCaptureFixture,
config: ConfigType,
hass_config: ConfigType,
unique: bool,
) -> None:
"""Test setup manual items is generating unique id's."""
await help_test_setup_manual_entity_from_yaml(
hass, {mqtt.DOMAIN: {"light": config}}
)
await help_test_setup_manual_entity_from_yaml(hass, hass_config)
assert hass.states.get("light.test1") is not None
assert (hass.states.get("light.test2") is not None) == unique
@@ -3450,9 +3420,13 @@ async def test_setup_manual_items_with_unique_ids(
# reload and assert again
caplog.clear()
await help_test_entry_reload_with_new_config(
hass, tmp_path, {"mqtt": {"light": config}}
await hass.services.async_call(
"mqtt",
SERVICE_RELOAD,
{},
blocking=True,
)
await hass.async_block_till_done()
assert hass.states.get("light.test1") is not None
assert (hass.states.get("light.test2") is not None) == unique
@@ -3489,21 +3463,26 @@ async def test_remove_unknown_conf_entry_options(
@patch("homeassistant.components.mqtt.PLATFORMS", [Platform.LIGHT])
@pytest.mark.parametrize(
"hass_config",
[
{
"mqtt": {
"light": [
{
"name": "test_manual",
"unique_id": "test_manual_unique_id123",
"command_topic": "test-topic_manual",
}
]
}
}
],
)
async def test_link_config_entry(
hass: HomeAssistant, tmp_path: Path, caplog: pytest.LogCaptureFixture
hass: HomeAssistant, hass_config: ConfigType, caplog: pytest.LogCaptureFixture
) -> None:
"""Test manual and dynamically setup entities are linked to the config entry."""
config_manual = {
"mqtt": {
"light": [
{
"name": "test_manual",
"unique_id": "test_manual_unique_id123",
"command_topic": "test-topic_manual",
}
]
}
}
config_discovery = {
"name": "test_discovery",
"unique_id": "test_discovery_unique456",
@@ -3511,7 +3490,7 @@ async def test_link_config_entry(
}
# set up manual item
await help_test_setup_manual_entity_from_yaml(hass, config_manual)
await help_test_setup_manual_entity_from_yaml(hass, hass_config)
# set up item through discovery
async_fire_mqtt_message(
@@ -3540,7 +3519,10 @@ async def test_link_config_entry(
assert _check_entities() == 2
# reload entry and assert again
await help_test_entry_reload_with_new_config(hass, tmp_path, config_manual)
with patch("paho.mqtt.client.Client"):
await hass.config_entries.async_reload(mqtt_config_entry.entry_id)
await hass.async_block_till_done()
# manual set up item should remain
assert _check_entities() == 1
# set up item through discovery
@@ -3551,7 +3533,13 @@ async def test_link_config_entry(
assert _check_entities() == 2
# reload manual configured items and assert again
await help_test_reload_with_config(hass, caplog, tmp_path, config_manual)
await hass.services.async_call(
"mqtt",
SERVICE_RELOAD,
{},
blocking=True,
)
await hass.async_block_till_done()
assert _check_entities() == 2