From 580b20b0a83c561986e7571b83df4a4bcb158392 Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Mon, 3 Apr 2023 07:27:41 +0200 Subject: [PATCH] Deprecate imap_content_sensor (#90429) * Deprecate imap_content_sensor * Rename unique_id to issue_id * Migrate config to imap entry * Improve dialogs * Improve dialog texts * Add repairs.py to .coveragerc * Test the integration component setup * Text tweak Co-authored-by: Martin Hjelmare * Use flow for creating entries * Rename schema add tests * Patch client instead * Add tests repairs - refactor async_step_confirm * Comments test, correct calling next step --------- Co-authored-by: Martin Hjelmare --- homeassistant/components/imap/config_flow.py | 32 +- .../components/imap_email_content/__init__.py | 11 + .../components/imap_email_content/const.py | 13 + .../imap_email_content/manifest.json | 1 + .../components/imap_email_content/repairs.py | 173 ++++++++++ .../components/imap_email_content/sensor.py | 23 +- .../imap_email_content/strings.json | 27 ++ tests/components/imap/test_config_flow.py | 67 ++++ .../imap_email_content/test_repairs.py | 296 ++++++++++++++++++ .../imap_email_content/test_sensor.py | 6 + 10 files changed, 633 insertions(+), 16 deletions(-) create mode 100644 homeassistant/components/imap_email_content/const.py create mode 100644 homeassistant/components/imap_email_content/repairs.py create mode 100644 homeassistant/components/imap_email_content/strings.json create mode 100644 tests/components/imap_email_content/test_repairs.py diff --git a/homeassistant/components/imap/config_flow.py b/homeassistant/components/imap/config_flow.py index 8dd3019878f..a6ef203283c 100644 --- a/homeassistant/components/imap/config_flow.py +++ b/homeassistant/components/imap/config_flow.py @@ -9,7 +9,7 @@ from aioimaplib import AioImapException import voluptuous as vol from homeassistant import config_entries -from homeassistant.const import CONF_PASSWORD, CONF_PORT, CONF_USERNAME +from homeassistant.const import CONF_NAME, CONF_PASSWORD, CONF_PORT, CONF_USERNAME from homeassistant.core import callback from homeassistant.data_entry_flow import AbortFlow, FlowResult from homeassistant.helpers import config_validation as cv @@ -25,7 +25,7 @@ from .const import ( from .coordinator import connect_to_server from .errors import InvalidAuth, InvalidFolder -STEP_USER_DATA_SCHEMA = vol.Schema( +CONFIG_SCHEMA = vol.Schema( { vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str, @@ -77,14 +77,34 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 _reauth_entry: config_entries.ConfigEntry | None + async def async_step_import(self, user_input: dict[str, Any]) -> FlowResult: + """Handle the import from imap_email_content integration.""" + data = CONFIG_SCHEMA( + { + CONF_SERVER: user_input[CONF_SERVER], + CONF_PORT: user_input[CONF_PORT], + CONF_USERNAME: user_input[CONF_USERNAME], + CONF_PASSWORD: user_input[CONF_PASSWORD], + CONF_FOLDER: user_input[CONF_FOLDER], + } + ) + self._async_abort_entries_match( + { + key: data[key] + for key in (CONF_USERNAME, CONF_SERVER, CONF_FOLDER, CONF_SEARCH) + } + ) + title = user_input[CONF_NAME] + if await validate_input(data): + raise AbortFlow("cannot_connect") + return self.async_create_entry(title=title, data=data) + async def async_step_user( self, user_input: dict[str, Any] | None = None ) -> FlowResult: """Handle the initial step.""" if user_input is None: - return self.async_show_form( - step_id="user", data_schema=STEP_USER_DATA_SCHEMA - ) + return self.async_show_form(step_id="user", data_schema=CONFIG_SCHEMA) self._async_abort_entries_match( { @@ -98,7 +118,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return self.async_create_entry(title=title, data=user_input) - schema = self.add_suggested_values_to_schema(STEP_USER_DATA_SCHEMA, user_input) + schema = self.add_suggested_values_to_schema(CONFIG_SCHEMA, user_input) return self.async_show_form(step_id="user", data_schema=schema, errors=errors) async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: diff --git a/homeassistant/components/imap_email_content/__init__.py b/homeassistant/components/imap_email_content/__init__.py index 263f57a3a9d..1a148f4591b 100644 --- a/homeassistant/components/imap_email_content/__init__.py +++ b/homeassistant/components/imap_email_content/__init__.py @@ -1 +1,12 @@ """The imap_email_content component.""" + +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers.typing import ConfigType + +PLATFORMS = [Platform.SENSOR] + + +async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: + """Set up imap_email_content.""" + return True diff --git a/homeassistant/components/imap_email_content/const.py b/homeassistant/components/imap_email_content/const.py new file mode 100644 index 00000000000..5f1c653030e --- /dev/null +++ b/homeassistant/components/imap_email_content/const.py @@ -0,0 +1,13 @@ +"""Constants for the imap email content integration.""" + +DOMAIN = "imap_email_content" + +CONF_SERVER = "server" +CONF_SENDERS = "senders" +CONF_FOLDER = "folder" + +ATTR_FROM = "from" +ATTR_BODY = "body" +ATTR_SUBJECT = "subject" + +DEFAULT_PORT = 993 diff --git a/homeassistant/components/imap_email_content/manifest.json b/homeassistant/components/imap_email_content/manifest.json index 2e510a8c426..b7d0589b83f 100644 --- a/homeassistant/components/imap_email_content/manifest.json +++ b/homeassistant/components/imap_email_content/manifest.json @@ -2,6 +2,7 @@ "domain": "imap_email_content", "name": "IMAP Email Content", "codeowners": [], + "dependencies": ["imap"], "documentation": "https://www.home-assistant.io/integrations/imap_email_content", "iot_class": "cloud_push" } diff --git a/homeassistant/components/imap_email_content/repairs.py b/homeassistant/components/imap_email_content/repairs.py new file mode 100644 index 00000000000..f19b0499040 --- /dev/null +++ b/homeassistant/components/imap_email_content/repairs.py @@ -0,0 +1,173 @@ +"""Repair flow for imap email content integration.""" + +from typing import Any + +import voluptuous as vol +import yaml + +from homeassistant import data_entry_flow +from homeassistant.components.imap import DOMAIN as IMAP_DOMAIN +from homeassistant.components.repairs import RepairsFlow +from homeassistant.config_entries import SOURCE_IMPORT +from homeassistant.const import ( + CONF_NAME, + CONF_PASSWORD, + CONF_PORT, + CONF_USERNAME, + CONF_VALUE_TEMPLATE, +) +from homeassistant.core import HomeAssistant, callback +from homeassistant.data_entry_flow import FlowResultType +from homeassistant.helpers import issue_registry as ir +from homeassistant.helpers.typing import ConfigType + +from .const import CONF_FOLDER, CONF_SENDERS, CONF_SERVER, DOMAIN + + +async def async_process_issue(hass: HomeAssistant, config: ConfigType) -> None: + """Register an issue and suggest new config.""" + + name: str = config.get(CONF_NAME) or config[CONF_USERNAME] + + issue_id = ( + f"{name}_{config[CONF_USERNAME]}_{config[CONF_SERVER]}_{config[CONF_FOLDER]}" + ) + + if CONF_VALUE_TEMPLATE in config: + template: str = config[CONF_VALUE_TEMPLATE].template + template = template.replace("subject", 'trigger.event.data["subject"]') + template = template.replace("from", 'trigger.event.data["sender"]') + template = template.replace("date", 'trigger.event.data["date"]') + template = template.replace("body", 'trigger.event.data["text"]') + else: + template = '{{ trigger.event.data["subject"] }}' + + template_sensor_config: ConfigType = { + "template": [ + { + "trigger": [ + { + "id": "custom_event", + "platform": "event", + "event_type": "imap_content", + "event_data": {"sender": config[CONF_SENDERS][0]}, + } + ], + "sensor": [ + { + "state": template, + "name": name, + } + ], + } + ] + } + + data = { + CONF_SERVER: config[CONF_SERVER], + CONF_PORT: config[CONF_PORT], + CONF_USERNAME: config[CONF_USERNAME], + CONF_PASSWORD: config[CONF_PASSWORD], + CONF_FOLDER: config[CONF_FOLDER], + } + data[CONF_VALUE_TEMPLATE] = template + data[CONF_NAME] = name + placeholders = {"yaml_example": yaml.dump(template_sensor_config)} + placeholders.update(data) + + ir.async_create_issue( + hass, + DOMAIN, + issue_id, + breaks_in_ha_version="2023.10.0", + is_fixable=True, + severity=ir.IssueSeverity.WARNING, + translation_key="migration", + translation_placeholders=placeholders, + data=data, + ) + + +class DeprecationRepairFlow(RepairsFlow): + """Handler for an issue fixing flow.""" + + def __init__(self, issue_id: str, config: ConfigType) -> None: + """Create flow.""" + self._name: str = config[CONF_NAME] + self._config: dict[str, Any] = config + self._issue_id = issue_id + super().__init__() + + async def async_step_init( + self, user_input: dict[str, str] | None = None + ) -> data_entry_flow.FlowResult: + """Handle the first step of a fix flow.""" + return await self.async_step_start() + + @callback + def _async_get_placeholders(self) -> dict[str, str] | None: + issue_registry = ir.async_get(self.hass) + description_placeholders = None + if issue := issue_registry.async_get_issue(self.handler, self.issue_id): + description_placeholders = issue.translation_placeholders + + return description_placeholders + + async def async_step_start( + self, user_input: dict[str, str] | None = None + ) -> data_entry_flow.FlowResult: + """Wait for the user to start the config migration.""" + placeholders = self._async_get_placeholders() + if user_input is None: + return self.async_show_form( + step_id="start", + data_schema=vol.Schema({}), + description_placeholders=placeholders, + ) + + return await self.async_step_confirm() + + async def async_step_confirm( + self, user_input: dict[str, str] | None = None + ) -> data_entry_flow.FlowResult: + """Handle the confirm step of a fix flow.""" + placeholders = self._async_get_placeholders() + if user_input is not None: + user_input[CONF_NAME] = self._name + result = await self.hass.config_entries.flow.async_init( + IMAP_DOMAIN, context={"source": SOURCE_IMPORT}, data=self._config + ) + if result["type"] == FlowResultType.ABORT: + ir.async_delete_issue(self.hass, DOMAIN, self._issue_id) + ir.async_create_issue( + self.hass, + DOMAIN, + self._issue_id, + breaks_in_ha_version="2023.10.0", + is_fixable=False, + severity=ir.IssueSeverity.WARNING, + translation_key="deprecation", + translation_placeholders=placeholders, + data=self._config, + learn_more_url="https://www.home-assistant.io/integrations/imap/#using-events", + ) + return self.async_abort(reason=result["reason"]) + return self.async_create_entry( + title="", + data={}, + ) + + return self.async_show_form( + step_id="confirm", + data_schema=vol.Schema({}), + description_placeholders=placeholders, + ) + + +async def async_create_fix_flow( + hass: HomeAssistant, + issue_id: str, + data: dict[str, str | int | float | None], +) -> RepairsFlow: + """Create flow.""" + return DeprecationRepairFlow(issue_id, data) diff --git a/homeassistant/components/imap_email_content/sensor.py b/homeassistant/components/imap_email_content/sensor.py index 53cb921860c..1df207e2968 100644 --- a/homeassistant/components/imap_email_content/sensor.py +++ b/homeassistant/components/imap_email_content/sensor.py @@ -26,18 +26,19 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.util.ssl import client_context +from .const import ( + ATTR_BODY, + ATTR_FROM, + ATTR_SUBJECT, + CONF_FOLDER, + CONF_SENDERS, + CONF_SERVER, + DEFAULT_PORT, +) +from .repairs import async_process_issue + _LOGGER = logging.getLogger(__name__) -CONF_SERVER = "server" -CONF_SENDERS = "senders" -CONF_FOLDER = "folder" - -ATTR_FROM = "from" -ATTR_BODY = "body" -ATTR_SUBJECT = "subject" - -DEFAULT_PORT = 993 - PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Optional(CONF_NAME): cv.string, @@ -79,6 +80,8 @@ def setup_platform( value_template, ) + hass.add_job(async_process_issue, hass, config) + if sensor.connected: add_entities([sensor], True) diff --git a/homeassistant/components/imap_email_content/strings.json b/homeassistant/components/imap_email_content/strings.json new file mode 100644 index 00000000000..f84435971bf --- /dev/null +++ b/homeassistant/components/imap_email_content/strings.json @@ -0,0 +1,27 @@ +{ + "issues": { + "deprecation": { + "title": "The IMAP email content integration is deprecated", + "description": "The IMAP email content integration is deprecated. Your IMAP server configuration was already migrated to to the [imap integration](https://my.home-assistant.io/redirect/config_flow_start?domain=imap). To set up a sensor for the IMAP email content, set up a template sensor with the config:\n\n```yaml\n{yaml_example}```\n\nPlease remove the deprecated `imap_email_plaform` sensor configuration from your `configuration.yaml`.\n\nNote that the event filter only filters on the first of the configured allowed senders, customize the filter if needed.\n\nYou can skip this part if you have already set up a template sensor." + }, + "migration": { + "title": "The IMAP email content integration needs attention", + "fix_flow": { + "step": { + "start": { + "title": "Migrate your IMAP email configuration", + "description": "The IMAP email content integration is deprecated. Your IMAP server configuration can be migrated automatically to the [imap integration](https://my.home-assistant.io/redirect/config_flow_start?domain=imap), this will enable using a custom `imap` event trigger. To set up a sensor that has an IMAP content state, a template sensor can be used. Remove the `imap_email_plaform` sensor configuration from your `configuration.yaml` after migration.\n\nSubmit to start migration of your IMAP server configuration to the `imap` integration." + }, + "confirm": { + "title": "Your IMAP server settings will be migrated", + "description": "In this step an `imap` config entry will be set up with the following configuration:\n\n```text\nServer\t{server}\nPort\t{port}\nUsername\t{username}\nPassword\t*****\nFolder\t{folder}\n```\n\nSee also: (https://www.home-assistant.io/integrations/imap/)\n\nFitering configuration on allowed `sender` is part of the template sensor config that can copied and placed in your `configuration.yaml.\n\nNote that the event filter only filters on the first of the configured allowed senders, customize the filter if needed.\n\n```yaml\n{yaml_example}```\nDo not forget to cleanup the your `configuration.yaml` after migration.\n\nSubmit to migrate your IMAP server configuration to an `imap` configuration entry." + } + }, + "abort": { + "already_configured": "The IMAP server config was already migrated to the imap integration. Remove the `imap_email_plaform` sensor configuration from your `configuration.yaml`.", + "cannot_connect": "Migration failed. Failed to connect to the IMAP server. Perform a manual migration." + } + } + } + } +} diff --git a/tests/components/imap/test_config_flow.py b/tests/components/imap/test_config_flow.py index 20c9ddf8938..098efb4280f 100644 --- a/tests/components/imap/test_config_flow.py +++ b/tests/components/imap/test_config_flow.py @@ -388,3 +388,70 @@ async def test_key_options_in_options_form(hass: HomeAssistant) -> None: await hass.async_block_till_done() assert result2["type"] == data_entry_flow.FlowResultType.FORM assert result2["errors"] == {"base": "already_configured"} + + +async def test_import_flow_success(hass: HomeAssistant) -> None: + """Test a successful import of yaml.""" + with patch( + "homeassistant.components.imap.config_flow.connect_to_server" + ) as mock_client, patch( + "homeassistant.components.imap.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + mock_client.return_value.search.return_value = ( + "OK", + [b""], + ) + result2 = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data={ + "name": "IMAP", + "username": "email@email.com", + "password": "password", + "server": "imap.server.com", + "port": 993, + "folder": "INBOX", + }, + ) + await hass.async_block_till_done() + + assert result2["type"] == FlowResultType.CREATE_ENTRY + assert result2["title"] == "IMAP" + assert result2["data"] == { + "username": "email@email.com", + "password": "password", + "server": "imap.server.com", + "port": 993, + "charset": "utf-8", + "folder": "INBOX", + "search": "UnSeen UnDeleted", + } + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_import_flow_connection_error(hass: HomeAssistant) -> None: + """Test a successful import of yaml.""" + with patch( + "homeassistant.components.imap.config_flow.connect_to_server", + side_effect=AioImapException("Unexpected error"), + ), patch( + "homeassistant.components.imap.async_setup_entry", + return_value=True, + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data={ + "name": "IMAP", + "username": "email@email.com", + "password": "password", + "server": "imap.server.com", + "port": 993, + "folder": "INBOX", + }, + ) + await hass.async_block_till_done() + + assert result["type"] == FlowResultType.ABORT + assert result["reason"] == "cannot_connect" diff --git a/tests/components/imap_email_content/test_repairs.py b/tests/components/imap_email_content/test_repairs.py new file mode 100644 index 00000000000..6323dcde377 --- /dev/null +++ b/tests/components/imap_email_content/test_repairs.py @@ -0,0 +1,296 @@ +"""Test repairs for imap_email_content.""" + +from collections.abc import Generator +from http import HTTPStatus +from unittest.mock import MagicMock, patch + +import pytest + +from homeassistant.components.repairs.websocket_api import ( + RepairsFlowIndexView, + RepairsFlowResourceView, +) +from homeassistant.core import HomeAssistant +from homeassistant.setup import async_setup_component + +from tests.common import MockConfigEntry +from tests.typing import ClientSessionGenerator, WebSocketGenerator + + +@pytest.fixture +def mock_client() -> Generator[MagicMock, None, None]: + """Mock the imap client.""" + with patch( + "homeassistant.components.imap_email_content.sensor.EmailReader.read_next", + return_value=None, + ), patch("imaplib.IMAP4_SSL") as mock_imap_client: + yield mock_imap_client + + +CONFIG = { + "platform": "imap_email_content", + "name": "Notifications", + "server": "imap.example.com", + "port": 993, + "username": "john.doe@example.com", + "password": "**SECRET**", + "folder": "INBOX.Notifications", + "value_template": "{{ body }}", + "senders": ["company@example.com"], +} +DESCRIPTION_PLACEHOLDERS = { + "yaml_example": "" + "template:\n" + "- sensor:\n" + " - name: Notifications\n" + " state: '{{ trigger.event.data[\"text\"] }}'\n" + " trigger:\n - event_data:\n" + " sender: company@example.com\n" + " event_type: imap_content\n" + " id: custom_event\n" + " platform: event\n", + "server": "imap.example.com", + "port": 993, + "username": "john.doe@example.com", + "password": "**SECRET**", + "folder": "INBOX.Notifications", + "value_template": '{{ trigger.event.data["text"] }}', + "name": "Notifications", +} + +CONFIG_DEFAULT = { + "platform": "imap_email_content", + "name": "Notifications", + "server": "imap.example.com", + "port": 993, + "username": "john.doe@example.com", + "password": "**SECRET**", + "folder": "INBOX.Notifications", + "senders": ["company@example.com"], +} +DESCRIPTION_PLACEHOLDERS_DEFAULT = { + "yaml_example": "" + "template:\n" + "- sensor:\n" + " - name: Notifications\n" + " state: '{{ trigger.event.data[\"subject\"] }}'\n" + " trigger:\n - event_data:\n" + " sender: company@example.com\n" + " event_type: imap_content\n" + " id: custom_event\n" + " platform: event\n", + "server": "imap.example.com", + "port": 993, + "username": "john.doe@example.com", + "password": "**SECRET**", + "folder": "INBOX.Notifications", + "value_template": '{{ trigger.event.data["subject"] }}', + "name": "Notifications", +} + + +@pytest.mark.parametrize( + ("config", "description_placeholders"), + [ + (CONFIG, DESCRIPTION_PLACEHOLDERS), + (CONFIG_DEFAULT, DESCRIPTION_PLACEHOLDERS_DEFAULT), + ], + ids=["with_value_template", "default_subject"], +) +async def test_deprecation_repair_flow( + hass: HomeAssistant, + mock_client: MagicMock, + hass_client: ClientSessionGenerator, + hass_ws_client: WebSocketGenerator, + config: str | None, + description_placeholders: str, +) -> None: + """Test the deprecation repair flow.""" + # setup config + await async_setup_component(hass, "sensor", {"sensor": config}) + await hass.async_block_till_done() + + state = hass.states.get("sensor.notifications") + assert state is not None + + ws_client = await hass_ws_client(hass) + client = await hass_client() + + await ws_client.send_json({"id": 1, "type": "repairs/list_issues"}) + + msg = await ws_client.receive_json() + + assert msg["success"] + assert len(msg["result"]["issues"]) > 0 + issue = None + for i in msg["result"]["issues"]: + if i["domain"] == "imap_email_content": + issue = i + assert issue is not None + assert ( + issue["issue_id"] + == "Notifications_john.doe@example.com_imap.example.com_INBOX.Notifications" + ) + assert issue["is_fixable"] + url = RepairsFlowIndexView.url + resp = await client.post( + url, json={"handler": "imap_email_content", "issue_id": issue["issue_id"]} + ) + assert resp.status == HTTPStatus.OK + data = await resp.json() + + flow_id = data["flow_id"] + assert data["description_placeholders"] == description_placeholders + assert data["step_id"] == "start" + + # Apply fix + url = RepairsFlowResourceView.url.format(flow_id=flow_id) + resp = await client.post(url) + assert resp.status == HTTPStatus.OK + data = await resp.json() + + flow_id = data["flow_id"] + assert data["description_placeholders"] == description_placeholders + assert data["step_id"] == "confirm" + + url = RepairsFlowResourceView.url.format(flow_id=flow_id) + + with patch( + "homeassistant.components.imap.config_flow.connect_to_server" + ) as mock_client, patch( + "homeassistant.components.imap.async_setup_entry", + return_value=True, + ): + mock_client.return_value.search.return_value = ( + "OK", + [b""], + ) + resp = await client.post(url) + + assert resp.status == HTTPStatus.OK + data = await resp.json() + + assert data["type"] == "create_entry" + + # Assert the issue is resolved + await ws_client.send_json({"id": 2, "type": "repairs/list_issues"}) + msg = await ws_client.receive_json() + assert msg["success"] + assert len(msg["result"]["issues"]) == 0 + + +@pytest.mark.parametrize( + ("config", "description_placeholders"), + [ + (CONFIG, DESCRIPTION_PLACEHOLDERS), + (CONFIG_DEFAULT, DESCRIPTION_PLACEHOLDERS_DEFAULT), + ], + ids=["with_value_template", "default_subject"], +) +async def test_repair_flow_where_entry_already_exists( + hass: HomeAssistant, + mock_client: MagicMock, + hass_client: ClientSessionGenerator, + hass_ws_client: WebSocketGenerator, + config: str | None, + description_placeholders: str, +) -> None: + """Test the deprecation repair flow and an entry already exists.""" + + await async_setup_component(hass, "sensor", {"sensor": config}) + await hass.async_block_till_done() + state = hass.states.get("sensor.notifications") + assert state is not None + + existing_imap_entry_config = { + "username": "john.doe@example.com", + "password": "password", + "server": "imap.example.com", + "port": 993, + "charset": "utf-8", + "folder": "INBOX.Notifications", + "search": "UnSeen UnDeleted", + } + + with patch("homeassistant.components.imap.async_setup_entry", return_value=True): + imap_entry = MockConfigEntry(domain="imap", data=existing_imap_entry_config) + imap_entry.add_to_hass(hass) + await hass.config_entries.async_setup(imap_entry.entry_id) + ws_client = await hass_ws_client(hass) + client = await hass_client() + + await ws_client.send_json({"id": 1, "type": "repairs/list_issues"}) + + msg = await ws_client.receive_json() + + assert msg["success"] + assert len(msg["result"]["issues"]) > 0 + issue = None + for i in msg["result"]["issues"]: + if i["domain"] == "imap_email_content": + issue = i + assert issue is not None + assert ( + issue["issue_id"] + == "Notifications_john.doe@example.com_imap.example.com_INBOX.Notifications" + ) + assert issue["is_fixable"] + assert issue["translation_key"] == "migration" + + url = RepairsFlowIndexView.url + resp = await client.post( + url, json={"handler": "imap_email_content", "issue_id": issue["issue_id"]} + ) + assert resp.status == HTTPStatus.OK + data = await resp.json() + + flow_id = data["flow_id"] + assert data["description_placeholders"] == description_placeholders + assert data["step_id"] == "start" + + url = RepairsFlowResourceView.url.format(flow_id=flow_id) + resp = await client.post(url) + assert resp.status == HTTPStatus.OK + data = await resp.json() + + flow_id = data["flow_id"] + assert data["description_placeholders"] == description_placeholders + assert data["step_id"] == "confirm" + + url = RepairsFlowResourceView.url.format(flow_id=flow_id) + + with patch( + "homeassistant.components.imap.config_flow.connect_to_server" + ) as mock_client, patch( + "homeassistant.components.imap.async_setup_entry", + return_value=True, + ): + mock_client.return_value.search.return_value = ( + "OK", + [b""], + ) + resp = await client.post(url) + + assert resp.status == HTTPStatus.OK + data = await resp.json() + + assert data["type"] == "abort" + assert data["reason"] == "already_configured" + + # We should now have a non_fixable issue left since there is still + # a config in configuration.yaml + await ws_client.send_json({"id": 2, "type": "repairs/list_issues"}) + msg = await ws_client.receive_json() + assert msg["success"] + assert len(msg["result"]["issues"]) > 0 + issue = None + for i in msg["result"]["issues"]: + if i["domain"] == "imap_email_content": + issue = i + assert issue is not None + assert ( + issue["issue_id"] + == "Notifications_john.doe@example.com_imap.example.com_INBOX.Notifications" + ) + assert not issue["is_fixable"] + assert issue["translation_key"] == "deprecation" diff --git a/tests/components/imap_email_content/test_sensor.py b/tests/components/imap_email_content/test_sensor.py index ba2b362af73..3e8a6c1e282 100644 --- a/tests/components/imap_email_content/test_sensor.py +++ b/tests/components/imap_email_content/test_sensor.py @@ -9,6 +9,7 @@ from homeassistant.components.imap_email_content import sensor as imap_email_con from homeassistant.core import HomeAssistant from homeassistant.helpers.event import async_track_state_change from homeassistant.helpers.template import Template +from homeassistant.setup import async_setup_component class FakeEMailReader: @@ -37,6 +38,11 @@ class FakeEMailReader: return self._messages.popleft() +async def test_integration_setup_(hass: HomeAssistant) -> None: + """Test the integration component setup is successful.""" + assert await async_setup_component(hass, "imap_email_content", {}) + + async def test_allowed_sender(hass: HomeAssistant) -> None: """Test emails from allowed sender.""" test_message = email.message.Message()