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 <marhje52@gmail.com> * 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 <marhje52@gmail.com>pull/90702/head
parent
ddb1610e90
commit
580b20b0a8
|
@ -9,7 +9,7 @@ from aioimaplib import AioImapException
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant import config_entries
|
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.core import callback
|
||||||
from homeassistant.data_entry_flow import AbortFlow, FlowResult
|
from homeassistant.data_entry_flow import AbortFlow, FlowResult
|
||||||
from homeassistant.helpers import config_validation as cv
|
from homeassistant.helpers import config_validation as cv
|
||||||
|
@ -25,7 +25,7 @@ from .const import (
|
||||||
from .coordinator import connect_to_server
|
from .coordinator import connect_to_server
|
||||||
from .errors import InvalidAuth, InvalidFolder
|
from .errors import InvalidAuth, InvalidFolder
|
||||||
|
|
||||||
STEP_USER_DATA_SCHEMA = vol.Schema(
|
CONFIG_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Required(CONF_USERNAME): str,
|
vol.Required(CONF_USERNAME): str,
|
||||||
vol.Required(CONF_PASSWORD): str,
|
vol.Required(CONF_PASSWORD): str,
|
||||||
|
@ -77,14 +77,34 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
VERSION = 1
|
VERSION = 1
|
||||||
_reauth_entry: config_entries.ConfigEntry | None
|
_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(
|
async def async_step_user(
|
||||||
self, user_input: dict[str, Any] | None = None
|
self, user_input: dict[str, Any] | None = None
|
||||||
) -> FlowResult:
|
) -> FlowResult:
|
||||||
"""Handle the initial step."""
|
"""Handle the initial step."""
|
||||||
if user_input is None:
|
if user_input is None:
|
||||||
return self.async_show_form(
|
return self.async_show_form(step_id="user", data_schema=CONFIG_SCHEMA)
|
||||||
step_id="user", data_schema=STEP_USER_DATA_SCHEMA
|
|
||||||
)
|
|
||||||
|
|
||||||
self._async_abort_entries_match(
|
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)
|
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)
|
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:
|
async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult:
|
||||||
|
|
|
@ -1 +1,12 @@
|
||||||
"""The imap_email_content component."""
|
"""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
|
||||||
|
|
|
@ -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
|
|
@ -2,6 +2,7 @@
|
||||||
"domain": "imap_email_content",
|
"domain": "imap_email_content",
|
||||||
"name": "IMAP Email Content",
|
"name": "IMAP Email Content",
|
||||||
"codeowners": [],
|
"codeowners": [],
|
||||||
|
"dependencies": ["imap"],
|
||||||
"documentation": "https://www.home-assistant.io/integrations/imap_email_content",
|
"documentation": "https://www.home-assistant.io/integrations/imap_email_content",
|
||||||
"iot_class": "cloud_push"
|
"iot_class": "cloud_push"
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
|
@ -26,18 +26,19 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||||
from homeassistant.util.ssl import client_context
|
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__)
|
_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(
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||||
{
|
{
|
||||||
vol.Optional(CONF_NAME): cv.string,
|
vol.Optional(CONF_NAME): cv.string,
|
||||||
|
@ -79,6 +80,8 @@ def setup_platform(
|
||||||
value_template,
|
value_template,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
hass.add_job(async_process_issue, hass, config)
|
||||||
|
|
||||||
if sensor.connected:
|
if sensor.connected:
|
||||||
add_entities([sensor], True)
|
add_entities([sensor], True)
|
||||||
|
|
||||||
|
|
|
@ -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."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -388,3 +388,70 @@ async def test_key_options_in_options_form(hass: HomeAssistant) -> None:
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert result2["type"] == data_entry_flow.FlowResultType.FORM
|
assert result2["type"] == data_entry_flow.FlowResultType.FORM
|
||||||
assert result2["errors"] == {"base": "already_configured"}
|
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"
|
||||||
|
|
|
@ -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"
|
|
@ -9,6 +9,7 @@ from homeassistant.components.imap_email_content import sensor as imap_email_con
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.event import async_track_state_change
|
from homeassistant.helpers.event import async_track_state_change
|
||||||
from homeassistant.helpers.template import Template
|
from homeassistant.helpers.template import Template
|
||||||
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
|
|
||||||
class FakeEMailReader:
|
class FakeEMailReader:
|
||||||
|
@ -37,6 +38,11 @@ class FakeEMailReader:
|
||||||
return self._messages.popleft()
|
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:
|
async def test_allowed_sender(hass: HomeAssistant) -> None:
|
||||||
"""Test emails from allowed sender."""
|
"""Test emails from allowed sender."""
|
||||||
test_message = email.message.Message()
|
test_message = email.message.Message()
|
||||||
|
|
Loading…
Reference in New Issue