"""The tests for the mailbox component.""" from datetime import datetime from hashlib import sha1 from http import HTTPStatus from typing import Any from aiohttp.test_utils import TestClient import pytest from homeassistant.bootstrap import async_setup_component import homeassistant.components.mailbox as mailbox from homeassistant.core import HomeAssistant from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.util import dt as dt_util from tests.common import MockModule, mock_integration, mock_platform from tests.typing import ClientSessionGenerator MAILBOX_NAME = "TestMailbox" MEDIA_DATA = b"3f67c4ea33b37d1710f" MESSAGE_TEXT = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " def _create_message(idx: int) -> dict[str, Any]: """Create a sample message.""" msgtime = dt_util.as_timestamp(datetime(2010, 12, idx + 1, 13, 17, 00)) msgtxt = f"Message {idx + 1}. {MESSAGE_TEXT}" msgsha = sha1(msgtxt.encode("utf-8")).hexdigest() return { "info": { "origtime": int(msgtime), "callerid": "John Doe <212-555-1212>", "duration": "10", }, "text": msgtxt, "sha": msgsha, } class TestMailbox(mailbox.Mailbox): """Test Mailbox, with 10 sample messages.""" # This class doesn't contain any tests! Skip pytest test collection. __test__ = False def __init__(self, hass: HomeAssistant, name: str) -> None: """Initialize Test mailbox.""" super().__init__(hass, name) self._messages: dict[str, dict[str, Any]] = {} for idx in range(0, 10): msg = _create_message(idx) msgsha = msg["sha"] self._messages[msgsha] = msg @property def media_type(self) -> str: """Return the supported media type.""" return mailbox.CONTENT_TYPE_MPEG @property def can_delete(self) -> bool: """Return if messages can be deleted.""" return True @property def has_media(self) -> bool: """Return if messages have attached media files.""" return True async def async_get_media(self, msgid: str) -> bytes: """Return the media blob for the msgid.""" if msgid not in self._messages: raise mailbox.StreamError("Message not found") return MEDIA_DATA async def async_get_messages(self) -> list[dict[str, Any]]: """Return a list of the current messages.""" return sorted( self._messages.values(), key=lambda item: item["info"]["origtime"], # type: ignore[no-any-return] reverse=True, ) async def async_delete(self, msgid: str) -> bool: """Delete the specified messages.""" if msgid in self._messages: del self._messages[msgid] self.async_update() return True class MockMailbox: """A mock mailbox platform.""" async def async_get_handler( self, hass: HomeAssistant, config: ConfigType, discovery_info: DiscoveryInfoType | None = None, ) -> mailbox.Mailbox: """Set up the Test mailbox.""" return TestMailbox(hass, MAILBOX_NAME) @pytest.fixture def mock_mailbox(hass: HomeAssistant) -> None: """Mock mailbox.""" mock_integration(hass, MockModule(domain="test")) mock_platform(hass, "test.mailbox", MockMailbox()) @pytest.fixture async def mock_http_client( hass: HomeAssistant, hass_client: ClientSessionGenerator, mock_mailbox: None ) -> TestClient: """Start the Home Assistant HTTP component.""" assert await async_setup_component( hass, mailbox.DOMAIN, {mailbox.DOMAIN: {"platform": "test"}} ) return await hass_client() async def test_get_platforms_from_mailbox(mock_http_client: TestClient) -> None: """Get platforms from mailbox.""" url = "/api/mailbox/platforms" req = await mock_http_client.get(url) assert req.status == HTTPStatus.OK result = await req.json() assert len(result) == 1 assert result[0].get("name") == "TestMailbox" async def test_get_messages_from_mailbox(mock_http_client: TestClient) -> None: """Get messages from mailbox.""" url = "/api/mailbox/messages/TestMailbox" req = await mock_http_client.get(url) assert req.status == HTTPStatus.OK result = await req.json() assert len(result) == 10 async def test_get_media_from_mailbox(mock_http_client: TestClient) -> None: """Get audio from mailbox.""" mp3sha = "7cad61312c7b66f619295be2da8c7ac73b4968f1" msgtxt = "Message 1. Lorem ipsum dolor sit amet, consectetur adipiscing elit. " msgsha = sha1(msgtxt.encode("utf-8")).hexdigest() url = f"/api/mailbox/media/TestMailbox/{msgsha}" req = await mock_http_client.get(url) assert req.status == HTTPStatus.OK data = await req.read() assert sha1(data).hexdigest() == mp3sha async def test_delete_from_mailbox(mock_http_client: TestClient) -> None: """Get audio from mailbox.""" msgtxt1 = "Message 1. Lorem ipsum dolor sit amet, consectetur adipiscing elit. " msgtxt2 = "Message 3. Lorem ipsum dolor sit amet, consectetur adipiscing elit. " msgsha1 = sha1(msgtxt1.encode("utf-8")).hexdigest() msgsha2 = sha1(msgtxt2.encode("utf-8")).hexdigest() for msg in [msgsha1, msgsha2]: url = f"/api/mailbox/delete/TestMailbox/{msg}" req = await mock_http_client.delete(url) assert req.status == HTTPStatus.OK url = "/api/mailbox/messages/TestMailbox" req = await mock_http_client.get(url) assert req.status == HTTPStatus.OK result = await req.json() assert len(result) == 8 async def test_get_messages_from_invalid_mailbox(mock_http_client: TestClient) -> None: """Get messages from mailbox.""" url = "/api/mailbox/messages/mailbox.invalid_mailbox" req = await mock_http_client.get(url) assert req.status == HTTPStatus.NOT_FOUND async def test_get_media_from_invalid_mailbox(mock_http_client: TestClient) -> None: """Get messages from mailbox.""" msgsha = "0000000000000000000000000000000000000000" url = f"/api/mailbox/media/mailbox.invalid_mailbox/{msgsha}" req = await mock_http_client.get(url) assert req.status == HTTPStatus.NOT_FOUND async def test_get_media_from_invalid_msgid(mock_http_client: TestClient) -> None: """Get messages from mailbox.""" msgsha = "0000000000000000000000000000000000000000" url = f"/api/mailbox/media/TestMailbox/{msgsha}" req = await mock_http_client.get(url) assert req.status == HTTPStatus.INTERNAL_SERVER_ERROR async def test_delete_from_invalid_mailbox(mock_http_client: TestClient) -> None: """Get audio from mailbox.""" msgsha = "0000000000000000000000000000000000000000" url = f"/api/mailbox/delete/mailbox.invalid_mailbox/{msgsha}" req = await mock_http_client.delete(url) assert req.status == HTTPStatus.NOT_FOUND