741 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			Python
		
	
	
			
		
		
	
	
			741 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			Python
		
	
	
"""The tests for the MQTT discovery."""
 | 
						|
 | 
						|
import copy
 | 
						|
import json
 | 
						|
from unittest.mock import ANY, patch
 | 
						|
 | 
						|
import pytest
 | 
						|
 | 
						|
from homeassistant.components.tasmota.const import DEFAULT_PREFIX
 | 
						|
from homeassistant.components.tasmota.discovery import ALREADY_DISCOVERED
 | 
						|
from homeassistant.core import HomeAssistant
 | 
						|
from homeassistant.helpers import (
 | 
						|
    device_registry as dr,
 | 
						|
    entity_registry as er,
 | 
						|
    issue_registry as ir,
 | 
						|
)
 | 
						|
from homeassistant.setup import async_setup_component
 | 
						|
 | 
						|
from .conftest import setup_tasmota_helper
 | 
						|
from .test_common import DEFAULT_CONFIG, DEFAULT_CONFIG_9_0_0_3, remove_device
 | 
						|
 | 
						|
from tests.common import MockConfigEntry, async_fire_mqtt_message
 | 
						|
from tests.typing import MqttMockHAClient, WebSocketGenerator
 | 
						|
 | 
						|
 | 
						|
async def test_subscribing_config_topic(
 | 
						|
    hass: HomeAssistant, mqtt_mock: MqttMockHAClient, setup_tasmota
 | 
						|
) -> None:
 | 
						|
    """Test setting up discovery."""
 | 
						|
    discovery_topic = DEFAULT_PREFIX
 | 
						|
 | 
						|
    assert mqtt_mock.async_subscribe.called
 | 
						|
    mqtt_mock.async_subscribe.assert_any_call(
 | 
						|
        discovery_topic + "/#", ANY, 0, "utf-8", ANY
 | 
						|
    )
 | 
						|
 | 
						|
 | 
						|
async def test_future_discovery_message(
 | 
						|
    hass: HomeAssistant, mqtt_mock: MqttMockHAClient, caplog: pytest.LogCaptureFixture
 | 
						|
) -> None:
 | 
						|
    """Test we handle backwards compatible discovery messages."""
 | 
						|
    config = copy.deepcopy(DEFAULT_CONFIG)
 | 
						|
    config["future_option"] = "BEST_SINCE_SLICED_BREAD"
 | 
						|
    config["so"]["another_future_option"] = "EVEN_BETTER"
 | 
						|
 | 
						|
    with patch(
 | 
						|
        "homeassistant.components.tasmota.discovery.tasmota_get_device_config",
 | 
						|
        return_value={},
 | 
						|
    ) as mock_tasmota_get_device_config:
 | 
						|
        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_get_device_config.called
 | 
						|
 | 
						|
 | 
						|
async def test_valid_discovery_message(
 | 
						|
    hass: HomeAssistant, mqtt_mock: MqttMockHAClient, caplog: pytest.LogCaptureFixture
 | 
						|
) -> None:
 | 
						|
    """Test discovery callback called."""
 | 
						|
    config = copy.deepcopy(DEFAULT_CONFIG)
 | 
						|
 | 
						|
    with patch(
 | 
						|
        "homeassistant.components.tasmota.discovery.tasmota_get_device_config",
 | 
						|
        return_value={},
 | 
						|
    ) as mock_tasmota_get_device_config:
 | 
						|
        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_get_device_config.called
 | 
						|
 | 
						|
 | 
						|
async def test_invalid_topic(hass: HomeAssistant, mqtt_mock: MqttMockHAClient) -> None:
 | 
						|
    """Test receiving discovery message on wrong topic."""
 | 
						|
    with patch(
 | 
						|
        "homeassistant.components.tasmota.discovery.tasmota_get_device_config"
 | 
						|
    ) as mock_tasmota_get_device_config:
 | 
						|
        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_get_device_config.called
 | 
						|
 | 
						|
 | 
						|
