hass-core/tests/components/tasmota/test_discovery.py

373 lines
11 KiB
Python
Raw Normal View History

"""The tests for the MQTT discovery."""
import copy
import json
from hatasmota.const import CONF_ONLINE
import pytest
from homeassistant.components.tasmota.const import DEFAULT_PREFIX
from homeassistant.components.tasmota.discovery import ALREADY_DISCOVERED
from tests.async_mock import patch
from tests.common import async_fire_mqtt_message
from tests.components.tasmota.conftest import setup_tasmota_helper
DEFAULT_CONFIG = {
"dn": "My Device",
"fn": ["Beer", "Milk", "Three", "Four", "Five"],
"hn": "tasmota_49A3BC",
"mac": "00000049A3BC",
"md": "Sonoff 123",
"ofl": "offline",
CONF_ONLINE: "online",
"state": ["OFF", "ON", "TOGGLE", "HOLD"],
"sw": "2.3.3.4",
"t": "tasmota_49A3BC",
"t_f": "%topic%/%prefix%/",
"t_p": ["cmnd", "stat", "tele"],
"li": [0, 0, 0, 0, 0, 0, 0, 0],
"rl": [0, 0, 0, 0, 0, 0, 0, 0],
"se": [],
"ver": 1,
}
async def test_subscribing_config_topic(hass, mqtt_mock, setup_tasmota):
"""Test setting up discovery."""
discovery_topic = DEFAULT_PREFIX
assert mqtt_mock.async_subscribe.called
call_args = mqtt_mock.async_subscribe.mock_calls[0][1]
assert call_args[0] == discovery_topic + "/#"
assert call_args[2] == 0
async def test_valid_discovery_message(hass, mqtt_mock, caplog):
"""Test discovery callback called."""
config = copy.deepcopy(DEFAULT_CONFIG)
with patch(
"homeassistant.components.tasmota.discovery.tasmota_has_entities_with_platform"
) as mock_tasmota_has_entities:
await setup_tasmota_helper(hass)
async_fire_mqtt_message(
hass, f"{DEFAULT_PREFIX}/00000049A3BC/config", json.dumps(config)
)
await hass.async_block_till_done()
assert mock_tasmota_has_entities.called
async def test_invalid_topic(hass, mqtt_mock):
"""Test receiving discovery message on wrong topic."""
with patch(
"homeassistant.components.tasmota.discovery.tasmota_has_entities_with_platform"
) as mock_tasmota_has_entities:
await setup_tasmota_helper(hass)
async_fire_mqtt_message(hass, f"{DEFAULT_PREFIX}/123456/configuration", "{}")
await hass.async_block_till_done()
assert not mock_tasmota_has_entities.called
async def test_invalid_message(hass, mqtt_mock, caplog):
"""Test receiving an invalid message."""
with patch(
"homeassistant.components.tasmota.discovery.tasmota_has_entities_with_platform"
) as mock_tasmota_has_entities:
await setup_tasmota_helper(hass)
async_fire_mqtt_message(hass, f"{DEFAULT_PREFIX}/123456/config", "asd")
await hass.async_block_till_done()
assert "Invalid discovery message" in caplog.text
assert not mock_tasmota_has_entities.called
async def test_invalid_mac(hass, mqtt_mock, caplog):
"""Test topic is not matching device MAC."""
config = copy.deepcopy(DEFAULT_CONFIG)
with patch(
"homeassistant.components.tasmota.discovery.tasmota_has_entities_with_platform"
) as mock_tasmota_has_entities:
await setup_tasmota_helper(hass)
async_fire_mqtt_message(
hass, f"{DEFAULT_PREFIX}/00000049A3BA/config", json.dumps(config)
)
await hass.async_block_till_done()
assert "MAC mismatch" in caplog.text
assert not mock_tasmota_has_entities.called
async def test_correct_config_discovery(
hass, mqtt_mock, caplog, device_reg, entity_reg, setup_tasmota
):
"""Test receiving valid discovery message."""
config = copy.deepcopy(DEFAULT_CONFIG)
config["rl"][0] = 1
mac = config["mac"]
async_fire_mqtt_message(
hass,
f"{DEFAULT_PREFIX}/{mac}/config",
json.dumps(config),
)
await hass.async_block_till_done()
# Verify device and registry entries are created
device_entry = device_reg.async_get_device(set(), {("mac", mac)})
assert device_entry is not None
entity_entry = entity_reg.async_get("switch.beer")
assert entity_entry is not None
state = hass.states.get("switch.beer")
assert state is not None
assert state.name == "Beer"
assert (mac, "switch", 0) in hass.data[ALREADY_DISCOVERED]
async def test_device_discover(
hass, mqtt_mock, caplog, device_reg, entity_reg, setup_tasmota
):
"""Test setting up a device."""
config = copy.deepcopy(DEFAULT_CONFIG)
mac = config["mac"]
async_fire_mqtt_message(
hass,
f"{DEFAULT_PREFIX}/{mac}/config",
json.dumps(config),
)
await hass.async_block_till_done()
# Verify device and registry entries are created
device_entry = device_reg.async_get_device(set(), {("mac", mac)})
assert device_entry is not None
assert device_entry.manufacturer == "Tasmota"
assert device_entry.model == config["md"]
assert device_entry.name == config["dn"]
assert device_entry.sw_version == config["sw"]
async def test_device_update(
hass, mqtt_mock, caplog, device_reg, entity_reg, setup_tasmota
):
"""Test updating a device."""
config = copy.deepcopy(DEFAULT_CONFIG)
config["md"] = "Model 1"
config["dn"] = "Name 1"
config["sw"] = "v1.2.3.4"
mac = config["mac"]
async_fire_mqtt_message(
hass,
f"{DEFAULT_PREFIX}/{mac}/config",
json.dumps(config),
)
await hass.async_block_till_done()
# Verify device entry is created
device_entry = device_reg.async_get_device(set(), {("mac", mac)})
assert device_entry is not None
# Update device parameters
config["md"] = "Another model"
config["dn"] = "Another name"
config["sw"] = "v6.6.6"
async_fire_mqtt_message(
hass,
f"{DEFAULT_PREFIX}/{mac}/config",
json.dumps(config),
)
await hass.async_block_till_done()
# Verify device entry is updated
device_entry = device_reg.async_get_device(set(), {("mac", mac)})
assert device_entry is not None
assert device_entry.model == "Another model"
assert device_entry.name == "Another name"
assert device_entry.sw_version == "v6.6.6"
@pytest.mark.no_fail_on_log_exception
async def test_discovery_broken(hass, mqtt_mock, caplog, device_reg, setup_tasmota):
"""Test handling of exception when creating discovered device."""
config = copy.deepcopy(DEFAULT_CONFIG)
mac = config["mac"]
data = json.dumps(config)
# Trigger an exception when the entity is added
with patch(
"hatasmota.discovery.get_device_config_helper",
return_value=object(),
):
async_fire_mqtt_message(hass, f"{DEFAULT_PREFIX}/{mac}/config", data)
await hass.async_block_till_done()
# Verify device entry is not created
device_entry = device_reg.async_get_device(set(), {("mac", mac)})
assert device_entry is None
assert (
"Exception in async_discover_device when dispatching 'tasmota_discovery_device'"
in caplog.text
)
async_fire_mqtt_message(hass, f"{DEFAULT_PREFIX}/{mac}/config", data)
await hass.async_block_till_done()
# Verify device entry is created
device_entry = device_reg.async_get_device(set(), {("mac", mac)})
assert device_entry is not None
async def test_device_remove(
hass, mqtt_mock, caplog, device_reg, entity_reg, setup_tasmota
):
"""Test removing a discovered device."""
config = copy.deepcopy(DEFAULT_CONFIG)
mac = config["mac"]
async_fire_mqtt_message(
hass,
f"{DEFAULT_PREFIX}/{mac}/config",
json.dumps(config),
)
await hass.async_block_till_done()
# Verify device entry is created
device_entry = device_reg.async_get_device(set(), {("mac", mac)})
assert device_entry is not None
async_fire_mqtt_message(
hass,
f"{DEFAULT_PREFIX}/{mac}/config",
"",
)
await hass.async_block_till_done()
# Verify device entry is removed
device_entry = device_reg.async_get_device(set(), {("mac", mac)})
assert device_entry is None
async def test_device_remove_stale(hass, mqtt_mock, caplog, device_reg, setup_tasmota):
"""Test removing a stale (undiscovered) device does not throw."""
mac = "00000049A3BC"
config_entry = hass.config_entries.async_entries("tasmota")[0]
# Create a device
device_reg.async_get_or_create(
config_entry_id=config_entry.entry_id,
connections={("mac", mac)},
)
# Verify device entry was created
device_entry = device_reg.async_get_device(set(), {("mac", mac)})
assert device_entry is not None
# Remove the device
device_reg.async_remove_device(device_entry.id)
# Verify device entry is removed
device_entry = device_reg.async_get_device(set(), {("mac", mac)})
assert device_entry is None
async def test_device_rediscover(
hass, mqtt_mock, caplog, device_reg, entity_reg, setup_tasmota
):
"""Test removing a device."""
config = copy.deepcopy(DEFAULT_CONFIG)
mac = config["mac"]
async_fire_mqtt_message(
hass,
f"{DEFAULT_PREFIX}/{mac}/config",
json.dumps(config),
)
await hass.async_block_till_done()
# Verify device entry is created
device_entry1 = device_reg.async_get_device(set(), {("mac", mac)})
assert device_entry1 is not None
async_fire_mqtt_message(
hass,
f"{DEFAULT_PREFIX}/{mac}/config",
"",
)
await hass.async_block_till_done()
# Verify device entry is removed
device_entry = device_reg.async_get_device(set(), {("mac", mac)})
assert device_entry is None
async_fire_mqtt_message(
hass,
f"{DEFAULT_PREFIX}/{mac}/config",
json.dumps(config),
)
await hass.async_block_till_done()
# Verify device entry is created, and id is reused
device_entry = device_reg.async_get_device(set(), {("mac", mac)})
assert device_entry is not None
assert device_entry1.id == device_entry.id
async def test_entity_duplicate_discovery(hass, mqtt_mock, caplog, setup_tasmota):
"""Test entities are not duplicated."""
config = copy.deepcopy(DEFAULT_CONFIG)
config["rl"][0] = 1
mac = config["mac"]
async_fire_mqtt_message(
hass,
f"{DEFAULT_PREFIX}/{mac}/config",
json.dumps(config),
)
async_fire_mqtt_message(
hass,
f"{DEFAULT_PREFIX}/{mac}/config",
json.dumps(config),
)
await hass.async_block_till_done()
state = hass.states.get("switch.beer")
state_duplicate = hass.states.get("binary_sensor.beer1")
assert state is not None
assert state.name == "Beer"
assert state_duplicate is None
assert (
f"Entity already added, sending update: switch ('{mac}', 'switch', 0)"
in caplog.text
)
async def test_entity_duplicate_removal(hass, mqtt_mock, caplog, setup_tasmota):
"""Test removing entity twice."""
config = copy.deepcopy(DEFAULT_CONFIG)
config["rl"][0] = 1
mac = config["mac"]
async_fire_mqtt_message(
hass,
f"{DEFAULT_PREFIX}/{mac}/config",
json.dumps(config),
)
await hass.async_block_till_done()
config["rl"][0] = 0
async_fire_mqtt_message(hass, f"{DEFAULT_PREFIX}/{mac}/config", json.dumps(config))
await hass.async_block_till_done()
assert f"Removing entity: switch ('{mac}', 'switch', 0)" in caplog.text
caplog.clear()
async_fire_mqtt_message(hass, f"{DEFAULT_PREFIX}/{mac}/config", json.dumps(config))
await hass.async_block_till_done()
assert f"Removing entity: switch ('{mac}', 'switch', 0)" not in caplog.text