core/tests/components/mastodon/test_services.py

266 lines
7.8 KiB
Python

"""Tests for the Mastodon services."""
from unittest.mock import AsyncMock, Mock, patch
from mastodon.Mastodon import MastodonAPIError, MediaAttachment
import pytest
from homeassistant.components.mastodon.const import (
ATTR_CONFIG_ENTRY_ID,
ATTR_CONTENT_WARNING,
ATTR_MEDIA,
ATTR_MEDIA_DESCRIPTION,
ATTR_STATUS,
ATTR_VISIBILITY,
DOMAIN,
)
from homeassistant.components.mastodon.services import SERVICE_POST
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from . import setup_integration
from tests.common import MockConfigEntry
@pytest.mark.parametrize(
("payload", "kwargs"),
[
(
{
ATTR_STATUS: "test toot",
},
{
"status": "test toot",
"spoiler_text": None,
"visibility": None,
"media_ids": None,
"sensitive": None,
},
),
(
{ATTR_STATUS: "test toot", ATTR_VISIBILITY: "private"},
{
"status": "test toot",
"spoiler_text": None,
"visibility": "private",
"media_ids": None,
"sensitive": None,
},
),
(
{
ATTR_STATUS: "test toot",
ATTR_CONTENT_WARNING: "Spoiler",
ATTR_VISIBILITY: "private",
},
{
"status": "test toot",
"spoiler_text": "Spoiler",
"visibility": "private",
"media_ids": None,
"sensitive": None,
},
),
(
{
ATTR_STATUS: "test toot",
ATTR_CONTENT_WARNING: "Spoiler",
ATTR_MEDIA: "/image.jpg",
},
{
"status": "test toot",
"spoiler_text": "Spoiler",
"visibility": None,
"media_ids": "1",
"sensitive": None,
},
),
(
{
ATTR_STATUS: "test toot",
ATTR_CONTENT_WARNING: "Spoiler",
ATTR_MEDIA: "/image.jpg",
ATTR_MEDIA_DESCRIPTION: "A test image",
},
{
"status": "test toot",
"spoiler_text": "Spoiler",
"visibility": None,
"media_ids": "1",
"sensitive": None,
},
),
],
)
async def test_service_post(
hass: HomeAssistant,
mock_mastodon_client: AsyncMock,
mock_config_entry: MockConfigEntry,
payload: dict[str, str],
kwargs: dict[str, str | None],
) -> None:
"""Test the post service."""
await setup_integration(hass, mock_config_entry)
with (
patch.object(hass.config, "is_allowed_path", return_value=True),
patch.object(
mock_mastodon_client, "media_post", return_value=MediaAttachment(id="1")
),
):
await hass.services.async_call(
DOMAIN,
SERVICE_POST,
{
ATTR_CONFIG_ENTRY_ID: mock_config_entry.entry_id,
}
| payload,
blocking=True,
return_response=False,
)
mock_mastodon_client.status_post.assert_called_with(**kwargs)
mock_mastodon_client.status_post.reset_mock()
@pytest.mark.parametrize(
("payload", "kwargs"),
[
(
{
ATTR_STATUS: "test toot",
},
{"status": "test toot", "spoiler_text": None, "visibility": None},
),
(
{
ATTR_STATUS: "test toot",
ATTR_CONTENT_WARNING: "Spoiler",
ATTR_MEDIA: "/image.jpg",
},
{
"status": "test toot",
"spoiler_text": "Spoiler",
"visibility": None,
"media_ids": "1",
"media_description": None,
"sensitive": None,
},
),
],
)
async def test_post_service_failed(
hass: HomeAssistant,
mock_mastodon_client: AsyncMock,
mock_config_entry: MockConfigEntry,
payload: dict[str, str],
kwargs: dict[str, str | None],
) -> None:
"""Test the post service raising an error."""
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
hass.config.is_allowed_path = Mock(return_value=True)
mock_mastodon_client.media_post.return_value = MediaAttachment(id="1")
mock_mastodon_client.status_post.side_effect = MastodonAPIError
with pytest.raises(HomeAssistantError, match="Unable to send message"):
await hass.services.async_call(
DOMAIN,
SERVICE_POST,
{ATTR_CONFIG_ENTRY_ID: mock_config_entry.entry_id} | payload,
blocking=True,
return_response=False,
)
async def test_post_media_upload_failed(
hass: HomeAssistant,
mock_mastodon_client: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test the post service raising an error because media upload fails."""
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
payload = {"status": "test toot", "media": "/fail.jpg"}
mock_mastodon_client.media_post.side_effect = MastodonAPIError
with (
patch.object(hass.config, "is_allowed_path", return_value=True),
pytest.raises(HomeAssistantError, match="Unable to upload image /fail.jpg"),
):
await hass.services.async_call(
DOMAIN,
SERVICE_POST,
{ATTR_CONFIG_ENTRY_ID: mock_config_entry.entry_id} | payload,
blocking=True,
return_response=False,
)
async def test_post_path_not_whitelisted(
hass: HomeAssistant,
mock_mastodon_client: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test the post service raising an error because the file path is not whitelisted."""
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
payload = {"status": "test toot", "media": "/fail.jpg"}
with pytest.raises(
HomeAssistantError, match="/fail.jpg is not a whitelisted directory"
):
await hass.services.async_call(
DOMAIN,
SERVICE_POST,
{ATTR_CONFIG_ENTRY_ID: mock_config_entry.entry_id} | payload,
blocking=True,
return_response=False,
)
async def test_service_entry_availability(
hass: HomeAssistant,
mock_mastodon_client: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test the services without valid entry."""
mock_config_entry.add_to_hass(hass)
mock_config_entry2 = MockConfigEntry(domain=DOMAIN)
mock_config_entry2.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
payload = {"status": "test toot"}
with pytest.raises(ServiceValidationError, match="Mock Title is not loaded"):
await hass.services.async_call(
DOMAIN,
SERVICE_POST,
{ATTR_CONFIG_ENTRY_ID: mock_config_entry2.entry_id} | payload,
blocking=True,
return_response=False,
)
with pytest.raises(
ServiceValidationError, match='Integration "mastodon" not found in registry'
):
await hass.services.async_call(
DOMAIN,
SERVICE_POST,
{ATTR_CONFIG_ENTRY_ID: "bad-config_id"} | payload,
blocking=True,
return_response=False,
)