Improve Fronius tests (#132872)

This commit is contained in:
Matthias Alphart 2024-12-15 19:40:51 +01:00 committed by GitHub
parent 544ebcf310
commit be6ed05aa2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 9132 additions and 399 deletions

View File

@ -3,20 +3,16 @@
from __future__ import annotations from __future__ import annotations
from collections.abc import Callable from collections.abc import Callable
from datetime import timedelta
import json import json
from typing import Any from typing import Any
from freezegun.api import FrozenDateTimeFactory
from homeassistant.components.fronius.const import DOMAIN from homeassistant.components.fronius.const import DOMAIN
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST from homeassistant.const import CONF_HOST
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.typing import UNDEFINED, UndefinedType from homeassistant.helpers.typing import UNDEFINED, UndefinedType
from tests.common import MockConfigEntry, async_fire_time_changed, load_fixture from tests.common import MockConfigEntry, load_fixture
from tests.test_util.aiohttp import AiohttpClientMocker from tests.test_util.aiohttp import AiohttpClientMocker
MOCK_HOST = "http://fronius" MOCK_HOST = "http://fronius"
@ -115,24 +111,3 @@ def mock_responses(
f"{host}/solar_api/v1/GetOhmPilotRealtimeData.cgi?Scope=System", f"{host}/solar_api/v1/GetOhmPilotRealtimeData.cgi?Scope=System",
text=_load(f"{fixture_set}/GetOhmPilotRealtimeData.json", "fronius"), text=_load(f"{fixture_set}/GetOhmPilotRealtimeData.json", "fronius"),
) )
async def enable_all_entities(
hass: HomeAssistant,
freezer: FrozenDateTimeFactory,
config_entry_id: str,
time_till_next_update: timedelta,
) -> None:
"""Enable all entities for a config entry and fast forward time to receive data."""
registry = er.async_get(hass)
entities = er.async_entries_for_config_entry(registry, config_entry_id)
for entry in [
entry
for entry in entities
if entry.disabled_by is er.RegistryEntryDisabler.INTEGRATION
]:
registry.async_update_entity(entry.entity_id, disabled_by=None)
await hass.async_block_till_done()
freezer.tick(time_till_next_update)
async_fire_time_changed(hass)
await hass.async_block_till_done()

File diff suppressed because it is too large Load Diff

View File

