340 lines
10 KiB
Python
340 lines
10 KiB
Python
"""The tests for the notify file platform."""
|
|
|
|
import os
|
|
from typing import Any
|
|
from unittest.mock import MagicMock, call, mock_open, patch
|
|
|
|
from freezegun.api import FrozenDateTimeFactory
|
|
import pytest
|
|
|
|
from homeassistant.components import notify
|
|
from homeassistant.components.file import DOMAIN
|
|
from homeassistant.components.notify import ATTR_TITLE_DEFAULT
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.exceptions import ServiceValidationError
|
|
from homeassistant.helpers.typing import ConfigType
|
|
from homeassistant.setup import async_setup_component
|
|
import homeassistant.util.dt as dt_util
|
|
|
|
from tests.common import MockConfigEntry, assert_setup_component
|
|
|
|
|
|
async def test_bad_config(hass: HomeAssistant) -> None:
|
|
"""Test set up the platform with bad/missing config."""
|
|
config = {notify.DOMAIN: {"name": "test", "platform": "file"}}
|
|
with assert_setup_component(0, domain="notify") as handle_config:
|
|
assert await async_setup_component(hass, notify.DOMAIN, config)
|
|
await hass.async_block_till_done()
|
|
assert not handle_config[notify.DOMAIN]
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("domain", "service", "params"),
|
|
[
|
|
(notify.DOMAIN, "test", {"message": "one, two, testing, testing"}),
|
|
(
|
|
notify.DOMAIN,
|
|
"send_message",
|
|
{"entity_id": "notify.test", "message": "one, two, testing, testing"},
|
|
),
|
|
],
|
|
ids=["legacy", "entity"],
|
|
)
|
|
@pytest.mark.parametrize(
|
|
("timestamp", "config"),
|
|
[
|
|
(
|
|
False,
|
|
{
|
|
"notify": [
|
|
{
|
|
"name": "test",
|
|
"platform": "file",
|
|
"filename": "mock_file",
|
|
"timestamp": False,
|
|
}
|
|
]
|
|
},
|
|
),
|
|
(
|
|
True,
|
|
{
|
|
"notify": [
|
|
{
|
|
"name": "test",
|
|
"platform": "file",
|
|
"filename": "mock_file",
|
|
"timestamp": True,
|
|
}
|
|
]
|
|
},
|
|
),
|
|
],
|
|
ids=["no_timestamp", "timestamp"],
|
|
)
|
|
async def test_notify_file(
|
|
hass: HomeAssistant,
|
|
freezer: FrozenDateTimeFactory,
|
|
timestamp: bool,
|
|
mock_is_allowed_path: MagicMock,
|
|
config: ConfigType,
|
|
domain: str,
|
|
service: str,
|
|
params: dict[str, str],
|
|
) -> None:
|
|
"""Test the notify file output."""
|
|
filename = "mock_file"
|
|
message = params["message"]
|
|
assert await async_setup_component(hass, notify.DOMAIN, config)
|
|
await hass.async_block_till_done()
|
|
assert await async_setup_component(hass, DOMAIN, config)
|
|
await hass.async_block_till_done(wait_background_tasks=True)
|
|
|
|
freezer.move_to(dt_util.utcnow())
|
|
|
|
m_open = mock_open()
|
|
with (
|
|
patch("homeassistant.components.file.notify.open", m_open, create=True),
|
|
patch("homeassistant.components.file.notify.os.stat") as mock_st,
|
|
):
|
|
mock_st.return_value.st_size = 0
|
|
title = (
|
|
f"{ATTR_TITLE_DEFAULT} notifications "
|
|
f"(Log started: {dt_util.utcnow().isoformat()})\n{'-' * 80}\n"
|
|
)
|
|
|
|
await hass.services.async_call(domain, service, params, blocking=True)
|
|
|
|
full_filename = os.path.join(hass.config.path(), filename)
|
|
assert m_open.call_count == 1
|
|
assert m_open.call_args == call(full_filename, "a", encoding="utf8")
|
|
|
|
assert m_open.return_value.write.call_count == 2
|
|
if not timestamp:
|
|
assert m_open.return_value.write.call_args_list == [
|
|
call(title),
|
|
call(f"{message}\n"),
|
|
]
|
|
else:
|
|
assert m_open.return_value.write.call_args_list == [
|
|
call(title),
|
|
call(f"{dt_util.utcnow().isoformat()} {message}\n"),
|
|
]
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("domain", "service", "params"),
|
|
[(notify.DOMAIN, "test", {"message": "one, two, testing, testing"})],
|
|
ids=["legacy"],
|
|
)
|
|
@pytest.mark.parametrize(
|
|
("is_allowed", "config"),
|
|
[
|
|
(
|
|
True,
|
|
{
|
|
"notify": [
|
|
{
|
|
"name": "test",
|
|
"platform": "file",
|
|
"filename": "mock_file",
|
|
}
|
|
]
|
|
},
|
|
),
|
|
],
|
|
ids=["allowed_but_access_failed"],
|
|
)
|
|
async def test_legacy_notify_file_exception(
|
|
hass: HomeAssistant,
|
|
freezer: FrozenDateTimeFactory,
|
|
mock_is_allowed_path: MagicMock,
|
|
config: ConfigType,
|
|
domain: str,
|
|
service: str,
|
|
params: dict[str, str],
|
|
) -> None:
|
|
"""Test legacy notify file output has exception."""
|
|
assert await async_setup_component(hass, notify.DOMAIN, config)
|
|
await hass.async_block_till_done()
|
|
assert await async_setup_component(hass, DOMAIN, config)
|
|
await hass.async_block_till_done(wait_background_tasks=True)
|
|
|
|
freezer.move_to(dt_util.utcnow())
|
|
|
|
m_open = mock_open()
|
|
with (
|
|
patch("homeassistant.components.file.notify.open", m_open, create=True),
|
|
patch("homeassistant.components.file.notify.os.stat") as mock_st,
|
|
):
|
|
mock_st.side_effect = OSError("Access Failed")
|
|
with pytest.raises(ServiceValidationError) as exc:
|
|
await hass.services.async_call(domain, service, params, blocking=True)
|
|
assert f"{exc.value!r}" == "ServiceValidationError('write_access_failed')"
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("timestamp", "data"),
|
|
[
|
|
(
|
|
False,
|
|
{
|
|
"name": "test",
|
|
"platform": "notify",
|
|
"file_path": "mock_file",
|
|
"timestamp": False,
|
|
},
|
|
),
|
|
(
|
|
True,
|
|
{
|
|
"name": "test",
|
|
"platform": "notify",
|
|
"file_path": "mock_file",
|
|
"timestamp": True,
|
|
},
|
|
),
|
|
],
|
|
ids=["no_timestamp", "timestamp"],
|
|
)
|
|
async def test_legacy_notify_file_entry_only_setup(
|
|
hass: HomeAssistant,
|
|
freezer: FrozenDateTimeFactory,
|
|
timestamp: bool,
|
|
mock_is_allowed_path: MagicMock,
|
|
data: dict[str, Any],
|
|
) -> None:
|
|
"""Test the legacy notify file output in entry only setup."""
|
|
filename = "mock_file"
|
|
|
|
domain = notify.DOMAIN
|
|
service = "test"
|
|
params = {"message": "one, two, testing, testing"}
|
|
message = params["message"]
|
|
|
|
entry = MockConfigEntry(
|
|
domain=DOMAIN, data=data, title=f"test [{data['file_path']}]"
|
|
)
|
|
entry.add_to_hass(hass)
|
|
await hass.config_entries.async_setup(entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
await hass.async_block_till_done(wait_background_tasks=True)
|
|
|
|
freezer.move_to(dt_util.utcnow())
|
|
|
|
m_open = mock_open()
|
|
with (
|
|
patch("homeassistant.components.file.notify.open", m_open, create=True),
|
|
patch("homeassistant.components.file.notify.os.stat") as mock_st,
|
|
):
|
|
mock_st.return_value.st_size = 0
|
|
title = (
|
|
f"{ATTR_TITLE_DEFAULT} notifications "
|
|
f"(Log started: {dt_util.utcnow().isoformat()})\n{'-' * 80}\n"
|
|
)
|
|
|
|
await hass.services.async_call(domain, service, params, blocking=True)
|
|
|
|
assert m_open.call_count == 1
|
|
assert m_open.call_args == call(filename, "a", encoding="utf8")
|
|
|
|
assert m_open.return_value.write.call_count == 2
|
|
if not timestamp:
|
|
assert m_open.return_value.write.call_args_list == [
|
|
call(title),
|
|
call(f"{message}\n"),
|
|
]
|
|
else:
|
|
assert m_open.return_value.write.call_args_list == [
|
|
call(title),
|
|
call(f"{dt_util.utcnow().isoformat()} {message}\n"),
|
|
]
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("is_allowed", "config"),
|
|
[
|
|
(
|
|
False,
|
|
{
|
|
"name": "test",
|
|
"platform": "notify",
|
|
"file_path": "mock_file",
|
|
"timestamp": False,
|
|
},
|
|
),
|
|
],
|
|
ids=["not_allowed"],
|
|
)
|
|
async def test_legacy_notify_file_not_allowed(
|
|
hass: HomeAssistant,
|
|
caplog: pytest.LogCaptureFixture,
|
|
mock_is_allowed_path: MagicMock,
|
|
config: dict[str, Any],
|
|
) -> None:
|
|
"""Test legacy notify file output not allowed."""
|
|
entry = MockConfigEntry(
|
|
domain=DOMAIN, data=config, title=f"test [{config['file_path']}]"
|
|
)
|
|
entry.add_to_hass(hass)
|
|
assert not await hass.config_entries.async_setup(entry.entry_id)
|
|
await hass.async_block_till_done(wait_background_tasks=True)
|
|
assert "is not allowed" in caplog.text
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("service", "params"),
|
|
[
|
|
("test", {"message": "one, two, testing, testing"}),
|
|
(
|
|
"send_message",
|
|
{"entity_id": "notify.test", "message": "one, two, testing, testing"},
|
|
),
|
|
],
|
|
)
|
|
@pytest.mark.parametrize(
|
|
("data", "is_allowed"),
|
|
[
|
|
(
|
|
{
|
|
"name": "test",
|
|
"platform": "notify",
|
|
"file_path": "mock_file",
|
|
"timestamp": False,
|
|
},
|
|
True,
|
|
),
|
|
],
|
|
ids=["not_allowed"],
|
|
)
|
|
async def test_notify_file_write_access_failed(
|
|
hass: HomeAssistant,
|
|
freezer: FrozenDateTimeFactory,
|
|
mock_is_allowed_path: MagicMock,
|
|
service: str,
|
|
params: dict[str, Any],
|
|
data: dict[str, Any],
|
|
) -> None:
|
|
"""Test the notify file fails."""
|
|
domain = notify.DOMAIN
|
|
|
|
entry = MockConfigEntry(
|
|
domain=DOMAIN, data=data, title=f"test [{data['file_path']}]"
|
|
)
|
|
entry.add_to_hass(hass)
|
|
await hass.config_entries.async_setup(entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
await hass.async_block_till_done(wait_background_tasks=True)
|
|
|
|
freezer.move_to(dt_util.utcnow())
|
|
|
|
m_open = mock_open()
|
|
with (
|
|
patch("homeassistant.components.file.notify.open", m_open, create=True),
|
|
patch("homeassistant.components.file.notify.os.stat") as mock_st,
|
|
):
|
|
mock_st.side_effect = OSError("Access Failed")
|
|
with pytest.raises(ServiceValidationError) as exc:
|
|
await hass.services.async_call(domain, service, params, blocking=True)
|
|
assert f"{exc.value!r}" == "ServiceValidationError('write_access_failed')"
|