Add config message items selector to imap option flow (#115108)

* Update const.py

* Update config_flow.py

* Update coordinator.py

* Update coordinator.py

* Update strings.json

* Update config_flow.py

* Update const.py

* Update coordinator.py

* Update config_flow.py

* Update config_flow.py

* Update test_diagnostics.py

* Update const.py

* Update test_init.py

* Update test_diagnostics.py

* Update test_diagnostics.py

* Update test_diagnostics.py

* Update test_init.py

* Update test_diagnostics.py

* Update test_init.py

* Update test_diagnostics.py

* Update test_diagnostics.py

* Update test_diagnostics.py

* Update test_config_flow.py

* Update config_flow.py

* Update test_config_flow.py

* Update test_init.py

* Update const.py

* Only make text and headers optional

* Add message data tests

* Add message data test

* Update test_config_flow.py

* Update test message data

* Fix ruff

* Fix ruff

* Update test_init.py

* Update strings.json

---------

Co-authored-by: jbouwh <jan@jbsoft.nl>
Co-authored-by: Jan Bouwhuis <jbouwh@users.noreply.github.com>
This commit is contained in:
Luca Angemi 2024-04-08 19:34:50 +02:00 committed by GitHub
parent f23e48f373
commit fc1ebdaaa3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 112 additions and 10 deletions

View file

@ -33,6 +33,7 @@ from .const import (
CONF_CHARSET,
CONF_CUSTOM_EVENT_DATA_TEMPLATE,
CONF_ENABLE_PUSH,
CONF_EVENT_MESSAGE_DATA,
CONF_FOLDER,
CONF_MAX_MESSAGE_SIZE,
CONF_SEARCH,
@ -42,6 +43,7 @@ from .const import (
DEFAULT_PORT,
DOMAIN,
MAX_MESSAGE_SIZE_LIMIT,
MESSAGE_DATA_OPTIONS,
)
from .coordinator import connect_to_server
from .errors import InvalidAuth, InvalidFolder
@ -55,6 +57,13 @@ CIPHER_SELECTOR = SelectSelector(
)
)
TEMPLATE_SELECTOR = TemplateSelector(TemplateSelectorConfig())
EVENT_MESSAGE_DATA_SELECTOR = SelectSelector(
SelectSelectorConfig(
options=MESSAGE_DATA_OPTIONS,
translation_key=CONF_EVENT_MESSAGE_DATA,
multiple=True,
)
)
CONFIG_SCHEMA = vol.Schema(
{
@ -65,6 +74,8 @@ CONFIG_SCHEMA = vol.Schema(
vol.Optional(CONF_CHARSET, default="utf-8"): str,
vol.Optional(CONF_FOLDER, default="INBOX"): str,
vol.Optional(CONF_SEARCH, default="UnSeen UnDeleted"): str,
# The default for new entries is to not include text and headers
vol.Optional(CONF_EVENT_MESSAGE_DATA, default=[]): cv.ensure_list,
}
)
CONFIG_SCHEMA_ADVANCED = {
@ -78,6 +89,10 @@ OPTIONS_SCHEMA = vol.Schema(
{
vol.Optional(CONF_FOLDER, default="INBOX"): str,
vol.Optional(CONF_SEARCH, default="UnSeen UnDeleted"): str,
# The default for older entries is to include text and headers
vol.Optional(
CONF_EVENT_MESSAGE_DATA, default=MESSAGE_DATA_OPTIONS
): EVENT_MESSAGE_DATA_SELECTOR,
}
)

View file

@ -8,7 +8,8 @@ CONF_SERVER: Final = "server"
CONF_FOLDER: Final = "folder"
CONF_SEARCH: Final = "search"
CONF_CHARSET: Final = "charset"
CONF_MAX_MESSAGE_SIZE = "max_message_size"
CONF_EVENT_MESSAGE_DATA: Final = "event_message_data"
CONF_MAX_MESSAGE_SIZE: Final = "max_message_size"
CONF_CUSTOM_EVENT_DATA_TEMPLATE: Final = "custom_event_data_template"
CONF_SSL_CIPHER_LIST: Final = "ssl_cipher_list"
CONF_ENABLE_PUSH: Final = "enable_push"
@ -17,4 +18,6 @@ DEFAULT_PORT: Final = 993
DEFAULT_MAX_MESSAGE_SIZE = 2048
MAX_MESSAGE_SIZE_LIMIT = 30000
MESSAGE_DATA_OPTIONS: Final = ["text", "headers"]
MAX_MESSAGE_SIZE_LIMIT: Final = 30000

View file

@ -41,6 +41,7 @@ from homeassistant.util.ssl import (
from .const import (
CONF_CHARSET,
CONF_CUSTOM_EVENT_DATA_TEMPLATE,
CONF_EVENT_MESSAGE_DATA,
CONF_FOLDER,
CONF_MAX_MESSAGE_SIZE,
CONF_SEARCH,
@ -48,6 +49,7 @@ from .const import (
CONF_SSL_CIPHER_LIST,
DEFAULT_MAX_MESSAGE_SIZE,
DOMAIN,
MESSAGE_DATA_OPTIONS,
)
from .errors import InvalidAuth, InvalidFolder
@ -225,6 +227,12 @@ class ImapDataUpdateCoordinator(DataUpdateCoordinator[int | None]):
self._last_message_id: str | None = None
self.custom_event_template = None
self._diagnostics_data: dict[str, Any] = {}
self._event_data_keys: list[str] = entry.data.get(
CONF_EVENT_MESSAGE_DATA, MESSAGE_DATA_OPTIONS
)
self._max_event_size: int = entry.data.get(
CONF_MAX_MESSAGE_SIZE, DEFAULT_MAX_MESSAGE_SIZE
)
_custom_event_template = entry.data.get(CONF_CUSTOM_EVENT_DATA_TEMPLATE)
if _custom_event_template is not None:
self.custom_event_template = Template(_custom_event_template, hass=hass)
@ -261,12 +269,11 @@ class ImapDataUpdateCoordinator(DataUpdateCoordinator[int | None]):
"folder": self.config_entry.data[CONF_FOLDER],
"initial": initial,
"date": message.date,
"text": message.text,
"sender": message.sender,
"subject": message.subject,
"headers": message.headers,
"uid": last_message_uid,
}
data.update({key: getattr(message, key) for key in self._event_data_keys})
if self.custom_event_template is not None:
try:
data["custom"] = self.custom_event_template.async_render(
@ -289,11 +296,8 @@ class ImapDataUpdateCoordinator(DataUpdateCoordinator[int | None]):
last_message_uid,
err,
)
data["text"] = message.text[
: self.config_entry.data.get(
CONF_MAX_MESSAGE_SIZE, DEFAULT_MAX_MESSAGE_SIZE
)
]
if "text" in data:
data["text"] = message.text[: self._max_event_size]
self._update_diagnostics(data)
if (size := len(json_bytes(data))) > MAX_EVENT_DATA_BYTES:
_LOGGER.warning(

View file

@ -72,7 +72,8 @@
"search": "[%key:component::imap::config::step::user::data::search%]",
"custom_event_data_template": "Template to create custom event data",
"max_message_size": "Max message size (2048 < size < 30000)",
"enable_push": "Enable Push-IMAP if the server supports it. Turn off if Push-IMAP updates are unreliable."
"enable_push": "Enable Push-IMAP if the server supports it. Turn off if Push-IMAP updates are unreliable.",
"event_message_data": "Message data to be included in the `imap_content` event data:"
}
}
},
@ -92,6 +93,12 @@
"modern": "Modern ciphers",
"intermediate": "Intermediate ciphers"
}
},
"event_message_data": {
"options": {
"text": "Body text",
"headers": "Message headers"
}
}
},
"services": {

View file

@ -29,11 +29,13 @@ MOCK_CONFIG = {
"charset": "utf-8",
"folder": "INBOX",
"search": "UnSeen UnDeleted",
"event_message_data": ["text", "headers"],
}
MOCK_OPTIONS = {
"folder": "INBOX",
"search": "UnSeen UnDeleted",
"event_message_data": ["text", "headers"],
}
pytestmark = pytest.mark.usefixtures("mock_setup_entry")
@ -504,6 +506,38 @@ async def test_config_flow_with_cipherlist_and_ssl_verify(
assert len(mock_setup_entry.mock_calls) == 1
@pytest.mark.parametrize("event_message_data", [[], ["headers"], ["text", "headers"]])
async def test_config_flow_with_event_message_data(
hass: HomeAssistant, mock_setup_entry: AsyncMock, event_message_data: list
) -> None:
"""Test with different message data."""
config = MOCK_CONFIG.copy()
config["event_message_data"] = event_message_data
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_USER, "show_advanced_options": False},
)
assert result["type"] is FlowResultType.FORM
assert result["errors"] is None
with patch(
"homeassistant.components.imap.config_flow.connect_to_server"
) as mock_client:
mock_client.return_value.search.return_value = (
"OK",
[b""],
)
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], config
)
await hass.async_block_till_done()
assert result2["type"] is FlowResultType.CREATE_ENTRY
assert result2["title"] == "email@email.com"
assert result2["data"] == config
assert len(mock_setup_entry.mock_calls) == 1
async def test_config_flow_from_with_advanced_settings(
hass: HomeAssistant, mock_setup_entry: AsyncMock
) -> None:

