mirror of
https://github.com/home-assistant/core.git
synced 2025-07-16 17:57:11 +00:00
Add optional test fixture collection to enphase_envoy diagnostic report (#116242)
* diagnostics_fixtures * fix codespell errors * fix merge order and typo * remove pointless-string-statement
This commit is contained in:
parent
deee10813c
commit
158c8b8400
@ -12,12 +12,22 @@ from pyenphase import AUTH_TOKEN_MIN_VERSION, Envoy, EnvoyError
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components import zeroconf
|
||||
from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult
|
||||
from homeassistant.config_entries import (
|
||||
ConfigEntry,
|
||||
ConfigFlow,
|
||||
ConfigFlowResult,
|
||||
OptionsFlowWithConfigEntry,
|
||||
)
|
||||
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.httpx_client import get_async_client
|
||||
|
||||
from .const import DOMAIN, INVALID_AUTH_ERRORS
|
||||
from .const import (
|
||||
DOMAIN,
|
||||
INVALID_AUTH_ERRORS,
|
||||
OPTION_DIAGNOSTICS_INCLUDE_FIXTURES,
|
||||
OPTION_DIAGNOSTICS_INCLUDE_FIXTURES_DEFAULT_VALUE,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -50,6 +60,12 @@ class EnphaseConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
self.protovers: str | None = None
|
||||
self._reauth_entry: ConfigEntry | None = None
|
||||
|
||||
@staticmethod
|
||||
@callback
|
||||
def async_get_options_flow(config_entry: ConfigEntry) -> EnvoyOptionsFlowHandler:
|
||||
"""Options flow handler for Enphase_Envoy."""
|
||||
return EnvoyOptionsFlowHandler(config_entry)
|
||||
|
||||
@callback
|
||||
def _async_generate_schema(self) -> vol.Schema:
|
||||
"""Generate schema."""
|
||||
@ -282,3 +298,33 @@ class EnphaseConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
description_placeholders=description_placeholders,
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
|
||||
class EnvoyOptionsFlowHandler(OptionsFlowWithConfigEntry):
|
||||
"""Envoy config flow options handler."""
|
||||
|
||||
async def async_step_init(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Manage the options."""
|
||||
if user_input is not None:
|
||||
return self.async_create_entry(title="", data=user_input)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="init",
|
||||
data_schema=vol.Schema(
|
||||
{
|
||||
vol.Required(
|
||||
OPTION_DIAGNOSTICS_INCLUDE_FIXTURES,
|
||||
default=self.config_entry.options.get(
|
||||
OPTION_DIAGNOSTICS_INCLUDE_FIXTURES,
|
||||
OPTION_DIAGNOSTICS_INCLUDE_FIXTURES_DEFAULT_VALUE,
|
||||
),
|
||||
): bool,
|
||||
}
|
||||
),
|
||||
description_placeholders={
|
||||
CONF_SERIAL: self.config_entry.unique_id,
|
||||
CONF_HOST: self.config_entry.data.get("host"),
|
||||
},
|
||||
)
|
||||
|
@ -15,3 +15,6 @@ PLATFORMS = [
|
||||
]
|
||||
|
||||
INVALID_AUTH_ERRORS = (EnvoyAuthenticationError, EnvoyAuthenticationRequired)
|
||||
|
||||
OPTION_DIAGNOSTICS_INCLUDE_FIXTURES = "diagnostics_include_fixtures"
|
||||
OPTION_DIAGNOSTICS_INCLUDE_FIXTURES_DEFAULT_VALUE = False
|
||||
|
@ -6,6 +6,8 @@ import copy
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from attr import asdict
|
||||
from pyenphase.envoy import Envoy
|
||||
from pyenphase.exceptions import EnvoyError
|
||||
|
||||
from homeassistant.components.diagnostics import async_redact_data
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
@ -21,7 +23,7 @@ from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
from homeassistant.helpers.json import json_dumps
|
||||
from homeassistant.util.json import json_loads
|
||||
|
||||
from .const import DOMAIN
|
||||
from .const import DOMAIN, OPTION_DIAGNOSTICS_INCLUDE_FIXTURES
|
||||
from .coordinator import EnphaseUpdateCoordinator
|
||||
|
||||
CONF_TITLE = "title"
|
||||
@ -38,6 +40,46 @@ TO_REDACT = {
|
||||
}
|
||||
|
||||
|
||||
async def _get_fixture_collection(envoy: Envoy, serial: str) -> dict[str, Any]:
|
||||
"""Collect Envoy endpoints to use for test fixture set."""
|
||||
fixture_data: dict[str, Any] = {}
|
||||
end_points = [
|
||||
"/info",
|
||||
"/api/v1/production",
|
||||
"/api/v1/production/inverters",
|
||||
"/production.json",
|
||||
"/production.json?details=1",
|
||||
"/production",
|
||||
"/ivp/ensemble/power",
|
||||
"/ivp/ensemble/inventory",
|
||||
"/ivp/ensemble/dry_contacts",
|
||||
"/ivp/ensemble/status",
|
||||
"/ivp/ensemble/secctrl",
|
||||
"/ivp/ss/dry_contact_settings",
|
||||
"/admin/lib/tariff",
|
||||
"/ivp/ss/gen_config",
|
||||
"/ivp/ss/gen_schedule",
|
||||
"/ivp/sc/pvlimit",
|
||||
"/ivp/ss/pel_settings",
|
||||
"/ivp/ensemble/generator",
|
||||
"/ivp/meters",
|
||||
"/ivp/meters/readings",
|
||||
]
|
||||
|
||||
for end_point in end_points:
|
||||
response = await envoy.request(end_point)
|
||||
fixture_data[end_point] = response.text.replace("\n", "").replace(
|
||||
serial, CLEAN_TEXT
|
||||
)
|
||||
fixture_data[f"{end_point}_log"] = json_dumps(
|
||||
{
|
||||
"headers": dict(response.headers.items()),
|
||||
"code": response.status_code,
|
||||
}
|
||||
)
|
||||
return fixture_data
|
||||
|
||||
|
||||
async def async_get_config_entry_diagnostics(
|
||||
hass: HomeAssistant, entry: ConfigEntry
|
||||
) -> dict[str, Any]:
|
||||
@ -113,12 +155,20 @@ async def async_get_config_entry_diagnostics(
|
||||
"ct_storage_meter": envoy.storage_meter_type,
|
||||
}
|
||||
|
||||
fixture_data: dict[str, Any] = {}
|
||||
if entry.options.get(OPTION_DIAGNOSTICS_INCLUDE_FIXTURES, False):
|
||||
try:
|
||||
fixture_data = await _get_fixture_collection(envoy=envoy, serial=old_serial)
|
||||
except EnvoyError as err:
|
||||
fixture_data["Error"] = repr(err)
|
||||
|
||||
diagnostic_data: dict[str, Any] = {
|
||||
"config_entry": async_redact_data(entry.as_dict(), TO_REDACT),
|
||||
"envoy_properties": envoy_properties,
|
||||
"raw_data": json_loads(coordinator_data_cleaned),
|
||||
"envoy_model_data": envoy_model,
|
||||
"envoy_entities_by_device": json_loads(device_entities_cleaned),
|
||||
"fixtures": fixture_data,
|
||||
}
|
||||
|
||||
return diagnostic_data
|
||||
|
@ -36,6 +36,16 @@
|
||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"step": {
|
||||
"init": {
|
||||
"title": "Envoy {serial} {host} options",
|
||||
"data": {
|
||||
"diagnostics_include_fixtures": "Include test fixture data in diagnostic report. Use when requested to provide test data for troubleshooting or development activies. With this option enabled the diagnostic report may take more time to download. When report is created best disable this option again."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"binary_sensor": {
|
||||
"communicating": {
|
||||
|
@ -339,11 +339,22 @@ def mock_envoy_fixture(
|
||||
raw={"varies_by": "firmware_version"},
|
||||
)
|
||||
mock_envoy.update = AsyncMock(return_value=mock_envoy.data)
|
||||
|
||||
response = Mock()
|
||||
response.status_code = 200
|
||||
response.text = "Testing request \nreplies."
|
||||
response.headers = {"Hello": "World"}
|
||||
mock_envoy.request = AsyncMock(return_value=response)
|
||||
|
||||
return mock_envoy
|
||||
|
||||
|
||||
@pytest.fixture(name="setup_enphase_envoy")
|
||||
async def setup_enphase_envoy_fixture(hass: HomeAssistant, config, mock_envoy):
|
||||
async def setup_enphase_envoy_fixture(
|
||||
hass: HomeAssistant,
|
||||
config,
|
||||
mock_envoy,
|
||||
):
|
||||
"""Define a fixture to set up Enphase Envoy."""
|
||||
with (
|
||||
patch(
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -10,7 +10,12 @@ from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components import zeroconf
|
||||
from homeassistant.components.enphase_envoy.const import DOMAIN, PLATFORMS
|
||||
from homeassistant.components.enphase_envoy.const import (
|
||||
DOMAIN,
|
||||
OPTION_DIAGNOSTICS_INCLUDE_FIXTURES,
|
||||
OPTION_DIAGNOSTICS_INCLUDE_FIXTURES_DEFAULT_VALUE,
|
||||
PLATFORMS,
|
||||
)
|
||||
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.data_entry_flow import FlowResultType
|
||||
@ -656,6 +661,41 @@ async def test_reauth(hass: HomeAssistant, config_entry, setup_enphase_envoy) ->
|
||||
assert result2["reason"] == "reauth_successful"
|
||||
|
||||
|
||||
async def test_options_default(
|
||||
hass: HomeAssistant, config_entry, setup_enphase_envoy
|
||||
) -> None:
|
||||
"""Test we can configure options."""
|
||||
result = await hass.config_entries.options.async_init(config_entry.entry_id)
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "init"
|
||||
|
||||
result = await hass.config_entries.options.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={
|
||||
OPTION_DIAGNOSTICS_INCLUDE_FIXTURES: OPTION_DIAGNOSTICS_INCLUDE_FIXTURES_DEFAULT_VALUE
|
||||
},
|
||||
)
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert config_entry.options == {
|
||||
OPTION_DIAGNOSTICS_INCLUDE_FIXTURES: OPTION_DIAGNOSTICS_INCLUDE_FIXTURES_DEFAULT_VALUE
|
||||
}
|
||||
|
||||
|
||||
async def test_options_set(
|
||||
hass: HomeAssistant, config_entry, setup_enphase_envoy
|
||||
) -> None:
|
||||
"""Test we can configure options."""
|
||||
result = await hass.config_entries.options.async_init(config_entry.entry_id)
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "init"
|
||||
|
||||
result = await hass.config_entries.options.async_configure(
|
||||
result["flow_id"], user_input={OPTION_DIAGNOSTICS_INCLUDE_FIXTURES: True}
|
||||
)
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert config_entry.options == {OPTION_DIAGNOSTICS_INCLUDE_FIXTURES: True}
|
||||
|
||||
|
||||
async def test_reconfigure(
|
||||
hass: HomeAssistant, config_entry, setup_enphase_envoy
|
||||
) -> None:
|
||||
|
@ -1,10 +1,20 @@
|
||||
"""Test Enphase Envoy diagnostics."""
|
||||
|
||||
from syrupy import SnapshotAssertion
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
from pyenphase.exceptions import EnvoyError
|
||||
import pytest
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.enphase_envoy.const import (
|
||||
DOMAIN,
|
||||
OPTION_DIAGNOSTICS_INCLUDE_FIXTURES,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
from tests.components.diagnostics import get_diagnostics_for_config_entry
|
||||
from tests.typing import ClientSessionGenerator
|
||||
|
||||
@ -35,3 +45,76 @@ async def test_entry_diagnostics(
|
||||
assert await get_diagnostics_for_config_entry(
|
||||
hass, hass_client, config_entry
|
||||
) == snapshot(exclude=limit_diagnostic_attrs)
|
||||
|
||||
|
||||
@pytest.fixture(name="config_entry_options")
|
||||
def config_entry_options_fixture(hass: HomeAssistant, config, serial_number):
|
||||
"""Define a config entry fixture."""
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
entry_id="45a36e55aaddb2007c5f6602e0c38e72",
|
||||
title=f"Envoy {serial_number}" if serial_number else "Envoy",
|
||||
unique_id=serial_number,
|
||||
data=config,
|
||||
options={OPTION_DIAGNOSTICS_INCLUDE_FIXTURES: True},
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
return entry
|
||||
|
||||
|
||||
async def test_entry_diagnostics_with_fixtures(
|
||||
hass: HomeAssistant,
|
||||
hass_client: ClientSessionGenerator,
|
||||
config_entry_options: ConfigEntry,
|
||||
setup_enphase_envoy,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test config entry diagnostics."""
|
||||
assert await get_diagnostics_for_config_entry(
|
||||
hass, hass_client, config_entry_options
|
||||
) == snapshot(exclude=limit_diagnostic_attrs)
|
||||
|
||||
|
||||
@pytest.fixture(name="setup_enphase_envoy_options_error")
|
||||
async def setup_enphase_envoy_options_error_fixture(
|
||||
hass: HomeAssistant,
|
||||
config,
|
||||
mock_envoy_options_error,
|
||||
):
|
||||
"""Define a fixture to set up Enphase Envoy."""
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.enphase_envoy.config_flow.Envoy",
|
||||
return_value=mock_envoy_options_error,
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.enphase_envoy.Envoy",
|
||||
return_value=mock_envoy_options_error,
|
||||
),
|
||||
):
|
||||
assert await async_setup_component(hass, DOMAIN, config)
|
||||
await hass.async_block_till_done()
|
||||
yield
|
||||
|
||||
|
||||
@pytest.fixture(name="mock_envoy_options_error")
|
||||
def mock_envoy_options_fixture(
|
||||
mock_envoy,
|
||||
):
|
||||
"""Mock envoy with error in request."""
|
||||
mock_envoy_options = mock_envoy
|
||||
mock_envoy_options.request.side_effect = AsyncMock(side_effect=EnvoyError("Test"))
|
||||
return mock_envoy_options
|
||||
|
||||
|
||||
async def test_entry_diagnostics_with_fixtures_with_error(
|
||||
hass: HomeAssistant,
|
||||
hass_client: ClientSessionGenerator,
|
||||
config_entry_options: ConfigEntry,
|
||||
setup_enphase_envoy_options_error,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test config entry diagnostics."""
|
||||
assert await get_diagnostics_for_config_entry(
|
||||
hass, hass_client, config_entry_options
|
||||
) == snapshot(exclude=limit_diagnostic_attrs)
|
||||
|
Loading…
x
Reference in New Issue
Block a user