Migrate SMTP integration to aiosmtplib (#180707)

Co-authored-by: Franck Nijhof <git@frenck.dev>
This commit is contained in:
Manu
2026-08-30 16:53:38 +02:00
committed by GitHub
co-authored by Franck Nijhof
parent f23cda199d
commit 48ccfc09c1
10 changed files with 224 additions and 260 deletions
+30 -3
View File
@@ -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."""
+70 -46
View File
@@ -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:
+11 -11
View File
@@ -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,
+44 -85
View File
@@ -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,