async def test_invalid_message(
 | 
						|
    hass: HomeAssistant, mqtt_mock: MqttMockHAClient, caplog: pytest.LogCaptureFixture
 | 
						|
) -> None:
 | 
						|
    """Test receiving an invalid message."""
 | 
						|
    with patch(
 | 
						|
        "homeassistant.components.tasmota.discovery.tasmota_get_device_config"
 | 
						|
    ) as mock_tasmota_get_device_config:
 | 
						|
        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_get_device_config.called
 | 
						|
 | 
						|
 | 
						|
async def test_invalid_mac(
 | 
						|
    hass: HomeAssistant, mqtt_mock: MqttMockHAClient, caplog: pytest.LogCaptureFixture
 | 
						|
) -> None:
 | 
						|
    """Test topic is not matching device MAC."""
 | 
						|
    config = copy.deepcopy(DEFAULT_CONFIG)
 | 
						|
 | 
						|
    with patch(
 | 
						|
        "homeassistant.components.tasmota.discovery.tasmota_get_device_config"
 | 
						|
    ) as mock_tasmota_get_device_config:
 | 
						|
        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_get_device_config.called
 | 
						|
 | 
						|
 | 
						|
async def test_correct_config_discovery(
 | 
						|
    hass: HomeAssistant,
 | 
						|
    mqtt_mock: MqttMockHAClient,
 | 
						|
    device_registry: dr.DeviceRegistry,
 | 
						|
    entity_registry: er.EntityRegistry,
 | 
						|
    setup_tasmota,
 | 
						|
) -> None:
 | 
						|
    """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_registry.async_get_device(
 | 
						|
        connections={(dr.CONNECTION_NETWORK_MAC, mac)}
 | 
						|
    )
 | 
						|
    assert device_entry is not None
 | 
						|
    entity_entry = entity_registry.async_get("switch.tasmota_test")
 | 
						|
    assert entity_entry is not None
 | 
						|
 | 
						|
    state = hass.states.get("switch.tasmota_test")
 | 
						|
    assert state is not None
 | 
						|
    assert state.name == "Tasmota Test"
 | 
						|
 | 
						|
    assert (mac, "switch", "relay", 0) in hass.data[ALREADY_DISCOVERED]
 | 
						|
 | 
						|
 | 
						|
async def test_device_discover(
 | 
						|
    hass: HomeAssistant,
 | 
						|
    mqtt_mock: MqttMockHAClient,
 | 
						|
    device_registry: dr.DeviceRegistry,
 | 
						|
    setup_tasmota,
 | 
						|
) -> None:
 | 
						|
    """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_registry.async_get_device(
 | 
						|
        connections={(dr.CONNECTION_NETWORK_MAC, mac)}
 | 
						|
    )
 | 
						|
    assert device_entry is not None
 | 
						|
    assert device_entry.configuration_url == f"http://{config['ip']}/"
 | 
						|
    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_discover_deprecated(
 | 
						|
    hass: HomeAssistant,
 | 
						|
    mqtt_mock: MqttMockHAClient,
 | 
						|
    device_registry: dr.DeviceRegistry,
 | 
						|
    setup_tasmota,
 | 
						|
) -> None:
 | 
						|
    """Test setting up a device with deprecated discovery message."""
 | 
						|
    config = copy.deepcopy(DEFAULT_CONFIG_9_0_0_3)
 | 
						|
    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_registry.async_get_device(
 | 
						|
        connections={(dr.CONNECTION_NETWORK_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: HomeAssistant,
 | 
						|
    mqtt_mock: MqttMockHAClient,
 | 
						|
    device_registry: dr.DeviceRegistry,
 | 
						|
    setup_tasmota,
 | 
						|
) -> None:
 | 
						|
    """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_registry.async_get_device(
 | 
						|
        connections={(dr.CONNECTION_NETWORK_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_registry.async_get_device(
 | 
						|
        connections={(dr.CONNECTION_NETWORK_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"
 | 
						|
 | 
						|
 | 
						|
async def test_device_remove(
 | 
						|
    hass: HomeAssistant,
 | 
						|
    mqtt_mock: MqttMockHAClient,
 | 
						|
    device_registry: dr.DeviceRegistry,
 | 
						|
    setup_tasmota,
 | 
						|
) -> None:
 | 
						|
    """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_registry.async_get_device(
 | 
						|
        connections={(dr.CONNECTION_NETWORK_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_registry.async_get_device(
 | 
						|
        connections={(dr.CONNECTION_NETWORK_MAC, mac)}
 | 
						|
    )
 | 
						|
    assert device_entry is None
 | 
						|
 | 
						|
 | 
						|
async def test_device_remove_multiple_config_entries_1(
 | 
						|
    hass: HomeAssistant,
 | 
						|
    mqtt_mock: MqttMockHAClient,
 | 
						|
    device_registry: dr.DeviceRegistry,
 | 
						|
    setup_tasmota,
 | 
						|
) -> None:
 | 
						|
    """Test removing a discovered device."""
 | 
						|
    config = copy.deepcopy(DEFAULT_CONFIG)
 | 
						|
    mac = config["mac"]
 | 
						|
 | 
						|
    mock_entry = MockConfigEntry(domain="test")
 | 
						|
    mock_entry.add_to_hass(hass)
 | 
						|
 | 
						|
    device_registry.async_get_or_create(
 | 
						|
        config_entry_id=mock_entry.entry_id,
 | 
						|
        connections={(dr.CONNECTION_NETWORK_MAC, mac)},
 | 
						|
    )
 | 
						|
 | 
						|
    tasmota_entry = hass.config_entries.async_entries("tasmota")[0]
 | 
						|
 | 
						|
    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_registry.async_get_device(
 | 
						|
        connections={(dr.CONNECTION_NETWORK_MAC, mac)}
 | 
						|
    )
 | 
						|
    assert device_entry is not None
 | 
						|
    assert device_entry.config_entries == {tasmota_entry.entry_id, mock_entry.entry_id}
 | 
						|
 | 
						|
    async_fire_mqtt_message(
 | 
						|
        hass,
 | 
						|
        f"{DEFAULT_PREFIX}/{mac}/config",
 | 
						|
        "",
 | 
						|
    )
 | 
						|
    await hass.async_block_till_done()
 | 
						|
 | 
						|
    # Verify device entry is not removed
 | 
						|
    device_entry = device_registry.async_get_device(
 | 
						|
        connections={(dr.CONNECTION_NETWORK_MAC, mac)}
 | 
						|
    )
 | 
						|
    assert device_entry is not None
 | 
						|
    assert device_entry.config_entries == {mock_entry.entry_id}
 | 
						|
 | 
						|
 | 
						|
async def test_device_remove_multiple_config_entries_2(
 | 
						|
    hass: HomeAssistant,
 | 
						|
    mqtt_mock: MqttMockHAClient,
 | 
						|
    device_registry: dr.DeviceRegistry,
 | 
						|
    setup_tasmota,
 | 
						|
) -> None:
 | 
						|
    """Test removing a discovered device."""
 | 
						|
    config = copy.deepcopy(DEFAULT_CONFIG)
 | 
						|
    mac = config["mac"]
 | 
						|
 | 
						|
    mock_entry = MockConfigEntry(domain="test")
 | 
						|
    mock_entry.add_to_hass(hass)
 | 
						|
 | 
						|
    device_registry.async_get_or_create(
 | 
						|
        config_entry_id=mock_entry.entry_id,
 | 
						|
        connections={(dr.CONNECTION_NETWORK_MAC, mac)},
 | 
						|
    )
 | 
						|
 | 
						|
    other_device_entry = device_registry.async_get_or_create(
 | 
						|
        config_entry_id=mock_entry.entry_id,
 | 
						|
        connections={(dr.CONNECTION_NETWORK_MAC, "other_device")},
 | 
						|
    )
 | 
						|
 | 
						|
    tasmota_entry = hass.config_entries.async_entries("tasmota")[0]
 | 
						|
 | 
						|
    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_registry.async_get_device(
 | 
						|
        connections={(dr.CONNECTION_NETWORK_MAC, mac)}
 | 
						|
    )
 | 
						|
    assert device_entry is not None
 | 
						|
    assert device_entry.config_entries == {tasmota_entry.entry_id, mock_entry.entry_id}
 | 
						|
    assert other_device_entry.id != device_entry.id
 | 
						|
 | 
						|
    # Remove other config entry from the device
 | 
						|
    device_registry.async_update_device(
 | 
						|
        device_entry.id, remove_config_entry_id=mock_entry.entry_id
 | 
						|
    )
 | 
						|
    await hass.async_block_till_done()
 | 
						|
 | 
						|
    # Verify device entry is not removed
 | 
						|
    device_entry = device_registry.async_get_device(
 | 
						|
        connections={(dr.CONNECTION_NETWORK_MAC, mac)}
 | 
						|
    )
 | 
						|
    assert device_entry is not None
 | 
						|
    assert device_entry.config_entries == {tasmota_entry.entry_id}
 | 
						|
    mqtt_mock.async_publish.assert_not_called()
 | 
						|
 | 
						|
    # Remove other config entry from the other device - Tasmota should not do any cleanup
 | 
						|
    device_registry.async_update_device(
 | 
						|
        other_device_entry.id, remove_config_entry_id=mock_entry.entry_id
 | 
						|
    )
 | 
						|
    await hass.async_block_till_done()
 | 
						|
    mqtt_mock.async_publish.assert_not_called()
 | 
						|
 | 
						|
 | 
						|
async def test_device_remove_stale(
 | 
						|
    hass: HomeAssistant,
 | 
						|
    hass_ws_client: WebSocketGenerator,
 | 
						|
    mqtt_mock: MqttMockHAClient,
 | 
						|
    device_registry: dr.DeviceRegistry,
 | 
						|
    setup_tasmota,
 | 
						|
) -> None:
 | 
						|
    """Test removing a stale (undiscovered) device does not throw."""
 | 
						|
    assert await async_setup_component(hass, "config", {})
 | 
						|
    mac = "00000049A3BC"
 | 
						|
 | 
						|
    config_entry = hass.config_entries.async_entries("tasmota")[0]
 | 
						|
 | 
						|
    # Create a device
 | 
						|
    device_registry.async_get_or_create(
 | 
						|
        config_entry_id=config_entry.entry_id,
 | 
						|
        connections={(dr.CONNECTION_NETWORK_MAC, mac)},
 | 
						|
    )
 | 
						|
 | 
						|
    # Verify device entry was created
 | 
						|
    device_entry = device_registry.async_get_device(
 | 
						|
        connections={(dr.CONNECTION_NETWORK_MAC, mac)}
 | 
						|
    )
 | 
						|
    assert device_entry is not None
 | 
						|
 | 
						|
    # Remove the device
 | 
						|
    await remove_device(hass, hass_ws_client, device_entry.id)
 | 
						|
 | 
						|
    # Verify device entry is removed
 | 
						|
    device_entry = device_registry.async_get_device(
 | 
						|
        connections={(dr.CONNECTION_NETWORK_MAC, mac)}
 | 
						|
    )
 | 
						|
    assert device_entry is None
 | 
						|
 | 
						|
 | 
						|
async def test_device_rediscover(
 | 
						|
    hass: HomeAssistant,
 | 
						|
    mqtt_mock: MqttMockHAClient,
 | 
						|
    device_registry: dr.DeviceRegistry,
 | 
						|
    setup_tasmota,
 | 
						|
) -> None:
 | 
						|
    """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_registry.async_get_device(
 | 
						|
        connections={(dr.CONNECTION_NETWORK_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_registry.async_get_device(
 | 
						|
        connections={(dr.CONNECTION_NETWORK_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_registry.async_get_device(
 | 
						|
        connections={(dr.CONNECTION_NETWORK_MAC, mac)}
 | 
						|
    )
 | 
						|
    assert device_entry is not None
 | 
						|
    assert device_entry1.id == device_entry.id
 | 
						|
 | 
						|
 | 
						|
async def test_entity_duplicate_discovery(
 | 
						|
    hass: HomeAssistant,
 | 
						|
    mqtt_mock: MqttMockHAClient,
 | 
						|
    caplog: pytest.LogCaptureFixture,
 | 
						|
    setup_tasmota,
 | 
						|
) -> None:
 | 
						|
    """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.tasmota_test")
 | 
						|
    state_duplicate = hass.states.get("binary_sensor.beer1")
 | 
						|
 | 
						|
    assert state is not None
 | 
						|
    assert state.name == "Tasmota Test"
 | 
						|
    assert state_duplicate is None
 | 
						|
    assert (
 | 
						|
        f"Entity already added, sending update: switch ('{mac}', 'switch', 'relay', 0)"
 | 
						|
        in caplog.text
 | 
						|
    )
 | 
						|
 | 
						|
 | 
						|
async def test_entity_duplicate_removal(
 | 
						|
    hass: HomeAssistant,
 | 
						|
    mqtt_mock: MqttMockHAClient,
 | 
						|
    caplog: pytest.LogCaptureFixture,
 | 
						|
    setup_tasmota,
 | 
						|
) -> None:
 | 
						|
    """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', 'relay', 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 "Removing entity: switch" not in caplog.text
 | 
						|
 | 
						|
 | 
						|
async def test_same_topic(
 | 
						|
    hass: HomeAssistant,
 | 
						|
    mqtt_mock: MqttMockHAClient,
 | 
						|
    device_registry: dr.DeviceRegistry,
 | 
						|
    entity_registry: er.EntityRegistry,
 | 
						|
    setup_tasmota,
 | 
						|
    issue_registry: ir.IssueRegistry,
 | 
						|
) -> None:
 | 
						|
    """Test detecting devices with same topic."""
 | 
						|
    configs = [
 | 
						|
        copy.deepcopy(DEFAULT_CONFIG),
 | 
						|
        copy.deepcopy(DEFAULT_CONFIG),
 | 
						|
        copy.deepcopy(DEFAULT_CONFIG),
 | 
						|
    ]
 | 
						|
    configs[0]["rl"][0] = 1
 | 
						|
    configs[1]["rl"][0] = 1
 | 
						|
    configs[2]["rl"][0] = 1
 | 
						|
    configs[0]["mac"] = "000000000001"
 | 
						|
    configs[1]["mac"] = "000000000002"
 | 
						|
    configs[2]["mac"] = "000000000003"
 | 
						|
 | 
						|
    for config in configs[0:2]:
 | 
						|
        async_fire_mqtt_message(
 | 
						|
            hass,
 | 
						|
            f"{DEFAULT_PREFIX}/{config['mac']}/config",
 | 
						|
            json.dumps(config),
 | 
						|
        )
 | 
						|
    await hass.async_block_till_done()
 | 
						|
 | 
						|
    # Verify device registry entries are created for both devices
 | 
						|
    for config in configs[0:2]:
 | 
						|
        device_entry = device_registry.async_get_device(
 | 
						|
            connections={(dr.CONNECTION_NETWORK_MAC, config["mac"])}
 | 
						|
        )
 | 
						|
        assert device_entry is not None
 | 
						|
        assert device_entry.configuration_url == f"http://{config['ip']}/"
 | 
						|
        assert device_entry.manufacturer == "Tasmota"
 | 
						|
        assert device_entry.model == config["md"]
 | 
						|
        assert device_entry.name == config["dn"]
 | 
						|
        assert device_entry.sw_version == config["sw"]
 | 
						|
 | 
						|
    # Verify entities are created only for the first device
 | 
						|
    device_entry = device_registry.async_get_device(
 | 
						|
        connections={(dr.CONNECTION_NETWORK_MAC, configs[0]["mac"])}
 | 
						|
    )
 | 
						|
    assert len(er.async_entries_for_device(entity_registry, device_entry.id, True)) == 1
 | 
						|
    device_entry = device_registry.async_get_device(
 | 
						|
        connections={(dr.CONNECTION_NETWORK_MAC, configs[1]["mac"])}
 | 
						|
    )
 | 
						|
    assert len(er.async_entries_for_device(entity_registry, device_entry.id, True)) == 0
 | 
						|
 | 
						|
    # Verify a repairs issue was created
 | 
						|
    issue_id = "topic_duplicated_tasmota_49A3BC/cmnd/"
 | 
						|
    issue = issue_registry.async_get_issue("tasmota", issue_id)
 | 
						|
    assert issue.data["mac"] == " ".join(config["mac"] for config in configs[0:2])
 | 
						|
 | 
						|
    # Discover a 3rd device with same topic
 | 
						|
    async_fire_mqtt_message(
 | 
						|
        hass,
 | 
						|
        f"{DEFAULT_PREFIX}/{configs[2]['mac']}/config",
 | 
						|
        json.dumps(configs[2]),
 | 
						|
    )
 | 
						|
    await hass.async_block_till_done()
 | 
						|
 | 
						|
    # Verify device registry entries was created
 | 
						|
    device_entry = device_registry.async_get_device(
 | 
						|
        connections={(dr.CONNECTION_NETWORK_MAC, configs[2]["mac"])}
 | 
						|
    )
 | 
						|
    assert device_entry is not None
 | 
						|
    assert device_entry.configuration_url == f"http://{configs[2]['ip']}/"
 | 
						|
    assert device_entry.manufacturer == "Tasmota"
 | 
						|
    assert device_entry.model == configs[2]["md"]
 | 
						|
    assert device_entry.name == configs[2]["dn"]
 | 
						|
    assert device_entry.sw_version == configs[2]["sw"]
 | 
						|
 | 
						|
    # Verify no entities were created
 | 
						|
    device_entry = device_registry.async_get_device(
 | 
						|
        connections={(dr.CONNECTION_NETWORK_MAC, configs[2]["mac"])}
 | 
						|
    )
 | 
						|
    assert len(er.async_entries_for_device(entity_registry, device_entry.id, True)) == 0
 | 
						|
 | 
						|
    # Verify the repairs issue has been updated
 | 
						|
    issue = issue_registry.async_get_issue("tasmota", issue_id)
 | 
						|
    assert issue.data["mac"] == " ".join(config["mac"] for config in configs[0:3])
 | 
						|
 | 
						|
    # Rediscover 3rd device with fixed config
 | 
						|
    configs[2]["t"] = "unique_topic_2"
 | 
						|
    async_fire_mqtt_message(
 | 
						|
        hass,
 | 
						|
        f"{DEFAULT_PREFIX}/{configs[2]['mac']}/config",
 | 
						|
        json.dumps(configs[2]),
 | 
						|
    )
 | 
						|
    await hass.async_block_till_done()
 | 
						|
 | 
						|
    # Verify entities are created also for the third device
 | 
						|
    device_entry = device_registry.async_get_device(
 | 
						|
        connections={(dr.CONNECTION_NETWORK_MAC, configs[2]["mac"])}
 | 
						|
    )
 | 
						|
    assert len(er.async_entries_for_device(entity_registry, device_entry.id, True)) == 1
 | 
						|
 | 
						|
    # Verify the repairs issue has been updated
 | 
						|
    issue = issue_registry.async_get_issue("tasmota", issue_id)
 | 
						|
    assert issue.data["mac"] == " ".join(config["mac"] for config in configs[0:2])
 | 
						|
 | 
						|
    # Rediscover 2nd device with fixed config
 | 
						|
    configs[1]["t"] = "unique_topic_1"
 | 
						|
    async_fire_mqtt_message(
 | 
						|
        hass,
 | 
						|
        f"{DEFAULT_PREFIX}/{configs[1]['mac']}/config",
 | 
						|
        json.dumps(configs[1]),
 | 
						|
    )
 | 
						|
    await hass.async_block_till_done()
 | 
						|
 | 
						|
    # Verify entities are created also for the second device
 | 
						|
    device_entry = device_registry.async_get_device(
 | 
						|
        connections={(dr.CONNECTION_NETWORK_MAC, configs[1]["mac"])}
 | 
						|
    )
 | 
						|
    assert len(er.async_entries_for_device(entity_registry, device_entry.id, True)) == 1
 | 
						|
 | 
						|
    # Verify the repairs issue has been removed
 | 
						|
    assert issue_registry.async_get_issue("tasmota", issue_id) is None
 | 
						|
 | 
						|
 | 
						|
async def test_topic_no_prefix(
 | 
						|
    hass: HomeAssistant,
 | 
						|
    mqtt_mock: MqttMockHAClient,
 | 
						|
    device_registry: dr.DeviceRegistry,
 | 
						|
    entity_registry: er.EntityRegistry,
 | 
						|
    setup_tasmota,
 | 
						|
    issue_registry: ir.IssueRegistry,
 | 
						|
) -> None:
 | 
						|
    """Test detecting devices with same topic."""
 | 
						|
    config = copy.deepcopy(DEFAULT_CONFIG)
 | 
						|
    config["rl"][0] = 1
 | 
						|
    config["ft"] = "%topic%/blah/"
 | 
						|
 | 
						|
    async_fire_mqtt_message(
 | 
						|
        hass,
 | 
						|
        f"{DEFAULT_PREFIX}/{config['mac']}/config",
 | 
						|
        json.dumps(config),
 | 
						|
    )
 | 
						|
    await hass.async_block_till_done()
 | 
						|
 | 
						|
    # Verify device registry entry is created
 | 
						|
    device_entry = device_registry.async_get_device(
 | 
						|
        connections={(dr.CONNECTION_NETWORK_MAC, config["mac"])}
 | 
						|
    )
 | 
						|
    assert device_entry is not None
 | 
						|
    assert device_entry.configuration_url == f"http://{config['ip']}/"
 | 
						|
    assert device_entry.manufacturer == "Tasmota"
 | 
						|
    assert device_entry.model == config["md"]
 | 
						|
    assert device_entry.name == config["dn"]
 | 
						|
    assert device_entry.sw_version == config["sw"]
 | 
						|
 | 
						|
    # Verify entities are not created
 | 
						|
    device_entry = device_registry.async_get_device(
 | 
						|
        connections={(dr.CONNECTION_NETWORK_MAC, config["mac"])}
 | 
						|
    )
 | 
						|
    assert len(er.async_entries_for_device(entity_registry, device_entry.id, True)) == 0
 | 
						|
 | 
						|
    # Verify a repairs issue was created
 | 
						|
    issue_id = "topic_no_prefix_00000049A3BC"
 | 
						|
    assert ("tasmota", issue_id) in issue_registry.issues
 | 
						|
 | 
						|
    # Rediscover device with fixed config
 | 
						|
    config["ft"] = "%topic%/%prefix%/"
 | 
						|
    async_fire_mqtt_message(
 | 
						|
        hass,
 | 
						|
        f"{DEFAULT_PREFIX}/{config['mac']}/config",
 | 
						|
        json.dumps(config),
 | 
						|
    )
 | 
						|
    await hass.async_block_till_done()
 | 
						|
 | 
						|
    # Verify entities are created
 | 
						|
    device_entry = device_registry.async_get_device(
 | 
						|
        connections={(dr.CONNECTION_NETWORK_MAC, config["mac"])}
 | 
						|
    )
 | 
						|
    assert len(er.async_entries_for_device(entity_registry, device_entry.id, True)) == 1
 | 
						|
 | 
						|
    # Verify the repairs issue has been removed
 | 
						|
    assert ("tasmota", issue_id) not in issue_registry.issues
 |