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"
 |