mirror of
https://github.com/home-assistant/core.git
synced 2025-07-30 00:27:19 +00:00
Add people and tags collections to Immich media source (#149340)
This commit is contained in:
parent
cf05f1046d
commit
596f6cd216
@ -5,6 +5,7 @@ from __future__ import annotations
|
|||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
|
|
||||||
from aiohttp.web import HTTPNotFound, Request, Response, StreamResponse
|
from aiohttp.web import HTTPNotFound, Request, Response, StreamResponse
|
||||||
|
from aioimmich.assets.models import ImmichAsset
|
||||||
from aioimmich.exceptions import ImmichError
|
from aioimmich.exceptions import ImmichError
|
||||||
|
|
||||||
from homeassistant.components.http import HomeAssistantView
|
from homeassistant.components.http import HomeAssistantView
|
||||||
@ -83,6 +84,10 @@ class ImmichMediaSource(MediaSource):
|
|||||||
self, item: MediaSourceItem, entries: list[ConfigEntry]
|
self, item: MediaSourceItem, entries: list[ConfigEntry]
|
||||||
) -> list[BrowseMediaSource]:
|
) -> list[BrowseMediaSource]:
|
||||||
"""Handle browsing different immich instances."""
|
"""Handle browsing different immich instances."""
|
||||||
|
|
||||||
|
# --------------------------------------------------------
|
||||||
|
# root level, render immich instances
|
||||||
|
# --------------------------------------------------------
|
||||||
if not item.identifier:
|
if not item.identifier:
|
||||||
LOGGER.debug("Render all Immich instances")
|
LOGGER.debug("Render all Immich instances")
|
||||||
return [
|
return [
|
||||||
@ -97,6 +102,10 @@ class ImmichMediaSource(MediaSource):
|
|||||||
)
|
)
|
||||||
for entry in entries
|
for entry in entries
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# --------------------------------------------------------
|
||||||
|
# 1st level, render collections overview
|
||||||
|
# --------------------------------------------------------
|
||||||
identifier = ImmichMediaSourceIdentifier(item.identifier)
|
identifier = ImmichMediaSourceIdentifier(item.identifier)
|
||||||
entry: ImmichConfigEntry | None = (
|
entry: ImmichConfigEntry | None = (
|
||||||
self.hass.config_entries.async_entry_for_domain_unique_id(
|
self.hass.config_entries.async_entry_for_domain_unique_id(
|
||||||
@ -111,50 +120,127 @@ class ImmichMediaSource(MediaSource):
|
|||||||
return [
|
return [
|
||||||
BrowseMediaSource(
|
BrowseMediaSource(
|
||||||
domain=DOMAIN,
|
domain=DOMAIN,
|
||||||
identifier=f"{identifier.unique_id}|albums",
|
identifier=f"{identifier.unique_id}|{collection}",
|
||||||
media_class=MediaClass.DIRECTORY,
|
media_class=MediaClass.DIRECTORY,
|
||||||
media_content_type=MediaClass.IMAGE,
|
media_content_type=MediaClass.IMAGE,
|
||||||
title="albums",
|
title=collection,
|
||||||
can_play=False,
|
can_play=False,
|
||||||
can_expand=True,
|
can_expand=True,
|
||||||
)
|
)
|
||||||
|
for collection in ("albums", "people", "tags")
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# --------------------------------------------------------
|
||||||
|
# 2nd level, render collection
|
||||||
|
# --------------------------------------------------------
|
||||||
if identifier.collection_id is None:
|
if identifier.collection_id is None:
|
||||||
LOGGER.debug("Render all albums for %s", entry.title)
|
if identifier.collection == "albums":
|
||||||
|
LOGGER.debug("Render all albums for %s", entry.title)
|
||||||
|
try:
|
||||||
|
albums = await immich_api.albums.async_get_all_albums()
|
||||||
|
except ImmichError:
|
||||||
|
return []
|
||||||
|
|
||||||
|
return [
|
||||||
|
BrowseMediaSource(
|
||||||
|
domain=DOMAIN,
|
||||||
|
identifier=f"{identifier.unique_id}|albums|{album.album_id}",
|
||||||
|
media_class=MediaClass.DIRECTORY,
|
||||||
|
media_content_type=MediaClass.IMAGE,
|
||||||
|
title=album.album_name,
|
||||||
|
can_play=False,
|
||||||
|
can_expand=True,
|
||||||
|
thumbnail=f"/immich/{identifier.unique_id}/{album.album_thumbnail_asset_id}/thumbnail/image/jpg",
|
||||||
|
)
|
||||||
|
for album in albums
|
||||||
|
]
|
||||||
|
|
||||||
|
if identifier.collection == "tags":
|
||||||
|
LOGGER.debug("Render all tags for %s", entry.title)
|
||||||
|
try:
|
||||||
|
tags = await immich_api.tags.async_get_all_tags()
|
||||||
|
except ImmichError:
|
||||||
|
return []
|
||||||
|
|
||||||
|
return [
|
||||||
|
BrowseMediaSource(
|
||||||
|
domain=DOMAIN,
|
||||||
|
identifier=f"{identifier.unique_id}|tags|{tag.tag_id}",
|
||||||
|
media_class=MediaClass.DIRECTORY,
|
||||||
|
media_content_type=MediaClass.IMAGE,
|
||||||
|
title=tag.name,
|
||||||
|
can_play=False,
|
||||||
|
can_expand=True,
|
||||||
|
)
|
||||||
|
for tag in tags
|
||||||
|
]
|
||||||
|
|
||||||
|
if identifier.collection == "people":
|
||||||
|
LOGGER.debug("Render all people for %s", entry.title)
|
||||||
|
try:
|
||||||
|
people = await immich_api.people.async_get_all_people()
|
||||||
|
except ImmichError:
|
||||||
|
return []
|
||||||
|
|
||||||
|
return [
|
||||||
|
BrowseMediaSource(
|
||||||
|
domain=DOMAIN,
|
||||||
|
identifier=f"{identifier.unique_id}|people|{person.person_id}",
|
||||||
|
media_class=MediaClass.DIRECTORY,
|
||||||
|
media_content_type=MediaClass.IMAGE,
|
||||||
|
title=person.name,
|
||||||
|
can_play=False,
|
||||||
|
can_expand=True,
|
||||||
|
thumbnail=f"/immich/{identifier.unique_id}/{person.person_id}/person/image/jpg",
|
||||||
|
)
|
||||||
|
for person in people
|
||||||
|
]
|
||||||
|
|
||||||
|
# --------------------------------------------------------
|
||||||
|
# final level, render assets
|
||||||
|
# --------------------------------------------------------
|
||||||
|
assert identifier.collection_id is not None
|
||||||
|
assets: list[ImmichAsset] = []
|
||||||
|
if identifier.collection == "albums":
|
||||||
|
LOGGER.debug(
|
||||||
|
"Render all assets of album %s for %s",
|
||||||
|
identifier.collection_id,
|
||||||
|
entry.title,
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
albums = await immich_api.albums.async_get_all_albums()
|
album_info = await immich_api.albums.async_get_album_info(
|
||||||
|
identifier.collection_id
|
||||||
|
)
|
||||||
|
assets = album_info.assets
|
||||||
except ImmichError:
|
except ImmichError:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
return [
|
elif identifier.collection == "tags":
|
||||||
BrowseMediaSource(
|
LOGGER.debug(
|
||||||
domain=DOMAIN,
|
"Render all assets with tag %s",
|
||||||
identifier=f"{identifier.unique_id}|albums|{album.album_id}",
|
identifier.collection_id,
|
||||||
media_class=MediaClass.DIRECTORY,
|
|
||||||
media_content_type=MediaClass.IMAGE,
|
|
||||||
title=album.album_name,
|
|
||||||
can_play=False,
|
|
||||||
can_expand=True,
|
|
||||||
thumbnail=f"/immich/{identifier.unique_id}/{album.album_thumbnail_asset_id}/thumbnail/image/jpg",
|
|
||||||
)
|
|
||||||
for album in albums
|
|
||||||
]
|
|
||||||
|
|
||||||
LOGGER.debug(
|
|
||||||
"Render all assets of album %s for %s",
|
|
||||||
identifier.collection_id,
|
|
||||||
entry.title,
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
album_info = await immich_api.albums.async_get_album_info(
|
|
||||||
identifier.collection_id
|
|
||||||
)
|
)
|
||||||
except ImmichError:
|
try:
|
||||||
return []
|
assets = await immich_api.search.async_get_all_by_tag_ids(
|
||||||
|
[identifier.collection_id]
|
||||||
|
)
|
||||||
|
except ImmichError:
|
||||||
|
return []
|
||||||
|
|
||||||
|
elif identifier.collection == "people":
|
||||||
|
LOGGER.debug(
|
||||||
|
"Render all assets for person %s",
|
||||||
|
identifier.collection_id,
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
assets = await immich_api.search.async_get_all_by_person_ids(
|
||||||
|
[identifier.collection_id]
|
||||||
|
)
|
||||||
|
except ImmichError:
|
||||||
|
return []
|
||||||
|
|
||||||
ret: list[BrowseMediaSource] = []
|
ret: list[BrowseMediaSource] = []
|
||||||
for asset in album_info.assets:
|
for asset in assets:
|
||||||
if not (mime_type := asset.original_mime_type) or not mime_type.startswith(
|
if not (mime_type := asset.original_mime_type) or not mime_type.startswith(
|
||||||
("image/", "video/")
|
("image/", "video/")
|
||||||
):
|
):
|
||||||
@ -173,7 +259,8 @@ class ImmichMediaSource(MediaSource):
|
|||||||
BrowseMediaSource(
|
BrowseMediaSource(
|
||||||
domain=DOMAIN,
|
domain=DOMAIN,
|
||||||
identifier=(
|
identifier=(
|
||||||
f"{identifier.unique_id}|albums|"
|
f"{identifier.unique_id}|"
|
||||||
|
f"{identifier.collection}|"
|
||||||
f"{identifier.collection_id}|"
|
f"{identifier.collection_id}|"
|
||||||
f"{asset.asset_id}|"
|
f"{asset.asset_id}|"
|
||||||
f"{asset.original_file_name}|"
|
f"{asset.original_file_name}|"
|
||||||
@ -257,7 +344,10 @@ class ImmichMediaView(HomeAssistantView):
|
|||||||
|
|
||||||
# web response for images
|
# web response for images
|
||||||
try:
|
try:
|
||||||
image = await immich_api.assets.async_view_asset(asset_id, size)
|
if size == "person":
|
||||||
|
image = await immich_api.people.async_get_person_thumbnail(asset_id)
|
||||||
|
else:
|
||||||
|
image = await immich_api.assets.async_view_asset(asset_id, size)
|
||||||
except ImmichError as exc:
|
except ImmichError as exc:
|
||||||
raise HTTPNotFound from exc
|
raise HTTPNotFound from exc
|
||||||
return Response(body=image, content_type=f"{mime_type_base}/{mime_type_format}")
|
return Response(body=image, content_type=f"{mime_type_base}/{mime_type_format}")
|
||||||
|
@ -4,15 +4,25 @@ from collections.abc import AsyncGenerator, Generator
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from unittest.mock import AsyncMock, MagicMock, patch
|
from unittest.mock import AsyncMock, MagicMock, patch
|
||||||
|
|
||||||
from aioimmich import ImmichAlbums, ImmichAssests, ImmichServer, ImmichUsers
|
from aioimmich import (
|
||||||
|
ImmichAlbums,
|
||||||
|
ImmichAssests,
|
||||||
|
ImmichPeople,
|
||||||
|
ImmichSearch,
|
||||||
|
ImmichServer,
|
||||||
|
ImmichTags,
|
||||||
|
ImmichUsers,
|
||||||
|
)
|
||||||
from aioimmich.albums.models import ImmichAddAssetsToAlbumResponse
|
from aioimmich.albums.models import ImmichAddAssetsToAlbumResponse
|
||||||
from aioimmich.assets.models import ImmichAssetUploadResponse
|
from aioimmich.assets.models import ImmichAssetUploadResponse
|
||||||
|
from aioimmich.people.models import ImmichPerson
|
||||||
from aioimmich.server.models import (
|
from aioimmich.server.models import (
|
||||||
ImmichServerAbout,
|
ImmichServerAbout,
|
||||||
ImmichServerStatistics,
|
ImmichServerStatistics,
|
||||||
ImmichServerStorage,
|
ImmichServerStorage,
|
||||||
ImmichServerVersionCheck,
|
ImmichServerVersionCheck,
|
||||||
)
|
)
|
||||||
|
from aioimmich.tags.models import ImmichTag
|
||||||
from aioimmich.users.models import ImmichUserObject
|
from aioimmich.users.models import ImmichUserObject
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@ -29,7 +39,12 @@ from homeassistant.core import HomeAssistant
|
|||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
from homeassistant.util.aiohttp import MockStreamReaderChunked
|
from homeassistant.util.aiohttp import MockStreamReaderChunked
|
||||||
|
|
||||||
from .const import MOCK_ALBUM_WITH_ASSETS, MOCK_ALBUM_WITHOUT_ASSETS
|
from .const import (
|
||||||
|
MOCK_ALBUM_WITH_ASSETS,
|
||||||
|
MOCK_ALBUM_WITHOUT_ASSETS,
|
||||||
|
MOCK_PEOPLE_ASSETS,
|
||||||
|
MOCK_TAGS_ASSETS,
|
||||||
|
)
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
@ -87,6 +102,58 @@ def mock_immich_assets() -> AsyncMock:
|
|||||||
return mock
|
return mock
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_immich_people() -> AsyncMock:
|
||||||
|
"""Mock the Immich server."""
|
||||||
|
mock = AsyncMock(spec=ImmichPeople)
|
||||||
|
mock.async_get_all_people.return_value = [
|
||||||
|
ImmichPerson.from_dict(
|
||||||
|
{
|
||||||
|
"id": "6176838a-ac5a-4d1f-9a35-91c591d962d8",
|
||||||
|
"name": "Me",
|
||||||
|
"birthDate": None,
|
||||||
|
"thumbnailPath": "upload/thumbs/e7ef5713-9dab-4bd4-b899-715b0ca4379e/61/76/6176838a-ac5a-4d1f-9a35-91c591d962d8.jpeg",
|
||||||
|
"isHidden": False,
|
||||||
|
"isFavorite": False,
|
||||||
|
"updatedAt": "2025-05-11T11:07:41.651Z",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
ImmichPerson.from_dict(
|
||||||
|
{
|
||||||
|
"id": "3e66aa4a-a4a8-41a4-86fe-2ae5e490078f",
|
||||||
|
"name": "I",
|
||||||
|
"birthDate": None,
|
||||||
|
"thumbnailPath": "upload/thumbs/e7ef5713-9dab-4bd4-b899-715b0ca4379e/3e/66/3e66aa4a-a4a8-41a4-86fe-2ae5e490078f.jpeg",
|
||||||
|
"isHidden": False,
|
||||||
|
"isFavorite": False,
|
||||||
|
"updatedAt": "2025-05-19T22:10:21.953Z",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
ImmichPerson.from_dict(
|
||||||
|
{
|
||||||
|
"id": "a3c83297-684a-4576-82dc-b07432e8a18f",
|
||||||
|
"name": "Myself",
|
||||||
|
"birthDate": None,
|
||||||
|
"thumbnailPath": "upload/thumbs/e7ef5713-9dab-4bd4-b899-715b0ca4379e/a3/c8/a3c83297-684a-4576-82dc-b07432e8a18f.jpeg",
|
||||||
|
"isHidden": False,
|
||||||
|
"isFavorite": False,
|
||||||
|
"updatedAt": "2025-05-12T21:07:04.044Z",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
]
|
||||||
|
mock.async_get_person_thumbnail.return_value = b"yyyy"
|
||||||
|
return mock
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_immich_search() -> AsyncMock:
|
||||||
|
"""Mock the Immich server."""
|
||||||
|
mock = AsyncMock(spec=ImmichSearch)
|
||||||
|
mock.async_get_all_by_person_ids.return_value = MOCK_PEOPLE_ASSETS
|
||||||
|
mock.async_get_all_by_tag_ids.return_value = MOCK_TAGS_ASSETS
|
||||||
|
return mock
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_immich_server() -> AsyncMock:
|
def mock_immich_server() -> AsyncMock:
|
||||||
"""Mock the Immich server."""
|
"""Mock the Immich server."""
|
||||||
@ -153,6 +220,33 @@ def mock_immich_server() -> AsyncMock:
|
|||||||
return mock
|
return mock
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_immich_tags() -> AsyncMock:
|
||||||
|
"""Mock the Immich server."""
|
||||||
|
mock = AsyncMock(spec=ImmichTags)
|
||||||
|
mock.async_get_all_tags.return_value = [
|
||||||
|
ImmichTag.from_dict(
|
||||||
|
{
|
||||||
|
"id": "67301cb8-cb73-4e8a-99e9-475cb3f7e7b5",
|
||||||
|
"name": "Halloween",
|
||||||
|
"value": "Halloween",
|
||||||
|
"createdAt": "2025-05-12T20:00:45.220Z",
|
||||||
|
"updatedAt": "2025-05-12T20:00:47.224Z",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ImmichTag.from_dict(
|
||||||
|
{
|
||||||
|
"id": "69bd487f-dc1e-4420-94c6-656f0515773d",
|
||||||
|
"name": "Holidays",
|
||||||
|
"value": "Holidays",
|
||||||
|
"createdAt": "2025-05-12T20:00:49.967Z",
|
||||||
|
"updatedAt": "2025-05-12T20:00:55.575Z",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
return mock
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_immich_user() -> AsyncMock:
|
def mock_immich_user() -> AsyncMock:
|
||||||
"""Mock the Immich server."""
|
"""Mock the Immich server."""
|
||||||
@ -185,7 +279,10 @@ def mock_immich_user() -> AsyncMock:
|
|||||||
async def mock_immich(
|
async def mock_immich(
|
||||||
mock_immich_albums: AsyncMock,
|
mock_immich_albums: AsyncMock,
|
||||||
mock_immich_assets: AsyncMock,
|
mock_immich_assets: AsyncMock,
|
||||||
|
mock_immich_people: AsyncMock,
|
||||||
|
mock_immich_search: AsyncMock,
|
||||||
mock_immich_server: AsyncMock,
|
mock_immich_server: AsyncMock,
|
||||||
|
mock_immich_tags: AsyncMock,
|
||||||
mock_immich_user: AsyncMock,
|
mock_immich_user: AsyncMock,
|
||||||
) -> AsyncGenerator[AsyncMock]:
|
) -> AsyncGenerator[AsyncMock]:
|
||||||
"""Mock the Immich API."""
|
"""Mock the Immich API."""
|
||||||
@ -196,7 +293,10 @@ async def mock_immich(
|
|||||||
client = mock_immich.return_value
|
client = mock_immich.return_value
|
||||||
client.albums = mock_immich_albums
|
client.albums = mock_immich_albums
|
||||||
client.assets = mock_immich_assets
|
client.assets = mock_immich_assets
|
||||||
|
client.people = mock_immich_people
|
||||||
|
client.search = mock_immich_search
|
||||||
client.server = mock_immich_server
|
client.server = mock_immich_server
|
||||||
|
client.tags = mock_immich_tags
|
||||||
client.users = mock_immich_user
|
client.users = mock_immich_user
|
||||||
yield client
|
yield client
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
"""Constants for the Immich integration tests."""
|
"""Constants for the Immich integration tests."""
|
||||||
|
|
||||||
from aioimmich.albums.models import ImmichAlbum
|
from aioimmich.albums.models import ImmichAlbum
|
||||||
|
from aioimmich.assets.models import ImmichAsset
|
||||||
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_API_KEY,
|
CONF_API_KEY,
|
||||||
@ -113,3 +114,131 @@ MOCK_ALBUM_WITH_ASSETS = ImmichAlbum.from_dict(
|
|||||||
],
|
],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
MOCK_PEOPLE_ASSETS = [
|
||||||
|
ImmichAsset.from_dict(
|
||||||
|
{
|
||||||
|
"id": "2242eda3-94c2-49ee-86d4-e9e071b6fbf4",
|
||||||
|
"deviceAssetId": "1000092019",
|
||||||
|
"ownerId": "e7ef5713-9dab-4bd4-b899-715b0ca4379e",
|
||||||
|
"deviceId": "5933dd9394fc6bf0493a26b4e38acca1076f30ab246442976d2917f1d57d99a1",
|
||||||
|
"libraryId": None,
|
||||||
|
"type": "IMAGE",
|
||||||
|
"originalPath": "/usr/src/app/upload/upload/e7ef5713-9dab-4bd4-b899-715b0ca4379e/8e/a3/8ea31ee8-49c3-4be9-aa9d-b8ef26ba0abe.jpg",
|
||||||
|
"originalFileName": "20250714_201122.jpg",
|
||||||
|
"originalMimeType": "image/jpeg",
|
||||||
|
"thumbhash": "XRgGDILGeMlPaJaMWIeagJcJSA==",
|
||||||
|
"fileCreatedAt": "2025-07-14T18:11:22.648Z",
|
||||||
|
"fileModifiedAt": "2025-07-14T18:11:25.000Z",
|
||||||
|
"localDateTime": "2025-07-14T20:11:22.648Z",
|
||||||
|
"updatedAt": "2025-07-26T10:16:39.131Z",
|
||||||
|
"isFavorite": False,
|
||||||
|
"isArchived": False,
|
||||||
|
"isTrashed": False,
|
||||||
|
"visibility": "timeline",
|
||||||
|
"duration": "0:00:00.00000",
|
||||||
|
"livePhotoVideoId": None,
|
||||||
|
"people": [],
|
||||||
|
"unassignedFaces": [],
|
||||||
|
"checksum": "GcBJkDFoXx9d/wyl1xH89R4/NBQ=",
|
||||||
|
"isOffline": False,
|
||||||
|
"hasMetadata": True,
|
||||||
|
"duplicateId": None,
|
||||||
|
"resized": True,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
ImmichAsset.from_dict(
|
||||||
|
{
|
||||||
|
"id": "046ac0d9-8acd-44d8-953f-ecb3c786358a",
|
||||||
|
"deviceAssetId": "1000092018",
|
||||||
|
"ownerId": "e7ef5713-9dab-4bd4-b899-715b0ca4379e",
|
||||||
|
"deviceId": "5933dd9394fc6bf0493a26b4e38acca1076f30ab246442976d2917f1d57d99a1",
|
||||||
|
"libraryId": None,
|
||||||
|
"type": "IMAGE",
|
||||||
|
"originalPath": "/usr/src/app/upload/upload/e7ef5713-9dab-4bd4-b899-715b0ca4379e/f5/b4/f5b4b200-47dd-45e8-98a4-4128df3f9189.jpg",
|
||||||
|
"originalFileName": "20250714_201121.jpg",
|
||||||
|
"originalMimeType": "image/jpeg",
|
||||||
|
"thumbhash": "XRgGDILHeMlPeJaMSJmKgJcIWQ==",
|
||||||
|
"fileCreatedAt": "2025-07-14T18:11:21.582Z",
|
||||||
|
"fileModifiedAt": "2025-07-14T18:11:24.000Z",
|
||||||
|
"localDateTime": "2025-07-14T20:11:21.582Z",
|
||||||
|
"updatedAt": "2025-07-26T10:16:39.131Z",
|
||||||
|
"isFavorite": False,
|
||||||
|
"isArchived": False,
|
||||||
|
"isTrashed": False,
|
||||||
|
"visibility": "timeline",
|
||||||
|
"duration": "0:00:00.00000",
|
||||||
|
"livePhotoVideoId": None,
|
||||||
|
"people": [],
|
||||||
|
"unassignedFaces": [],
|
||||||
|
"checksum": "X6kMpPulu/HJQnKmTqCoQYl3Sjc=",
|
||||||
|
"isOffline": False,
|
||||||
|
"hasMetadata": True,
|
||||||
|
"duplicateId": None,
|
||||||
|
"resized": True,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
MOCK_TAGS_ASSETS = [
|
||||||
|
ImmichAsset.from_dict(
|
||||||
|
{
|
||||||
|
"id": "ae3d82fc-beb5-4abc-ae83-11fcfa5e7629",
|
||||||
|
"deviceAssetId": "2132393",
|
||||||
|
"ownerId": "e7ef5713-9dab-4bd4-b899-715b0ca4379e",
|
||||||
|
"deviceId": "CLI",
|
||||||
|
"libraryId": None,
|
||||||
|
"type": "IMAGE",
|
||||||
|
"originalPath": "/usr/src/app/upload/upload/e7ef5713-9dab-4bd4-b899-715b0ca4379e/07/d0/07d04d86-7188-4335-95ca-9bd9fd2b399d.JPG",
|
||||||
|
"originalFileName": "20110306_025024.jpg",
|
||||||
|
"originalMimeType": "image/jpeg",
|
||||||
|
"thumbhash": "WCgSFYRXaYdQiYineIiHd4SghQUY",
|
||||||
|
"fileCreatedAt": "2011-03-06T01:50:24.000Z",
|
||||||
|
"fileModifiedAt": "2011-03-06T01:50:24.000Z",
|
||||||
|
"localDateTime": "2011-03-06T02:50:24.000Z",
|
||||||
|
"updatedAt": "2025-07-26T10:16:39.477Z",
|
||||||
|
"isFavorite": False,
|
||||||
|
"isArchived": False,
|
||||||
|
"isTrashed": False,
|
||||||
|
"visibility": "timeline",
|
||||||
|
"duration": "0:00:00.00000",
|
||||||
|
"livePhotoVideoId": None,
|
||||||
|
"people": [],
|
||||||
|
"checksum": "eNwN0AN2hEYZJJkonl7ylGzJzko=",
|
||||||
|
"isOffline": False,
|
||||||
|
"hasMetadata": True,
|
||||||
|
"duplicateId": None,
|
||||||
|
"resized": True,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ImmichAsset.from_dict(
|
||||||
|
{
|
||||||
|
"id": "b71d0d08-6727-44ae-8bba-83c190f95df4",
|
||||||
|
"deviceAssetId": "2142137",
|
||||||
|
"ownerId": "e7ef5713-9dab-4bd4-b899-715b0ca4379e",
|
||||||
|
"deviceId": "CLI",
|
||||||
|
"libraryId": None,
|
||||||
|
"type": "IMAGE",
|
||||||
|
"originalPath": "/usr/src/app/upload/upload/e7ef5713-9dab-4bd4-b899-715b0ca4379e/4a/f4/4af42484-86f8-47a0-958a-f32da89ee03a.JPG",
|
||||||
|
"originalFileName": "20110306_024053.jpg",
|
||||||
|
"originalMimeType": "image/jpeg",
|
||||||
|
"thumbhash": "4AcKFYZPZnhSmGl5daaYeG859ytT",
|
||||||
|
"fileCreatedAt": "2011-03-06T01:40:53.000Z",
|
||||||
|
"fileModifiedAt": "2011-03-06T01:40:52.000Z",
|
||||||
|
"localDateTime": "2011-03-06T02:40:53.000Z",
|
||||||
|
"updatedAt": "2025-07-26T10:16:39.474Z",
|
||||||
|
"isFavorite": False,
|
||||||
|
"isArchived": False,
|
||||||
|
"isTrashed": False,
|
||||||
|
"visibility": "timeline",
|
||||||
|
"duration": "0:00:00.00000",
|
||||||
|
"livePhotoVideoId": None,
|
||||||
|
"people": [],
|
||||||
|
"checksum": "VtokCjIwKqnHBFzH3kHakIJiq5I=",
|
||||||
|
"isOffline": False,
|
||||||
|
"hasMetadata": True,
|
||||||
|
"duplicateId": None,
|
||||||
|
"resized": True,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
@ -26,7 +26,6 @@ from homeassistant.setup import async_setup_component
|
|||||||
from homeassistant.util.aiohttp import MockRequest, MockStreamReaderChunked
|
from homeassistant.util.aiohttp import MockRequest, MockStreamReaderChunked
|
||||||
|
|
||||||
from . import setup_integration
|
from . import setup_integration
|
||||||
from .const import MOCK_ALBUM_WITHOUT_ASSETS
|
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
@ -143,7 +142,8 @@ async def test_browse_media_get_root(
|
|||||||
result = await source.async_browse_media(item)
|
result = await source.async_browse_media(item)
|
||||||
|
|
||||||
assert result
|
assert result
|
||||||
assert len(result.children) == 1
|
assert len(result.children) == 3
|
||||||
|
|
||||||
media_file = result.children[0]
|
media_file = result.children[0]
|
||||||
assert isinstance(media_file, BrowseMedia)
|
assert isinstance(media_file, BrowseMedia)
|
||||||
assert media_file.title == "albums"
|
assert media_file.title == "albums"
|
||||||
@ -151,174 +151,289 @@ async def test_browse_media_get_root(
|
|||||||
"media-source://immich/e7ef5713-9dab-4bd4-b899-715b0ca4379e|albums"
|
"media-source://immich/e7ef5713-9dab-4bd4-b899-715b0ca4379e|albums"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
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|albums", 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|albums|"
|
|
||||||
"721e1a4b-aa12-441e-8d3b-5ac7ab283bb6"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def test_browse_media_get_albums_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, f"{mock_config_entry.unique_id}|albums", 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_album_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|albums|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|albums|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_album_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|albums|721e1a4b-aa12-441e-8d3b-5ac7ab283bb6",
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
result = await source.async_browse_media(item)
|
|
||||||
|
|
||||||
assert result
|
|
||||||
assert len(result.children) == 2
|
|
||||||
media_file = result.children[0]
|
|
||||||
assert isinstance(media_file, BrowseMedia)
|
|
||||||
assert media_file.identifier == (
|
|
||||||
"e7ef5713-9dab-4bd4-b899-715b0ca4379e|albums|"
|
|
||||||
"721e1a4b-aa12-441e-8d3b-5ac7ab283bb6|"
|
|
||||||
"2e94c203-50aa-4ad2-8e29-56dd74e0eff4|filename.jpg|image/jpeg"
|
|
||||||
)
|
|
||||||
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/thumbnail/image/jpeg"
|
|
||||||
)
|
|
||||||
|
|
||||||
media_file = result.children[1]
|
media_file = result.children[1]
|
||||||
assert isinstance(media_file, BrowseMedia)
|
assert isinstance(media_file, BrowseMedia)
|
||||||
assert media_file.identifier == (
|
assert media_file.title == "people"
|
||||||
"e7ef5713-9dab-4bd4-b899-715b0ca4379e|albums|"
|
assert media_file.media_content_id == (
|
||||||
"721e1a4b-aa12-441e-8d3b-5ac7ab283bb6|"
|
"media-source://immich/e7ef5713-9dab-4bd4-b899-715b0ca4379e|people"
|
||||||
"2e65a5f2-db83-44c4-81ab-f5ff20c9bd7b|filename.mp4|video/mp4"
|
|
||||||
)
|
)
|
||||||
assert media_file.title == "filename.mp4"
|
|
||||||
assert media_file.media_class == MediaClass.VIDEO
|
media_file = result.children[2]
|
||||||
assert media_file.media_content_type == "video/mp4"
|
assert isinstance(media_file, BrowseMedia)
|
||||||
assert media_file.can_play is True
|
assert media_file.title == "tags"
|
||||||
assert not media_file.can_expand
|
assert media_file.media_content_id == (
|
||||||
assert media_file.thumbnail == (
|
"media-source://immich/e7ef5713-9dab-4bd4-b899-715b0ca4379e|tags"
|
||||||
"/immich/e7ef5713-9dab-4bd4-b899-715b0ca4379e/"
|
|
||||||
"2e65a5f2-db83-44c4-81ab-f5ff20c9bd7b/thumbnail/image/jpeg"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("collection", "children"),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"albums",
|
||||||
|
[{"title": "My Album", "asset_id": "721e1a4b-aa12-441e-8d3b-5ac7ab283bb6"}],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"people",
|
||||||
|
[
|
||||||
|
{"title": "Me", "asset_id": "6176838a-ac5a-4d1f-9a35-91c591d962d8"},
|
||||||
|
{"title": "I", "asset_id": "3e66aa4a-a4a8-41a4-86fe-2ae5e490078f"},
|
||||||
|
{"title": "Myself", "asset_id": "a3c83297-684a-4576-82dc-b07432e8a18f"},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"tags",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"title": "Halloween",
|
||||||
|
"asset_id": "67301cb8-cb73-4e8a-99e9-475cb3f7e7b5",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Holidays",
|
||||||
|
"asset_id": "69bd487f-dc1e-4420-94c6-656f0515773d",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_browse_media_collections(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_immich: Mock,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
|
collection: str,
|
||||||
|
children: list[dict],
|
||||||
|
) -> None:
|
||||||
|
"""Test browse through collections."""
|
||||||
|
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, f"{mock_config_entry.unique_id}|{collection}", None
|
||||||
|
)
|
||||||
|
result = await source.async_browse_media(item)
|
||||||
|
|
||||||
|
assert result
|
||||||
|
assert len(result.children) == len(children)
|
||||||
|
for idx, child in enumerate(children):
|
||||||
|
media_file = result.children[idx]
|
||||||
|
assert isinstance(media_file, BrowseMedia)
|
||||||
|
assert media_file.title == child["title"]
|
||||||
|
assert media_file.media_content_id == (
|
||||||
|
"media-source://immich/"
|
||||||
|
f"{mock_config_entry.unique_id}|{collection}|"
|
||||||
|
f"{child['asset_id']}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("collection", "mocked_get_fn"),
|
||||||
|
[
|
||||||
|
("albums", ("albums", "async_get_all_albums")),
|
||||||
|
("people", ("people", "async_get_all_people")),
|
||||||
|
("tags", ("tags", "async_get_all_tags")),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_browse_media_collections_error(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_immich: Mock,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
|
collection: str,
|
||||||
|
mocked_get_fn: tuple[str, str],
|
||||||
|
) -> None:
|
||||||
|
"""Test browse_media with unknown collection."""
|
||||||
|
assert await async_setup_component(hass, "media_source", {})
|
||||||
|
|
||||||
|
with patch("homeassistant.components.immich.PLATFORMS", []):
|
||||||
|
await setup_integration(hass, mock_config_entry)
|
||||||
|
|
||||||
|
getattr(
|
||||||
|
getattr(mock_immich, mocked_get_fn[0]), mocked_get_fn[1]
|
||||||
|
).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, f"{mock_config_entry.unique_id}|{collection}", None
|
||||||
|
)
|
||||||
|
result = await source.async_browse_media(item)
|
||||||
|
|
||||||
|
assert result
|
||||||
|
assert result.identifier is None
|
||||||
|
assert len(result.children) == 0
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("collection", "mocked_get_fn"),
|
||||||
|
[
|
||||||
|
("albums", ("albums", "async_get_album_info")),
|
||||||
|
("people", ("search", "async_get_all_by_person_ids")),
|
||||||
|
("tags", ("search", "async_get_all_by_tag_ids")),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_browse_media_collection_items_error(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_immich: Mock,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
|
collection: str,
|
||||||
|
mocked_get_fn: tuple[str, str],
|
||||||
|
) -> 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)
|
||||||
|
|
||||||
|
getattr(
|
||||||
|
getattr(mock_immich, mocked_get_fn[0]), mocked_get_fn[1]
|
||||||
|
).side_effect = ImmichError(
|
||||||
|
{
|
||||||
|
"message": "Not found or no album.read access",
|
||||||
|
"error": "Bad Request",
|
||||||
|
"statusCode": 400,
|
||||||
|
"correlationId": "e0hlizyl",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
item = MediaSourceItem(
|
||||||
|
hass,
|
||||||
|
DOMAIN,
|
||||||
|
f"{mock_config_entry.unique_id}|{collection}|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
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("collection", "collection_id", "children"),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"albums",
|
||||||
|
"721e1a4b-aa12-441e-8d3b-5ac7ab283bb6",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"original_file_name": "filename.jpg",
|
||||||
|
"asset_id": "2e94c203-50aa-4ad2-8e29-56dd74e0eff4",
|
||||||
|
"media_class": MediaClass.IMAGE,
|
||||||
|
"media_content_type": "image/jpeg",
|
||||||
|
"thumb_mime_type": "image/jpeg",
|
||||||
|
"can_play": False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"original_file_name": "filename.mp4",
|
||||||
|
"asset_id": "2e65a5f2-db83-44c4-81ab-f5ff20c9bd7b",
|
||||||
|
"media_class": MediaClass.VIDEO,
|
||||||
|
"media_content_type": "video/mp4",
|
||||||
|
"thumb_mime_type": "image/jpeg",
|
||||||
|
"can_play": True,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"people",
|
||||||
|
"6176838a-ac5a-4d1f-9a35-91c591d962d8",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"original_file_name": "20250714_201122.jpg",
|
||||||
|
"asset_id": "2242eda3-94c2-49ee-86d4-e9e071b6fbf4",
|
||||||
|
"media_class": MediaClass.IMAGE,
|
||||||
|
"media_content_type": "image/jpeg",
|
||||||
|
"thumb_mime_type": "image/jpeg",
|
||||||
|
"can_play": False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"original_file_name": "20250714_201121.jpg",
|
||||||
|
"asset_id": "046ac0d9-8acd-44d8-953f-ecb3c786358a",
|
||||||
|
"media_class": MediaClass.IMAGE,
|
||||||
|
"media_content_type": "image/jpeg",
|
||||||
|
"thumb_mime_type": "image/jpeg",
|
||||||
|
"can_play": False,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"tags",
|
||||||
|
"6176838a-ac5a-4d1f-9a35-91c591d962d8",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"original_file_name": "20110306_025024.jpg",
|
||||||
|
"asset_id": "ae3d82fc-beb5-4abc-ae83-11fcfa5e7629",
|
||||||
|
"media_class": MediaClass.IMAGE,
|
||||||
|
"media_content_type": "image/jpeg",
|
||||||
|
"thumb_mime_type": "image/jpeg",
|
||||||
|
"can_play": False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"original_file_name": "20110306_024053.jpg",
|
||||||
|
"asset_id": "b71d0d08-6727-44ae-8bba-83c190f95df4",
|
||||||
|
"media_class": MediaClass.IMAGE,
|
||||||
|
"media_content_type": "image/jpeg",
|
||||||
|
"thumb_mime_type": "image/jpeg",
|
||||||
|
"can_play": False,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_browse_media_collection_get_items(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_immich: Mock,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
|
collection: str,
|
||||||
|
collection_id: str,
|
||||||
|
children: list[dict],
|
||||||
|
) -> 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,
|
||||||
|
f"{mock_config_entry.unique_id}|{collection}|{collection_id}",
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
result = await source.async_browse_media(item)
|
||||||
|
|
||||||
|
assert result
|
||||||
|
assert len(result.children) == len(children)
|
||||||
|
|
||||||
|
for idx, child in enumerate(children):
|
||||||
|
media_file = result.children[idx]
|
||||||
|
assert isinstance(media_file, BrowseMedia)
|
||||||
|
assert media_file.identifier == (
|
||||||
|
f"{mock_config_entry.unique_id}|{collection}|{collection_id}|"
|
||||||
|
f"{child['asset_id']}|{child['original_file_name']}|{child['media_content_type']}"
|
||||||
|
)
|
||||||
|
assert media_file.title == child["original_file_name"]
|
||||||
|
assert media_file.media_class == child["media_class"]
|
||||||
|
assert media_file.media_content_type == child["media_content_type"]
|
||||||
|
assert media_file.can_play is child["can_play"]
|
||||||
|
assert not media_file.can_expand
|
||||||
|
assert media_file.thumbnail == (
|
||||||
|
f"/immich/{mock_config_entry.unique_id}/"
|
||||||
|
f"{child['asset_id']}/thumbnail/{child['thumb_mime_type']}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def test_media_view(
|
async def test_media_view(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
tmp_path: Path,
|
tmp_path: Path,
|
||||||
@ -362,6 +477,22 @@ async def test_media_view(
|
|||||||
"2e94c203-50aa-4ad2-8e29-56dd74e0eff4/thumbnail/image/jpeg",
|
"2e94c203-50aa-4ad2-8e29-56dd74e0eff4/thumbnail/image/jpeg",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# exception in async_get_person_thumbnail()
|
||||||
|
mock_immich.people.async_get_person_thumbnail.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/person/image/jpeg",
|
||||||
|
)
|
||||||
|
|
||||||
# exception in async_play_video_stream()
|
# exception in async_play_video_stream()
|
||||||
mock_immich.assets.async_play_video_stream.side_effect = ImmichError(
|
mock_immich.assets.async_play_video_stream.side_effect = ImmichError(
|
||||||
{
|
{
|
||||||
@ -396,6 +527,24 @@ async def test_media_view(
|
|||||||
)
|
)
|
||||||
assert isinstance(result, web.Response)
|
assert isinstance(result, web.Response)
|
||||||
|
|
||||||
|
mock_immich.people.async_get_person_thumbnail.side_effect = None
|
||||||
|
mock_immich.people.async_get_person_thumbnail.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/person/image/jpeg",
|
||||||
|
)
|
||||||
|
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/fullsize/image/jpeg",
|
||||||
|
)
|
||||||
|
assert isinstance(result, web.Response)
|
||||||
|
|
||||||
mock_immich.assets.async_play_video_stream.side_effect = None
|
mock_immich.assets.async_play_video_stream.side_effect = None
|
||||||
mock_immich.assets.async_play_video_stream.return_value = MockStreamReaderChunked(
|
mock_immich.assets.async_play_video_stream.return_value = MockStreamReaderChunked(
|
||||||
b"xxxx"
|
b"xxxx"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user