Add config flow to Proliphix

This commit is contained in:
Franck Nijhof 2025-07-03 21:59:24 +00:00
parent e5f7421703
commit 2b465773c4
No known key found for this signature in database
GPG Key ID: AB33ADACE7101952
18 changed files with 788 additions and 30 deletions

2
CODEOWNERS generated
View File

@ -1193,6 +1193,8 @@ build.json @home-assistant/supervisor
/tests/components/profiler/ @bdraco
/homeassistant/components/progettihwsw/ @ardaseremet
/tests/components/progettihwsw/ @ardaseremet
/homeassistant/components/proliphix/ @frenck
/tests/components/proliphix/ @frenck
/homeassistant/components/prometheus/ @knyar
/tests/components/prometheus/ @knyar
/homeassistant/components/prosegur/ @dgomes

View File

@ -1 +1,41 @@
"""The proliphix component."""
from __future__ import annotations
from proliphix import PDP
import requests
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
type ProliphixConfigEntry = ConfigEntry[PDP]
PLATFORMS = [Platform.CLIMATE]
async def async_setup_entry(hass: HomeAssistant, entry: ProliphixConfigEntry) -> bool:
"""Set up Proliphix from a config entry."""
pdp = PDP(
entry.data[CONF_HOST],
entry.data[CONF_USERNAME],
entry.data[CONF_PASSWORD],
)
try:
await hass.async_add_executor_job(pdp.update)
except requests.exceptions.RequestException as ex:
raise ConfigEntryNotReady(
f"Unable to connect to Proliphix thermostat at {entry.data[CONF_HOST]}"
) from ex
# Store the client instance in runtime_data
entry.runtime_data = pdp
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, entry: ProliphixConfigEntry) -> bool:
"""Unload Proliphix config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

View File

@ -2,9 +2,9 @@
from __future__ import annotations
import asyncio
from typing import Any
import proliphix
import voluptuous as vol
from homeassistant.components.climate import (
@ -23,10 +23,16 @@ from homeassistant.const import (
UnitOfTemperature,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers import config_validation as cv, issue_registry as ir
from homeassistant.helpers.entity_platform import (
AddConfigEntryEntitiesCallback,
AddEntitiesCallback,
)
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from . import ProliphixConfigEntry
from .const import DOMAIN
ATTR_FAN = "fan"
PLATFORM_SCHEMA = CLIMATE_PLATFORM_SCHEMA.extend(
@ -45,50 +51,72 @@ def setup_platform(
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up the Proliphix thermostats."""
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
host = config.get(CONF_HOST)
pdp = proliphix.PDP(host, username, password)
pdp.update()
# Handle YAML import by creating config entry
asyncio.run_coroutine_threadsafe(
hass.config_entries.flow.async_init(
DOMAIN,
context={"source": "import"},
data=config,
),
hass.loop,
)
add_entities([ProliphixThermostat(pdp)], True)
# Create repair issue for deprecated YAML configuration
ir.create_issue(
hass,
DOMAIN,
"deprecated_yaml",
is_fixable=False,
issue_domain=DOMAIN,
severity=ir.IssueSeverity.WARNING,
translation_key="deprecated_yaml",
translation_placeholders={
"domain": DOMAIN,
"integration_title": "Proliphix",
},
)
async def async_setup_entry(
hass: HomeAssistant,
entry: ProliphixConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up Proliphix thermostat from a config entry."""
async_add_entities([ProliphixThermostat(entry)], True)
class ProliphixThermostat(ClimateEntity):
"""Representation a Proliphix thermostat."""
_attr_hvac_modes = [HVACMode.HEAT, HVACMode.COOL, HVACMode.OFF]
_attr_precision = PRECISION_TENTHS
_attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
_attr_temperature_unit = UnitOfTemperature.FAHRENHEIT
def __init__(self, pdp):
def __init__(self, entry: ProliphixConfigEntry) -> None:
"""Initialize the thermostat."""
self._pdp = pdp
self._name = None
self._pdp = entry.runtime_data
self._attr_name = self._pdp.name
self._attr_unique_id = entry.entry_id
def update(self) -> None:
"""Update the data from the thermostat."""
self._pdp.update()
self._name = self._pdp.name
@property
def name(self):
"""Return the name of the thermostat."""
return self._name
@property
def extra_state_attributes(self):
def extra_state_attributes(self) -> dict[str, Any]:
"""Return the device specific state attributes."""
return {ATTR_FAN: self._pdp.fan_state}
@property
def current_temperature(self):
def current_temperature(self) -> float | None:
"""Return the current temperature."""
return self._pdp.cur_temp
@property
def target_temperature(self):
def target_temperature(self) -> float | None:
"""Return the temperature we try to reach."""
return self._pdp.setback
@ -113,11 +141,6 @@ class ProliphixThermostat(ClimateEntity):
return HVACMode.COOL
return HVACMode.OFF
@property
def hvac_modes(self) -> list[HVACMode]:
"""Return available HVAC modes."""
return []
def set_temperature(self, **kwargs: Any) -> None:
"""Set new target temperature."""
if (temperature := kwargs.get(ATTR_TEMPERATURE)) is None:

View File

@ -0,0 +1,87 @@
"""Config flow to configure the Proliphix integration."""
from __future__ import annotations
from typing import Any
from proliphix import PDP
import requests
import voluptuous as vol
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.helpers.selector import (
TextSelector,
TextSelectorConfig,
TextSelectorType,
)
from .const import DOMAIN, LOGGER
class ProliphixConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Proliphix."""
VERSION = 1
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle the initial step."""
errors = {}
if user_input is not None:
# Check if already configured with this host
self._async_abort_entries_match({CONF_HOST: user_input[CONF_HOST]})
# Test connection to the thermostat
try:
pdp = PDP(
user_input[CONF_HOST],
user_input[CONF_USERNAME],
user_input[CONF_PASSWORD],
)
await self.hass.async_add_executor_job(pdp.update)
title = await self.hass.async_add_executor_job(lambda: pdp.name)
except requests.exceptions.RequestException:
LOGGER.exception("Network error connecting to Proliphix thermostat")
errors["base"] = "cannot_connect"
except Exception: # noqa: BLE001
LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
else:
return self.async_create_entry(
title=title or "Proliphix", data=user_input
)
return self.async_show_form(
step_id="user",
data_schema=vol.Schema(
{
vol.Required(CONF_HOST): TextSelector(
TextSelectorConfig(autocomplete="off")
),
vol.Required(CONF_USERNAME): TextSelector(
TextSelectorConfig(autocomplete="off")
),
vol.Required(CONF_PASSWORD): TextSelector(
TextSelectorConfig(type=TextSelectorType.PASSWORD)
),
}
),
errors=errors,
)
async def async_step_import(
self, import_config: dict[str, Any]
) -> ConfigFlowResult:
"""Handle YAML import."""
# Check if already configured with this host
self._async_abort_entries_match({CONF_HOST: import_config[CONF_HOST]})
return self.async_create_entry(
title="Proliphix",
data={
CONF_HOST: import_config[CONF_HOST],
CONF_USERNAME: import_config[CONF_USERNAME],
CONF_PASSWORD: import_config[CONF_PASSWORD],
},
)

View File

@ -0,0 +1,10 @@
"""Constants for the Proliphix integration."""
from __future__ import annotations
import logging
from typing import Final
DOMAIN: Final = "proliphix"
LOGGER = logging.getLogger(__package__)

View File

@ -1,8 +1,10 @@
{
"domain": "proliphix",
"name": "Proliphix",
"codeowners": [],
"codeowners": ["@frenck"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/proliphix",
"integration_type": "device",
"iot_class": "local_polling",
"loggers": ["proliphix"],
"quality_scale": "legacy",

View File

@ -0,0 +1,123 @@
rules:
# Bronze
action-setup:
status: exempt
comment: Integration does not register custom actions.
appropriate-polling: done
brands: done
common-modules: done
config-flow-test-coverage: done
config-flow: done
dependency-transparency: done
docs-actions:
status: exempt
comment: |
This integration does not have any custom actions.
docs-high-level-description: done
docs-installation-instructions: todo
docs-removal-instructions: todo
entity-event-setup:
status: exempt
comment: |
Entities of this integration does not explicitly subscribe to events.
entity-unique-id: done
has-entity-name: todo
runtime-data: done
test-before-configure: done
test-before-setup: done
unique-config-entry: done
# Silver
action-exceptions:
status: exempt
comment: Integration does not register custom actions.
config-entry-unloading: done
docs-configuration-parameters:
status: exempt
comment: |
This integration does not have any configuration parameters.
docs-installation-parameters: todo
entity-unavailable:
status: todo
comment: Climate entity does not implement availability checks for device connectivity.
integration-owner: done
log-when-unavailable:
status: todo
comment: Integration does not log when device becomes unavailable.
parallel-updates:
status: todo
comment: No PARALLEL_UPDATES setting configured for platform.
reauthentication-flow:
status: todo
comment: Config flow does not implement reauthentication support.
test-coverage:
status: todo
comment: Test coverage needs to be improved.
# Gold
devices:
status: todo
comment: Integration does not create device entries in device registry.
diagnostics:
status: todo
comment: Integration does not provide diagnostic data collection.
discovery-update-info:
status: exempt
comment: Integration does not support discovery.
discovery:
status: exempt
comment: Integration does not support discovery.
docs-data-update:
status: todo
comment: Documentation needs data update information.
docs-examples:
status: todo
comment: Documentation needs usage examples.
docs-known-limitations:
status: todo
comment: Documentation needs known limitations section.
docs-supported-devices:
status: todo
comment: Documentation needs supported devices list.
docs-supported-functions:
status: todo
comment: Documentation needs supported functions description.
docs-troubleshooting:
status: todo
comment: Documentation needs troubleshooting section.
docs-use-cases:
status: todo
comment: Documentation needs use cases section.
dynamic-devices:
status: exempt
comment: |
This integration connects to a single device.
entity-category:
status: todo
comment: Entities should have appropriate categories assigned.
entity-device-class: done
entity-disabled-by-default:
status: exempt
comment: All entities should be enabled by default for this integration.
entity-translations:
status: todo
comment: Entity names should use translation keys.
exception-translations:
status: todo
comment: User-facing exceptions should use translation keys.
icon-translations:
status: todo
comment: Dynamic icons based on state should be implemented.
reconfiguration-flow:
status: todo
comment: Config flow should support reconfiguration.
repair-issues: done
stale-devices:
status: exempt
comment: |
This integration connects to a single device.
# Platinum
async-dependency: todo
inject-websession: todo
strict-typing: todo

View File

@ -0,0 +1,33 @@
{
"config": {
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"import_failed": "Failed to import the YAML configuration."
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"step": {
"user": {
"data": {
"host": "[%key:common::config_flow::data::host%]",
"username": "[%key:common::config_flow::data::username%]",
"password": "[%key:common::config_flow::data::password%]"
},
"data_description": {
"host": "The hostname or IP address of your Proliphix thermostat on your home network.",
"username": "The username to authenticate with your Proliphix thermostat.",
"password": "The password to authenticate with your Proliphix thermostat."
},
"description": "Set up your Proliphix thermostat to integrate with Home Assistant.\n\nTo do so, you will need to get the IP address of your Proliphix thermostat and the username and password you use to authenticate with it."
}
}
},
"issues": {
"deprecated_yaml": {
"title": "The {integration_title} YAML configuration is being removed",
"description": "Configuring {integration_title} using YAML is being removed.\n\nYour existing YAML configuration has been imported into the UI automatically.\n\nRemove the `{domain}` configuration from your configuration.yaml file and restart Home Assistant to fix this issue."
}
}
}

View File

@ -493,6 +493,7 @@ FLOWS = {
"probe_plus",
"profiler",
"progettihwsw",
"proliphix",
"prosegur",
"proximity",
"prusalink",

View File

@ -5078,8 +5078,8 @@
},
"proliphix": {
"name": "Proliphix",
"integration_type": "hub",
"config_flow": false,
"integration_type": "device",
"config_flow": true,
"iot_class": "local_polling"
},
"prometheus": {

View File

@ -1444,6 +1444,9 @@ praw==7.5.0
# homeassistant.components.islamic_prayer_times
prayer-times-calculator-offline==1.0.3
# homeassistant.components.proliphix
proliphix==0.4.1
# homeassistant.components.prometheus
prometheus-client==0.21.0

View File

@ -787,7 +787,6 @@ INTEGRATIONS_WITHOUT_QUALITY_SCALE_FILE = [
"private_ble_device",
"profiler",
"progettihwsw",
"proliphix",
"prometheus",
"prosegur",
"prowl",

View File

@ -0,0 +1 @@
"""Tests for the Proliphix integration."""

View File

@ -0,0 +1,72 @@
"""Test fixtures for Proliphix integration."""
from collections.abc import Generator
from unittest.mock import MagicMock, patch
import pytest
from homeassistant.components.proliphix.const import DOMAIN
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry
@pytest.fixture
def mock_setup_entry() -> Generator[None]:
"""Override async_setup_entry."""
with patch(
"homeassistant.components.proliphix.async_setup_entry", return_value=True
):
yield
@pytest.fixture
def mock_proliphix():
"""Return a mocked Proliphix PDP instance."""
with (
patch(
"homeassistant.components.proliphix.config_flow.PDP",
autospec=True,
) as mock_class,
patch("homeassistant.components.proliphix.PDP", new=mock_class),
):
pdp = mock_class.return_value
pdp.name = "Living Room Thermostat"
pdp.cur_temp = 72.5
pdp.setback = 70.0
pdp.fan_state = "Auto"
pdp.hvac_state = 3 # Heating
pdp.is_heating = True
pdp.is_cooling = False
pdp.update.return_value = None
yield pdp
@pytest.fixture
def mock_config_entry() -> MockConfigEntry:
"""Return the default mocked config entry."""
return MockConfigEntry(
title="Proliphix Thermostat",
domain=DOMAIN,
data={
CONF_HOST: "192.168.1.100",
CONF_USERNAME: "admin",
CONF_PASSWORD: "password123",
},
entry_id="01JZ8Z7KKH3FIXEDTESTENTRY01",
)
@pytest.fixture
async def init_integration(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_proliphix: MagicMock,
) -> MockConfigEntry:
"""Set up the Proliphix integration for testing."""
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
return mock_config_entry

View File

@ -0,0 +1,69 @@
# serializer version: 1
# name: test_climate_entities[climate.living_room_thermostat-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'hvac_modes': list([
<HVACMode.HEAT: 'heat'>,
<HVACMode.COOL: 'cool'>,
<HVACMode.OFF: 'off'>,
]),
'max_temp': 35.0,
'min_temp': 7.0,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'climate',
'entity_category': None,
'entity_id': 'climate.living_room_thermostat',
'has_entity_name': False,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Living Room Thermostat',
'platform': 'proliphix',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': <ClimateEntityFeature: 1>,
'translation_key': None,
'unique_id': '01JZ8Z7KKH3FIXEDTESTENTRY01',
'unit_of_measurement': None,
})
# ---
# name: test_climate_entities[climate.living_room_thermostat-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'current_temperature': 22.5,
'fan': 'Auto',
'friendly_name': 'Living Room Thermostat',
'hvac_action': <HVACAction.HEATING: 'heating'>,
'hvac_modes': list([
<HVACMode.HEAT: 'heat'>,
<HVACMode.COOL: 'cool'>,
<HVACMode.OFF: 'off'>,
]),
'max_temp': 35.0,
'min_temp': 7.0,
'supported_features': <ClimateEntityFeature: 1>,
'temperature': 21.1,
}),
'context': <ANY>,
'entity_id': 'climate.living_room_thermostat',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'heat',
})
# ---

View File

@ -0,0 +1,20 @@
"""Test the Proliphix climate platform."""
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from tests.common import MockConfigEntry, snapshot_platform
@pytest.mark.usefixtures("init_integration")
async def test_climate_entities(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test climate entities are set up correctly."""
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)

View File

@ -0,0 +1,180 @@
"""Configuration flow tests for the Proliphix integration."""
from unittest.mock import MagicMock
import pytest
import requests
from homeassistant.components.proliphix.const import DOMAIN
from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from tests.common import MockConfigEntry
pytestmark = pytest.mark.usefixtures("mock_setup_entry", "mock_proliphix")
async def test_user_flow(hass: HomeAssistant) -> None:
"""Test the full happy path user flow from start to finish."""
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_USER},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user"
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={
CONF_HOST: "192.168.1.100",
CONF_USERNAME: "admin",
CONF_PASSWORD: "password123",
},
)
assert result["type"] is FlowResultType.CREATE_ENTRY
config_entry = result["result"]
assert config_entry.data == {
CONF_HOST: "192.168.1.100",
CONF_USERNAME: "admin",
CONF_PASSWORD: "password123",
}
assert not config_entry.options
async def test_user_flow_already_configured(hass: HomeAssistant) -> None:
"""Test configuration flow aborts when the device is already configured."""
config_entry = MockConfigEntry(
domain=DOMAIN,
data={
CONF_HOST: "192.168.1.100",
CONF_USERNAME: "admin",
CONF_PASSWORD: "password123",
},
entry_id="01JZ8Z7KKH3FIXEDTESTENTRY01",
)
config_entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_USER},
data={
CONF_HOST: "192.168.1.100",
CONF_USERNAME: "admin",
CONF_PASSWORD: "password123",
},
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "already_configured"
async def test_import_flow(hass: HomeAssistant) -> None:
"""Test the YAML import flow."""
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_IMPORT},
data={
CONF_HOST: "192.168.1.100",
CONF_USERNAME: "admin",
CONF_PASSWORD: "password123",
},
)
assert result["type"] is FlowResultType.CREATE_ENTRY
config_entry = result["result"]
assert config_entry.data == {
CONF_HOST: "192.168.1.100",
CONF_USERNAME: "admin",
CONF_PASSWORD: "password123",
}
async def test_import_flow_already_configured(hass: HomeAssistant) -> None:
"""Test YAML import flow when device is already configured."""
config_entry = MockConfigEntry(
domain=DOMAIN,
data={
CONF_HOST: "192.168.1.100",
CONF_USERNAME: "admin",
CONF_PASSWORD: "password123",
},
entry_id="01JZ8Z7KKH3FIXEDTESTENTRY01",
)
config_entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_IMPORT},
data={
CONF_HOST: "192.168.1.100",
CONF_USERNAME: "admin",
CONF_PASSWORD: "password123",
},
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "already_configured"
async def test_user_flow_network_error(
hass: HomeAssistant, mock_proliphix: MagicMock
) -> None:
"""Test configuration flow with network connection error."""
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_USER},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user"
# Mock network connection failure
mock_proliphix.update.side_effect = requests.exceptions.ConnectionError(
"Network unreachable"
)
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={
CONF_HOST: "192.168.1.100",
CONF_USERNAME: "admin",
CONF_PASSWORD: "password123",
},
)
assert result["type"] is FlowResultType.FORM
assert result["errors"] and result["errors"]["base"] == "cannot_connect"
async def test_user_flow_unknown_error(
hass: HomeAssistant, mock_proliphix: MagicMock
) -> None:
"""Test configuration flow with unknown error."""
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_USER},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user"
# Mock unknown exception on update method
mock_proliphix.update.side_effect = ValueError("Some unexpected error")
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={
CONF_HOST: "192.168.1.100",
CONF_USERNAME: "admin",
CONF_PASSWORD: "password123",
},
)
assert result["type"] is FlowResultType.FORM
assert result["errors"] and result["errors"]["base"] == "unknown"

