mirror of
https://github.com/home-assistant/core.git
synced 2025-07-29 16:17:20 +00:00
Add config flow to Proliphix
This commit is contained in:
parent
e5f7421703
commit
2b465773c4
2
CODEOWNERS
generated
2
CODEOWNERS
generated
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
|
87
homeassistant/components/proliphix/config_flow.py
Normal file
87
homeassistant/components/proliphix/config_flow.py
Normal 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],
|
||||
},
|
||||
)
|
10
homeassistant/components/proliphix/const.py
Normal file
10
homeassistant/components/proliphix/const.py
Normal 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__)
|
@ -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",
|
||||
|
123
homeassistant/components/proliphix/quality_scale.yaml
Normal file
123
homeassistant/components/proliphix/quality_scale.yaml
Normal 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
|
33
homeassistant/components/proliphix/strings.json
Normal file
33
homeassistant/components/proliphix/strings.json
Normal 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."
|
||||
}
|
||||
}
|
||||
}
|
1
homeassistant/generated/config_flows.py
generated
1
homeassistant/generated/config_flows.py
generated
@ -493,6 +493,7 @@ FLOWS = {
|
||||
"probe_plus",
|
||||
"profiler",
|
||||
"progettihwsw",
|
||||
"proliphix",
|
||||
"prosegur",
|
||||
"proximity",
|
||||
"prusalink",
|
||||
|
@ -5078,8 +5078,8 @@
|
||||
},
|
||||
"proliphix": {
|
||||
"name": "Proliphix",
|
||||
"integration_type": "hub",
|
||||
"config_flow": false,
|
||||
"integration_type": "device",
|
||||
"config_flow": true,
|
||||
"iot_class": "local_polling"
|
||||
},
|
||||
"prometheus": {
|
||||
|
3
requirements_test_all.txt
generated
3
requirements_test_all.txt
generated
@ -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
|
||||
|
||||
|
@ -787,7 +787,6 @@ INTEGRATIONS_WITHOUT_QUALITY_SCALE_FILE = [
|
||||
"private_ble_device",
|
||||
"profiler",
|
||||
"progettihwsw",
|
||||
"proliphix",
|
||||
"prometheus",
|
||||
"prosegur",
|
||||
"prowl",
|
||||
|
1
tests/components/proliphix/__init__.py
Normal file
1
tests/components/proliphix/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
"""Tests for the Proliphix integration."""
|
72
tests/components/proliphix/conftest.py
Normal file
72
tests/components/proliphix/conftest.py
Normal 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
|
69
tests/components/proliphix/snapshots/test_climate.ambr
Normal file
69
tests/components/proliphix/snapshots/test_climate.ambr
Normal 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',
|
||||
})
|
||||
# ---
|
20
tests/components/proliphix/test_climate.py
Normal file
20
tests/components/proliphix/test_climate.py
Normal 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)
|
180
tests/components/proliphix/test_config_flow.py
Normal file
180
tests/components/proliphix/test_config_flow.py
Normal 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"
|
93
tests/components/proliphix/test_init.py
Normal file
93
tests/components/proliphix/test_init.py
Normal 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",
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user