mirror of
https://github.com/home-assistant/core.git
synced 2026-11-06 13:41:27 -04:00
Migrate SMTP integration to aiosmtplib (#180707)
Co-authored-by: Franck Nijhof <git@frenck.dev>
This commit is contained in:
@@ -49,12 +49,23 @@ def mock_setup_entry() -> Generator[AsyncMock]:
|
||||
def mock_smtp() -> Generator[MagicMock]:
|
||||
"""Mock smtplib.SMTP."""
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.smtp.helpers.smtplib.SMTP", autospec=True
|
||||
) as mock_client:
|
||||
client = mock_client.return_value
|
||||
client.cls = mock_client
|
||||
yield client
|
||||
|
||||
|
||||
@pytest.fixture(name="aiosmtplib")
|
||||
def mock_aiosmtplib() -> Generator[AsyncMock]:
|
||||
"""Mock aiosmtplib."""
|
||||
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.smtp.config_flow.SMTP_SSL", autospec=True
|
||||
"homeassistant.components.smtp.config_flow.SMTP", autospec=True
|
||||
) as mock_client,
|
||||
patch("homeassistant.components.smtp.helpers.smtplib.SMTP", new=mock_client),
|
||||
patch("homeassistant.components.smtp.config_flow.SMTP", new=mock_client),
|
||||
patch("homeassistant.components.smtp.SMTP", new=mock_client),
|
||||
):
|
||||
client = mock_client.return_value
|
||||
client.cls = mock_client
|
||||
@@ -83,6 +94,22 @@ def mock_randrange() -> Generator[None]:
|
||||
yield
|
||||
|
||||
|
||||
@pytest.fixture(name="client_context")
|
||||
def mock_client_context() -> Generator[None]:
|
||||
"""Mock client_context."""
|
||||
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.smtp.config_flow.client_context"
|
||||
) as mock_client,
|
||||
patch(
|
||||
"homeassistant.components.smtp.client_context",
|
||||
new=mock_client,
|
||||
),
|
||||
):
|
||||
yield mock_client
|
||||
|
||||
|
||||
@pytest.fixture(name="config_entry")
|
||||
def mock_config_entry() -> MockConfigEntry:
|
||||
"""Mock smtp configuration entry."""
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
"""Test the SMTP config flow."""
|
||||
|
||||
from smtplib import SMTPAuthenticationError, SMTPServerDisconnected
|
||||
from socket import gaierror
|
||||
from ssl import SSLCertVerificationError
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
from aiosmtplib import SMTPAuthenticationError, SMTPException, SMTPTimeoutError
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.smtp.const import (
|
||||
CONF_ENCRYPTION,
|
||||
CONF_SENDER_NAME,
|
||||
DOMAIN,
|
||||
SECTION_OPTIONS,
|
||||
@@ -38,9 +35,12 @@ from .conftest import USER_INPUT
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
@pytest.mark.parametrize("encryption", ["tls", "starttls"])
|
||||
@pytest.mark.usefixtures("smtp")
|
||||
async def test_form(
|
||||
hass: HomeAssistant, mock_setup_entry: AsyncMock, encryption: str, smtp: MagicMock
|
||||
hass: HomeAssistant,
|
||||
mock_setup_entry: AsyncMock,
|
||||
aiosmtplib: AsyncMock,
|
||||
client_context: MagicMock,
|
||||
) -> None:
|
||||
"""Test we get the form."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
@@ -53,7 +53,6 @@ async def test_form(
|
||||
result["flow_id"],
|
||||
{
|
||||
**USER_INPUT,
|
||||
CONF_ENCRYPTION: encryption,
|
||||
SECTION_OPTIONS: {CONF_TIMEOUT: 60},
|
||||
},
|
||||
)
|
||||
@@ -61,10 +60,7 @@ async def test_form(
|
||||
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result["title"] == "Home Assistant"
|
||||
assert result["data"] == {
|
||||
**USER_INPUT,
|
||||
CONF_ENCRYPTION: encryption,
|
||||
}
|
||||
assert result["data"] == USER_INPUT
|
||||
assert result["options"] == {CONF_TIMEOUT: 60}
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
@@ -81,11 +77,19 @@ async def test_form(
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result["title"] == "Recipient"
|
||||
assert result["unique_id"] == "recipient@example.com"
|
||||
assert smtp.cls.call_args[0] == ("mail.example.com", 587)
|
||||
assert smtp.cls.call_args[1]["timeout"] == 60
|
||||
aiosmtplib.cls.assert_called_once_with(
|
||||
hostname="mail.example.com",
|
||||
port=587,
|
||||
username="test-username",
|
||||
password="test-password",
|
||||
timeout=60,
|
||||
use_tls=False,
|
||||
start_tls=True,
|
||||
tls_context=client_context(),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("smtp")
|
||||
@pytest.mark.usefixtures("smtp", "aiosmtplib")
|
||||
async def test_form_already_configured(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
@@ -117,18 +121,16 @@ async def test_form_already_configured(
|
||||
("exception", "text_error"),
|
||||
[
|
||||
(SMTPAuthenticationError(0, ""), "invalid_auth"),
|
||||
(ConnectionRefusedError, "cannot_connect"),
|
||||
(TimeoutError, "timeout_connect"),
|
||||
(SMTPServerDisconnected, "cannot_connect"),
|
||||
(gaierror, "cannot_connect"),
|
||||
(SSLCertVerificationError, "invalid_cert"),
|
||||
(SMTPException(""), "cannot_connect"),
|
||||
(SMTPTimeoutError(""), "timeout_connect"),
|
||||
(ValueError, "unknown"),
|
||||
],
|
||||
)
|
||||
@pytest.mark.usefixtures("smtp")
|
||||
async def test_form_errors(
|
||||
hass: HomeAssistant,
|
||||
mock_setup_entry: AsyncMock,
|
||||
smtp: MagicMock,
|
||||
aiosmtplib: MagicMock,
|
||||
exception: Exception,
|
||||
text_error: str,
|
||||
) -> None:
|
||||
@@ -137,7 +139,7 @@ async def test_form_errors(
|
||||
DOMAIN, context={"source": SOURCE_USER}
|
||||
)
|
||||
|
||||
smtp.login.side_effect = exception
|
||||
aiosmtplib.cls.side_effect = exception
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
@@ -150,7 +152,7 @@ async def test_form_errors(
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["errors"] == {"base": text_error}
|
||||
|
||||
smtp.login.side_effect = None
|
||||
aiosmtplib.cls.side_effect = None
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
@@ -168,7 +170,7 @@ async def test_form_errors(
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("smtp")
|
||||
@pytest.mark.usefixtures("smtp", "aiosmtplib")
|
||||
async def test_form_recipient_already_configured(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
@@ -198,7 +200,7 @@ async def test_form_recipient_already_configured(
|
||||
assert result["reason"] == "already_configured"
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("smtp")
|
||||
@pytest.mark.usefixtures("smtp", "aiosmtplib")
|
||||
async def test_options_flow(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
@@ -229,8 +231,12 @@ async def test_options_flow(
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("smtp")
|
||||
async def test_form_reconfigure(
|
||||
hass: HomeAssistant, config_entry: MockConfigEntry, smtp: MagicMock
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
aiosmtplib: AsyncMock,
|
||||
client_context: MagicMock,
|
||||
) -> None:
|
||||
"""Test reconfigure flow."""
|
||||
|
||||
@@ -263,10 +269,19 @@ async def test_form_reconfigure(
|
||||
}
|
||||
|
||||
assert len(hass.config_entries.async_entries()) == 1
|
||||
smtp.cls.assert_called_with("mail.example.com", 587, timeout=1312)
|
||||
aiosmtplib.cls.assert_called_once_with(
|
||||
hostname="mail.example.com",
|
||||
port=587,
|
||||
username="new-username",
|
||||
password="new-password",
|
||||
timeout=1312,
|
||||
use_tls=False,
|
||||
start_tls=True,
|
||||
tls_context=client_context(),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("smtp")
|
||||
@pytest.mark.usefixtures("smtp", "aiosmtplib")
|
||||
async def test_form_reconfigure_already_configured(
|
||||
hass: HomeAssistant, config_entry: MockConfigEntry
|
||||
) -> None:
|
||||
@@ -308,11 +323,8 @@ async def test_form_reconfigure_already_configured(
|
||||
("exception", "text_error"),
|
||||
[
|
||||
(SMTPAuthenticationError(0, ""), "invalid_auth"),
|
||||
(ConnectionRefusedError, "cannot_connect"),
|
||||
(SMTPServerDisconnected, "cannot_connect"),
|
||||
(TimeoutError, "timeout_connect"),
|
||||
(gaierror, "cannot_connect"),
|
||||
(SSLCertVerificationError, "invalid_cert"),
|
||||
(SMTPException(""), "cannot_connect"),
|
||||
(SMTPTimeoutError(""), "timeout_connect"),
|
||||
(ValueError, "unknown"),
|
||||
],
|
||||
)
|
||||
@@ -320,13 +332,13 @@ async def test_form_reconfigure_already_configured(
|
||||
async def test_form_reconfigure_errors(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
smtp: MagicMock,
|
||||
aiosmtplib: AsyncMock,
|
||||
exception: Exception,
|
||||
text_error: str,
|
||||
) -> None:
|
||||
"""Test reconfigure flow connection errors."""
|
||||
|
||||
smtp.login.side_effect = exception
|
||||
aiosmtplib.cls.side_effect = exception
|
||||
|
||||
config_entry.add_to_hass(hass)
|
||||
|
||||
@@ -348,7 +360,7 @@ async def test_form_reconfigure_errors(
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["errors"] == {"base": text_error}
|
||||
|
||||
smtp.login.side_effect = None
|
||||
aiosmtplib.cls.side_effect = None
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
@@ -372,8 +384,12 @@ async def test_form_reconfigure_errors(
|
||||
assert len(hass.config_entries.async_entries()) == 1
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("smtp")
|
||||
async def test_form_reauth(
|
||||
hass: HomeAssistant, config_entry: MockConfigEntry, smtp: MagicMock
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
aiosmtplib: AsyncMock,
|
||||
client_context: MagicMock,
|
||||
) -> None:
|
||||
"""Test reauth flow."""
|
||||
|
||||
@@ -403,16 +419,24 @@ async def test_form_reauth(
|
||||
}
|
||||
|
||||
assert len(hass.config_entries.async_entries()) == 1
|
||||
smtp.cls.assert_called_with("mail.example.com", 587, timeout=1312)
|
||||
aiosmtplib.cls.assert_called_once_with(
|
||||
hostname="mail.example.com",
|
||||
port=587,
|
||||
username="new-username",
|
||||
password="new-password",
|
||||
timeout=1312,
|
||||
use_tls=False,
|
||||
start_tls=True,
|
||||
tls_context=client_context(),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("exception", "text_error"),
|
||||
[
|
||||
(SMTPAuthenticationError(0, ""), "invalid_auth"),
|
||||
(ConnectionRefusedError, "cannot_connect"),
|
||||
(gaierror, "cannot_connect"),
|
||||
(SSLCertVerificationError, "invalid_cert"),
|
||||
(SMTPException(""), "cannot_connect"),
|
||||
(SMTPTimeoutError(""), "timeout_connect"),
|
||||
(ValueError, "unknown"),
|
||||
],
|
||||
)
|
||||
@@ -420,13 +444,13 @@ async def test_form_reauth(
|
||||
async def test_form_reauth_errors(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
smtp: MagicMock,
|
||||
aiosmtplib: AsyncMock,
|
||||
exception: Exception,
|
||||
text_error: str,
|
||||
) -> None:
|
||||
"""Test reauth flow connection errors."""
|
||||
|
||||
smtp.login.side_effect = exception
|
||||
aiosmtplib.cls.side_effect = exception
|
||||
|
||||
config_entry.add_to_hass(hass)
|
||||
|
||||
@@ -446,7 +470,7 @@ async def test_form_reauth_errors(
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["errors"] == {"base": text_error}
|
||||
|
||||
smtp.login.side_effect = None
|
||||
aiosmtplib.cls.side_effect = None
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
@@ -467,7 +491,7 @@ async def test_form_reauth_errors(
|
||||
assert len(hass.config_entries.async_entries()) == 1
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("smtp")
|
||||
@pytest.mark.usefixtures("smtp", "aiosmtplib")
|
||||
async def test_form_subentry_reconfigure(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
@@ -508,7 +532,7 @@ async def test_form_subentry_reconfigure(
|
||||
assert entity.unique_id == "123456789_changed@example.com"
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("smtp")
|
||||
@pytest.mark.usefixtures("smtp", "aiosmtplib")
|
||||
async def test_form_subentry_reconfigure_already_configured(
|
||||
hass: HomeAssistant,
|
||||
) -> None:
|
||||
@@ -555,7 +579,7 @@ async def test_form_subentry_reconfigure_already_configured(
|
||||
assert result["reason"] == "already_configured"
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("smtp")
|
||||
@pytest.mark.usefixtures("smtp", "aiosmtplib")
|
||||
async def test_form_subentry_reconfigure_updates_title(
|
||||
hass: HomeAssistant,
|
||||
) -> None:
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
"""Tests for the SMTP integration."""
|
||||
|
||||
from smtplib import SMTPAuthenticationError
|
||||
from socket import gaierror
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from aiosmtplib import SMTPAuthenticationError, SMTPException
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.notify import DOMAIN as NOTIFY_DOMAIN
|
||||
@@ -33,7 +32,7 @@ from homeassistant.setup import async_setup_component
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("smtp")
|
||||
@pytest.mark.usefixtures("smtp", "aiosmtplib")
|
||||
async def test_entry_setup_unload(
|
||||
hass: HomeAssistant, config_entry: MockConfigEntry
|
||||
) -> None:
|
||||
@@ -57,21 +56,21 @@ async def test_entry_setup_unload(
|
||||
@pytest.mark.parametrize(
|
||||
("exception", "state"),
|
||||
[
|
||||
(ConnectionRefusedError, ConfigEntryState.SETUP_RETRY),
|
||||
(gaierror, ConfigEntryState.SETUP_RETRY),
|
||||
(SMTPException(""), ConfigEntryState.SETUP_RETRY),
|
||||
(SMTPAuthenticationError(0, ""), ConfigEntryState.SETUP_ERROR),
|
||||
],
|
||||
)
|
||||
@pytest.mark.usefixtures("smtp")
|
||||
async def test_config_entry_not_ready(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
smtp: MagicMock,
|
||||
aiosmtplib: AsyncMock,
|
||||
exception: Exception,
|
||||
state: ConfigEntryState,
|
||||
) -> None:
|
||||
"""Test config entry not ready."""
|
||||
|
||||
smtp.login.side_effect = exception
|
||||
aiosmtplib.__aenter__.side_effect = exception
|
||||
|
||||
config_entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
@@ -80,7 +79,7 @@ async def test_config_entry_not_ready(
|
||||
assert config_entry.state is state
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("smtp")
|
||||
@pytest.mark.usefixtures("smtp", "aiosmtplib")
|
||||
async def test_import(
|
||||
hass: HomeAssistant,
|
||||
mock_setup_entry: AsyncMock,
|
||||
@@ -203,14 +202,15 @@ async def test_import_already_configured(
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("smtp")
|
||||
async def test_import_errors(
|
||||
hass: HomeAssistant,
|
||||
mock_setup_entry: AsyncMock,
|
||||
issue_registry: ir.IssueRegistry,
|
||||
smtp: MagicMock,
|
||||
aiosmtplib: AsyncMock,
|
||||
) -> None:
|
||||
"""Test yaml triggers import flow, aborts with errors, and creates error issue."""
|
||||
smtp.login.side_effect = ValueError
|
||||
aiosmtplib.__aenter__.side_effect = ValueError
|
||||
|
||||
await async_setup_component(
|
||||
hass,
|
||||
|
||||
@@ -2,16 +2,10 @@
|
||||
|
||||
from pathlib import Path
|
||||
import re
|
||||
from smtplib import (
|
||||
SMTPAuthenticationError,
|
||||
SMTPException,
|
||||
SMTPHeloError,
|
||||
SMTPSenderRefused,
|
||||
SMTPServerDisconnected,
|
||||
)
|
||||
from socket import gaierror
|
||||
from unittest.mock import MagicMock, patch
|
||||
from smtplib import SMTPException, SMTPServerDisconnected
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import aiosmtplib
|
||||
import pytest
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
@@ -211,7 +205,7 @@ def test_send_target_message(target, hass: HomeAssistant, message) -> None:
|
||||
assert recipient == expected_recipient
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("smtp")
|
||||
@pytest.mark.usefixtures("smtp", "aiosmtplib")
|
||||
async def test_notify_platform(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
@@ -229,12 +223,12 @@ async def test_notify_platform(
|
||||
await snapshot_platform(hass, entity_registry, snapshot, config_entry.entry_id)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("make_msgid")
|
||||
@pytest.mark.usefixtures("make_msgid", "smtp")
|
||||
@pytest.mark.freeze_time("2026-05-03T03:09:37+00:00")
|
||||
async def test_notify_send_message(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
smtp: MagicMock,
|
||||
aiosmtplib: AsyncMock,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test sending an email message via notify.send_message action."""
|
||||
@@ -263,30 +257,26 @@ async def test_notify_send_message(
|
||||
assert state
|
||||
assert state.state == "2026-05-03T03:09:37+00:00"
|
||||
|
||||
assert smtp.sendmail.call_args[0][0] == "email@example.com"
|
||||
assert smtp.sendmail.call_args[0][1] == "recipient@example.com"
|
||||
assert smtp.sendmail.call_args[0][2] == snapshot
|
||||
msg = aiosmtplib.__aenter__.return_value.send_message.call_args[0][0]
|
||||
assert msg.as_string() == snapshot
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("call_method", "exception", "translation_key"),
|
||||
("exception", "translation_key", "call_count"),
|
||||
[
|
||||
("login", SMTPAuthenticationError(0, ""), "authentication_error"),
|
||||
("login", gaierror, "send_mail_connection_error"),
|
||||
("login", ConnectionRefusedError, "send_mail_connection_error"),
|
||||
("login", SMTPHeloError(0, ""), "send_mail_connection_error"),
|
||||
("sendmail", SMTPServerDisconnected, "send_mail_connection_error"),
|
||||
("sendmail", SMTPSenderRefused(0, b"", ""), "send_mail_connection_error"),
|
||||
(aiosmtplib.SMTPAuthenticationError(0, ""), "authentication_error", 1),
|
||||
(aiosmtplib.SMTPException(""), "send_mail_connection_error", 2),
|
||||
],
|
||||
)
|
||||
@pytest.mark.usefixtures("make_msgid", "smtp")
|
||||
@pytest.mark.freeze_time("2026-05-03T03:09:37+00:00")
|
||||
async def test_notify_send_message_exceptions(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
smtp: MagicMock,
|
||||
call_method: str,
|
||||
aiosmtplib: AsyncMock,
|
||||
exception: Exception,
|
||||
translation_key: str,
|
||||
call_count: int,
|
||||
) -> None:
|
||||
"""Test exceptions via notify.send_message action."""
|
||||
|
||||
@@ -296,7 +286,7 @@ async def test_notify_send_message_exceptions(
|
||||
|
||||
assert config_entry.state is ConfigEntryState.LOADED
|
||||
|
||||
getattr(smtp, call_method).side_effect = exception
|
||||
aiosmtplib.__aenter__.return_value.send_message.side_effect = exception
|
||||
|
||||
with pytest.raises(HomeAssistantError) as e:
|
||||
await hass.services.async_call(
|
||||
@@ -310,40 +300,11 @@ async def test_notify_send_message_exceptions(
|
||||
)
|
||||
|
||||
assert e.value.translation_key == translation_key
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("make_msgid")
|
||||
@pytest.mark.freeze_time("2026-05-03T03:09:37+00:00")
|
||||
async def test_notify_retry_on_disconnect_with_broken_quit(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
smtp: MagicMock,
|
||||
) -> None:
|
||||
"""Test retry succeeds when quit() raises on a dead connection."""
|
||||
|
||||
config_entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert config_entry.state is ConfigEntryState.LOADED
|
||||
|
||||
smtp.sendmail.side_effect = [SMTPServerDisconnected("gone"), None]
|
||||
smtp.quit.side_effect = SMTPServerDisconnected("please run connect() first")
|
||||
|
||||
await hass.services.async_call(
|
||||
NOTIFY_DOMAIN,
|
||||
SERVICE_SEND_MESSAGE,
|
||||
{
|
||||
ATTR_ENTITY_ID: "notify.home_assistant_recipient",
|
||||
ATTR_MESSAGE: "Hello World",
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
assert smtp.sendmail.call_count == 2
|
||||
assert aiosmtplib.__aenter__.return_value.send_message.call_count == call_count
|
||||
|
||||
|
||||
@pytest.mark.parametrize("exception", [SMTPServerDisconnected, SMTPException])
|
||||
@pytest.mark.usefixtures("aiosmtplib")
|
||||
async def test_legacy_notify_exception(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
@@ -375,12 +336,12 @@ async def test_legacy_notify_exception(
|
||||
assert smtp.sendmail.call_count == 2
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("make_msgid", "randrange")
|
||||
@pytest.mark.usefixtures("make_msgid", "smtp", "randrange")
|
||||
@pytest.mark.freeze_time("2026-05-03T03:09:37+00:00")
|
||||
async def test_smtp_send_message(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
smtp: MagicMock,
|
||||
aiosmtplib: AsyncMock,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test sending an email message via smtp.send_message action."""
|
||||
@@ -411,17 +372,16 @@ async def test_smtp_send_message(
|
||||
assert state
|
||||
assert state.state == "2026-05-03T03:09:37+00:00"
|
||||
|
||||
assert smtp.sendmail.call_args[0][0] == "email@example.com"
|
||||
assert smtp.sendmail.call_args[0][1] == "recipient@example.com"
|
||||
assert smtp.sendmail.call_args[0][2] == snapshot
|
||||
msg = aiosmtplib.__aenter__.return_value.send_message.call_args[0][0]
|
||||
assert msg.as_string() == snapshot
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("make_msgid", "randrange")
|
||||
@pytest.mark.usefixtures("make_msgid", "smtp", "randrange")
|
||||
@pytest.mark.freeze_time("2026-05-03T03:09:37+00:00")
|
||||
async def test_smtp_send_message_local_media_source(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
smtp: MagicMock,
|
||||
aiosmtplib: AsyncMock,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test sending an email message via smtp.send_message action with attachment from local media source."""
|
||||
@@ -453,17 +413,16 @@ async def test_smtp_send_message_local_media_source(
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
assert smtp.sendmail.call_args[0][0] == "email@example.com"
|
||||
assert smtp.sendmail.call_args[0][1] == "recipient@example.com"
|
||||
assert smtp.sendmail.call_args[0][2] == snapshot
|
||||
msg = aiosmtplib.__aenter__.return_value.send_message.call_args[0][0]
|
||||
assert msg.as_string() == snapshot
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("make_msgid", "randrange")
|
||||
@pytest.mark.usefixtures("make_msgid", "smtp", "randrange")
|
||||
@pytest.mark.freeze_time("2026-05-03T03:09:37+00:00")
|
||||
async def test_smtp_send_message_camera_source(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
smtp: MagicMock,
|
||||
aiosmtplib: AsyncMock,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test sending an email message via smtp.send_message action with attachment from camera source snapshot."""
|
||||
@@ -500,17 +459,16 @@ async def test_smtp_send_message_camera_source(
|
||||
blocking=True,
|
||||
)
|
||||
mock_get_image.assert_called_once_with(hass, "camera.demo_camera")
|
||||
assert smtp.sendmail.call_args[0][0] == "email@example.com"
|
||||
assert smtp.sendmail.call_args[0][1] == "recipient@example.com"
|
||||
assert smtp.sendmail.call_args[0][2] == snapshot
|
||||
msg = aiosmtplib.__aenter__.return_value.send_message.call_args[0][0]
|
||||
assert msg.as_string() == snapshot
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("make_msgid", "randrange")
|
||||
@pytest.mark.usefixtures("make_msgid", "smtp", "randrange")
|
||||
@pytest.mark.freeze_time("2026-05-03T03:09:37+00:00")
|
||||
async def test_smtp_send_message_image_source(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
smtp: MagicMock,
|
||||
aiosmtplib: AsyncMock,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test sending an email message via smtp.send_message action with attachment from image source."""
|
||||
@@ -552,17 +510,16 @@ async def test_smtp_send_message_image_source(
|
||||
blocking=True,
|
||||
)
|
||||
mock_get_image.assert_called_with(hass, "image.test")
|
||||
assert smtp.sendmail.call_args[0][0] == "email@example.com"
|
||||
assert smtp.sendmail.call_args[0][1] == "recipient@example.com"
|
||||
assert smtp.sendmail.call_args[0][2] == snapshot
|
||||
msg = aiosmtplib.__aenter__.return_value.send_message.call_args[0][0]
|
||||
assert msg.as_string() == snapshot
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("make_msgid", "randrange")
|
||||
@pytest.mark.usefixtures("make_msgid", "smtp", "randrange")
|
||||
@pytest.mark.freeze_time("2026-05-03T03:09:37+00:00")
|
||||
async def test_smtp_send_message_tts_source(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
smtp: MagicMock,
|
||||
aiosmtplib: AsyncMock,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test sending an email message via smtp.send_message action with audio attachment from tts source."""
|
||||
@@ -575,7 +532,7 @@ async def test_smtp_send_message_tts_source(
|
||||
with patch(
|
||||
"homeassistant.components.tts.async_get_media_source_audio",
|
||||
return_value=("mp3", b"Hello World!"),
|
||||
):
|
||||
) as mock_get_media_source_audio:
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_SEND_MESSAGE,
|
||||
@@ -596,12 +553,14 @@ async def test_smtp_send_message_tts_source(
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
assert smtp.sendmail.call_args[0][0] == "email@example.com"
|
||||
assert smtp.sendmail.call_args[0][1] == "recipient@example.com"
|
||||
assert smtp.sendmail.call_args[0][2] == snapshot
|
||||
mock_get_media_source_audio.assert_called_with(
|
||||
hass, "media-source://tts/demo?message=Hello+World%21&language=en"
|
||||
)
|
||||
msg = aiosmtplib.__aenter__.return_value.send_message.call_args[0][0]
|
||||
assert msg.as_string() == snapshot
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("smtp")
|
||||
@pytest.mark.usefixtures("smtp", "aiosmtplib")
|
||||
@pytest.mark.freeze_time("2026-05-03T03:09:37+00:00")
|
||||
async def test_smtp_send_message_media_source_not_supported(
|
||||
hass: HomeAssistant,
|
||||
@@ -650,7 +609,7 @@ async def test_smtp_send_message_media_source_not_supported(
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("smtp")
|
||||
@pytest.mark.usefixtures("smtp", "aiosmtplib")
|
||||
@pytest.mark.freeze_time("2026-05-03T03:09:37+00:00")
|
||||
async def test_smtp_send_message_media_source_missing_filename(
|
||||
hass: HomeAssistant,
|
||||
@@ -696,7 +655,7 @@ async def test_smtp_send_message_media_source_missing_filename(
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("smtp")
|
||||
@pytest.mark.usefixtures("smtp", "aiosmtplib")
|
||||
async def test_deprecated_legacy_notify_action(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
|
||||
Reference in New Issue
Block a user