@ -44,43 +44,62 @@ MOCK_DHCP_DATA = DhcpServiceInfo(
) )
async def test_form_with_logger(hass: HomeAssistant) -> None: async def assert_finish_flow_with_logger(hass: HomeAssistant, flow_id: str) -> None:
"""Test we get the form.""" """Assert finishing the flow with a logger device."""
result = await hass.config_entries.flow.async_init( with patch(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] is FlowResultType.FORM
assert not result["errors"]
with (
patch(
"pyfronius.Fronius.current_logger_info", "pyfronius.Fronius.current_logger_info",
return_value=LOGGER_INFO_RETURN_VALUE, return_value=LOGGER_INFO_RETURN_VALUE,
),
patch(
"homeassistant.components.fronius.async_setup_entry",
return_value=True,
) as mock_setup_entry,
): ):
result2 = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], flow_id,
{ {
"host": "10.9.8.1", "host": "10.9.8.1",
}, },
) )
await hass.async_block_till_done() await hass.async_block_till_done()
assert result2["type"] is FlowResultType.CREATE_ENTRY assert result["type"] is FlowResultType.CREATE_ENTRY
assert result2["title"] == "SolarNet Datalogger at 10.9.8.1" assert result["title"] == "SolarNet Datalogger at 10.9.8.1"
assert result2["data"] == { assert result["data"] == {
"host": "10.9.8.1", "host": "10.9.8.1",
"is_logger": True, "is_logger": True,
} }
assert len(mock_setup_entry.mock_calls) == 1 assert result["result"].unique_id == "123.4567"
async def assert_abort_flow_with_logger(
hass: HomeAssistant, flow_id: str, reason: str
) -> config_entries.ConfigFlowResult:
"""Assert the flow was aborted when a logger device responded."""
with patch(
"pyfronius.Fronius.current_logger_info",
return_value=LOGGER_INFO_RETURN_VALUE,
):
result = await hass.config_entries.flow.async_configure(
flow_id,
{
"host": "10.9.8.1",
},
)
await hass.async_block_till_done()
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == reason
return result
async def test_form_with_logger(hass: HomeAssistant) -> None:
"""Test the basic flow with a logger device."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] is FlowResultType.FORM
assert not result["errors"]
await assert_finish_flow_with_logger(hass, result["flow_id"])
async def test_form_with_inverter(hass: HomeAssistant) -> None: async def test_form_with_inverter(hass: HomeAssistant) -> None:
"""Test we get the form.""" """Test the basic flow with a Gen24 device."""
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER} DOMAIN, context={"source": config_entries.SOURCE_USER}
) )
@ -96,10 +115,6 @@ async def test_form_with_inverter(hass: HomeAssistant) -> None:
"pyfronius.Fronius.inverter_info", "pyfronius.Fronius.inverter_info",
return_value=INVERTER_INFO_RETURN_VALUE, return_value=INVERTER_INFO_RETURN_VALUE,
), ),
patch(
"homeassistant.components.fronius.async_setup_entry",
return_value=True,
) as mock_setup_entry,
): ):
result2 = await hass.config_entries.flow.async_configure( result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
@ -115,7 +130,7 @@ async def test_form_with_inverter(hass: HomeAssistant) -> None:
"host": "10.9.1.1", "host": "10.9.1.1",
"is_logger": False, "is_logger": False,
} }
assert len(mock_setup_entry.mock_calls) == 1 assert result2["result"].unique_id == "1234567"
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -154,6 +169,7 @@ async def test_form_cannot_connect(
assert result2["type"] is FlowResultType.FORM assert result2["type"] is FlowResultType.FORM
assert result2["errors"] == {"base": "cannot_connect"} assert result2["errors"] == {"base": "cannot_connect"}
await assert_finish_flow_with_logger(hass, result2["flow_id"])
async def test_form_unexpected(hass: HomeAssistant) -> None: async def test_form_unexpected(hass: HomeAssistant) -> None:
@ -175,13 +191,14 @@ async def test_form_unexpected(hass: HomeAssistant) -> None:
assert result2["type"] is FlowResultType.FORM assert result2["type"] is FlowResultType.FORM
assert result2["errors"] == {"base": "unknown"} assert result2["errors"] == {"base": "unknown"}
await assert_finish_flow_with_logger(hass, result2["flow_id"])
async def test_form_already_existing(hass: HomeAssistant) -> None: async def test_form_already_existing(hass: HomeAssistant) -> None:
"""Test existing entry.""" """Test existing entry."""
MockConfigEntry( MockConfigEntry(
domain=DOMAIN, domain=DOMAIN,
unique_id="123.4567", unique_id=LOGGER_INFO_RETURN_VALUE["unique_identifier"]["value"],
data={CONF_HOST: "10.9.8.1", "is_logger": True}, data={CONF_HOST: "10.9.8.1", "is_logger": True},
).add_to_hass(hass) ).add_to_hass(hass)
assert len(hass.config_entries.async_entries(DOMAIN)) == 1 assert len(hass.config_entries.async_entries(DOMAIN)) == 1
@ -189,20 +206,9 @@ async def test_form_already_existing(hass: HomeAssistant) -> None:
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER} DOMAIN, context={"source": config_entries.SOURCE_USER}
) )
with patch( await assert_abort_flow_with_logger(
"pyfronius.Fronius.current_logger_info", hass, result["flow_id"], reason="already_configured"
return_value=LOGGER_INFO_RETURN_VALUE,
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
"host": "10.9.8.1",
},
) )
await hass.async_block_till_done()
assert result2["type"] is FlowResultType.ABORT
assert result2["reason"] == "already_configured"
async def test_config_flow_already_configured( async def test_config_flow_already_configured(
@ -273,6 +279,7 @@ async def test_dhcp(hass: HomeAssistant, aioclient_mock: AiohttpClientMocker) ->
"host": MOCK_DHCP_DATA.ip, "host": MOCK_DHCP_DATA.ip,
"is_logger": True, "is_logger": True,
} }
assert result["result"].unique_id == "123.4567"
async def test_dhcp_already_configured( async def test_dhcp_already_configured(
@ -345,10 +352,6 @@ async def test_reconfigure(hass: HomeAssistant) -> None:
"pyfronius.Fronius.inverter_info", "pyfronius.Fronius.inverter_info",
return_value=INVERTER_INFO_RETURN_VALUE, return_value=INVERTER_INFO_RETURN_VALUE,
), ),
patch(
"homeassistant.components.fronius.async_setup_entry",
return_value=True,
) as mock_setup_entry,
): ):
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
@ -364,14 +367,13 @@ async def test_reconfigure(hass: HomeAssistant) -> None:
"host": new_host, "host": new_host,
"is_logger": False, "is_logger": False,
} }
assert len(mock_setup_entry.mock_calls) == 1
async def test_reconfigure_cannot_connect(hass: HomeAssistant) -> None: async def test_reconfigure_cannot_connect(hass: HomeAssistant) -> None:
"""Test we handle cannot connect error.""" """Test we handle cannot connect error."""
entry = MockConfigEntry( entry = MockConfigEntry(
domain=DOMAIN, domain=DOMAIN,
unique_id="123.4567890", unique_id=LOGGER_INFO_RETURN_VALUE["unique_identifier"]["value"],
data={ data={
CONF_HOST: "10.1.2.3", CONF_HOST: "10.1.2.3",
"is_logger": True, "is_logger": True,
@ -401,12 +403,16 @@ async def test_reconfigure_cannot_connect(hass: HomeAssistant) -> None:
assert result2["type"] is FlowResultType.FORM assert result2["type"] is FlowResultType.FORM
assert result2["errors"] == {"base": "cannot_connect"} assert result2["errors"] == {"base": "cannot_connect"}
await assert_abort_flow_with_logger(
hass, result2["flow_id"], reason="reconfigure_successful"
)
async def test_reconfigure_unexpected(hass: HomeAssistant) -> None: async def test_reconfigure_unexpected(hass: HomeAssistant) -> None:
"""Test we handle unexpected error.""" """Test we handle unexpected error."""
entry = MockConfigEntry( entry = MockConfigEntry(
domain=DOMAIN, domain=DOMAIN,
unique_id="123.4567890", unique_id=LOGGER_INFO_RETURN_VALUE["unique_identifier"]["value"],
data={ data={
CONF_HOST: "10.1.2.3", CONF_HOST: "10.1.2.3",
"is_logger": True, "is_logger": True,
@ -430,12 +436,16 @@ async def test_reconfigure_unexpected(hass: HomeAssistant) -> None:
assert result2["type"] is FlowResultType.FORM assert result2["type"] is FlowResultType.FORM
assert result2["errors"] == {"base": "unknown"} assert result2["errors"] == {"base": "unknown"}
await assert_abort_flow_with_logger(
hass, result2["flow_id"], reason="reconfigure_successful"
)
async def test_reconfigure_already_configured(hass: HomeAssistant) -> None:
"""Test reconfiguring an entry.""" async def test_reconfigure_to_different_device(hass: HomeAssistant) -> None:
"""Test reconfiguring an entry to a different device."""
entry = MockConfigEntry( entry = MockConfigEntry(
domain=DOMAIN, domain=DOMAIN,
unique_id="123.4567890", unique_id="999.9999999",
data={ data={
CONF_HOST: "10.1.2.3", CONF_HOST: "10.1.2.3",
"is_logger": True, "is_logger": True,
@ -447,68 +457,6 @@ async def test_reconfigure_already_configured(hass: HomeAssistant) -> None:
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "reconfigure" assert result["step_id"] == "reconfigure"
with ( await assert_abort_flow_with_logger(
patch( hass, result["flow_id"], reason="unique_id_mismatch"
"pyfronius.Fronius.current_logger_info",
return_value=LOGGER_INFO_RETURN_VALUE,
),
patch(
"pyfronius.Fronius.inverter_info",
return_value=INVERTER_INFO_RETURN_VALUE,
),
patch(
"homeassistant.components.fronius.async_setup_entry",
return_value=True,
) as mock_setup_entry,
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={
"host": "10.1.2.3",
},
) )
await hass.async_block_till_done()
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "unique_id_mismatch"
assert len(mock_setup_entry.mock_calls) == 0
async def test_reconfigure_already_existing(hass: HomeAssistant) -> None:
"""Test reconfiguring entry to already existing device."""
entry = MockConfigEntry(
domain=DOMAIN,
unique_id="123.4567890",
data={
CONF_HOST: "10.1.2.3",
"is_logger": True,
},
)
entry.add_to_hass(hass)
entry_2_uid = "222.2222222"
entry_2 = MockConfigEntry(
domain=DOMAIN,
unique_id=entry_2_uid,
data={
CONF_HOST: "10.2.2.2",
"is_logger": True,
},
)
entry_2.add_to_hass(hass)
result = await entry.start_reconfigure_flow(hass)
with patch(
"pyfronius.Fronius.current_logger_info",
return_value={"unique_identifier": {"value": entry_2_uid}},
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
"host": "10.1.1.1",
},
)
await hass.async_block_till_done()
assert result2["type"] is FlowResultType.ABORT
assert result2["reason"] == "unique_id_mismatch"

View File

@ -29,7 +29,7 @@ async def test_adaptive_update_interval(
mock_inverter_data.reset_mock() mock_inverter_data.reset_mock()
freezer.tick(FroniusInverterUpdateCoordinator.default_interval) freezer.tick(FroniusInverterUpdateCoordinator.default_interval)
async_fire_time_changed(hass, None) async_fire_time_changed(hass)
await hass.async_block_till_done() await hass.async_block_till_done()
mock_inverter_data.assert_called_once() mock_inverter_data.assert_called_once()
mock_inverter_data.reset_mock() mock_inverter_data.reset_mock()
@ -38,13 +38,13 @@ async def test_adaptive_update_interval(
# first 3 bad requests at default interval - 4th has different interval # first 3 bad requests at default interval - 4th has different interval
for _ in range(3): for _ in range(3):
freezer.tick(FroniusInverterUpdateCoordinator.default_interval) freezer.tick(FroniusInverterUpdateCoordinator.default_interval)
async_fire_time_changed(hass, None) async_fire_time_changed(hass)
await hass.async_block_till_done() await hass.async_block_till_done()
assert mock_inverter_data.call_count == 3 assert mock_inverter_data.call_count == 3
mock_inverter_data.reset_mock() mock_inverter_data.reset_mock()
freezer.tick(FroniusInverterUpdateCoordinator.error_interval) freezer.tick(FroniusInverterUpdateCoordinator.error_interval)
async_fire_time_changed(hass, None) async_fire_time_changed(hass)
await hass.async_block_till_done() await hass.async_block_till_done()
assert mock_inverter_data.call_count == 1 assert mock_inverter_data.call_count == 1
mock_inverter_data.reset_mock() mock_inverter_data.reset_mock()
@ -52,13 +52,13 @@ async def test_adaptive_update_interval(
mock_inverter_data.side_effect = None mock_inverter_data.side_effect = None
# next successful request resets to default interval # next successful request resets to default interval
freezer.tick(FroniusInverterUpdateCoordinator.error_interval) freezer.tick(FroniusInverterUpdateCoordinator.error_interval)
async_fire_time_changed(hass, None) async_fire_time_changed(hass)
await hass.async_block_till_done() await hass.async_block_till_done()
mock_inverter_data.assert_called_once() mock_inverter_data.assert_called_once()
mock_inverter_data.reset_mock() mock_inverter_data.reset_mock()
freezer.tick(FroniusInverterUpdateCoordinator.default_interval) freezer.tick(FroniusInverterUpdateCoordinator.default_interval)
async_fire_time_changed(hass, None) async_fire_time_changed(hass)
await hass.async_block_till_done() await hass.async_block_till_done()
mock_inverter_data.assert_called_once() mock_inverter_data.assert_called_once()
mock_inverter_data.reset_mock() mock_inverter_data.reset_mock()
@ -68,7 +68,7 @@ async def test_adaptive_update_interval(
# first 3 requests at default interval - 4th has different interval # first 3 requests at default interval - 4th has different interval
for _ in range(3): for _ in range(3):
freezer.tick(FroniusInverterUpdateCoordinator.default_interval) freezer.tick(FroniusInverterUpdateCoordinator.default_interval)
async_fire_time_changed(hass, None) async_fire_time_changed(hass)
await hass.async_block_till_done() await hass.async_block_till_done()
# BadStatusError does 3 silent retries for inverter endpoint * 3 request intervals = 9 # BadStatusError does 3 silent retries for inverter endpoint * 3 request intervals = 9
assert mock_inverter_data.call_count == 9 assert mock_inverter_data.call_count == 9

View File

@ -3,6 +3,7 @@
from datetime import timedelta from datetime import timedelta
from unittest.mock import patch from unittest.mock import patch
from freezegun.api import FrozenDateTimeFactory
from pyfronius import FroniusError from pyfronius import FroniusError
from homeassistant.components.fronius.const import DOMAIN, SOLAR_NET_RESCAN_TIMER from homeassistant.components.fronius.const import DOMAIN, SOLAR_NET_RESCAN_TIMER
@ -10,7 +11,6 @@ from homeassistant.config_entries import ConfigEntryState
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
from homeassistant.util import dt as dt_util
from . import mock_responses, setup_fronius_integration from . import mock_responses, setup_fronius_integration
@ -66,6 +66,7 @@ async def test_inverter_night_rescan(
hass: HomeAssistant, hass: HomeAssistant,
device_registry: dr.DeviceRegistry, device_registry: dr.DeviceRegistry,
aioclient_mock: AiohttpClientMocker, aioclient_mock: AiohttpClientMocker,
freezer: FrozenDateTimeFactory,
) -> None: ) -> None:
"""Test dynamic adding of an inverter discovered automatically after a Home Assistant reboot during the night.""" """Test dynamic adding of an inverter discovered automatically after a Home Assistant reboot during the night."""
mock_responses(aioclient_mock, fixture_set="igplus_v2", night=True) mock_responses(aioclient_mock, fixture_set="igplus_v2", night=True)
@ -78,9 +79,8 @@ async def test_inverter_night_rescan(
# Switch to daytime # Switch to daytime
mock_responses(aioclient_mock, fixture_set="igplus_v2", night=False) mock_responses(aioclient_mock, fixture_set="igplus_v2", night=False)
async_fire_time_changed( freezer.tick(timedelta(minutes=SOLAR_NET_RESCAN_TIMER))
hass, dt_util.utcnow() + timedelta(minutes=SOLAR_NET_RESCAN_TIMER) async_fire_time_changed(hass)
)
await hass.async_block_till_done() await hass.async_block_till_done()
# We expect our inverter to be present now # We expect our inverter to be present now
@ -88,9 +88,8 @@ async def test_inverter_night_rescan(
assert inverter_1.manufacturer == "Fronius" assert inverter_1.manufacturer == "Fronius"
# After another re-scan we still only expect this inverter # After another re-scan we still only expect this inverter
async_fire_time_changed( freezer.tick(timedelta(minutes=SOLAR_NET_RESCAN_TIMER))
hass, dt_util.utcnow() + timedelta(minutes=SOLAR_NET_RESCAN_TIMER * 2) async_fire_time_changed(hass)
)
await hass.async_block_till_done() await hass.async_block_till_done()
inverter_1 = device_registry.async_get_device(identifiers={(DOMAIN, "203200")}) inverter_1 = device_registry.async_get_device(identifiers={(DOMAIN, "203200")})
assert inverter_1.manufacturer == "Fronius" assert inverter_1.manufacturer == "Fronius"
@ -100,6 +99,7 @@ async def test_inverter_rescan_interruption(
hass: HomeAssistant, hass: HomeAssistant,
device_registry: dr.DeviceRegistry, device_registry: dr.DeviceRegistry,
aioclient_mock: AiohttpClientMocker, aioclient_mock: AiohttpClientMocker,
freezer: FrozenDateTimeFactory,
) -> None: ) -> None:
"""Test interruption of re-scan during runtime to process further.""" """Test interruption of re-scan during runtime to process further."""
mock_responses(aioclient_mock, fixture_set="igplus_v2", night=True) mock_responses(aioclient_mock, fixture_set="igplus_v2", night=True)
@ -115,9 +115,8 @@ async def test_inverter_rescan_interruption(
"pyfronius.Fronius.inverter_info", "pyfronius.Fronius.inverter_info",
side_effect=FroniusError, side_effect=FroniusError,
): ):
async_fire_time_changed( freezer.tick(timedelta(minutes=SOLAR_NET_RESCAN_TIMER))
hass, dt_util.utcnow() + timedelta(minutes=SOLAR_NET_RESCAN_TIMER) async_fire_time_changed(hass)
)
await hass.async_block_till_done() await hass.async_block_till_done()
# No increase of devices expected because of a FroniusError # No increase of devices expected because of a FroniusError
@ -132,9 +131,8 @@ async def test_inverter_rescan_interruption(
# Next re-scan will pick up the new inverter. Expect 2 devices now. # Next re-scan will pick up the new inverter. Expect 2 devices now.
mock_responses(aioclient_mock, fixture_set="igplus_v2", night=False) mock_responses(aioclient_mock, fixture_set="igplus_v2", night=False)
async_fire_time_changed( freezer.tick(timedelta(minutes=SOLAR_NET_RESCAN_TIMER))
hass, dt_util.utcnow() + timedelta(minutes=SOLAR_NET_RESCAN_TIMER * 2) async_fire_time_changed(hass)
)
await hass.async_block_till_done() await hass.async_block_till_done()
assert ( assert (

View File

@ -2,27 +2,29 @@
from freezegun.api import FrozenDateTimeFactory from freezegun.api import FrozenDateTimeFactory
import pytest import pytest
from syrupy import SnapshotAssertion
from homeassistant.components.fronius.const import DOMAIN from homeassistant.components.fronius.const import DOMAIN
from homeassistant.components.fronius.coordinator import ( from homeassistant.components.fronius.coordinator import (
FroniusInverterUpdateCoordinator, FroniusInverterUpdateCoordinator,
FroniusMeterUpdateCoordinator,
FroniusPowerFlowUpdateCoordinator, FroniusPowerFlowUpdateCoordinator,
) )
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr from homeassistant.helpers import device_registry as dr, entity_registry as er
from . import enable_all_entities, mock_responses, setup_fronius_integration from . import mock_responses, setup_fronius_integration
from tests.common import async_fire_time_changed from tests.common import async_fire_time_changed, snapshot_platform
from tests.test_util.aiohttp import AiohttpClientMocker from tests.test_util.aiohttp import AiohttpClientMocker
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_symo_inverter( async def test_symo_inverter(
hass: HomeAssistant, hass: HomeAssistant,
aioclient_mock: AiohttpClientMocker, aioclient_mock: AiohttpClientMocker,
freezer: FrozenDateTimeFactory, freezer: FrozenDateTimeFactory,
snapshot: SnapshotAssertion,
) -> None: ) -> None:
"""Test Fronius Symo inverter entities.""" """Test Fronius Symo inverter entities."""
@ -32,15 +34,8 @@ async def test_symo_inverter(
# Init at night # Init at night
mock_responses(aioclient_mock, night=True) mock_responses(aioclient_mock, night=True)
config_entry = await setup_fronius_integration(hass) await setup_fronius_integration(hass)
assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 22
await enable_all_entities(
hass,
freezer,
config_entry.entry_id,
FroniusInverterUpdateCoordinator.default_interval,
)
assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 58 assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 58
assert_state("sensor.symo_20_dc_current", 0) assert_state("sensor.symo_20_dc_current", 0)
assert_state("sensor.symo_20_energy_day", 10828) assert_state("sensor.symo_20_energy_day", 10828)
@ -54,13 +49,6 @@ async def test_symo_inverter(
freezer.tick(FroniusInverterUpdateCoordinator.default_interval) freezer.tick(FroniusInverterUpdateCoordinator.default_interval)
async_fire_time_changed(hass) async_fire_time_changed(hass)
await hass.async_block_till_done() await hass.async_block_till_done()
assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 62
await enable_all_entities(
hass,
freezer,
config_entry.entry_id,
FroniusInverterUpdateCoordinator.default_interval,
)
assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 64 assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 64
# 4 additional AC entities # 4 additional AC entities
assert_state("sensor.symo_20_dc_current", 2.19) assert_state("sensor.symo_20_dc_current", 2.19)
@ -104,6 +92,7 @@ async def test_symo_logger(
assert_state("sensor.solarnet_grid_import_tariff", 0.15) assert_state("sensor.solarnet_grid_import_tariff", 0.15)
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_symo_meter( async def test_symo_meter(
hass: HomeAssistant, hass: HomeAssistant,
aioclient_mock: AiohttpClientMocker, aioclient_mock: AiohttpClientMocker,
@ -117,15 +106,8 @@ async def test_symo_meter(
assert state.state == str(expected_state) assert state.state == str(expected_state)
mock_responses(aioclient_mock) mock_responses(aioclient_mock)
config_entry = await setup_fronius_integration(hass) await setup_fronius_integration(hass)
assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 26
await enable_all_entities(
hass,
freezer,
config_entry.entry_id,
FroniusMeterUpdateCoordinator.default_interval,
)
assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 64 assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 64
# states are rounded to 4 decimals # states are rounded to 4 decimals
assert_state("sensor.smart_meter_63a_current_phase_1", 7.755) assert_state("sensor.smart_meter_63a_current_phase_1", 7.755)
@ -206,6 +188,7 @@ async def test_symo_meter_forged(
) )
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_symo_power_flow( async def test_symo_power_flow(
hass: HomeAssistant, hass: HomeAssistant,
aioclient_mock: AiohttpClientMocker, aioclient_mock: AiohttpClientMocker,
@ -220,15 +203,8 @@ async def test_symo_power_flow(
# First test at night # First test at night
mock_responses(aioclient_mock, night=True) mock_responses(aioclient_mock, night=True)
config_entry = await setup_fronius_integration(hass) await setup_fronius_integration(hass)
assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 22
await enable_all_entities(
hass,
freezer,
config_entry.entry_id,
FroniusInverterUpdateCoordinator.default_interval,
)
assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 58 assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 58
# states are rounded to 4 decimals # states are rounded to 4 decimals
assert_state("sensor.solarnet_energy_day", 10828) assert_state("sensor.solarnet_energy_day", 10828)
@ -277,10 +253,13 @@ async def test_symo_power_flow(
assert_state("sensor.solarnet_relative_self_consumption", 0) assert_state("sensor.solarnet_relative_self_consumption", 0)
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_gen24( async def test_gen24(
hass: HomeAssistant, hass: HomeAssistant,
aioclient_mock: AiohttpClientMocker, aioclient_mock: AiohttpClientMocker,
freezer: FrozenDateTimeFactory, freezer: FrozenDateTimeFactory,
entity_registry: er.EntityRegistry,
snapshot: SnapshotAssertion,
) -> None: ) -> None:
"""Test Fronius Gen24 inverter entities.""" """Test Fronius Gen24 inverter entities."""
@ -292,72 +271,10 @@ async def test_gen24(
mock_responses(aioclient_mock, fixture_set="gen24") mock_responses(aioclient_mock, fixture_set="gen24")
config_entry = await setup_fronius_integration(hass, is_logger=False) config_entry = await setup_fronius_integration(hass, is_logger=False)
assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 24
await enable_all_entities(
hass,
freezer,
config_entry.entry_id,
FroniusMeterUpdateCoordinator.default_interval,
)
assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 58 assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 58
# inverter 1 await snapshot_platform(hass, entity_registry, snapshot, config_entry.entry_id)
assert_state("sensor.inverter_name_ac_current", 0.1589)
assert_state("sensor.inverter_name_dc_current_2", 0.0754)
assert_state("sensor.inverter_name_status_code", 7)
assert_state("sensor.inverter_name_status_message", "running")
assert_state("sensor.inverter_name_dc_current", 0.0783)
assert_state("sensor.inverter_name_dc_voltage_2", 403.4312)
assert_state("sensor.inverter_name_ac_power", 37.3204)
assert_state("sensor.inverter_name_error_code", 0)
assert_state("sensor.inverter_name_dc_voltage", 411.3811)
assert_state("sensor.inverter_name_total_energy", 1530193.42)
assert_state("sensor.inverter_name_inverter_state", "Running")
assert_state("sensor.inverter_name_ac_voltage", 234.9168)
assert_state("sensor.inverter_name_frequency", 49.9917)
# meter
assert_state("sensor.smart_meter_ts_65a_3_real_energy_produced", 3863340.0)
assert_state("sensor.smart_meter_ts_65a_3_real_energy_consumed", 2013105.0)
assert_state("sensor.smart_meter_ts_65a_3_real_power", 653.1)
assert_state("sensor.smart_meter_ts_65a_3_frequency_phase_average", 49.9)
assert_state("sensor.smart_meter_ts_65a_3_meter_location", 0)
assert_state("sensor.smart_meter_ts_65a_3_meter_location_description", "feed_in")
assert_state("sensor.smart_meter_ts_65a_3_power_factor", 0.828)
assert_state("sensor.smart_meter_ts_65a_3_reactive_energy_consumed", 88221.0)
assert_state("sensor.smart_meter_ts_65a_3_real_energy_minus", 3863340.0)
assert_state("sensor.smart_meter_ts_65a_3_current_phase_2", 2.33)
assert_state("sensor.smart_meter_ts_65a_3_voltage_phase_1", 235.9)
assert_state("sensor.smart_meter_ts_65a_3_voltage_phase_1_2", 408.7)
assert_state("sensor.smart_meter_ts_65a_3_real_power_phase_2", 294.9)
assert_state("sensor.smart_meter_ts_65a_3_real_energy_plus", 2013105.0)
assert_state("sensor.smart_meter_ts_65a_3_voltage_phase_2", 236.1)
assert_state("sensor.smart_meter_ts_65a_3_reactive_energy_produced", 1989125.0)
assert_state("sensor.smart_meter_ts_65a_3_voltage_phase_3", 236.9)
assert_state("sensor.smart_meter_ts_65a_3_power_factor_phase_1", 0.441)
assert_state("sensor.smart_meter_ts_65a_3_voltage_phase_2_3", 409.6)
assert_state("sensor.smart_meter_ts_65a_3_current_phase_3", 1.825)
assert_state("sensor.smart_meter_ts_65a_3_power_factor_phase_3", 0.832)
assert_state("sensor.smart_meter_ts_65a_3_apparent_power_phase_1", 243.3)
assert_state("sensor.smart_meter_ts_65a_3_voltage_phase_3_1", 409.4)
assert_state("sensor.smart_meter_ts_65a_3_apparent_power_phase_2", 323.4)
assert_state("sensor.smart_meter_ts_65a_3_apparent_power_phase_3", 301.2)
assert_state("sensor.smart_meter_ts_65a_3_real_power_phase_1", 106.8)
assert_state("sensor.smart_meter_ts_65a_3_power_factor_phase_2", 0.934)
assert_state("sensor.smart_meter_ts_65a_3_real_power_phase_3", 251.3)
assert_state("sensor.smart_meter_ts_65a_3_reactive_power_phase_1", -218.6)
assert_state("sensor.smart_meter_ts_65a_3_reactive_power_phase_2", -132.8)
assert_state("sensor.smart_meter_ts_65a_3_reactive_power_phase_3", -166.0)
assert_state("sensor.smart_meter_ts_65a_3_apparent_power", 868.0)
assert_state("sensor.smart_meter_ts_65a_3_reactive_power", -517.4)
assert_state("sensor.smart_meter_ts_65a_3_current_phase_1", 1.145)
# power_flow
assert_state("sensor.solarnet_power_grid", 658.4)
assert_state("sensor.solarnet_relative_self_consumption", 100.0)
assert_state("sensor.solarnet_power_photovoltaics", 62.9481)
assert_state("sensor.solarnet_power_load", -695.6827)
assert_state("sensor.solarnet_meter_mode", "meter")
assert_state("sensor.solarnet_relative_autonomy", 5.3592)
assert_state("sensor.solarnet_total_energy", 1530193.42)
assert_state("sensor.inverter_name_total_energy", 1530193.42)
# Gen24 devices may report 0 for total energy while doing firmware updates. # Gen24 devices may report 0 for total energy while doing firmware updates.
# This should yield "unknown" state instead of 0. # This should yield "unknown" state instead of 0.
mock_responses( mock_responses(
@ -375,11 +292,14 @@ async def test_gen24(
assert_state("sensor.inverter_name_total_energy", "unknown") assert_state("sensor.inverter_name_total_energy", "unknown")
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_gen24_storage( async def test_gen24_storage(
hass: HomeAssistant, hass: HomeAssistant,
aioclient_mock: AiohttpClientMocker, aioclient_mock: AiohttpClientMocker,
device_registry: dr.DeviceRegistry, device_registry: dr.DeviceRegistry,
freezer: FrozenDateTimeFactory, freezer: FrozenDateTimeFactory,
entity_registry: er.EntityRegistry,
snapshot: SnapshotAssertion,
) -> None: ) -> None:
"""Test Fronius Gen24 inverter with BYD battery and Ohmpilot entities.""" """Test Fronius Gen24 inverter with BYD battery and Ohmpilot entities."""
@ -393,87 +313,8 @@ async def test_gen24_storage(
hass, is_logger=False, unique_id="12345678" hass, is_logger=False, unique_id="12345678"
) )
assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 37
await enable_all_entities(
hass,
freezer,
config_entry.entry_id,
FroniusMeterUpdateCoordinator.default_interval,
)
assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 72 assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 72
# inverter 1 await snapshot_platform(hass, entity_registry, snapshot, config_entry.entry_id)
assert_state("sensor.gen24_storage_dc_current", 0.3952)
assert_state("sensor.gen24_storage_dc_voltage_2", 318.8103)
assert_state("sensor.gen24_storage_dc_current_2", 0.3564)
assert_state("sensor.gen24_storage_ac_current", 1.1087)
assert_state("sensor.gen24_storage_ac_power", 250.9093)
assert_state("sensor.gen24_storage_error_code", 0)
assert_state("sensor.gen24_storage_status_code", 7)
assert_state("sensor.gen24_storage_status_message", "running")
assert_state("sensor.gen24_storage_total_energy", 7512794.0117)
assert_state("sensor.gen24_storage_inverter_state", "Running")
assert_state("sensor.gen24_storage_dc_voltage", 419.1009)
assert_state("sensor.gen24_storage_ac_voltage", 227.354)
assert_state("sensor.gen24_storage_frequency", 49.9816)
# meter
assert_state("sensor.smart_meter_ts_65a_3_real_energy_produced", 1705128.0)
assert_state("sensor.smart_meter_ts_65a_3_real_power", 487.7)
assert_state("sensor.smart_meter_ts_65a_3_power_factor", 0.698)
assert_state("sensor.smart_meter_ts_65a_3_real_energy_consumed", 1247204.0)
assert_state("sensor.smart_meter_ts_65a_3_frequency_phase_average", 49.9)
assert_state("sensor.smart_meter_ts_65a_3_meter_location", 0)
assert_state("sensor.smart_meter_ts_65a_3_meter_location_description", "feed_in")
assert_state("sensor.smart_meter_ts_65a_3_reactive_power", -501.5)
assert_state("sensor.smart_meter_ts_65a_3_reactive_energy_produced", 3266105.0)
assert_state("sensor.smart_meter_ts_65a_3_real_power_phase_3", 19.6)
assert_state("sensor.smart_meter_ts_65a_3_current_phase_3", 0.645)
assert_state("sensor.smart_meter_ts_65a_3_real_energy_minus", 1705128.0)
assert_state("sensor.smart_meter_ts_65a_3_apparent_power_phase_2", 383.9)
assert_state("sensor.smart_meter_ts_65a_3_current_phase_1", 1.701)
assert_state("sensor.smart_meter_ts_65a_3_current_phase_2", 1.832)
assert_state("sensor.smart_meter_ts_65a_3_apparent_power_phase_1", 319.5)
assert_state("sensor.smart_meter_ts_65a_3_voltage_phase_1", 229.4)
assert_state("sensor.smart_meter_ts_65a_3_real_power_phase_2", 150.0)
assert_state("sensor.smart_meter_ts_65a_3_voltage_phase_3_1", 394.3)
assert_state("sensor.smart_meter_ts_65a_3_voltage_phase_2", 225.6)
assert_state("sensor.smart_meter_ts_65a_3_reactive_energy_consumed", 5482.0)
assert_state("sensor.smart_meter_ts_65a_3_real_energy_plus", 1247204.0)
assert_state("sensor.smart_meter_ts_65a_3_power_factor_phase_1", 0.995)
assert_state("sensor.smart_meter_ts_65a_3_power_factor_phase_3", 0.163)
assert_state("sensor.smart_meter_ts_65a_3_power_factor_phase_2", 0.389)
assert_state("sensor.smart_meter_ts_65a_3_reactive_power_phase_1", -31.3)
assert_state("sensor.smart_meter_ts_65a_3_reactive_power_phase_3", -116.7)
assert_state("sensor.smart_meter_ts_65a_3_voltage_phase_1_2", 396.0)
assert_state("sensor.smart_meter_ts_65a_3_voltage_phase_2_3", 393.0)
assert_state("sensor.smart_meter_ts_65a_3_reactive_power_phase_2", -353.4)
assert_state("sensor.smart_meter_ts_65a_3_real_power_phase_1", 317.9)
assert_state("sensor.smart_meter_ts_65a_3_voltage_phase_3", 228.3)
assert_state("sensor.smart_meter_ts_65a_3_apparent_power", 821.9)
assert_state("sensor.smart_meter_ts_65a_3_apparent_power_phase_3", 118.4)
# ohmpilot
assert_state("sensor.ohmpilot_energy_consumed", 1233295.0)
assert_state("sensor.ohmpilot_power", 0.0)
assert_state("sensor.ohmpilot_temperature", 38.9)
assert_state("sensor.ohmpilot_state_code", 0.0)
assert_state("sensor.ohmpilot_state_message", "up_and_running")
# power_flow
assert_state("sensor.solarnet_power_grid", 2274.9)
assert_state("sensor.solarnet_power_battery", 0.1591)
assert_state("sensor.solarnet_power_battery_charge", 0)
assert_state("sensor.solarnet_power_battery_discharge", 0.1591)
assert_state("sensor.solarnet_power_load", -2459.3092)
assert_state("sensor.solarnet_relative_self_consumption", 100.0)
assert_state("sensor.solarnet_power_photovoltaics", 216.4328)
assert_state("sensor.solarnet_relative_autonomy", 7.4984)
assert_state("sensor.solarnet_meter_mode", "bidirectional")
assert_state("sensor.solarnet_total_energy", 7512664.4042)
# storage
assert_state("sensor.byd_battery_box_premium_hv_dc_current", 0.0)
assert_state("sensor.byd_battery_box_premium_hv_state_of_charge", 4.6)
assert_state("sensor.byd_battery_box_premium_hv_maximum_capacity", 16588)
assert_state("sensor.byd_battery_box_premium_hv_temperature", 21.5)
assert_state("sensor.byd_battery_box_premium_hv_designed_capacity", 16588)
assert_state("sensor.byd_battery_box_premium_hv_dc_voltage", 0.0)
# Devices # Devices
solar_net = device_registry.async_get_device( solar_net = device_registry.async_get_device(
@ -507,11 +348,14 @@ async def test_gen24_storage(
assert storage.name == "BYD Battery-Box Premium HV" assert storage.name == "BYD Battery-Box Premium HV"
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_primo_s0( async def test_primo_s0(
hass: HomeAssistant, hass: HomeAssistant,
aioclient_mock: AiohttpClientMocker, aioclient_mock: AiohttpClientMocker,
device_registry: dr.DeviceRegistry, device_registry: dr.DeviceRegistry,
freezer: FrozenDateTimeFactory, freezer: FrozenDateTimeFactory,
entity_registry: er.EntityRegistry,
snapshot: SnapshotAssertion,
) -> None: ) -> None:
"""Test Fronius Primo dual inverter with S0 meter entities.""" """Test Fronius Primo dual inverter with S0 meter entities."""
@ -523,64 +367,8 @@ async def test_primo_s0(
mock_responses(aioclient_mock, fixture_set="primo_s0", inverter_ids=[1, 2]) mock_responses(aioclient_mock, fixture_set="primo_s0", inverter_ids=[1, 2])
config_entry = await setup_fronius_integration(hass, is_logger=True) config_entry = await setup_fronius_integration(hass, is_logger=True)
assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 31
await enable_all_entities(
hass,
freezer,
config_entry.entry_id,
FroniusMeterUpdateCoordinator.default_interval,
)
assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 47 assert len(hass.states.async_all(domain_filter=SENSOR_DOMAIN)) == 47
# logger await snapshot_platform(hass, entity_registry, snapshot, config_entry.entry_id)
assert_state("sensor.solarnet_grid_export_tariff", 1)
assert_state("sensor.solarnet_co2_factor", 0.53)
assert_state("sensor.solarnet_grid_import_tariff", 1)
# inverter 1
assert_state("sensor.primo_5_0_1_total_energy", 17114940)
assert_state("sensor.primo_5_0_1_energy_day", 22504)
assert_state("sensor.primo_5_0_1_dc_voltage", 452.3)
assert_state("sensor.primo_5_0_1_ac_power", 862)
assert_state("sensor.primo_5_0_1_error_code", 0)
assert_state("sensor.primo_5_0_1_dc_current", 4.23)
assert_state("sensor.primo_5_0_1_status_code", 7)
assert_state("sensor.primo_5_0_1_status_message", "running")
assert_state("sensor.primo_5_0_1_energy_year", 7532755.5)
assert_state("sensor.primo_5_0_1_ac_current", 3.85)
assert_state("sensor.primo_5_0_1_ac_voltage", 223.9)
assert_state("sensor.primo_5_0_1_frequency", 60)
assert_state("sensor.primo_5_0_1_led_color", 2)
assert_state("sensor.primo_5_0_1_led_state", 0)
# inverter 2
assert_state("sensor.primo_3_0_1_total_energy", 5796010)
assert_state("sensor.primo_3_0_1_energy_day", 14237)
assert_state("sensor.primo_3_0_1_dc_voltage", 329.5)
assert_state("sensor.primo_3_0_1_ac_power", 296)
assert_state("sensor.primo_3_0_1_error_code", 0)
assert_state("sensor.primo_3_0_1_dc_current", 0.97)
assert_state("sensor.primo_3_0_1_status_code", 7)
assert_state("sensor.primo_3_0_1_status_message", "running")
assert_state("sensor.primo_3_0_1_energy_year", 3596193.25)
assert_state("sensor.primo_3_0_1_ac_current", 1.32)
assert_state("sensor.primo_3_0_1_ac_voltage", 223.6)
assert_state("sensor.primo_3_0_1_frequency", 60.01)
assert_state("sensor.primo_3_0_1_led_color", 2)
assert_state("sensor.primo_3_0_1_led_state", 0)
# meter
assert_state("sensor.s0_meter_at_inverter_1_meter_location", 1)
assert_state(
"sensor.s0_meter_at_inverter_1_meter_location_description", "consumption_path"
)
assert_state("sensor.s0_meter_at_inverter_1_real_power", -2216.7487)
# power_flow
assert_state("sensor.solarnet_power_load", -2218.9349)
assert_state("sensor.solarnet_meter_mode", "vague-meter")
assert_state("sensor.solarnet_power_photovoltaics", 1834)
assert_state("sensor.solarnet_power_grid", 384.9349)
assert_state("sensor.solarnet_relative_self_consumption", 100)
assert_state("sensor.solarnet_relative_autonomy", 82.6523)
assert_state("sensor.solarnet_total_energy", 22910919.5)
assert_state("sensor.solarnet_energy_day", 36724)
assert_state("sensor.solarnet_energy_year", 11128933.25)
# Devices # Devices
solar_net = device_registry.async_get_device( solar_net = device_registry.async_get_device(