mirror of https://github.com/home-assistant/core
288 lines
8.3 KiB
Python
288 lines
8.3 KiB
Python
"""The tests for local file camera component."""
|
|
|
|
from http import HTTPStatus
|
|
from typing import Any
|
|
from unittest.mock import Mock, mock_open, patch
|
|
|
|
import pytest
|
|
|
|
from homeassistant.components.local_file.const import (
|
|
DEFAULT_NAME,
|
|
DOMAIN,
|
|
SERVICE_UPDATE_FILE_PATH,
|
|
)
|
|
from homeassistant.config_entries import SOURCE_USER
|
|
from homeassistant.const import ATTR_ENTITY_ID, CONF_FILE_PATH
|
|
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant
|
|
from homeassistant.exceptions import ServiceValidationError
|
|
from homeassistant.helpers import issue_registry as ir
|
|
from homeassistant.setup import async_setup_component
|
|
from homeassistant.util import slugify
|
|
|
|
from tests.common import MockConfigEntry
|
|
from tests.typing import ClientSessionGenerator
|
|
|
|
|
|
async def test_loading_file(
|
|
hass: HomeAssistant,
|
|
hass_client: ClientSessionGenerator,
|
|
loaded_entry: MockConfigEntry,
|
|
) -> None:
|
|
"""Test that it loads image from disk."""
|
|
|
|
client = await hass_client()
|
|
|
|
m_open = mock_open(read_data=b"hello")
|
|
with patch("homeassistant.components.local_file.camera.open", m_open, create=True):
|
|
resp = await client.get("/api/camera_proxy/camera.local_file")
|
|
|
|
assert resp.status == HTTPStatus.OK
|
|
body = await resp.text()
|
|
assert body == "hello"
|
|
|
|
|
|
async def test_file_not_readable_after_setup(
|
|
hass: HomeAssistant,
|
|
hass_client: ClientSessionGenerator,
|
|
caplog: pytest.LogCaptureFixture,
|
|
loaded_entry: MockConfigEntry,
|
|
) -> None:
|
|
"""Test a warning is shown setup when file is not readable."""
|
|
|
|
client = await hass_client()
|
|
|
|
with patch(
|
|
"homeassistant.components.local_file.camera.open", side_effect=FileNotFoundError
|
|
):
|
|
resp = await client.get("/api/camera_proxy/camera.local_file")
|
|
|
|
assert resp.status == HTTPStatus.INTERNAL_SERVER_ERROR
|
|
assert "Could not read camera Local File image from file: mock.file" in caplog.text
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("config", "url", "content_type"),
|
|
[
|
|
(
|
|
{
|
|
"name": "test_jpg",
|
|
"file_path": "/path/to/image.jpg",
|
|
},
|
|
"/api/camera_proxy/camera.test_jpg",
|
|
"image/jpeg",
|
|
),
|
|
(
|
|
{
|
|
"name": "test_png",
|
|
"file_path": "/path/to/image.png",
|
|
},
|
|
"/api/camera_proxy/camera.test_png",
|
|
"image/png",
|
|
),
|
|
(
|
|
{
|
|
"name": "test_svg",
|
|
"file_path": "/path/to/image.svg",
|
|
},
|
|
"/api/camera_proxy/camera.test_svg",
|
|
"image/svg+xml",
|
|
),
|
|
(
|
|
{
|
|
"name": "test_no_ext",
|
|
"file_path": "/path/to/image",
|
|
},
|
|
"/api/camera_proxy/camera.test_no_ext",
|
|
"image/jpeg",
|
|
),
|
|
],
|
|
)
|
|
async def test_camera_content_type(
|
|
hass: HomeAssistant,
|
|
hass_client: ClientSessionGenerator,
|
|
config: dict[str, Any],
|
|
url: str,
|
|
content_type: str,
|
|
) -> None:
|
|
"""Test local_file camera content_type."""
|
|
config_entry = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
source=SOURCE_USER,
|
|
options=config,
|
|
entry_id="1",
|
|
)
|
|
|
|
config_entry.add_to_hass(hass)
|
|
with (
|
|
patch("os.path.isfile", Mock(return_value=True)),
|
|
patch("os.access", Mock(return_value=True)),
|
|
):
|
|
await hass.config_entries.async_setup(config_entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
|
|
client = await hass_client()
|
|
|
|
image = "hello"
|
|
m_open = mock_open(read_data=image.encode())
|
|
with patch("homeassistant.components.local_file.camera.open", m_open, create=True):
|
|
resp_1 = await client.get(url)
|
|
|
|
assert resp_1.status == HTTPStatus.OK
|
|
assert resp_1.content_type == content_type
|
|
body = await resp_1.text()
|
|
assert body == image
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"get_config",
|
|
[
|
|
{
|
|
"name": DEFAULT_NAME,
|
|
"file_path": "mock/path.jpg",
|
|
}
|
|
],
|
|
)
|
|
async def test_update_file_path(
|
|
hass: HomeAssistant, loaded_entry: MockConfigEntry
|
|
) -> None:
|
|
"""Test update_file_path service."""
|
|
# Setup platform
|
|
config_entry = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
source=SOURCE_USER,
|
|
options={
|
|
"name": "local_file_camera_2",
|
|
"file_path": "mock/path_2.jpg",
|
|
},
|
|
entry_id="2",
|
|
)
|
|
|
|
config_entry.add_to_hass(hass)
|
|
with (
|
|
patch("os.path.isfile", Mock(return_value=True)),
|
|
patch("os.access", Mock(return_value=True)),
|
|
patch(
|
|
"homeassistant.components.local_file.camera.mimetypes.guess_type",
|
|
Mock(return_value=(None, None)),
|
|
),
|
|
):
|
|
await hass.config_entries.async_setup(config_entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
|
|
# Fetch state and check motion detection attribute
|
|
state = hass.states.get("camera.local_file")
|
|
assert state.attributes.get("friendly_name") == "Local File"
|
|
assert state.attributes.get("file_path") == "mock/path.jpg"
|
|
|
|
service_data = {"entity_id": "camera.local_file", "file_path": "new/path.jpg"}
|
|
|
|
with (
|
|
patch("os.path.isfile", Mock(return_value=True)),
|
|
patch("os.access", Mock(return_value=True)),
|
|
patch(
|
|
"homeassistant.components.local_file.camera.mimetypes.guess_type",
|
|
Mock(return_value=(None, None)),
|
|
),
|
|
):
|
|
await hass.services.async_call(
|
|
DOMAIN,
|
|
SERVICE_UPDATE_FILE_PATH,
|
|
service_data,
|
|
blocking=True,
|
|
)
|
|
|
|
state = hass.states.get("camera.local_file")
|
|
assert state.attributes.get("file_path") == "new/path.jpg"
|
|
|
|
# Check that local_file_camera_2 file_path is still as configured
|
|
state = hass.states.get("camera.local_file_camera_2")
|
|
assert state.attributes.get("file_path") == "mock/path_2.jpg"
|
|
|
|
# Assert it fails if file is not readable
|
|
service_data = {
|
|
ATTR_ENTITY_ID: "camera.local_file",
|
|
CONF_FILE_PATH: "new/path2.jpg",
|
|
}
|
|
with pytest.raises(
|
|
ServiceValidationError, match="Path new/path2.jpg is not accessible"
|
|
):
|
|
await hass.services.async_call(
|
|
DOMAIN,
|
|
SERVICE_UPDATE_FILE_PATH,
|
|
service_data,
|
|
blocking=True,
|
|
)
|
|
|
|
|
|
async def test_import_from_yaml_success(
|
|
hass: HomeAssistant, issue_registry: ir.IssueRegistry
|
|
) -> None:
|
|
"""Test import."""
|
|
|
|
with (
|
|
patch("os.path.isfile", Mock(return_value=True)),
|
|
patch("os.access", Mock(return_value=True)),
|
|
patch(
|
|
"homeassistant.components.local_file.camera.mimetypes.guess_type",
|
|
Mock(return_value=(None, None)),
|
|
),
|
|
):
|
|
await async_setup_component(
|
|
hass,
|
|
"camera",
|
|
{
|
|
"camera": {
|
|
"name": "config_test",
|
|
"platform": "local_file",
|
|
"file_path": "mock.file",
|
|
}
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
assert hass.config_entries.async_has_entries(DOMAIN)
|
|
state = hass.states.get("camera.config_test")
|
|
assert state.attributes.get("file_path") == "mock.file"
|
|
|
|
issue = issue_registry.async_get_issue(
|
|
HOMEASSISTANT_DOMAIN, f"deprecated_yaml_{DOMAIN}"
|
|
)
|
|
assert issue
|
|
assert issue.translation_key == "deprecated_yaml"
|
|
|
|
|
|
async def test_import_from_yaml_fails(
|
|
hass: HomeAssistant, issue_registry: ir.IssueRegistry
|
|
) -> None:
|
|
"""Test import fails due to not accessible file."""
|
|
|
|
with (
|
|
patch("os.path.isfile", Mock(return_value=True)),
|
|
patch("os.access", Mock(return_value=False)),
|
|
patch(
|
|
"homeassistant.components.local_file.camera.mimetypes.guess_type",
|
|
Mock(return_value=(None, None)),
|
|
),
|
|
):
|
|
await async_setup_component(
|
|
hass,
|
|
"camera",
|
|
{
|
|
"camera": {
|
|
"name": "config_test",
|
|
"platform": "local_file",
|
|
"file_path": "mock.file",
|
|
}
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
assert not hass.config_entries.async_has_entries(DOMAIN)
|
|
assert not hass.states.get("camera.config_test")
|
|
|
|
issue = issue_registry.async_get_issue(
|
|
DOMAIN, f"no_access_path_{slugify("mock.file")}"
|
|
)
|
|
assert issue
|
|
assert issue.translation_key == "no_access_path"
|