View File

@ -0,0 +1,93 @@
"""Test the Proliphix integration initialization."""
from unittest.mock import MagicMock
import pytest
import requests
from homeassistant.components.proliphix.const import DOMAIN
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant
from homeassistant.helpers import issue_registry as ir
from homeassistant.setup import async_setup_component
from tests.common import MockConfigEntry
pytestmark = pytest.mark.usefixtures("mock_proliphix")
async def test_setup_and_unload_entry(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test setting up and unloading a config entry."""
mock_config_entry.add_to_hass(hass)
# Test setup
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
assert mock_config_entry.state == ConfigEntryState.LOADED
# Test unload
assert await hass.config_entries.async_unload(mock_config_entry.entry_id)
await hass.async_block_till_done()
assert mock_config_entry.state == ConfigEntryState.NOT_LOADED
async def test_setup_entry_connection_error(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_proliphix: MagicMock,
) -> None:
"""Test config entry setup with connection error."""
mock_config_entry.add_to_hass(hass)
# Mock connection error during setup
mock_proliphix.update.side_effect = requests.exceptions.ConnectionError(
"Connection failed"
)
# Setup should fail
assert not await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
assert mock_config_entry.state == ConfigEntryState.SETUP_RETRY
@pytest.mark.usefixtures("mock_proliphix")
async def test_setup_yaml_import(
hass: HomeAssistant,
issue_registry: ir.IssueRegistry,
) -> None:
"""Test setting up Proliphix via YAML configuration."""
config = {
"climate": {
"platform": "proliphix",
CONF_HOST: "192.168.1.100",
CONF_USERNAME: "admin",
CONF_PASSWORD: "password123",
}
}
# Setup the integration with YAML config
assert await async_setup_component(hass, "climate", config)
await hass.async_block_till_done()
# Check that repair issue was created for deprecated YAML configuration
issue = issue_registry.async_get_issue(DOMAIN, "deprecated_yaml")
assert issue is not None
assert issue.severity == ir.IssueSeverity.WARNING
assert issue.translation_key == "deprecated_yaml"
# Wait for the import flow to complete
await hass.async_block_till_done()
# Check that a config entry was created via import
config_entries = hass.config_entries.async_entries(DOMAIN)
assert len(config_entries) == 1
assert config_entries[0].source == "import"
assert config_entries[0].data == {
CONF_HOST: "192.168.1.100",
CONF_USERNAME: "admin",
CONF_PASSWORD: "password123",
}