View file

@ -66,6 +66,10 @@ async def test_entry_diagnostics(
"port": 993,
"charset": "utf-8",
"folder": "INBOX",
"event_message_data": [
"text",
"headers",
],
"search": "UnSeen UnDeleted",
"custom_event_data_template": "{{ 4 * 4 }}",
}

View file

@ -674,6 +674,41 @@ async def test_message_is_truncated(
assert len(event_data["text"]) == 3
@pytest.mark.parametrize("imap_search", [TEST_SEARCH_RESPONSE])
@pytest.mark.parametrize(
"imap_fetch", [(TEST_FETCH_RESPONSE_TEXT_PLAIN)], ids=["plain"]
)
@pytest.mark.parametrize("imap_has_capability", [True, False], ids=["push", "poll"])
@pytest.mark.parametrize("event_message_data", [[], ["text"], ["text", "headers"]])
async def test_message_data(
hass: HomeAssistant,
mock_imap_protocol: MagicMock,
caplog: pytest.LogCaptureFixture,
event_message_data: list,
) -> None:
"""Test with different message data."""
event_called = async_capture_events(hass, "imap_content")
config = MOCK_CONFIG.copy()
# Mock different message data
config["event_message_data"] = event_message_data
config_entry = MockConfigEntry(domain=DOMAIN, data=config)
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
# Make sure we have had one update (when polling)
async_fire_time_changed(hass, utcnow() + timedelta(seconds=5))
await hass.async_block_till_done()
state = hass.states.get("sensor.imap_email_email_com")
# We should have received one message
assert state is not None
assert state.state == "1"
assert len(event_called) == 1
event_data = event_called[0].data
assert set(event_message_data).issubset(set(event_data))
@pytest.mark.parametrize(
("imap_search", "imap_fetch"),
[(TEST_SEARCH_RESPONSE, TEST_FETCH_RESPONSE_TEXT_PLAIN)],