mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 05:07:41 +00:00
Add media_source platform to Immich integration (#145159)
* add media_source platform * fix error messages * use mime-type from asset info, instead of guessing it * add dependency for http * add tests * use direct imports and set can_play=False for images * fix tests
This commit is contained in:
parent
d580f8a8a2
commit
e76bd1bbb9
@ -3,6 +3,7 @@
|
||||
"name": "Immich",
|
||||
"codeowners": ["@mib1185"],
|
||||
"config_flow": true,
|
||||
"dependencies": ["http"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/immich",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["aioimmich"],
|
||||
|
209
homeassistant/components/immich/media_source.py
Normal file
209
homeassistant/components/immich/media_source.py
Normal file
@ -0,0 +1,209 @@
|
||||
"""Immich as a media source."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from logging import getLogger
|
||||
import mimetypes
|
||||
|
||||
from aiohttp.web import HTTPNotFound, Request, Response
|
||||
from aioimmich.exceptions import ImmichError
|
||||
|
||||
from homeassistant.components.http import HomeAssistantView
|
||||
from homeassistant.components.media_player import MediaClass
|
||||
from homeassistant.components.media_source import (
|
||||
BrowseError,
|
||||
BrowseMediaSource,
|
||||
MediaSource,
|
||||
MediaSourceItem,
|
||||
PlayMedia,
|
||||
Unresolvable,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import ImmichConfigEntry
|
||||
|
||||
LOGGER = getLogger(__name__)
|
||||
|
||||
|
||||
async def async_get_media_source(hass: HomeAssistant) -> MediaSource:
|
||||
"""Set up Immich media source."""
|
||||
entries = hass.config_entries.async_entries(
|
||||
DOMAIN, include_disabled=False, include_ignore=False
|
||||
)
|
||||
hass.http.register_view(ImmichMediaView(hass))
|
||||
return ImmichMediaSource(hass, entries)
|
||||
|
||||
|
||||
class ImmichMediaSourceIdentifier:
|
||||
"""Immich media item identifier."""
|
||||
|
||||
def __init__(self, identifier: str) -> None:
|
||||
"""Split identifier into parts."""
|
||||
parts = identifier.split("/")
|
||||
# coonfig_entry.unique_id/album_id/asset_it/filename
|
||||
self.unique_id = parts[0]
|
||||
self.album_id = parts[1] if len(parts) > 1 else None
|
||||
self.asset_id = parts[2] if len(parts) > 2 else None
|
||||
self.file_name = parts[3] if len(parts) > 2 else None
|
||||
|
||||
|
||||
class ImmichMediaSource(MediaSource):
|
||||
"""Provide Immich as media sources."""
|
||||
|
||||
name = "Immich"
|
||||
|
||||
def __init__(self, hass: HomeAssistant, entries: list[ConfigEntry]) -> None:
|
||||
"""Initialize Immich media source."""
|
||||
super().__init__(DOMAIN)
|
||||
self.hass = hass
|
||||
self.entries = entries
|
||||
|
||||
async def async_browse_media(
|
||||
self,
|
||||
item: MediaSourceItem,
|
||||
) -> BrowseMediaSource:
|
||||
"""Return media."""
|
||||
if not self.hass.config_entries.async_loaded_entries(DOMAIN):
|
||||
raise BrowseError("Immich is not configured")
|
||||
return BrowseMediaSource(
|
||||
domain=DOMAIN,
|
||||
identifier=None,
|
||||
media_class=MediaClass.DIRECTORY,
|
||||
media_content_type=MediaClass.IMAGE,
|
||||
title="Immich",
|
||||
can_play=False,
|
||||
can_expand=True,
|
||||
children_media_class=MediaClass.DIRECTORY,
|
||||
children=[
|
||||
*await self._async_build_immich(item),
|
||||
],
|
||||
)
|
||||
|
||||
async def _async_build_immich(
|
||||
self, item: MediaSourceItem
|
||||
) -> list[BrowseMediaSource]:
|
||||
"""Handle browsing different immich instances."""
|
||||
if not item.identifier:
|
||||
return [
|
||||
BrowseMediaSource(
|
||||
domain=DOMAIN,
|
||||
identifier=entry.unique_id,
|
||||
media_class=MediaClass.DIRECTORY,
|
||||
media_content_type=MediaClass.IMAGE,
|
||||
title=entry.title,
|
||||
can_play=False,
|
||||
can_expand=True,
|
||||
)
|
||||
for entry in self.entries
|
||||
]
|
||||
identifier = ImmichMediaSourceIdentifier(item.identifier)
|
||||
entry: ImmichConfigEntry | None = (
|
||||
self.hass.config_entries.async_entry_for_domain_unique_id(
|
||||
DOMAIN, identifier.unique_id
|
||||
)
|
||||
)
|
||||
assert entry
|
||||
immich_api = entry.runtime_data.api
|
||||
|
||||
if identifier.album_id is None:
|
||||
# Get Albums
|
||||
try:
|
||||
albums = await immich_api.albums.async_get_all_albums()
|
||||
except ImmichError:
|
||||
return []
|
||||
|
||||
return [
|
||||
BrowseMediaSource(
|
||||
domain=DOMAIN,
|
||||
identifier=f"{item.identifier}/{album.album_id}",
|
||||
media_class=MediaClass.DIRECTORY,
|
||||
media_content_type=MediaClass.IMAGE,
|
||||
title=album.name,
|
||||
can_play=False,
|
||||
can_expand=True,
|
||||
thumbnail=f"/immich/{identifier.unique_id}/{album.thumbnail_asset_id}/thumb.jpg/thumbnail",
|
||||
)
|
||||
for album in albums
|
||||
]
|
||||
|
||||
# Request items of album
|
||||
try:
|
||||
album_info = await immich_api.albums.async_get_album_info(
|
||||
identifier.album_id
|
||||
)
|
||||
except ImmichError:
|
||||
return []
|
||||
|
||||
return [
|
||||
BrowseMediaSource(
|
||||
domain=DOMAIN,
|
||||
identifier=(
|
||||
f"{identifier.unique_id}/"
|
||||
f"{identifier.album_id}/"
|
||||
f"{asset.asset_id}/"
|
||||
f"{asset.file_name}"
|
||||
),
|
||||
media_class=MediaClass.IMAGE,
|
||||
media_content_type=asset.mime_type,
|
||||
title=asset.file_name,
|
||||
can_play=False,
|
||||
can_expand=False,
|
||||
thumbnail=f"/immich/{identifier.unique_id}/{asset.asset_id}/{asset.file_name}/thumbnail",
|
||||
)
|
||||
for asset in album_info.assets
|
||||
if asset.mime_type.startswith("image/")
|
||||
]
|
||||
|
||||
async def async_resolve_media(self, item: MediaSourceItem) -> PlayMedia:
|
||||
"""Resolve media to a url."""
|
||||
identifier = ImmichMediaSourceIdentifier(item.identifier)
|
||||
if identifier.file_name is None:
|
||||
raise Unresolvable("No file name")
|
||||
mime_type, _ = mimetypes.guess_type(identifier.file_name)
|
||||
if not isinstance(mime_type, str):
|
||||
raise Unresolvable("No file extension")
|
||||
return PlayMedia(
|
||||
(
|
||||
f"/immich/{identifier.unique_id}/{identifier.asset_id}/{identifier.file_name}/fullsize"
|
||||
),
|
||||
mime_type,
|
||||
)
|
||||
|
||||
|
||||
class ImmichMediaView(HomeAssistantView):
|
||||
"""Immich Media Finder View."""
|
||||
|
||||
url = "/immich/{source_dir_id}/{location:.*}"
|
||||
name = "immich"
|
||||
|
||||
def __init__(self, hass: HomeAssistant) -> None:
|
||||
"""Initialize the media view."""
|
||||
self.hass = hass
|
||||
|
||||
async def get(
|
||||
self, request: Request, source_dir_id: str, location: str
|
||||
) -> Response:
|
||||
"""Start a GET request."""
|
||||
if not self.hass.config_entries.async_loaded_entries(DOMAIN):
|
||||
raise HTTPNotFound
|
||||
asset_id, file_name, size = location.split("/")
|
||||
|
||||
mime_type, _ = mimetypes.guess_type(file_name)
|
||||
if not isinstance(mime_type, str):
|
||||
raise HTTPNotFound
|
||||
|
||||
entry: ImmichConfigEntry | None = (
|
||||
self.hass.config_entries.async_entry_for_domain_unique_id(
|
||||
DOMAIN, source_dir_id
|
||||
)
|
||||
)
|
||||
assert entry
|
||||
immich_api = entry.runtime_data.api
|
||||
|
||||
try:
|
||||
image = await immich_api.assets.async_view_asset(asset_id, size)
|
||||
except ImmichError as exc:
|
||||
raise HTTPNotFound from exc
|
||||
return Response(body=image, content_type=mime_type)
|
@ -4,7 +4,7 @@ from collections.abc import AsyncGenerator, Generator
|
||||
from datetime import datetime
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
from aioimmich import ImmichServer, ImmichUsers
|
||||
from aioimmich import ImmichAlbums, ImmichAssests, ImmichServer, ImmichUsers
|
||||
from aioimmich.server.models import (
|
||||
ImmichServerAbout,
|
||||
ImmichServerStatistics,
|
||||
@ -21,6 +21,10 @@ from homeassistant.const import (
|
||||
CONF_SSL,
|
||||
CONF_VERIFY_SSL,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from .const import MOCK_ALBUM_WITH_ASSETS, MOCK_ALBUM_WITHOUT_ASSETS
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
@ -51,6 +55,23 @@ def mock_config_entry() -> MockConfigEntry:
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_immich_albums() -> AsyncMock:
|
||||
"""Mock the Immich server."""
|
||||
mock = AsyncMock(spec=ImmichAlbums)
|
||||
mock.async_get_all_albums.return_value = [MOCK_ALBUM_WITHOUT_ASSETS]
|
||||
mock.async_get_album_info.return_value = MOCK_ALBUM_WITH_ASSETS
|
||||
return mock
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_immich_assets() -> AsyncMock:
|
||||
"""Mock the Immich server."""
|
||||
mock = AsyncMock(spec=ImmichAssests)
|
||||
mock.async_view_asset.return_value = b"xxxx"
|
||||
return mock
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_immich_server() -> AsyncMock:
|
||||
"""Mock the Immich server."""
|
||||
@ -116,7 +137,10 @@ def mock_immich_user() -> AsyncMock:
|
||||
|
||||
@pytest.fixture
|
||||
async def mock_immich(
|
||||
mock_immich_server: AsyncMock, mock_immich_user: AsyncMock
|
||||
mock_immich_albums: AsyncMock,
|
||||
mock_immich_assets: AsyncMock,
|
||||
mock_immich_server: AsyncMock,
|
||||
mock_immich_user: AsyncMock,
|
||||
) -> AsyncGenerator[AsyncMock]:
|
||||
"""Mock the Immich API."""
|
||||
with (
|
||||
@ -124,6 +148,8 @@ async def mock_immich(
|
||||
patch("homeassistant.components.immich.config_flow.Immich", new=mock_immich),
|
||||
):
|
||||
client = mock_immich.return_value
|
||||
client.albums = mock_immich_albums
|
||||
client.assets = mock_immich_assets
|
||||
client.server = mock_immich_server
|
||||
client.users = mock_immich_user
|
||||
yield client
|
||||
@ -134,3 +160,9 @@ async def mock_non_admin_immich(mock_immich: AsyncMock) -> AsyncMock:
|
||||
"""Mock the Immich API."""
|
||||
mock_immich.users.async_get_my_user.return_value.is_admin = False
|
||||
return mock_immich
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def setup_media_source(hass: HomeAssistant) -> None:
|
||||
"""Set up media source."""
|
||||
assert await async_setup_component(hass, "media_source", {})
|
||||
|
@ -1,5 +1,8 @@
|
||||
"""Constants for the Immich integration tests."""
|
||||
|
||||
from aioimmich.albums.models import ImmichAlbum
|
||||
from aioimmich.assets.models import ImmichAsset
|
||||
|
||||
from homeassistant.const import (
|
||||
CONF_API_KEY,
|
||||
CONF_HOST,
|
||||
@ -22,3 +25,21 @@ MOCK_CONFIG_ENTRY_DATA = {
|
||||
CONF_SSL: False,
|
||||
CONF_VERIFY_SSL: False,
|
||||
}
|
||||
|
||||
MOCK_ALBUM_WITHOUT_ASSETS = ImmichAlbum(
|
||||
"721e1a4b-aa12-441e-8d3b-5ac7ab283bb6",
|
||||
"My Album",
|
||||
"This is my first great album",
|
||||
"0d03a7ad-ddc7-45a6-adee-68d322a6d2f5",
|
||||
1,
|
||||
[],
|
||||
)
|
||||
|
||||
MOCK_ALBUM_WITH_ASSETS = ImmichAlbum(
|
||||
"721e1a4b-aa12-441e-8d3b-5ac7ab283bb6",
|
||||
"My Album",
|
||||
"This is my first great album",
|
||||
"0d03a7ad-ddc7-45a6-adee-68d322a6d2f5",
|
||||
1,
|
||||
[ImmichAsset("2e94c203-50aa-4ad2-8e29-56dd74e0eff4", "filename.jpg", "image/jpeg")],
|
||||
)
|
||||
|
336
tests/components/immich/test_media_source.py
Normal file
336
tests/components/immich/test_media_source.py
Normal file
@ -0,0 +1,336 @@
|
||||
"""Tests for Immich media source."""
|
||||
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from aiohttp import web
|
||||
from aioimmich.exceptions import ImmichError
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.immich.const import DOMAIN
|
||||
from homeassistant.components.immich.media_source import (
|
||||
ImmichMediaSource,
|
||||
ImmichMediaView,
|
||||
async_get_media_source,
|
||||
)
|
||||
from homeassistant.components.media_player import MediaClass
|
||||
from homeassistant.components.media_source import (
|
||||
BrowseError,
|
||||
BrowseMedia,
|
||||
MediaSourceItem,
|
||||
Unresolvable,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.setup import async_setup_component
|
||||
from homeassistant.util.aiohttp import MockRequest
|
||||
|
||||
from . import setup_integration
|
||||
from .const import MOCK_ALBUM_WITHOUT_ASSETS
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
async def test_get_media_source(hass: HomeAssistant) -> None:
|
||||
"""Test the async_get_media_source."""
|
||||
assert await async_setup_component(hass, "media_source", {})
|
||||
|
||||
source = await async_get_media_source(hass)
|
||||
assert isinstance(source, ImmichMediaSource)
|
||||
assert source.domain == DOMAIN
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("identifier", "exception_msg"),
|
||||
[
|
||||
("unique_id", "No file name"),
|
||||
("unique_id/album_id", "No file name"),
|
||||
("unique_id/album_id/asset_id/filename", "No file extension"),
|
||||
],
|
||||
)
|
||||
async def test_resolve_media_bad_identifier(
|
||||
hass: HomeAssistant, identifier: str, exception_msg: str
|
||||
) -> None:
|
||||
"""Test resolve_media with bad identifiers."""
|
||||
assert await async_setup_component(hass, "media_source", {})
|
||||
|
||||
source = await async_get_media_source(hass)
|
||||
item = MediaSourceItem(hass, DOMAIN, identifier, None)
|
||||
with pytest.raises(Unresolvable, match=exception_msg):
|
||||
await source.async_resolve_media(item)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("identifier", "url", "mime_type"),
|
||||
[
|
||||
(
|
||||
"unique_id/album_id/asset_id/filename.jpg",
|
||||
"/immich/unique_id/asset_id/filename.jpg/fullsize",
|
||||
"image/jpeg",
|
||||
),
|
||||
(
|
||||
"unique_id/album_id/asset_id/filename.png",
|
||||
"/immich/unique_id/asset_id/filename.png/fullsize",
|
||||
"image/png",
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_resolve_media_success(
|
||||
hass: HomeAssistant, identifier: str, url: str, mime_type: str
|
||||
) -> None:
|
||||
"""Test successful resolving an item."""
|
||||
assert await async_setup_component(hass, "media_source", {})
|
||||
|
||||
source = await async_get_media_source(hass)
|
||||
item = MediaSourceItem(hass, DOMAIN, identifier, None)
|
||||
result = await source.async_resolve_media(item)
|
||||
|
||||
assert result.url == url
|
||||
assert result.mime_type == mime_type
|
||||
|
||||
|
||||
async def test_browse_media_unconfigured(hass: HomeAssistant) -> None:
|
||||
"""Test browse_media without any devices being configured."""
|
||||
assert await async_setup_component(hass, "media_source", {})
|
||||
|
||||
source = await async_get_media_source(hass)
|
||||
item = MediaSourceItem(
|
||||
hass, DOMAIN, "unique_id/album_id/asset_id/filename.png", None
|
||||
)
|
||||
with pytest.raises(BrowseError, match="Immich is not configured"):
|
||||
await source.async_browse_media(item)
|
||||
|
||||
|
||||
async def test_browse_media_album_error(
|
||||
hass: HomeAssistant,
|
||||
mock_immich: Mock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test browse_media with unknown album."""
|
||||
assert await async_setup_component(hass, "media_source", {})
|
||||
|
||||
with patch("homeassistant.components.immich.PLATFORMS", []):
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
# exception in get_albums()
|
||||
mock_immich.albums.async_get_all_albums.side_effect = ImmichError(
|
||||
{
|
||||
"message": "Not found or no album.read access",
|
||||
"error": "Bad Request",
|
||||
"statusCode": 400,
|
||||
"correlationId": "e0hlizyl",
|
||||
}
|
||||
)
|
||||
|
||||
source = await async_get_media_source(hass)
|
||||
|
||||
item = MediaSourceItem(hass, DOMAIN, mock_config_entry.unique_id, None)
|
||||
result = await source.async_browse_media(item)
|
||||
|
||||
assert result
|
||||
assert result.identifier is None
|
||||
assert len(result.children) == 0
|
||||
|
||||
|
||||
async def test_browse_media_get_root(
|
||||
hass: HomeAssistant,
|
||||
mock_immich: Mock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test browse_media returning root media sources."""
|
||||
assert await async_setup_component(hass, "media_source", {})
|
||||
|
||||
with patch("homeassistant.components.immich.PLATFORMS", []):
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
source = await async_get_media_source(hass)
|
||||
item = MediaSourceItem(hass, DOMAIN, "", None)
|
||||
result = await source.async_browse_media(item)
|
||||
|
||||
assert result
|
||||
assert len(result.children) == 1
|
||||
media_file = result.children[0]
|
||||
assert isinstance(media_file, BrowseMedia)
|
||||
assert media_file.title == "Someone"
|
||||
assert media_file.media_content_id == (
|
||||
"media-source://immich/e7ef5713-9dab-4bd4-b899-715b0ca4379e"
|
||||
)
|
||||
|
||||
|
||||
async def test_browse_media_get_albums(
|
||||
hass: HomeAssistant,
|
||||
mock_immich: Mock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test browse_media returning albums."""
|
||||
assert await async_setup_component(hass, "media_source", {})
|
||||
|
||||
with patch("homeassistant.components.immich.PLATFORMS", []):
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
source = await async_get_media_source(hass)
|
||||
item = MediaSourceItem(hass, DOMAIN, "e7ef5713-9dab-4bd4-b899-715b0ca4379e", None)
|
||||
result = await source.async_browse_media(item)
|
||||
|
||||
assert result
|
||||
assert len(result.children) == 1
|
||||
media_file = result.children[0]
|
||||
assert isinstance(media_file, BrowseMedia)
|
||||
assert media_file.title == "My Album"
|
||||
assert media_file.media_content_id == (
|
||||
"media-source://immich/"
|
||||
"e7ef5713-9dab-4bd4-b899-715b0ca4379e/"
|
||||
"721e1a4b-aa12-441e-8d3b-5ac7ab283bb6"
|
||||
)
|
||||
|
||||
|
||||
async def test_browse_media_get_items_error(
|
||||
hass: HomeAssistant,
|
||||
mock_immich: Mock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test browse_media returning albums."""
|
||||
assert await async_setup_component(hass, "media_source", {})
|
||||
|
||||
with patch("homeassistant.components.immich.PLATFORMS", []):
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
source = await async_get_media_source(hass)
|
||||
|
||||
# unknown album
|
||||
mock_immich.albums.async_get_album_info.return_value = MOCK_ALBUM_WITHOUT_ASSETS
|
||||
item = MediaSourceItem(
|
||||
hass,
|
||||
DOMAIN,
|
||||
"e7ef5713-9dab-4bd4-b899-715b0ca4379e/721e1a4b-aa12-441e-8d3b-5ac7ab283bb6",
|
||||
None,
|
||||
)
|
||||
result = await source.async_browse_media(item)
|
||||
|
||||
assert result
|
||||
assert result.identifier is None
|
||||
assert len(result.children) == 0
|
||||
|
||||
# exception in async_get_album_info()
|
||||
mock_immich.albums.async_get_album_info.side_effect = ImmichError(
|
||||
{
|
||||
"message": "Not found or no album.read access",
|
||||
"error": "Bad Request",
|
||||
"statusCode": 400,
|
||||
"correlationId": "e0hlizyl",
|
||||
}
|
||||
)
|
||||
item = MediaSourceItem(
|
||||
hass,
|
||||
DOMAIN,
|
||||
"e7ef5713-9dab-4bd4-b899-715b0ca4379e/721e1a4b-aa12-441e-8d3b-5ac7ab283bb6",
|
||||
None,
|
||||
)
|
||||
result = await source.async_browse_media(item)
|
||||
|
||||
assert result
|
||||
assert result.identifier is None
|
||||
assert len(result.children) == 0
|
||||
|
||||
|
||||
async def test_browse_media_get_items(
|
||||
hass: HomeAssistant,
|
||||
mock_immich: Mock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test browse_media returning albums."""
|
||||
assert await async_setup_component(hass, "media_source", {})
|
||||
|
||||
with patch("homeassistant.components.immich.PLATFORMS", []):
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
source = await async_get_media_source(hass)
|
||||
|
||||
item = MediaSourceItem(
|
||||
hass,
|
||||
DOMAIN,
|
||||
"e7ef5713-9dab-4bd4-b899-715b0ca4379e/721e1a4b-aa12-441e-8d3b-5ac7ab283bb6",
|
||||
None,
|
||||
)
|
||||
result = await source.async_browse_media(item)
|
||||
|
||||
assert result
|
||||
assert len(result.children) == 1
|
||||
media_file = result.children[0]
|
||||
assert isinstance(media_file, BrowseMedia)
|
||||
assert media_file.identifier == (
|
||||
"e7ef5713-9dab-4bd4-b899-715b0ca4379e/"
|
||||
"721e1a4b-aa12-441e-8d3b-5ac7ab283bb6/"
|
||||
"2e94c203-50aa-4ad2-8e29-56dd74e0eff4/filename.jpg"
|
||||
)
|
||||
assert media_file.title == "filename.jpg"
|
||||
assert media_file.media_class == MediaClass.IMAGE
|
||||
assert media_file.media_content_type == "image/jpeg"
|
||||
assert media_file.can_play is False
|
||||
assert not media_file.can_expand
|
||||
assert media_file.thumbnail == (
|
||||
"/immich/e7ef5713-9dab-4bd4-b899-715b0ca4379e/"
|
||||
"2e94c203-50aa-4ad2-8e29-56dd74e0eff4/filename.jpg/thumbnail"
|
||||
)
|
||||
|
||||
|
||||
async def test_media_view(
|
||||
hass: HomeAssistant,
|
||||
tmp_path: Path,
|
||||
mock_immich: Mock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test SynologyDsmMediaView returning albums."""
|
||||
view = ImmichMediaView(hass)
|
||||
request = MockRequest(b"", DOMAIN)
|
||||
|
||||
# immich noch configured
|
||||
with pytest.raises(web.HTTPNotFound):
|
||||
await view.get(request, "", "")
|
||||
|
||||
# setup immich
|
||||
assert await async_setup_component(hass, "media_source", {})
|
||||
with patch("homeassistant.components.immich.PLATFORMS", []):
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
# wrong url (without file extension)
|
||||
with pytest.raises(web.HTTPNotFound):
|
||||
await view.get(
|
||||
request,
|
||||
"e7ef5713-9dab-4bd4-b899-715b0ca4379e",
|
||||
"2e94c203-50aa-4ad2-8e29-56dd74e0eff4/filename/thumbnail",
|
||||
)
|
||||
|
||||
# exception in async_view_asset()
|
||||
mock_immich.assets.async_view_asset.side_effect = ImmichError(
|
||||
{
|
||||
"message": "Not found or no asset.read access",
|
||||
"error": "Bad Request",
|
||||
"statusCode": 400,
|
||||
"correlationId": "e0hlizyl",
|
||||
}
|
||||
)
|
||||
with pytest.raises(web.HTTPNotFound):
|
||||
await view.get(
|
||||
request,
|
||||
"e7ef5713-9dab-4bd4-b899-715b0ca4379e",
|
||||
"2e94c203-50aa-4ad2-8e29-56dd74e0eff4/filename.jpg/thumbnail",
|
||||
)
|
||||
|
||||
# success
|
||||
mock_immich.assets.async_view_asset.side_effect = None
|
||||
mock_immich.assets.async_view_asset.return_value = b"xxxx"
|
||||
with patch.object(tempfile, "tempdir", tmp_path):
|
||||
result = await view.get(
|
||||
request,
|
||||
"e7ef5713-9dab-4bd4-b899-715b0ca4379e",
|
||||
"2e94c203-50aa-4ad2-8e29-56dd74e0eff4/filename.jpg/thumbnail",
|
||||
)
|
||||
assert isinstance(result, web.Response)
|
||||
with patch.object(tempfile, "tempdir", tmp_path):
|
||||
result = await view.get(
|
||||
request,
|
||||
"e7ef5713-9dab-4bd4-b899-715b0ca4379e",
|
||||
"2e94c203-50aa-4ad2-8e29-56dd74e0eff4/filename.jpg/fullsize",
|
||||
)
|
||||
assert isinstance(result, web.Response)
|
Loading…
x
Reference in New Issue
Block a user