Update DSMR integration to import yaml to ConfigEntry (#39473)
* Rewrite to import from platform setup * Add config flow for import * Implement reload * Update sensor tests * Add config flow tests * Remove some code * Fix pylint issue * Remove update options code * Add platform import test * Remove infinite while loop * Move async_setup_platform * Check for unload_ok * Remove commented out test code * Implement function to check on host/port already existing Co-authored-by: Chris Talkington <chris@talkingtontech.com> * Implement new method in import * Update tests * Fix test setup platform * Add string * Patch setup_platform * Add block till done to patch block Co-authored-by: Chris Talkington <chris@talkingtontech.com>
This commit is contained in:
parent
77f5fb765b
commit
d0120d5e0a
9 changed files with 498 additions and 68 deletions
102
tests/components/dsmr/test_config_flow.py
Normal file
102
tests/components/dsmr/test_config_flow.py
Normal file
|
@ -0,0 +1,102 @@
|
|||
"""Test the DSMR config flow."""
|
||||
from homeassistant import config_entries, setup
|
||||
from homeassistant.components.dsmr import DOMAIN
|
||||
|
||||
from tests.async_mock import patch
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
async def test_import_usb(hass):
|
||||
"""Test we can import."""
|
||||
await setup.async_setup_component(hass, "persistent_notification", {})
|
||||
|
||||
entry_data = {
|
||||
"port": "/dev/ttyUSB0",
|
||||
"dsmr_version": "2.2",
|
||||
"precision": 4,
|
||||
"reconnect_interval": 30,
|
||||
}
|
||||
|
||||
with patch("homeassistant.components.dsmr.async_setup_entry", return_value=True):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_IMPORT},
|
||||
data=entry_data,
|
||||
)
|
||||
|
||||
assert result["type"] == "create_entry"
|
||||
assert result["title"] == "/dev/ttyUSB0"
|
||||
assert result["data"] == entry_data
|
||||
|
||||
|
||||
async def test_import_network(hass):
|
||||
"""Test we can import from network."""
|
||||
await setup.async_setup_component(hass, "persistent_notification", {})
|
||||
|
||||
entry_data = {
|
||||
"host": "localhost",
|
||||
"port": "1234",
|
||||
"dsmr_version": "2.2",
|
||||
"precision": 4,
|
||||
"reconnect_interval": 30,
|
||||
}
|
||||
|
||||
with patch("homeassistant.components.dsmr.async_setup_entry", return_value=True):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_IMPORT},
|
||||
data=entry_data,
|
||||
)
|
||||
|
||||
assert result["type"] == "create_entry"
|
||||
assert result["title"] == "localhost:1234"
|
||||
assert result["data"] == entry_data
|
||||
|
||||
|
||||
async def test_import_update(hass):
|
||||
"""Test we can import."""
|
||||
await setup.async_setup_component(hass, "persistent_notification", {})
|
||||
|
||||
entry_data = {
|
||||
"port": "/dev/ttyUSB0",
|
||||
"dsmr_version": "2.2",
|
||||
"precision": 4,
|
||||
"reconnect_interval": 30,
|
||||
}
|
||||
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data=entry_data,
|
||||
unique_id="/dev/ttyUSB0",
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.dsmr.async_setup_entry", return_value=True
|
||||
), patch("homeassistant.components.dsmr.async_unload_entry", return_value=True):
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
||||
new_entry_data = {
|
||||
"port": "/dev/ttyUSB0",
|
||||
"dsmr_version": "2.2",
|
||||
"precision": 3,
|
||||
"reconnect_interval": 30,
|
||||
}
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.dsmr.async_setup_entry", return_value=True
|
||||
), patch("homeassistant.components.dsmr.async_unload_entry", return_value=True):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_IMPORT},
|
||||
data=new_entry_data,
|
||||
)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["type"] == "abort"
|
||||
assert result["reason"] == "already_configured"
|
||||
|
||||
assert entry.data["precision"] == 3
|
|
@ -12,13 +12,15 @@ from itertools import chain, repeat
|
|||
|
||||
import pytest
|
||||
|
||||
from homeassistant.bootstrap import async_setup_component
|
||||
from homeassistant.components.dsmr.const import DOMAIN
|
||||
from homeassistant.components.dsmr.sensor import DerivativeDSMREntity
|
||||
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
||||
from homeassistant.const import ENERGY_KILO_WATT_HOUR, TIME_HOURS, VOLUME_CUBIC_METERS
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
import tests.async_mock
|
||||
from tests.async_mock import DEFAULT, Mock
|
||||
from tests.common import assert_setup_component
|
||||
from tests.async_mock import DEFAULT, MagicMock, Mock
|
||||
from tests.common import MockConfigEntry, patch
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
@ -47,6 +49,39 @@ def mock_connection_factory(monkeypatch):
|
|||
return connection_factory, transport, protocol
|
||||
|
||||
|
||||
async def test_setup_platform(hass, mock_connection_factory):
|
||||
"""Test setup of platform."""
|
||||
async_add_entities = MagicMock()
|
||||
|
||||
entry_data = {
|
||||
"platform": DOMAIN,
|
||||
"port": "/dev/ttyUSB0",
|
||||
"dsmr_version": "2.2",
|
||||
"precision": 4,
|
||||
"reconnect_interval": 30,
|
||||
}
|
||||
|
||||
with patch("homeassistant.components.dsmr.async_setup", return_value=True), patch(
|
||||
"homeassistant.components.dsmr.async_setup_entry", return_value=True
|
||||
):
|
||||
assert await async_setup_component(
|
||||
hass, SENSOR_DOMAIN, {SENSOR_DOMAIN: entry_data}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert not async_add_entities.called
|
||||
|
||||
# Check config entry
|
||||
conf_entries = hass.config_entries.async_entries(DOMAIN)
|
||||
|
||||
assert len(conf_entries) == 1
|
||||
|
||||
entry = conf_entries[0]
|
||||
|
||||
assert entry.state == "loaded"
|
||||
assert entry.data == entry_data
|
||||
|
||||
|
||||
async def test_default_setup(hass, mock_connection_factory):
|
||||
"""Test the default setup."""
|
||||
(connection_factory, transport, protocol) = mock_connection_factory
|
||||
|
@ -58,7 +93,12 @@ async def test_default_setup(hass, mock_connection_factory):
|
|||
)
|
||||
from dsmr_parser.objects import CosemObject, MBusObject
|
||||
|
||||
config = {"platform": "dsmr"}
|
||||
entry_data = {
|
||||
"port": "/dev/ttyUSB0",
|
||||
"dsmr_version": "2.2",
|
||||
"precision": 4,
|
||||
"reconnect_interval": 30,
|
||||
}
|
||||
|
||||
telegram = {
|
||||
CURRENT_ELECTRICITY_USAGE: CosemObject(
|
||||
|
@ -73,9 +113,14 @@ async def test_default_setup(hass, mock_connection_factory):
|
|||
),
|
||||
}
|
||||
|
||||
with assert_setup_component(1):
|
||||
await async_setup_component(hass, "sensor", {"sensor": config})
|
||||
await hass.async_block_till_done()
|
||||
mock_entry = MockConfigEntry(
|
||||
domain="dsmr", unique_id="/dev/ttyUSB0", data=entry_data
|
||||
)
|
||||
|
||||
mock_entry.add_to_hass(hass)
|
||||
|
||||
await hass.config_entries.async_setup(mock_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
telegram_callback = connection_factory.call_args_list[0][0][2]
|
||||
|
||||
|
@ -107,6 +152,10 @@ async def test_default_setup(hass, mock_connection_factory):
|
|||
assert gas_consumption.state == "745.695"
|
||||
assert gas_consumption.attributes.get("unit_of_measurement") == VOLUME_CUBIC_METERS
|
||||
|
||||
await hass.config_entries.async_unload(mock_entry.entry_id)
|
||||
|
||||
assert mock_entry.state == "not_loaded"
|
||||
|
||||
|
||||
async def test_derivative():
|
||||
"""Test calculation of derivative value."""
|
||||
|
@ -158,7 +207,12 @@ async def test_v4_meter(hass, mock_connection_factory):
|
|||
)
|
||||
from dsmr_parser.objects import CosemObject, MBusObject
|
||||
|
||||
config = {"platform": "dsmr", "dsmr_version": "4"}
|
||||
entry_data = {
|
||||
"port": "/dev/ttyUSB0",
|
||||
"dsmr_version": "4",
|
||||
"precision": 4,
|
||||
"reconnect_interval": 30,
|
||||
}
|
||||
|
||||
telegram = {
|
||||
HOURLY_GAS_METER_READING: MBusObject(
|
||||
|
@ -170,9 +224,14 @@ async def test_v4_meter(hass, mock_connection_factory):
|
|||
ELECTRICITY_ACTIVE_TARIFF: CosemObject([{"value": "0001", "unit": ""}]),
|
||||
}
|
||||
|
||||
with assert_setup_component(1):
|
||||
await async_setup_component(hass, "sensor", {"sensor": config})
|
||||
await hass.async_block_till_done()
|
||||
mock_entry = MockConfigEntry(
|
||||
domain="dsmr", unique_id="/dev/ttyUSB0", data=entry_data
|
||||
)
|
||||
|
||||
mock_entry.add_to_hass(hass)
|
||||
|
||||
await hass.config_entries.async_setup(mock_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
telegram_callback = connection_factory.call_args_list[0][0][2]
|
||||
|
||||
|
@ -192,6 +251,10 @@ async def test_v4_meter(hass, mock_connection_factory):
|
|||
assert gas_consumption.state == "745.695"
|
||||
assert gas_consumption.attributes.get("unit_of_measurement") == VOLUME_CUBIC_METERS
|
||||
|
||||
await hass.config_entries.async_unload(mock_entry.entry_id)
|
||||
|
||||
assert mock_entry.state == "not_loaded"
|
||||
|
||||
|
||||
async def test_v5_meter(hass, mock_connection_factory):
|
||||
"""Test if v5 meter is correctly parsed."""
|
||||
|
@ -203,7 +266,12 @@ async def test_v5_meter(hass, mock_connection_factory):
|
|||
)
|
||||
from dsmr_parser.objects import CosemObject, MBusObject
|
||||
|
||||
config = {"platform": "dsmr", "dsmr_version": "5"}
|
||||
entry_data = {
|
||||
"port": "/dev/ttyUSB0",
|
||||
"dsmr_version": "5",
|
||||
"precision": 4,
|
||||
"reconnect_interval": 30,
|
||||
}
|
||||
|
||||
telegram = {
|
||||
HOURLY_GAS_METER_READING: MBusObject(
|
||||
|
@ -215,9 +283,14 @@ async def test_v5_meter(hass, mock_connection_factory):
|
|||
ELECTRICITY_ACTIVE_TARIFF: CosemObject([{"value": "0001", "unit": ""}]),
|
||||
}
|
||||
|
||||
with assert_setup_component(1):
|
||||
await async_setup_component(hass, "sensor", {"sensor": config})
|
||||
await hass.async_block_till_done()
|
||||
mock_entry = MockConfigEntry(
|
||||
domain="dsmr", unique_id="/dev/ttyUSB0", data=entry_data
|
||||
)
|
||||
|
||||
mock_entry.add_to_hass(hass)
|
||||
|
||||
await hass.config_entries.async_setup(mock_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
telegram_callback = connection_factory.call_args_list[0][0][2]
|
||||
|
||||
|
@ -237,6 +310,10 @@ async def test_v5_meter(hass, mock_connection_factory):
|
|||
assert gas_consumption.state == "745.695"
|
||||
assert gas_consumption.attributes.get("unit_of_measurement") == VOLUME_CUBIC_METERS
|
||||
|
||||
await hass.config_entries.async_unload(mock_entry.entry_id)
|
||||
|
||||
assert mock_entry.state == "not_loaded"
|
||||
|
||||
|
||||
async def test_belgian_meter(hass, mock_connection_factory):
|
||||
"""Test if Belgian meter is correctly parsed."""
|
||||
|
@ -248,7 +325,12 @@ async def test_belgian_meter(hass, mock_connection_factory):
|
|||
)
|
||||
from dsmr_parser.objects import CosemObject, MBusObject
|
||||
|
||||
config = {"platform": "dsmr", "dsmr_version": "5B"}
|
||||
entry_data = {
|
||||
"port": "/dev/ttyUSB0",
|
||||
"dsmr_version": "5B",
|
||||
"precision": 4,
|
||||
"reconnect_interval": 30,
|
||||
}
|
||||
|
||||
telegram = {
|
||||
BELGIUM_HOURLY_GAS_METER_READING: MBusObject(
|
||||
|
@ -260,9 +342,14 @@ async def test_belgian_meter(hass, mock_connection_factory):
|
|||
ELECTRICITY_ACTIVE_TARIFF: CosemObject([{"value": "0001", "unit": ""}]),
|
||||
}
|
||||
|
||||
with assert_setup_component(1):
|
||||
await async_setup_component(hass, "sensor", {"sensor": config})
|
||||
await hass.async_block_till_done()
|
||||
mock_entry = MockConfigEntry(
|
||||
domain="dsmr", unique_id="/dev/ttyUSB0", data=entry_data
|
||||
)
|
||||
|
||||
mock_entry.add_to_hass(hass)
|
||||
|
||||
await hass.config_entries.async_setup(mock_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
telegram_callback = connection_factory.call_args_list[0][0][2]
|
||||
|
||||
|
@ -282,6 +369,10 @@ async def test_belgian_meter(hass, mock_connection_factory):
|
|||
assert gas_consumption.state == "745.695"
|
||||
assert gas_consumption.attributes.get("unit_of_measurement") == VOLUME_CUBIC_METERS
|
||||
|
||||
await hass.config_entries.async_unload(mock_entry.entry_id)
|
||||
|
||||
assert mock_entry.state == "not_loaded"
|
||||
|
||||
|
||||
async def test_belgian_meter_low(hass, mock_connection_factory):
|
||||
"""Test if Belgian meter is correctly parsed."""
|
||||
|
@ -290,13 +381,23 @@ async def test_belgian_meter_low(hass, mock_connection_factory):
|
|||
from dsmr_parser.obis_references import ELECTRICITY_ACTIVE_TARIFF
|
||||
from dsmr_parser.objects import CosemObject
|
||||
|
||||
config = {"platform": "dsmr", "dsmr_version": "5B"}
|
||||
entry_data = {
|
||||
"port": "/dev/ttyUSB0",
|
||||
"dsmr_version": "5B",
|
||||
"precision": 4,
|
||||
"reconnect_interval": 30,
|
||||
}
|
||||
|
||||
telegram = {ELECTRICITY_ACTIVE_TARIFF: CosemObject([{"value": "0002", "unit": ""}])}
|
||||
|
||||
with assert_setup_component(1):
|
||||
await async_setup_component(hass, "sensor", {"sensor": config})
|
||||
await hass.async_block_till_done()
|
||||
mock_entry = MockConfigEntry(
|
||||
domain="dsmr", unique_id="/dev/ttyUSB0", data=entry_data
|
||||
)
|
||||
|
||||
mock_entry.add_to_hass(hass)
|
||||
|
||||
await hass.config_entries.async_setup(mock_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
telegram_callback = connection_factory.call_args_list[0][0][2]
|
||||
|
||||
|
@ -311,26 +412,50 @@ async def test_belgian_meter_low(hass, mock_connection_factory):
|
|||
assert power_tariff.state == "low"
|
||||
assert power_tariff.attributes.get("unit_of_measurement") == ""
|
||||
|
||||
await hass.config_entries.async_unload(mock_entry.entry_id)
|
||||
|
||||
assert mock_entry.state == "not_loaded"
|
||||
|
||||
|
||||
async def test_tcp(hass, mock_connection_factory):
|
||||
"""If proper config provided TCP connection should be made."""
|
||||
(connection_factory, transport, protocol) = mock_connection_factory
|
||||
|
||||
config = {"platform": "dsmr", "host": "localhost", "port": 1234}
|
||||
entry_data = {
|
||||
"host": "localhost",
|
||||
"port": "1234",
|
||||
"dsmr_version": "2.2",
|
||||
"precision": 4,
|
||||
"reconnect_interval": 30,
|
||||
}
|
||||
|
||||
with assert_setup_component(1):
|
||||
await async_setup_component(hass, "sensor", {"sensor": config})
|
||||
await hass.async_block_till_done()
|
||||
mock_entry = MockConfigEntry(
|
||||
domain="dsmr", unique_id="/dev/ttyUSB0", data=entry_data
|
||||
)
|
||||
|
||||
mock_entry.add_to_hass(hass)
|
||||
|
||||
await hass.config_entries.async_setup(mock_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert connection_factory.call_args_list[0][0][0] == "localhost"
|
||||
assert connection_factory.call_args_list[0][0][1] == "1234"
|
||||
|
||||
await hass.config_entries.async_unload(mock_entry.entry_id)
|
||||
|
||||
assert mock_entry.state == "not_loaded"
|
||||
|
||||
|
||||
async def test_connection_errors_retry(hass, monkeypatch, mock_connection_factory):
|
||||
"""Connection should be retried on error during setup."""
|
||||
(connection_factory, transport, protocol) = mock_connection_factory
|
||||
|
||||
config = {"platform": "dsmr", "reconnect_interval": 0}
|
||||
entry_data = {
|
||||
"port": "/dev/ttyUSB0",
|
||||
"dsmr_version": "2.2",
|
||||
"precision": 4,
|
||||
"reconnect_interval": 0,
|
||||
}
|
||||
|
||||
# override the mock to have it fail the first time and succeed after
|
||||
first_fail_connection_factory = tests.async_mock.AsyncMock(
|
||||
|
@ -342,17 +467,35 @@ async def test_connection_errors_retry(hass, monkeypatch, mock_connection_factor
|
|||
"homeassistant.components.dsmr.sensor.create_dsmr_reader",
|
||||
first_fail_connection_factory,
|
||||
)
|
||||
await async_setup_component(hass, "sensor", {"sensor": config})
|
||||
|
||||
mock_entry = MockConfigEntry(
|
||||
domain="dsmr", unique_id="/dev/ttyUSB0", data=entry_data
|
||||
)
|
||||
|
||||
mock_entry.add_to_hass(hass)
|
||||
|
||||
await hass.config_entries.async_setup(mock_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# wait for sleep to resolve
|
||||
await hass.async_block_till_done()
|
||||
assert first_fail_connection_factory.call_count >= 2, "connecting not retried"
|
||||
|
||||
await hass.config_entries.async_unload(mock_entry.entry_id)
|
||||
|
||||
assert mock_entry.state == "not_loaded"
|
||||
|
||||
|
||||
async def test_reconnect(hass, monkeypatch, mock_connection_factory):
|
||||
"""If transport disconnects, the connection should be retried."""
|
||||
(connection_factory, transport, protocol) = mock_connection_factory
|
||||
config = {"platform": "dsmr", "reconnect_interval": 0}
|
||||
|
||||
entry_data = {
|
||||
"port": "/dev/ttyUSB0",
|
||||
"dsmr_version": "2.2",
|
||||
"precision": 4,
|
||||
"reconnect_interval": 0,
|
||||
}
|
||||
|
||||
# mock waiting coroutine while connection lasts
|
||||
closed = asyncio.Event()
|
||||
|
@ -365,7 +508,13 @@ async def test_reconnect(hass, monkeypatch, mock_connection_factory):
|
|||
|
||||
protocol.wait_closed = wait_closed
|
||||
|
||||
await async_setup_component(hass, "sensor", {"sensor": config})
|
||||
mock_entry = MockConfigEntry(
|
||||
domain="dsmr", unique_id="/dev/ttyUSB0", data=entry_data
|
||||
)
|
||||
|
||||
mock_entry.add_to_hass(hass)
|
||||
|
||||
await hass.config_entries.async_setup(mock_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert connection_factory.call_count == 1
|
||||
|
@ -382,3 +531,7 @@ async def test_reconnect(hass, monkeypatch, mock_connection_factory):
|
|||
assert connection_factory.call_count >= 2, "connecting not retried"
|
||||
# setting it so teardown can be successful
|
||||
closed.set()
|
||||
|
||||
await hass.config_entries.async_unload(mock_entry.entry_id)
|
||||
|
||||
assert mock_entry.state == "not_loaded"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue