From 1b6af6945ad98df73a300e92c1d42a2082eb6ae7 Mon Sep 17 00:00:00 2001 From: Erik Date: Mon, 26 Feb 2024 14:23:28 +0100 Subject: [PATCH 1/4] Remove JuiceNet integration --- .coveragerc | 6 - CODEOWNERS | 2 - homeassistant/components/juicenet/__init__.py | 113 ++++------------ .../components/juicenet/config_flow.py | 70 +--------- homeassistant/components/juicenet/const.py | 6 - homeassistant/components/juicenet/device.py | 19 --- homeassistant/components/juicenet/entity.py | 34 ----- .../components/juicenet/manifest.json | 7 +- homeassistant/components/juicenet/number.py | 99 -------------- homeassistant/components/juicenet/sensor.py | 116 ---------------- .../components/juicenet/strings.json | 41 +----- homeassistant/components/juicenet/switch.py | 49 ------- homeassistant/generated/config_flows.py | 1 - homeassistant/generated/integrations.json | 6 - requirements_all.txt | 3 - requirements_test_all.txt | 3 - script/hassfest/requirements.py | 1 - tests/components/juicenet/test_config_flow.py | 124 ------------------ 18 files changed, 31 insertions(+), 669 deletions(-) delete mode 100644 homeassistant/components/juicenet/const.py delete mode 100644 homeassistant/components/juicenet/device.py delete mode 100644 homeassistant/components/juicenet/entity.py delete mode 100644 homeassistant/components/juicenet/number.py delete mode 100644 homeassistant/components/juicenet/sensor.py delete mode 100644 homeassistant/components/juicenet/switch.py delete mode 100644 tests/components/juicenet/test_config_flow.py diff --git a/.coveragerc b/.coveragerc index c882215e294ca..260e1b5af9e19 100644 --- a/.coveragerc +++ b/.coveragerc @@ -638,12 +638,6 @@ omit = homeassistant/components/izone/climate.py homeassistant/components/izone/discovery.py homeassistant/components/joaoapps_join/* - homeassistant/components/juicenet/__init__.py - homeassistant/components/juicenet/device.py - homeassistant/components/juicenet/entity.py - homeassistant/components/juicenet/number.py - homeassistant/components/juicenet/sensor.py - homeassistant/components/juicenet/switch.py homeassistant/components/justnimbus/coordinator.py homeassistant/components/justnimbus/entity.py homeassistant/components/justnimbus/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index 6c278651ed117..beca5a9b025ee 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -669,8 +669,6 @@ build.json @home-assistant/supervisor /tests/components/jellyfin/ @j-stienstra @ctalkington /homeassistant/components/jewish_calendar/ @tsvi /tests/components/jewish_calendar/ @tsvi -/homeassistant/components/juicenet/ @jesserockz -/tests/components/juicenet/ @jesserockz /homeassistant/components/justnimbus/ @kvanzuijlen /tests/components/justnimbus/ @kvanzuijlen /homeassistant/components/jvc_projector/ @SteveEasley @msavazzi diff --git a/homeassistant/components/juicenet/__init__.py b/homeassistant/components/juicenet/__init__.py index bcefe763e159f..566a42e4405a1 100644 --- a/homeassistant/components/juicenet/__init__.py +++ b/homeassistant/components/juicenet/__init__.py @@ -1,107 +1,38 @@ """The JuiceNet integration.""" -from datetime import timedelta -import logging +from __future__ import annotations -import aiohttp -from pyjuicenet import Api, TokenError -import voluptuous as vol - -from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry -from homeassistant.const import CONF_ACCESS_TOKEN, Platform +from homeassistant.config_entries import ConfigEntry, ConfigEntryState from homeassistant.core import HomeAssistant -from homeassistant.exceptions import ConfigEntryNotReady -from homeassistant.helpers import config_validation as cv -from homeassistant.helpers.aiohttp_client import async_get_clientsession -from homeassistant.helpers.typing import ConfigType -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator - -from .const import DOMAIN, JUICENET_API, JUICENET_COORDINATOR -from .device import JuiceNetApi - -_LOGGER = logging.getLogger(__name__) - -PLATFORMS = [Platform.NUMBER, Platform.SENSOR, Platform.SWITCH] - -CONFIG_SCHEMA = vol.Schema( - vol.All( - cv.deprecated(DOMAIN), - {DOMAIN: vol.Schema({vol.Required(CONF_ACCESS_TOKEN): cv.string})}, - ), - extra=vol.ALLOW_EXTRA, -) - +from homeassistant.helpers import issue_registry as ir -async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: - """Set up the JuiceNet component.""" - conf = config.get(DOMAIN) - hass.data.setdefault(DOMAIN, {}) - - if not conf: - return True - - hass.async_create_task( - hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_IMPORT}, data=conf - ) - ) - return True +DOMAIN = "myq" async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up JuiceNet from a config entry.""" - - config = entry.data - - session = async_get_clientsession(hass) - - access_token = config[CONF_ACCESS_TOKEN] - api = Api(access_token, session) - - juicenet = JuiceNetApi(api) - - try: - await juicenet.setup() - except TokenError as error: - _LOGGER.error("JuiceNet Error %s", error) - return False - except aiohttp.ClientError as error: - _LOGGER.error("Could not reach the JuiceNet API %s", error) - raise ConfigEntryNotReady from error - - if not juicenet.devices: - _LOGGER.error("No JuiceNet devices found for this account") - return False - _LOGGER.info("%d JuiceNet device(s) found", len(juicenet.devices)) - - async def async_update_data(): - """Update all device states from the JuiceNet API.""" - for device in juicenet.devices: - await device.update_state(True) - return True - - coordinator = DataUpdateCoordinator( + ir.async_create_issue( hass, - _LOGGER, - name="JuiceNet", - update_method=async_update_data, - update_interval=timedelta(seconds=30), + DOMAIN, + DOMAIN, + is_fixable=False, + severity=ir.IssueSeverity.ERROR, + translation_key="integration_removed", + translation_placeholders={ + "blog": "https://www.home-assistant.io/blog/2024/02/26/removal-of-juicenet-integration/", + "entries": "/config/integrations/integration/juicenet", + }, ) - await coordinator.async_config_entry_first_refresh() - - hass.data[DOMAIN][entry.entry_id] = { - JUICENET_API: juicenet, - JUICENET_COORDINATOR: coordinator, - } - - await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) - return True async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload a config entry.""" - unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) - if unload_ok: - hass.data[DOMAIN].pop(entry.entry_id) - return unload_ok + if all( + config_entry.state is ConfigEntryState.NOT_LOADED + for config_entry in hass.config_entries.async_entries(DOMAIN) + if config_entry.entry_id != entry.entry_id + ): + ir.async_delete_issue(hass, DOMAIN, DOMAIN) + + return True diff --git a/homeassistant/components/juicenet/config_flow.py b/homeassistant/components/juicenet/config_flow.py index 35c1853b974fc..7fdc024df47c9 100644 --- a/homeassistant/components/juicenet/config_flow.py +++ b/homeassistant/components/juicenet/config_flow.py @@ -1,77 +1,11 @@ """Config flow for JuiceNet integration.""" -import logging -import aiohttp -from pyjuicenet import Api, TokenError -import voluptuous as vol +from homeassistant import config_entries -from homeassistant import config_entries, core, exceptions -from homeassistant.const import CONF_ACCESS_TOKEN -from homeassistant.helpers.aiohttp_client import async_get_clientsession - -from .const import DOMAIN - -_LOGGER = logging.getLogger(__name__) - -DATA_SCHEMA = vol.Schema({vol.Required(CONF_ACCESS_TOKEN): str}) - - -async def validate_input(hass: core.HomeAssistant, data): - """Validate the user input allows us to connect. - - Data has the keys from DATA_SCHEMA with values provided by the user. - """ - session = async_get_clientsession(hass) - juicenet = Api(data[CONF_ACCESS_TOKEN], session) - - try: - await juicenet.get_devices() - except TokenError as error: - _LOGGER.error("Token Error %s", error) - raise InvalidAuth from error - except aiohttp.ClientError as error: - _LOGGER.error("Error connecting %s", error) - raise CannotConnect from error - - # Return info that you want to store in the config entry. - return {"title": "JuiceNet"} +from . import DOMAIN class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Handle a config flow for JuiceNet.""" VERSION = 1 - - async def async_step_user(self, user_input=None): - """Handle the initial step.""" - errors = {} - if user_input is not None: - await self.async_set_unique_id(user_input[CONF_ACCESS_TOKEN]) - self._abort_if_unique_id_configured() - - try: - info = await validate_input(self.hass, user_input) - return self.async_create_entry(title=info["title"], data=user_input) - except CannotConnect: - errors["base"] = "cannot_connect" - except InvalidAuth: - errors["base"] = "invalid_auth" - except Exception: # pylint: disable=broad-except - _LOGGER.exception("Unexpected exception") - errors["base"] = "unknown" - - return self.async_show_form( - step_id="user", data_schema=DATA_SCHEMA, errors=errors - ) - - async def async_step_import(self, user_input): - """Handle import.""" - return await self.async_step_user(user_input) - - -class CannotConnect(exceptions.HomeAssistantError): - """Error to indicate we cannot connect.""" - - -class InvalidAuth(exceptions.HomeAssistantError): - """Error to indicate there is invalid auth.""" diff --git a/homeassistant/components/juicenet/const.py b/homeassistant/components/juicenet/const.py deleted file mode 100644 index 5dc3e5c3e2758..0000000000000 --- a/homeassistant/components/juicenet/const.py +++ /dev/null @@ -1,6 +0,0 @@ -"""Constants used by the JuiceNet component.""" - -DOMAIN = "juicenet" - -JUICENET_API = "juicenet_api" -JUICENET_COORDINATOR = "juicenet_coordinator" diff --git a/homeassistant/components/juicenet/device.py b/homeassistant/components/juicenet/device.py deleted file mode 100644 index 86e1c92e4da80..0000000000000 --- a/homeassistant/components/juicenet/device.py +++ /dev/null @@ -1,19 +0,0 @@ -"""Adapter to wrap the pyjuicenet api for home assistant.""" - - -class JuiceNetApi: - """Represent a connection to JuiceNet.""" - - def __init__(self, api): - """Create an object from the provided API instance.""" - self.api = api - self._devices = [] - - async def setup(self): - """JuiceNet device setup.""" # noqa: D403 - self._devices = await self.api.get_devices() - - @property - def devices(self) -> list: - """Get a list of devices managed by this account.""" - return self._devices diff --git a/homeassistant/components/juicenet/entity.py b/homeassistant/components/juicenet/entity.py deleted file mode 100644 index b343394858281..0000000000000 --- a/homeassistant/components/juicenet/entity.py +++ /dev/null @@ -1,34 +0,0 @@ -"""Adapter to wrap the pyjuicenet api for home assistant.""" - -from pyjuicenet import Charger - -from homeassistant.helpers.device_registry import DeviceInfo -from homeassistant.helpers.update_coordinator import ( - CoordinatorEntity, - DataUpdateCoordinator, -) - -from .const import DOMAIN - - -class JuiceNetDevice(CoordinatorEntity): - """Represent a base JuiceNet device.""" - - _attr_has_entity_name = True - - def __init__( - self, device: Charger, key: str, coordinator: DataUpdateCoordinator - ) -> None: - """Initialise the sensor.""" - super().__init__(coordinator) - self.device = device - self.key = key - self._attr_unique_id = f"{device.id}-{key}" - self._attr_device_info = DeviceInfo( - configuration_url=( - f"https://home.juice.net/Portal/Details?unitID={device.id}" - ), - identifiers={(DOMAIN, device.id)}, - manufacturer="JuiceNet", - name=device.name, - ) diff --git a/homeassistant/components/juicenet/manifest.json b/homeassistant/components/juicenet/manifest.json index 979e540af014d..5bdad83ac1ec5 100644 --- a/homeassistant/components/juicenet/manifest.json +++ b/homeassistant/components/juicenet/manifest.json @@ -1,10 +1,9 @@ { "domain": "juicenet", "name": "JuiceNet", - "codeowners": ["@jesserockz"], - "config_flow": true, + "codeowners": [], "documentation": "https://www.home-assistant.io/integrations/juicenet", + "integration_type": "system", "iot_class": "cloud_polling", - "loggers": ["pyjuicenet"], - "requirements": ["python-juicenet==1.1.0"] + "requirements": [] } diff --git a/homeassistant/components/juicenet/number.py b/homeassistant/components/juicenet/number.py deleted file mode 100644 index fd2535c5bf391..0000000000000 --- a/homeassistant/components/juicenet/number.py +++ /dev/null @@ -1,99 +0,0 @@ -"""Support for controlling juicenet/juicepoint/juicebox based EVSE numbers.""" -from __future__ import annotations - -from dataclasses import dataclass - -from pyjuicenet import Api, Charger - -from homeassistant.components.number import ( - DEFAULT_MAX_VALUE, - NumberEntity, - NumberEntityDescription, -) -from homeassistant.config_entries import ConfigEntry -from homeassistant.core import HomeAssistant -from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator - -from .const import DOMAIN, JUICENET_API, JUICENET_COORDINATOR -from .entity import JuiceNetDevice - - -@dataclass(frozen=True) -class JuiceNetNumberEntityDescriptionMixin: - """Mixin for required keys.""" - - setter_key: str - - -@dataclass(frozen=True) -class JuiceNetNumberEntityDescription( - NumberEntityDescription, JuiceNetNumberEntityDescriptionMixin -): - """An entity description for a JuiceNetNumber.""" - - native_max_value_key: str | None = None - - -NUMBER_TYPES: tuple[JuiceNetNumberEntityDescription, ...] = ( - JuiceNetNumberEntityDescription( - translation_key="amperage_limit", - key="current_charging_amperage_limit", - native_min_value=6, - native_max_value_key="max_charging_amperage", - native_step=1, - setter_key="set_charging_amperage_limit", - ), -) - - -async def async_setup_entry( - hass: HomeAssistant, - config_entry: ConfigEntry, - async_add_entities: AddEntitiesCallback, -) -> None: - """Set up the JuiceNet Numbers.""" - juicenet_data = hass.data[DOMAIN][config_entry.entry_id] - api: Api = juicenet_data[JUICENET_API] - coordinator = juicenet_data[JUICENET_COORDINATOR] - - entities = [ - JuiceNetNumber(device, description, coordinator) - for device in api.devices - for description in NUMBER_TYPES - ] - async_add_entities(entities) - - -class JuiceNetNumber(JuiceNetDevice, NumberEntity): - """Implementation of a JuiceNet number.""" - - entity_description: JuiceNetNumberEntityDescription - - def __init__( - self, - device: Charger, - description: JuiceNetNumberEntityDescription, - coordinator: DataUpdateCoordinator, - ) -> None: - """Initialise the number.""" - super().__init__(device, description.key, coordinator) - self.entity_description = description - - @property - def native_value(self) -> float | None: - """Return the value of the entity.""" - return getattr(self.device, self.entity_description.key, None) - - @property - def native_max_value(self) -> float: - """Return the maximum value.""" - if self.entity_description.native_max_value_key is not None: - return getattr(self.device, self.entity_description.native_max_value_key) - if self.entity_description.native_max_value is not None: - return self.entity_description.native_max_value - return DEFAULT_MAX_VALUE - - async def async_set_native_value(self, value: float) -> None: - """Update the current value.""" - await getattr(self.device, self.entity_description.setter_key)(value) diff --git a/homeassistant/components/juicenet/sensor.py b/homeassistant/components/juicenet/sensor.py deleted file mode 100644 index 5f71e066b9c24..0000000000000 --- a/homeassistant/components/juicenet/sensor.py +++ /dev/null @@ -1,116 +0,0 @@ -"""Support for monitoring juicenet/juicepoint/juicebox based EVSE sensors.""" -from __future__ import annotations - -from homeassistant.components.sensor import ( - SensorDeviceClass, - SensorEntity, - SensorEntityDescription, - SensorStateClass, -) -from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ( - UnitOfElectricCurrent, - UnitOfElectricPotential, - UnitOfEnergy, - UnitOfPower, - UnitOfTemperature, - UnitOfTime, -) -from homeassistant.core import HomeAssistant -from homeassistant.helpers.entity_platform import AddEntitiesCallback - -from .const import DOMAIN, JUICENET_API, JUICENET_COORDINATOR -from .entity import JuiceNetDevice - -SENSOR_TYPES: tuple[SensorEntityDescription, ...] = ( - SensorEntityDescription( - key="status", - name="Charging Status", - ), - SensorEntityDescription( - key="temperature", - native_unit_of_measurement=UnitOfTemperature.CELSIUS, - device_class=SensorDeviceClass.TEMPERATURE, - state_class=SensorStateClass.MEASUREMENT, - ), - SensorEntityDescription( - key="voltage", - native_unit_of_measurement=UnitOfElectricPotential.VOLT, - device_class=SensorDeviceClass.VOLTAGE, - ), - SensorEntityDescription( - key="amps", - native_unit_of_measurement=UnitOfElectricCurrent.AMPERE, - device_class=SensorDeviceClass.CURRENT, - state_class=SensorStateClass.MEASUREMENT, - ), - SensorEntityDescription( - key="watts", - native_unit_of_measurement=UnitOfPower.WATT, - device_class=SensorDeviceClass.POWER, - state_class=SensorStateClass.MEASUREMENT, - ), - SensorEntityDescription( - key="charge_time", - translation_key="charge_time", - native_unit_of_measurement=UnitOfTime.SECONDS, - icon="mdi:timer-outline", - ), - SensorEntityDescription( - key="energy_added", - translation_key="energy_added", - native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, - device_class=SensorDeviceClass.ENERGY, - state_class=SensorStateClass.TOTAL_INCREASING, - ), -) - - -async def async_setup_entry( - hass: HomeAssistant, - config_entry: ConfigEntry, - async_add_entities: AddEntitiesCallback, -) -> None: - """Set up the JuiceNet Sensors.""" - juicenet_data = hass.data[DOMAIN][config_entry.entry_id] - api = juicenet_data[JUICENET_API] - coordinator = juicenet_data[JUICENET_COORDINATOR] - - entities = [ - JuiceNetSensorDevice(device, coordinator, description) - for device in api.devices - for description in SENSOR_TYPES - ] - async_add_entities(entities) - - -class JuiceNetSensorDevice(JuiceNetDevice, SensorEntity): - """Implementation of a JuiceNet sensor.""" - - def __init__( - self, device, coordinator, description: SensorEntityDescription - ) -> None: - """Initialise the sensor.""" - super().__init__(device, description.key, coordinator) - self.entity_description = description - - @property - def icon(self): - """Return the icon of the sensor.""" - icon = None - if self.entity_description.key == "status": - status = self.device.status - if status == "standby": - icon = "mdi:power-plug-off" - elif status == "plugged": - icon = "mdi:power-plug" - elif status == "charging": - icon = "mdi:battery-positive" - else: - icon = self.entity_description.icon - return icon - - @property - def native_value(self): - """Return the state.""" - return getattr(self.device, self.entity_description.key, None) diff --git a/homeassistant/components/juicenet/strings.json b/homeassistant/components/juicenet/strings.json index 0e3732c66d2bf..a113ca12d8ff1 100644 --- a/homeassistant/components/juicenet/strings.json +++ b/homeassistant/components/juicenet/strings.json @@ -1,41 +1,8 @@ { - "config": { - "abort": { - "already_configured": "[%key:common::config_flow::abort::already_configured_account%]" - }, - "error": { - "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", - "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", - "unknown": "[%key:common::config_flow::error::unknown%]" - }, - "step": { - "user": { - "data": { - "api_token": "[%key:common::config_flow::data::api_token%]" - }, - "description": "You will need the API Token from https://home.juice.net/Manage.", - "title": "Connect to JuiceNet" - } - } - }, - "entity": { - "number": { - "amperage_limit": { - "name": "Amperage limit" - } - }, - "sensor": { - "charge_time": { - "name": "Charge time" - }, - "energy_added": { - "name": "Energy added" - } - }, - "switch": { - "charge_now": { - "name": "Charge now" - } + "issues": { + "integration_removed": { + "title": "The JuiceNet integration has been removed", + "description": "Enel X has dropped support for JuiceNet in favor of JuicePass, and the JuiceNet integration has been removed from Home Assistant as it was no longer working.\n\nRead about it [here]({blog}).\n\nTo resolve this issue, please remove the (now defunct) integration entries from your Home Assistant setup. [Click here to see your existing MyQ integration entries]({entries})." } } } diff --git a/homeassistant/components/juicenet/switch.py b/homeassistant/components/juicenet/switch.py deleted file mode 100644 index 7c373eeeb245b..0000000000000 --- a/homeassistant/components/juicenet/switch.py +++ /dev/null @@ -1,49 +0,0 @@ -"""Support for monitoring juicenet/juicepoint/juicebox based EVSE switches.""" -from typing import Any - -from homeassistant.components.switch import SwitchEntity -from homeassistant.config_entries import ConfigEntry -from homeassistant.core import HomeAssistant -from homeassistant.helpers.entity_platform import AddEntitiesCallback - -from .const import DOMAIN, JUICENET_API, JUICENET_COORDINATOR -from .entity import JuiceNetDevice - - -async def async_setup_entry( - hass: HomeAssistant, - config_entry: ConfigEntry, - async_add_entities: AddEntitiesCallback, -) -> None: - """Set up the JuiceNet switches.""" - entities = [] - juicenet_data = hass.data[DOMAIN][config_entry.entry_id] - api = juicenet_data[JUICENET_API] - coordinator = juicenet_data[JUICENET_COORDINATOR] - - for device in api.devices: - entities.append(JuiceNetChargeNowSwitch(device, coordinator)) - async_add_entities(entities) - - -class JuiceNetChargeNowSwitch(JuiceNetDevice, SwitchEntity): - """Implementation of a JuiceNet switch.""" - - _attr_translation_key = "charge_now" - - def __init__(self, device, coordinator): - """Initialise the switch.""" - super().__init__(device, "charge_now", coordinator) - - @property - def is_on(self): - """Return true if switch is on.""" - return self.device.override_time != 0 - - async def async_turn_on(self, **kwargs: Any) -> None: - """Charge now.""" - await self.device.set_override(True) - - async def async_turn_off(self, **kwargs: Any) -> None: - """Don't charge now.""" - await self.device.set_override(False) diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index e485bd8dde987..65b3dfade22c4 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -255,7 +255,6 @@ "isy994", "izone", "jellyfin", - "juicenet", "justnimbus", "jvc_projector", "kaleidescape", diff --git a/homeassistant/generated/integrations.json b/homeassistant/generated/integrations.json index 393df32594304..c85abaf5eae00 100644 --- a/homeassistant/generated/integrations.json +++ b/homeassistant/generated/integrations.json @@ -2911,12 +2911,6 @@ "config_flow": false, "iot_class": "cloud_push" }, - "juicenet": { - "name": "JuiceNet", - "integration_type": "hub", - "config_flow": true, - "iot_class": "cloud_polling" - }, "justnimbus": { "name": "JustNimbus", "integration_type": "hub", diff --git a/requirements_all.txt b/requirements_all.txt index 8af1a2305e2bb..62ad673ac1663 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2244,9 +2244,6 @@ python-izone==1.2.9 # homeassistant.components.joaoapps_join python-join-api==0.0.9 -# homeassistant.components.juicenet -python-juicenet==1.1.0 - # homeassistant.components.tplink python-kasa[speedups]==0.6.2.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 1e26dc86ebac6..e128bffae0124 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1720,9 +1720,6 @@ python-homewizard-energy==4.3.1 # homeassistant.components.izone python-izone==1.2.9 -# homeassistant.components.juicenet -python-juicenet==1.1.0 - # homeassistant.components.tplink python-kasa[speedups]==0.6.2.1 diff --git a/script/hassfest/requirements.py b/script/hassfest/requirements.py index 8b9f73336fe48..9ad9c3676b810 100644 --- a/script/hassfest/requirements.py +++ b/script/hassfest/requirements.py @@ -33,7 +33,6 @@ "blink", "ezviz", "hdmi_cec", - "juicenet", "lupusec", "rainbird", "slide", diff --git a/tests/components/juicenet/test_config_flow.py b/tests/components/juicenet/test_config_flow.py deleted file mode 100644 index 6adc841862e8f..0000000000000 --- a/tests/components/juicenet/test_config_flow.py +++ /dev/null @@ -1,124 +0,0 @@ -"""Test the JuiceNet config flow.""" -from unittest.mock import MagicMock, patch - -import aiohttp -from pyjuicenet import TokenError - -from homeassistant import config_entries -from homeassistant.components.juicenet.const import DOMAIN -from homeassistant.const import CONF_ACCESS_TOKEN -from homeassistant.core import HomeAssistant - - -def _mock_juicenet_return_value(get_devices=None): - juicenet_mock = MagicMock() - type(juicenet_mock).get_devices = MagicMock(return_value=get_devices) - return juicenet_mock - - -async def test_form(hass: HomeAssistant) -> None: - """Test we get the form.""" - - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) - assert result["type"] == "form" - assert result["errors"] == {} - - with patch( - "homeassistant.components.juicenet.config_flow.Api.get_devices", - return_value=MagicMock(), - ), patch( - "homeassistant.components.juicenet.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.juicenet.async_setup_entry", return_value=True - ) as mock_setup_entry: - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], {CONF_ACCESS_TOKEN: "access_token"} - ) - await hass.async_block_till_done() - - assert result2["type"] == "create_entry" - assert result2["title"] == "JuiceNet" - assert result2["data"] == {CONF_ACCESS_TOKEN: "access_token"} - assert len(mock_setup.mock_calls) == 1 - assert len(mock_setup_entry.mock_calls) == 1 - - -async def test_form_invalid_auth(hass: HomeAssistant) -> None: - """Test we handle invalid auth.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) - - with patch( - "homeassistant.components.juicenet.config_flow.Api.get_devices", - side_effect=TokenError, - ): - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], {CONF_ACCESS_TOKEN: "access_token"} - ) - - assert result2["type"] == "form" - assert result2["errors"] == {"base": "invalid_auth"} - - -async def test_form_cannot_connect(hass: HomeAssistant) -> None: - """Test we handle cannot connect error.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) - - with patch( - "homeassistant.components.juicenet.config_flow.Api.get_devices", - side_effect=aiohttp.ClientError, - ): - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], {CONF_ACCESS_TOKEN: "access_token"} - ) - - assert result2["type"] == "form" - assert result2["errors"] == {"base": "cannot_connect"} - - -async def test_form_catch_unknown_errors(hass: HomeAssistant) -> None: - """Test we handle cannot connect error.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) - - with patch( - "homeassistant.components.juicenet.config_flow.Api.get_devices", - side_effect=Exception, - ): - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], {CONF_ACCESS_TOKEN: "access_token"} - ) - - assert result2["type"] == "form" - assert result2["errors"] == {"base": "unknown"} - - -async def test_import(hass: HomeAssistant) -> None: - """Test that import works as expected.""" - - with patch( - "homeassistant.components.juicenet.config_flow.Api.get_devices", - return_value=MagicMock(), - ), patch( - "homeassistant.components.juicenet.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.juicenet.async_setup_entry", return_value=True - ) as mock_setup_entry: - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": config_entries.SOURCE_IMPORT}, - data={CONF_ACCESS_TOKEN: "access_token"}, - ) - await hass.async_block_till_done() - - assert result["type"] == "create_entry" - assert result["title"] == "JuiceNet" - assert result["data"] == {CONF_ACCESS_TOKEN: "access_token"} - assert len(mock_setup.mock_calls) == 1 - assert len(mock_setup_entry.mock_calls) == 1 From 186786e29dd1d6bc2dba38299be92922f4278210 Mon Sep 17 00:00:00 2001 From: Erik Date: Mon, 26 Feb 2024 15:49:43 +0100 Subject: [PATCH 2/4] Adjust issue --- homeassistant/components/juicenet/__init__.py | 1 - homeassistant/components/juicenet/strings.json | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/homeassistant/components/juicenet/__init__.py b/homeassistant/components/juicenet/__init__.py index 566a42e4405a1..63e42a4b7a7bb 100644 --- a/homeassistant/components/juicenet/__init__.py +++ b/homeassistant/components/juicenet/__init__.py @@ -18,7 +18,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: severity=ir.IssueSeverity.ERROR, translation_key="integration_removed", translation_placeholders={ - "blog": "https://www.home-assistant.io/blog/2024/02/26/removal-of-juicenet-integration/", "entries": "/config/integrations/integration/juicenet", }, ) diff --git a/homeassistant/components/juicenet/strings.json b/homeassistant/components/juicenet/strings.json index a113ca12d8ff1..6e25130955b11 100644 --- a/homeassistant/components/juicenet/strings.json +++ b/homeassistant/components/juicenet/strings.json @@ -2,7 +2,7 @@ "issues": { "integration_removed": { "title": "The JuiceNet integration has been removed", - "description": "Enel X has dropped support for JuiceNet in favor of JuicePass, and the JuiceNet integration has been removed from Home Assistant as it was no longer working.\n\nRead about it [here]({blog}).\n\nTo resolve this issue, please remove the (now defunct) integration entries from your Home Assistant setup. [Click here to see your existing MyQ integration entries]({entries})." + "description": "Enel X has dropped support for JuiceNet in favor of JuicePass, and the JuiceNet integration has been removed from Home Assistant as it was no longer working.\n\nTo resolve this issue, please remove the (now defunct) integration entries from your Home Assistant setup. [Click here to see your existing JuiceNet integration entries]({entries})." } } } From b8c910fe822d719c12c72c9edcd2ee82366cc1e6 Mon Sep 17 00:00:00 2001 From: Erik Date: Mon, 26 Feb 2024 15:57:15 +0100 Subject: [PATCH 3/4] Add test --- tests/components/juicenet/test_init.py | 50 ++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 tests/components/juicenet/test_init.py diff --git a/tests/components/juicenet/test_init.py b/tests/components/juicenet/test_init.py new file mode 100644 index 0000000000000..8896798abe32e --- /dev/null +++ b/tests/components/juicenet/test_init.py @@ -0,0 +1,50 @@ +"""Tests for the JuiceNet component.""" + +from homeassistant.components.juicenet import DOMAIN +from homeassistant.config_entries import ConfigEntryState +from homeassistant.core import HomeAssistant +from homeassistant.helpers import issue_registry as ir + +from tests.common import MockConfigEntry + + +async def test_juicenet_repair_issue( + hass: HomeAssistant, issue_registry: ir.IssueRegistry +) -> None: + """Test the JuiceNet configuration entry loading/unloading handles the repair.""" + config_entry_1 = MockConfigEntry( + title="Example 1", + domain=DOMAIN, + ) + config_entry_1.add_to_hass(hass) + await hass.config_entries.async_setup(config_entry_1.entry_id) + await hass.async_block_till_done() + assert config_entry_1.state is ConfigEntryState.LOADED + + # Add a second one + config_entry_2 = MockConfigEntry( + title="Example 2", + domain=DOMAIN, + ) + config_entry_2.add_to_hass(hass) + await hass.config_entries.async_setup(config_entry_2.entry_id) + await hass.async_block_till_done() + + assert config_entry_2.state is ConfigEntryState.LOADED + assert issue_registry.async_get_issue(DOMAIN, DOMAIN) + + # Remove the first one + await hass.config_entries.async_remove(config_entry_1.entry_id) + await hass.async_block_till_done() + + assert config_entry_1.state is ConfigEntryState.NOT_LOADED + assert config_entry_2.state is ConfigEntryState.LOADED + assert issue_registry.async_get_issue(DOMAIN, DOMAIN) + + # Remove the second one + await hass.config_entries.async_remove(config_entry_2.entry_id) + await hass.async_block_till_done() + + assert config_entry_1.state is ConfigEntryState.NOT_LOADED + assert config_entry_2.state is ConfigEntryState.NOT_LOADED + assert issue_registry.async_get_issue(DOMAIN, DOMAIN) is None From 6587e7439a8747a5eac613ca25d23da7c9cb2801 Mon Sep 17 00:00:00 2001 From: Erik Date: Mon, 26 Feb 2024 17:39:18 +0100 Subject: [PATCH 4/4] Fix stale DOMAIN constant --- homeassistant/components/juicenet/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/juicenet/__init__.py b/homeassistant/components/juicenet/__init__.py index 63e42a4b7a7bb..820f0d1fcc031 100644 --- a/homeassistant/components/juicenet/__init__.py +++ b/homeassistant/components/juicenet/__init__.py @@ -5,7 +5,7 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers import issue_registry as ir -DOMAIN = "myq" +DOMAIN = "juicenet" async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: