Streamline Enphase Envoy config flow tests (#79914)

* Streamline Enphase Envoy config flow tests

* Don't test data results using constants

* Fix data issues

* Fixtures

* Simplify mock creation

* Docstrings
This commit is contained in:
Aaron Bach 2022-10-17 11:29:10 -06:00 committed by GitHub
parent 5ead3b2605
commit 72f4665d33
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 279 additions and 268 deletions

View File

@ -0,0 +1,110 @@
"""Define test fixtures for Enphase Envoy."""
import json
from unittest.mock import AsyncMock, Mock, patch
import pytest
from homeassistant.components.enphase_envoy import DOMAIN
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component
from tests.common import MockConfigEntry, load_fixture
@pytest.fixture(name="config_entry")
def config_entry_fixture(hass: HomeAssistant, config, serial_number):
"""Define a config entry fixture."""
entry = MockConfigEntry(
domain=DOMAIN,
title=f"Envoy {serial_number}" if serial_number else "Envoy",
unique_id=serial_number,
data=config,
)
entry.add_to_hass(hass)
return entry
@pytest.fixture(name="config")
def config_fixture():
"""Define a config entry data fixture."""
return {
CONF_HOST: "1.1.1.1",
CONF_NAME: "Envoy 1234",
CONF_USERNAME: "test-username",
CONF_PASSWORD: "test-password",
}
@pytest.fixture(name="gateway_data", scope="session")
def gateway_data_fixture():
"""Define a fixture to return gateway data."""
return json.loads(load_fixture("data.json", "enphase_envoy"))
@pytest.fixture(name="inverters_production_data", scope="session")
def inverters_production_data_fixture():
"""Define a fixture to return inverter production data."""
return json.loads(load_fixture("inverters_production.json", "enphase_envoy"))
@pytest.fixture(name="mock_envoy_reader")
def mock_envoy_reader_fixture(
gateway_data,
mock_get_data,
mock_get_full_serial_number,
mock_inverters_production,
serial_number,
):
"""Define a mocked EnvoyReader fixture."""
mock_envoy_reader = Mock(
getData=mock_get_data,
get_full_serial_number=mock_get_full_serial_number,
inverters_production=mock_inverters_production,
)
for key, value in gateway_data.items():
setattr(mock_envoy_reader, key, AsyncMock(return_value=value))
return mock_envoy_reader
@pytest.fixture(name="mock_get_full_serial_number")
def mock_get_full_serial_number_fixture(serial_number):
"""Define a mocked EnvoyReader.get_full_serial_number fixture."""
return AsyncMock(return_value=serial_number)
@pytest.fixture(name="mock_get_data")
def mock_get_data_fixture():
"""Define a mocked EnvoyReader.getData fixture."""
return AsyncMock()
@pytest.fixture(name="mock_inverters_production")
def mock_inverters_production_fixture(inverters_production_data):
"""Define a mocked EnvoyReader.inverters_production fixture."""
return AsyncMock(return_value=inverters_production_data)
@pytest.fixture(name="setup_enphase_envoy")
async def setup_enphase_envoy_fixture(hass, config, mock_envoy_reader):
"""Define a fixture to set up Enphase Envoy."""
with patch(
"homeassistant.components.enphase_envoy.config_flow.EnvoyReader",
return_value=mock_envoy_reader,
), patch(
"homeassistant.components.enphase_envoy.EnvoyReader",
return_value=mock_envoy_reader,
), patch(
"homeassistant.components.enphase_envoy.PLATFORMS", []
):
assert await async_setup_component(hass, DOMAIN, config)
await hass.async_block_till_done()
yield
@pytest.fixture(name="serial_number")
def serial_number_fixture():
"""Define a serial number fixture."""
return "1234"

View File

@ -0,0 +1 @@
"""Define data fixtures for Enphase Envoy."""

View File

@ -0,0 +1,10 @@
{
"production": 1840,
"daily_production": 28223,
"seven_days_production": 174482,
"lifetime_production": 5924391,
"consumption": 1840,
"daily_consumption": 5923857,
"seven_days_consumption": 5923857,
"lifetime_consumption": 5923857
}

View File

@ -0,0 +1,18 @@
{
"202140024014": [136, "2022-10-08 16:43:36"],
"202140023294": [163, "2022-10-08 16:43:41"],
"202140013819": [130, "2022-10-08 16:43:31"],
"202140023794": [139, "2022-10-08 16:43:38"],
"202140023381": [130, "2022-10-08 16:43:47"],
"202140024176": [54, "2022-10-08 16:43:59"],
"202140003284": [132, "2022-10-08 16:43:55"],
"202140019854": [129, "2022-10-08 16:43:58"],
"202140020743": [131, "2022-10-08 16:43:49"],
"202140023531": [28, "2022-10-08 16:43:53"],
"202140024241": [164, "2022-10-08 16:43:33"],
"202140022963": [164, "2022-10-08 16:43:41"],
"202140023149": [118, "2022-10-08 16:43:47"],
"202140024828": [129, "2022-10-08 16:43:36"],
"202140023269": [133, "2022-10-08 16:43:43"],
"202140024157": [112, "2022-10-08 16:43:52"]
}

View File

@ -1,46 +1,31 @@
"""Test the Enphase Envoy config flow.""" """Test the Enphase Envoy config flow."""
from unittest.mock import MagicMock, patch from unittest.mock import AsyncMock, MagicMock
import httpx import httpx
import pytest
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.components import zeroconf from homeassistant.components import zeroconf
from homeassistant.components.enphase_envoy.const import DOMAIN from homeassistant.components.enphase_envoy.const import DOMAIN
from homeassistant.const import CONF_HOST
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry
async def test_form(hass: HomeAssistant, config, setup_enphase_envoy) -> None:
async def test_form(hass: HomeAssistant) -> None:
"""Test we get the form.""" """Test we get the form."""
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}
) )
assert result["type"] == "form" assert result["type"] == "form"
assert result["errors"] == {} assert result["errors"] == {}
with patch( result2 = await hass.config_entries.flow.async_configure(
"homeassistant.components.enphase_envoy.config_flow.EnvoyReader.getData", result["flow_id"],
return_value=True, {
), patch( "host": "1.1.1.1",
"homeassistant.components.enphase_envoy.config_flow.EnvoyReader.get_full_serial_number", "username": "test-username",
return_value="1234", "password": "test-password",
), patch( },
"homeassistant.components.enphase_envoy.async_setup_entry", )
return_value=True,
) as mock_setup_entry:
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
"host": "1.1.1.1",
"username": "test-username",
"password": "test-password",
},
)
await hass.async_block_till_done()
assert result2["type"] == "create_entry" assert result2["type"] == "create_entry"
assert result2["title"] == "Envoy 1234" assert result2["title"] == "Envoy 1234"
assert result2["data"] == { assert result2["data"] == {
@ -49,38 +34,27 @@ async def test_form(hass: HomeAssistant) -> None:
"username": "test-username", "username": "test-username",
"password": "test-password", "password": "test-password",
} }
assert len(mock_setup_entry.mock_calls) == 1
async def test_user_no_serial_number(hass: HomeAssistant) -> None: @pytest.mark.parametrize("serial_number", [None])
async def test_user_no_serial_number(
hass: HomeAssistant, config, setup_enphase_envoy
) -> None:
"""Test user setup without a serial number.""" """Test user setup without a serial number."""
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}
) )
assert result["type"] == "form" assert result["type"] == "form"
assert result["errors"] == {} assert result["errors"] == {}
with patch( result2 = await hass.config_entries.flow.async_configure(
"homeassistant.components.enphase_envoy.config_flow.EnvoyReader.getData", result["flow_id"],
return_value=True, {
), patch( "host": "1.1.1.1",
"homeassistant.components.enphase_envoy.config_flow.EnvoyReader.get_full_serial_number", "username": "test-username",
return_value=None, "password": "test-password",
), patch( },
"homeassistant.components.enphase_envoy.async_setup_entry", )
return_value=True,
) as mock_setup_entry:
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
"host": "1.1.1.1",
"username": "test-username",
"password": "test-password",
},
)
await hass.async_block_till_done()
assert result2["type"] == "create_entry" assert result2["type"] == "create_entry"
assert result2["title"] == "Envoy" assert result2["title"] == "Envoy"
assert result2["data"] == { assert result2["data"] == {
@ -89,40 +63,36 @@ async def test_user_no_serial_number(hass: HomeAssistant) -> None:
"username": "test-username", "username": "test-username",
"password": "test-password", "password": "test-password",
} }
assert len(mock_setup_entry.mock_calls) == 1
async def test_user_fetching_serial_fails(hass: HomeAssistant) -> None: @pytest.mark.parametrize(
"mock_get_full_serial_number",
[
AsyncMock(
side_effect=httpx.HTTPStatusError(
"any", request=MagicMock(), response=MagicMock()
)
)
],
)
async def test_user_fetching_serial_fails(
hass: HomeAssistant, setup_enphase_envoy
) -> None:
"""Test user setup without a serial number.""" """Test user setup without a serial number."""
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}
) )
assert result["type"] == "form" assert result["type"] == "form"
assert result["errors"] == {} assert result["errors"] == {}
with patch( result2 = await hass.config_entries.flow.async_configure(
"homeassistant.components.enphase_envoy.config_flow.EnvoyReader.getData", result["flow_id"],
return_value=True, {
), patch( "host": "1.1.1.1",
"homeassistant.components.enphase_envoy.config_flow.EnvoyReader.get_full_serial_number", "username": "test-username",
side_effect=httpx.HTTPStatusError( "password": "test-password",
"any", request=MagicMock(), response=MagicMock() },
), )
), patch(
"homeassistant.components.enphase_envoy.async_setup_entry",
return_value=True,
) as mock_setup_entry:
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
"host": "1.1.1.1",
"username": "test-username",
"password": "test-password",
},
)
await hass.async_block_till_done()
assert result2["type"] == "create_entry" assert result2["type"] == "create_entry"
assert result2["title"] == "Envoy" assert result2["title"] == "Envoy"
assert result2["data"] == { assert result2["data"] == {
@ -131,83 +101,75 @@ async def test_user_fetching_serial_fails(hass: HomeAssistant) -> None:
"username": "test-username", "username": "test-username",
"password": "test-password", "password": "test-password",
} }
assert len(mock_setup_entry.mock_calls) == 1
async def test_form_invalid_auth(hass: HomeAssistant) -> None: @pytest.mark.parametrize(
"mock_get_data",
[
AsyncMock(
side_effect=httpx.HTTPStatusError(
"any", request=MagicMock(), response=MagicMock()
)
)
],
)
async def test_form_invalid_auth(hass: HomeAssistant, setup_enphase_envoy) -> None:
"""Test we handle invalid auth.""" """Test we handle invalid auth."""
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}
) )
result2 = await hass.config_entries.flow.async_configure(
with patch( result["flow_id"],
"homeassistant.components.enphase_envoy.config_flow.EnvoyReader.getData", {
side_effect=httpx.HTTPStatusError( "host": "1.1.1.1",
"any", request=MagicMock(), response=MagicMock() "username": "test-username",
), "password": "test-password",
): },
result2 = await hass.config_entries.flow.async_configure( )
result["flow_id"],
{
"host": "1.1.1.1",
"username": "test-username",
"password": "test-password",
},
)
assert result2["type"] == "form" assert result2["type"] == "form"
assert result2["errors"] == {"base": "invalid_auth"} assert result2["errors"] == {"base": "invalid_auth"}
async def test_form_cannot_connect(hass: HomeAssistant) -> None: @pytest.mark.parametrize(
"mock_get_data", [AsyncMock(side_effect=httpx.HTTPError("any"))]
)
async def test_form_cannot_connect(hass: HomeAssistant, setup_enphase_envoy) -> None:
"""Test we handle cannot connect error.""" """Test we handle cannot connect error."""
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}
) )
result2 = await hass.config_entries.flow.async_configure(
with patch( result["flow_id"],
"homeassistant.components.enphase_envoy.config_flow.EnvoyReader.getData", {
side_effect=httpx.HTTPError("any"), "host": "1.1.1.1",
): "username": "test-username",
result2 = await hass.config_entries.flow.async_configure( "password": "test-password",
result["flow_id"], },
{ )
"host": "1.1.1.1",
"username": "test-username",
"password": "test-password",
},
)
assert result2["type"] == "form" assert result2["type"] == "form"
assert result2["errors"] == {"base": "cannot_connect"} assert result2["errors"] == {"base": "cannot_connect"}
async def test_form_unknown_error(hass: HomeAssistant) -> None: @pytest.mark.parametrize("mock_get_data", [AsyncMock(side_effect=ValueError)])
async def test_form_unknown_error(hass: HomeAssistant, setup_enphase_envoy) -> None:
"""Test we handle unknown error.""" """Test we handle unknown error."""
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}
) )
result2 = await hass.config_entries.flow.async_configure(
with patch( result["flow_id"],
"homeassistant.components.enphase_envoy.config_flow.EnvoyReader.getData", {
side_effect=ValueError, "host": "1.1.1.1",
): "username": "test-username",
result2 = await hass.config_entries.flow.async_configure( "password": "test-password",
result["flow_id"], },
{ )
"host": "1.1.1.1",
"username": "test-username",
"password": "test-password",
},
)
assert result2["type"] == "form" assert result2["type"] == "form"
assert result2["errors"] == {"base": "unknown"} assert result2["errors"] == {"base": "unknown"}
async def test_zeroconf(hass: HomeAssistant) -> None: async def test_zeroconf(hass: HomeAssistant, setup_enphase_envoy) -> None:
"""Test we can setup from zeroconf.""" """Test we can setup from zeroconf."""
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, DOMAIN,
context={"source": config_entries.SOURCE_ZEROCONF}, context={"source": config_entries.SOURCE_ZEROCONF},
@ -221,28 +183,17 @@ async def test_zeroconf(hass: HomeAssistant) -> None:
type="mock_type", type="mock_type",
), ),
) )
await hass.async_block_till_done()
assert result["type"] == "form" assert result["type"] == "form"
assert result["step_id"] == "user" assert result["step_id"] == "user"
with patch( result2 = await hass.config_entries.flow.async_configure(
"homeassistant.components.enphase_envoy.config_flow.EnvoyReader.getData", result["flow_id"],
return_value=True, {
), patch( "host": "1.1.1.1",
"homeassistant.components.enphase_envoy.async_setup_entry", "username": "test-username",
return_value=True, "password": "test-password",
) as mock_setup_entry: },
result2 = await hass.config_entries.flow.async_configure( )
result["flow_id"],
{
"host": "1.1.1.1",
"username": "test-username",
"password": "test-password",
},
)
await hass.async_block_till_done()
assert result2["type"] == "create_entry" assert result2["type"] == "create_entry"
assert result2["title"] == "Envoy 1234" assert result2["title"] == "Envoy 1234"
assert result2["result"].unique_id == "1234" assert result2["result"].unique_id == "1234"
@ -252,63 +203,34 @@ async def test_zeroconf(hass: HomeAssistant) -> None:
"username": "test-username", "username": "test-username",
"password": "test-password", "password": "test-password",
} }
assert len(mock_setup_entry.mock_calls) == 1
async def test_form_host_already_exists(hass: HomeAssistant) -> None: async def test_form_host_already_exists(
hass: HomeAssistant, config_entry, setup_enphase_envoy
) -> None:
"""Test host already exists.""" """Test host already exists."""
config_entry = MockConfigEntry(
domain=DOMAIN,
data={
"host": "1.1.1.1",
"name": "Envoy",
"username": "test-username",
"password": "test-password",
},
title="Envoy",
)
config_entry.add_to_hass(hass)
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}
) )
assert result["type"] == "form" assert result["type"] == "form"
assert result["errors"] == {} assert result["errors"] == {}
with patch( result2 = await hass.config_entries.flow.async_configure(
"homeassistant.components.enphase_envoy.config_flow.EnvoyReader.getData", result["flow_id"],
return_value=True, {
): "host": "1.1.1.1",
result2 = await hass.config_entries.flow.async_configure( "username": "test-username",
result["flow_id"], "password": "test-password",
{ },
"host": "1.1.1.1", )
"username": "test-username",
"password": "test-password",
},
)
await hass.async_block_till_done()
assert result2["type"] == "abort" assert result2["type"] == "abort"
assert result2["reason"] == "already_configured" assert result2["reason"] == "already_configured"
async def test_zeroconf_serial_already_exists(hass: HomeAssistant) -> None: async def test_zeroconf_serial_already_exists(
hass: HomeAssistant, config_entry, setup_enphase_envoy
) -> None:
"""Test serial number already exists from zeroconf.""" """Test serial number already exists from zeroconf."""
config_entry = MockConfigEntry(
domain=DOMAIN,
data={
"host": "1.1.1.1",
"name": "Envoy",
"username": "test-username",
"password": "test-password",
},
unique_id="1234",
title="Envoy",
)
config_entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, DOMAIN,
context={"source": config_entries.SOURCE_ZEROCONF}, context={"source": config_entries.SOURCE_ZEROCONF},
@ -322,28 +244,16 @@ async def test_zeroconf_serial_already_exists(hass: HomeAssistant) -> None:
type="mock_type", type="mock_type",
), ),
) )
assert result["type"] == "abort" assert result["type"] == "abort"
assert result["reason"] == "already_configured" assert result["reason"] == "already_configured"
assert config_entry.data[CONF_HOST] == "4.4.4.4"
assert config_entry.data["host"] == "4.4.4.4"
async def test_zeroconf_serial_already_exists_ignores_ipv6(hass: HomeAssistant) -> None: async def test_zeroconf_serial_already_exists_ignores_ipv6(
hass: HomeAssistant, config_entry, setup_enphase_envoy
) -> None:
"""Test serial number already exists from zeroconf but the discovery is ipv6.""" """Test serial number already exists from zeroconf but the discovery is ipv6."""
config_entry = MockConfigEntry(
domain=DOMAIN,
data={
"host": "1.1.1.1",
"name": "Envoy",
"username": "test-username",
"password": "test-password",
},
unique_id="1234",
title="Envoy",
)
config_entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, DOMAIN,
context={"source": config_entries.SOURCE_ZEROCONF}, context={"source": config_entries.SOURCE_ZEROCONF},
@ -357,71 +267,39 @@ async def test_zeroconf_serial_already_exists_ignores_ipv6(hass: HomeAssistant)
type="mock_type", type="mock_type",
), ),
) )
assert result["type"] == "abort" assert result["type"] == "abort"
assert result["reason"] == "not_ipv4_address" assert result["reason"] == "not_ipv4_address"
assert config_entry.data[CONF_HOST] == "1.1.1.1"
assert config_entry.data["host"] == "1.1.1.1"
async def test_zeroconf_host_already_exists(hass: HomeAssistant) -> None: @pytest.mark.parametrize("serial_number", [None])
async def test_zeroconf_host_already_exists(
hass: HomeAssistant, config_entry, setup_enphase_envoy
) -> None:
"""Test hosts already exists from zeroconf.""" """Test hosts already exists from zeroconf."""
result = await hass.config_entries.flow.async_init(
config_entry = MockConfigEntry( DOMAIN,
domain=DOMAIN, context={"source": config_entries.SOURCE_ZEROCONF},
data={ data=zeroconf.ZeroconfServiceInfo(
"host": "1.1.1.1", host="1.1.1.1",
"name": "Envoy", addresses=["1.1.1.1"],
"username": "test-username", hostname="mock_hostname",
"password": "test-password", name="mock_name",
}, port=None,
title="Envoy", properties={"serialnum": "1234"},
type="mock_type",
),
) )
config_entry.add_to_hass(hass)
with patch(
"homeassistant.components.enphase_envoy.config_flow.EnvoyReader.getData",
return_value=True,
), patch(
"homeassistant.components.enphase_envoy.async_setup_entry",
return_value=True,
) as mock_setup_entry:
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_ZEROCONF},
data=zeroconf.ZeroconfServiceInfo(
host="1.1.1.1",
addresses=["1.1.1.1"],
hostname="mock_hostname",
name="mock_name",
port=None,
properties={"serialnum": "1234"},
type="mock_type",
),
)
await hass.async_block_till_done()
assert result["type"] == "abort" assert result["type"] == "abort"
assert result["reason"] == "already_configured" assert result["reason"] == "already_configured"
assert config_entry.unique_id == "1234" assert config_entry.unique_id == "1234"
assert config_entry.title == "Envoy 1234" assert config_entry.title == "Envoy 1234"
assert len(mock_setup_entry.mock_calls) == 1
async def test_reauth(hass: HomeAssistant) -> None: async def test_reauth(hass: HomeAssistant, config_entry, setup_enphase_envoy) -> None:
"""Test we reauth auth.""" """Test we reauth auth."""
config_entry = MockConfigEntry(
domain=DOMAIN,
data={
"host": "1.1.1.1",
"name": "Envoy",
"username": "test-username",
"password": "test-password",
},
title="Envoy",
)
config_entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, DOMAIN,
context={ context={
@ -430,19 +308,13 @@ async def test_reauth(hass: HomeAssistant) -> None:
"entry_id": config_entry.entry_id, "entry_id": config_entry.entry_id,
}, },
) )
result2 = await hass.config_entries.flow.async_configure(
with patch( result["flow_id"],
"homeassistant.components.enphase_envoy.config_flow.EnvoyReader.getData", {
return_value=True, "host": "1.1.1.1",
): "username": "test-username",
result2 = await hass.config_entries.flow.async_configure( "password": "test-password",
result["flow_id"], },
{ )
"host": "1.1.1.1",
"username": "test-username",
"password": "test-password",
},
)
assert result2["type"] == "abort" assert result2["type"] == "abort"
assert result2["reason"] == "reauth_successful" assert result2["reason"] == "reauth_successful"