Add Synology Photos support (#86894)

Co-authored-by: Michael <35783820+mib1185@users.noreply.github.com>
Co-authored-by: mib1185 <mail@mib85.de>
Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
Lode Smets 2023-04-17 00:29:15 +02:00 committed by GitHub
parent 5001a50876
commit 263901841f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 668 additions and 0 deletions

View File

@ -12,6 +12,7 @@ from synology_dsm.api.core.upgrade import SynoCoreUpgrade
from synology_dsm.api.core.utilization import SynoCoreUtilization from synology_dsm.api.core.utilization import SynoCoreUtilization
from synology_dsm.api.dsm.information import SynoDSMInformation from synology_dsm.api.dsm.information import SynoDSMInformation
from synology_dsm.api.dsm.network import SynoDSMNetwork from synology_dsm.api.dsm.network import SynoDSMNetwork
from synology_dsm.api.photos import SynoPhotos
from synology_dsm.api.storage.storage import SynoStorage from synology_dsm.api.storage.storage import SynoStorage
from synology_dsm.api.surveillance_station import SynoSurveillanceStation from synology_dsm.api.surveillance_station import SynoSurveillanceStation
from synology_dsm.exceptions import ( from synology_dsm.exceptions import (
@ -56,6 +57,7 @@ class SynoApi:
self.network: SynoDSMNetwork = None self.network: SynoDSMNetwork = None
self.security: SynoCoreSecurity = None self.security: SynoCoreSecurity = None
self.storage: SynoStorage = None self.storage: SynoStorage = None
self.photos: SynoPhotos = None
self.surveillance_station: SynoSurveillanceStation = None self.surveillance_station: SynoSurveillanceStation = None
self.system: SynoCoreSystem = None self.system: SynoCoreSystem = None
self.upgrade: SynoCoreUpgrade = None self.upgrade: SynoCoreUpgrade = None
@ -66,6 +68,7 @@ class SynoApi:
self._with_information = True self._with_information = True
self._with_security = True self._with_security = True
self._with_storage = True self._with_storage = True
self._with_photos = True
self._with_surveillance_station = True self._with_surveillance_station = True
self._with_system = True self._with_system = True
self._with_upgrade = True self._with_upgrade = True
@ -163,6 +166,7 @@ class SynoApi:
self._fetching_entities.get(SynoCoreSecurity.API_KEY) self._fetching_entities.get(SynoCoreSecurity.API_KEY)
) )
self._with_storage = bool(self._fetching_entities.get(SynoStorage.API_KEY)) self._with_storage = bool(self._fetching_entities.get(SynoStorage.API_KEY))
self._with_photos = bool(self._fetching_entities.get(SynoStorage.API_KEY))
self._with_upgrade = bool(self._fetching_entities.get(SynoCoreUpgrade.API_KEY)) self._with_upgrade = bool(self._fetching_entities.get(SynoCoreUpgrade.API_KEY))
self._with_utilisation = bool( self._with_utilisation = bool(
self._fetching_entities.get(SynoCoreUtilization.API_KEY) self._fetching_entities.get(SynoCoreUtilization.API_KEY)
@ -180,6 +184,13 @@ class SynoApi:
self.dsm.reset(self.security) self.dsm.reset(self.security)
self.security = None self.security = None
if not self._with_photos:
LOGGER.debug(
"Disable photos api from being updated or '%s'", self._entry.unique_id
)
self.dsm.reset(self.photos)
self.photos = None
if not self._with_storage: if not self._with_storage:
LOGGER.debug( LOGGER.debug(
"Disable storage api from being updatedf or '%s'", self._entry.unique_id "Disable storage api from being updatedf or '%s'", self._entry.unique_id
@ -219,6 +230,10 @@ class SynoApi:
LOGGER.debug("Enable security api updates for '%s'", self._entry.unique_id) LOGGER.debug("Enable security api updates for '%s'", self._entry.unique_id)
self.security = self.dsm.security self.security = self.dsm.security
if self._with_photos:
LOGGER.debug("Enable photos api updates for '%s'", self._entry.unique_id)
self.photos = self.dsm.photos
if self._with_storage: if self._with_storage:
LOGGER.debug("Enable storage api updates for '%s'", self._entry.unique_id) LOGGER.debug("Enable storage api updates for '%s'", self._entry.unique_id)
self.storage = self.dsm.storage self.storage = self.dsm.storage

View File

@ -3,6 +3,7 @@
"name": "Synology DSM", "name": "Synology DSM",
"codeowners": ["@hacf-fr", "@Quentame", "@mib1185"], "codeowners": ["@hacf-fr", "@Quentame", "@mib1185"],
"config_flow": true, "config_flow": true,
"dependencies": ["http"],
"documentation": "https://www.home-assistant.io/integrations/synology_dsm", "documentation": "https://www.home-assistant.io/integrations/synology_dsm",
"iot_class": "local_polling", "iot_class": "local_polling",
"loggers": ["synology_dsm"], "loggers": ["synology_dsm"],

View File

@ -0,0 +1,231 @@
"""Expose Synology DSM as a media source."""
from __future__ import annotations
import mimetypes
from aiohttp import web
from synology_dsm.api.photos import SynoPhotosAlbum, SynoPhotosItem
from synology_dsm.exceptions import SynologyDSMException
from homeassistant.components import http
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 .models import SynologyDSMData
async def async_get_media_source(hass: HomeAssistant) -> MediaSource:
"""Set up Synology media source."""
entries = hass.config_entries.async_entries(DOMAIN)
hass.http.register_view(SynologyDsmMediaView(hass))
return SynologyPhotosMediaSource(hass, entries)
class SynologyPhotosMediaSourceIdentifier:
"""Synology Photos item identifier."""
def __init__(self, identifier: str) -> None:
"""Split identifier into parts."""
parts = identifier.split("/")
self.unique_id = None
self.album_id = None
self.cache_key = None
self.file_name = None
if parts:
self.unique_id = parts[0]
if len(parts) > 1:
self.album_id = parts[1]
if len(parts) > 2:
self.cache_key = parts[2]
if len(parts) > 3:
self.file_name = parts[3]
class SynologyPhotosMediaSource(MediaSource):
"""Provide Synology Photos as media sources."""
name = "Synology Photos"
def __init__(self, hass: HomeAssistant, entries: list[ConfigEntry]) -> None:
"""Initialize Synology 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.data.get(DOMAIN):
raise BrowseError("Diskstation not initialized")
return BrowseMediaSource(
domain=DOMAIN,
identifier=None,
media_class=MediaClass.DIRECTORY,
media_content_type=MediaClass.IMAGE,
title="Synology Photos",
can_play=False,
can_expand=True,
children_media_class=MediaClass.DIRECTORY,
children=[
*await self._async_build_diskstations(item),
],
)
async def _async_build_diskstations(
self, item: MediaSourceItem
) -> list[BrowseMediaSource]:
"""Handle browsing different diskstations."""
if not item.identifier:
ret = []
for entry in self.entries:
ret.append(
BrowseMediaSource(
domain=DOMAIN,
identifier=entry.unique_id,
media_class=MediaClass.DIRECTORY,
media_content_type=MediaClass.IMAGE,
title=f"{entry.title} - {entry.unique_id}",
can_play=False,
can_expand=True,
)
)
return ret
identifier = SynologyPhotosMediaSourceIdentifier(item.identifier)
diskstation: SynologyDSMData = self.hass.data[DOMAIN][identifier.unique_id]
if identifier.album_id is None:
# Get Albums
try:
albums = await diskstation.api.photos.get_albums()
except SynologyDSMException:
return []
ret = [
BrowseMediaSource(
domain=DOMAIN,
identifier=f"{item.identifier}/0",
media_class=MediaClass.DIRECTORY,
media_content_type=MediaClass.IMAGE,
title="All images",
can_play=False,
can_expand=True,
)
]
for album in albums:
ret.append(
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,
)
)
return ret
# Request items of album
# Get Items
album = SynoPhotosAlbum(int(identifier.album_id), "", 0)
try:
album_items = await diskstation.api.photos.get_items_from_album(
album, 0, 1000
)
except SynologyDSMException:
return []
ret = []
for album_item in album_items:
mime_type, _ = mimetypes.guess_type(album_item.file_name)
assert isinstance(mime_type, str)
if mime_type.startswith("image/"):
# Force small small thumbnails
album_item.thumbnail_size = "sm"
ret.append(
BrowseMediaSource(
domain=DOMAIN,
identifier=f"{identifier.unique_id}/{identifier.album_id}/{album_item.thumbnail_cache_key}/{album_item.file_name}",
media_class=MediaClass.IMAGE,
media_content_type=mime_type,
title=album_item.file_name,
can_play=True,
can_expand=False,
thumbnail=await self.async_get_thumbnail(
album_item, diskstation
),
)
)
return ret
async def async_resolve_media(self, item: MediaSourceItem) -> PlayMedia:
"""Resolve media to a url."""
identifier = SynologyPhotosMediaSourceIdentifier(item.identifier)
if identifier.album_id is None:
raise Unresolvable("No album id")
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"/synology_dsm/{identifier.unique_id}/{identifier.cache_key}/{identifier.file_name}",
mime_type,
)
async def async_get_thumbnail(
self, item: SynoPhotosItem, diskstation: SynologyDSMData
) -> str | None:
"""Get thumbnail."""
try:
thumbnail = await diskstation.api.photos.get_item_thumbnail_url(item)
except SynologyDSMException:
return None
return str(thumbnail)
class SynologyDsmMediaView(http.HomeAssistantView):
"""Synology Media Finder View."""
url = "/synology_dsm/{source_dir_id}/{location:.*}"
name = "synology_dsm"
def __init__(self, hass: HomeAssistant) -> None:
"""Initialize the media view."""
self.hass = hass
async def get(
self, request: web.Request, source_dir_id: str, location: str
) -> web.Response:
"""Start a GET request."""
if not self.hass.data.get(DOMAIN):
raise web.HTTPNotFound()
# location: {cache_key}/{filename}
cache_key, file_name = location.split("/")
image_id = cache_key.split("_")[0]
mime_type, _ = mimetypes.guess_type(file_name)
if not isinstance(mime_type, str):
raise web.HTTPNotFound()
diskstation: SynologyDSMData = self.hass.data[DOMAIN][source_dir_id]
item = SynoPhotosItem(image_id, "", "", "", cache_key, "")
try:
image = await diskstation.api.photos.download_item(item)
except SynologyDSMException as exc:
raise web.HTTPNotFound() from exc
return web.Response(body=image, content_type=mime_type)

View File

@ -4,6 +4,9 @@ from unittest.mock import AsyncMock, patch
import pytest import pytest
from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component
@pytest.fixture @pytest.fixture
def mock_setup_entry() -> Generator[AsyncMock, None, None]: def mock_setup_entry() -> Generator[AsyncMock, None, None]:
@ -14,6 +17,12 @@ def mock_setup_entry() -> Generator[AsyncMock, None, None]:
yield mock_setup yield mock_setup
@pytest.fixture
async def setup_media_source(hass: HomeAssistant) -> None:
"""Set up media source."""
assert await async_setup_component(hass, "media_source", {})
@pytest.fixture(name="mock_dsm") @pytest.fixture(name="mock_dsm")
def fixture_dsm(): def fixture_dsm():
"""Set up SynologyDSM API fixture.""" """Set up SynologyDSM API fixture."""

View File

@ -0,0 +1,412 @@
"""Tests for Synology DSM Media Source."""
from pathlib import Path
import tempfile
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from synology_dsm.api.photos import SynoPhotosAlbum, SynoPhotosItem
from synology_dsm.exceptions import SynologyDSMException
from homeassistant.components.media_player import MediaClass
from homeassistant.components.media_source import (
BrowseError,
BrowseMedia,
MediaSourceItem,
Unresolvable,
)
from homeassistant.components.synology_dsm.const import DOMAIN
from homeassistant.components.synology_dsm.media_source import (
SynologyDsmMediaView,
SynologyPhotosMediaSource,
async_get_media_source,
)
from homeassistant.const import (
CONF_HOST,
CONF_MAC,
CONF_PASSWORD,
CONF_PORT,
CONF_SSL,
CONF_USERNAME,
)
from homeassistant.core import HomeAssistant
from homeassistant.util.aiohttp import MockRequest, web
from .consts import HOST, MACS, PASSWORD, PORT, USE_SSL, USERNAME
from tests.common import MockConfigEntry
@pytest.fixture
def dsm_with_photos() -> MagicMock:
"""Set up SynologyDSM API fixture."""
dsm = MagicMock()
dsm.login = AsyncMock(return_value=True)
dsm.update = AsyncMock(return_value=True)
dsm.network.update = AsyncMock(return_value=True)
dsm.surveillance_station.update = AsyncMock(return_value=True)
dsm.upgrade.update = AsyncMock(return_value=True)
dsm.photos.get_albums = AsyncMock(return_value=[SynoPhotosAlbum(1, "Album 1", 10)])
dsm.photos.get_items_from_album = AsyncMock(
return_value=[SynoPhotosItem(10, "", "filename.jpg", 12345, "10_1298753", "sm")]
)
dsm.photos.get_item_thumbnail_url = AsyncMock(
return_value="http://my.thumbnail.url"
)
return dsm
@pytest.mark.usefixtures("setup_media_source")
async def test_get_media_source(hass: HomeAssistant) -> None:
"""Test the async_get_media_source function and SynologyPhotosMediaSource constructor."""
source = await async_get_media_source(hass)
assert isinstance(source, SynologyPhotosMediaSource)
assert source.domain == DOMAIN
@pytest.mark.usefixtures("setup_media_source")
@pytest.mark.parametrize(
("identifier", "exception_msg"),
[
("unique_id", "No album id"),
("unique_id/1", "No file name"),
("unique_id/1/cache_key", "No file name"),
("unique_id/1/cache_key/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."""
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.usefixtures("setup_media_source")
@pytest.mark.parametrize(
("identifier", "url", "mime_type"),
[
(
"ABC012345/10/27643_876876/filename.jpg",
"/synology_dsm/ABC012345/27643_876876/filename.jpg",
"image/jpeg",
),
(
"ABC012345/12/12631_47189/filename.png",
"/synology_dsm/ABC012345/12631_47189/filename.png",
"image/png",
),
],
)
async def test_resolve_media_success(
hass: HomeAssistant, identifier: str, url: str, mime_type: str
) -> None:
"""Test successful resolving an item."""
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
@pytest.mark.usefixtures("setup_media_source")
async def test_browse_media_unconfigured(hass: HomeAssistant) -> None:
"""Test browse_media without any devices being configured."""
source = await async_get_media_source(hass)
item = MediaSourceItem(
hass, DOMAIN, "unique_id/album_id/cache_key/filename.jpg", None
)
with pytest.raises(BrowseError, match="Diskstation not initialized"):
await source.async_browse_media(item)
@pytest.mark.usefixtures("setup_media_source")
async def test_browse_media_album_error(
hass: HomeAssistant, dsm_with_photos: MagicMock
) -> None:
"""Test browse_media with unknown album."""
with patch(
"homeassistant.components.synology_dsm.common.SynologyDSM",
return_value=dsm_with_photos,
), patch("homeassistant.components.synology_dsm.PLATFORMS", return_value=[]):
entry = MockConfigEntry(
domain=DOMAIN,
data={
CONF_HOST: HOST,
CONF_PORT: PORT,
CONF_SSL: USE_SSL,
CONF_USERNAME: USERNAME,
CONF_PASSWORD: PASSWORD,
CONF_MAC: MACS[0],
},
unique_id="mocked_syno_dsm_entry",
)
entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(entry.entry_id)
# exception in get_albums()
dsm_with_photos.photos.get_albums = AsyncMock(
side_effect=SynologyDSMException("", None)
)
source = await async_get_media_source(hass)
item = MediaSourceItem(hass, DOMAIN, entry.unique_id, None)
result = await source.async_browse_media(item)
assert result
assert result.identifier is None
assert len(result.children) == 0
@pytest.mark.usefixtures("setup_media_source")
async def test_browse_media_get_root(
hass: HomeAssistant, dsm_with_photos: MagicMock
) -> None:
"""Test browse_media returning root media sources."""
with patch(
"homeassistant.components.synology_dsm.common.SynologyDSM",
return_value=dsm_with_photos,
), patch("homeassistant.components.synology_dsm.PLATFORMS", return_value=[]):
entry = MockConfigEntry(
domain=DOMAIN,
data={
CONF_HOST: HOST,
CONF_PORT: PORT,
CONF_SSL: USE_SSL,
CONF_USERNAME: USERNAME,
CONF_PASSWORD: PASSWORD,
CONF_MAC: MACS[0],
},
unique_id="mocked_syno_dsm_entry",
)
entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(entry.entry_id)
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
assert isinstance(result.children[0], BrowseMedia)
assert result.children[0].identifier == "mocked_syno_dsm_entry"
@pytest.mark.usefixtures("setup_media_source")
async def test_browse_media_get_albums(
hass: HomeAssistant, dsm_with_photos: MagicMock
) -> None:
"""Test browse_media returning albums."""
with patch(
"homeassistant.components.synology_dsm.common.SynologyDSM",
return_value=dsm_with_photos,
), patch("homeassistant.components.synology_dsm.PLATFORMS", return_value=[]):
entry = MockConfigEntry(
domain=DOMAIN,
data={
CONF_HOST: HOST,
CONF_PORT: PORT,
CONF_SSL: USE_SSL,
CONF_USERNAME: USERNAME,
CONF_PASSWORD: PASSWORD,
CONF_MAC: MACS[0],
},
unique_id="mocked_syno_dsm_entry",
)
entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(entry.entry_id)
source = await async_get_media_source(hass)
item = MediaSourceItem(hass, DOMAIN, "mocked_syno_dsm_entry", None)
result = await source.async_browse_media(item)
assert result
assert len(result.children) == 2
assert isinstance(result.children[0], BrowseMedia)
assert result.children[0].identifier == "mocked_syno_dsm_entry/0"
assert result.children[0].title == "All images"
assert isinstance(result.children[1], BrowseMedia)
assert result.children[1].identifier == "mocked_syno_dsm_entry/1"
assert result.children[1].title == "Album 1"
@pytest.mark.usefixtures("setup_media_source")
async def test_browse_media_get_items_error(
hass: HomeAssistant, dsm_with_photos: MagicMock
) -> None:
"""Test browse_media returning albums."""
with patch(
"homeassistant.components.synology_dsm.common.SynologyDSM",
return_value=dsm_with_photos,
), patch("homeassistant.components.synology_dsm.PLATFORMS", return_value=[]):
entry = MockConfigEntry(
domain=DOMAIN,
data={
CONF_HOST: HOST,
CONF_PORT: PORT,
CONF_SSL: USE_SSL,
CONF_USERNAME: USERNAME,
CONF_PASSWORD: PASSWORD,
CONF_MAC: MACS[0],
},
unique_id="mocked_syno_dsm_entry",
)
entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(entry.entry_id)
source = await async_get_media_source(hass)
# unknown album
dsm_with_photos.photos.get_items_from_album = AsyncMock(return_value=[])
item = MediaSourceItem(hass, DOMAIN, "mocked_syno_dsm_entry/1", None)
result = await source.async_browse_media(item)
assert result
assert result.identifier is None
assert len(result.children) == 0
# exception in get_items_from_album()
dsm_with_photos.photos.get_items_from_album = AsyncMock(
side_effect=SynologyDSMException("", None)
)
item = MediaSourceItem(hass, DOMAIN, "mocked_syno_dsm_entry/1", None)
result = await source.async_browse_media(item)
assert result
assert result.identifier is None
assert len(result.children) == 0
@pytest.mark.usefixtures("setup_media_source")
async def test_browse_media_get_items_thumbnail_error(
hass: HomeAssistant, dsm_with_photos: MagicMock
) -> None:
"""Test browse_media returning albums."""
with patch(
"homeassistant.components.synology_dsm.common.SynologyDSM",
return_value=dsm_with_photos,
), patch("homeassistant.components.synology_dsm.PLATFORMS", return_value=[]):
entry = MockConfigEntry(
domain=DOMAIN,
data={
CONF_HOST: HOST,
CONF_PORT: PORT,
CONF_SSL: USE_SSL,
CONF_USERNAME: USERNAME,
CONF_PASSWORD: PASSWORD,
CONF_MAC: MACS[0],
},
unique_id="mocked_syno_dsm_entry",
)
entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(entry.entry_id)
source = await async_get_media_source(hass)
dsm_with_photos.photos.get_item_thumbnail_url = AsyncMock(
side_effect=SynologyDSMException("", None)
)
item = MediaSourceItem(hass, DOMAIN, "mocked_syno_dsm_entry/1", None)
result = await source.async_browse_media(item)
assert result
assert len(result.children) == 1
item = result.children[0]
assert isinstance(item, BrowseMedia)
assert item.thumbnail is None
@pytest.mark.usefixtures("setup_media_source")
async def test_browse_media_get_items(
hass: HomeAssistant, dsm_with_photos: MagicMock
) -> None:
"""Test browse_media returning albums."""
with patch(
"homeassistant.components.synology_dsm.common.SynologyDSM",
return_value=dsm_with_photos,
), patch("homeassistant.components.synology_dsm.PLATFORMS", return_value=[]):
entry = MockConfigEntry(
domain=DOMAIN,
data={
CONF_HOST: HOST,
CONF_PORT: PORT,
CONF_SSL: USE_SSL,
CONF_USERNAME: USERNAME,
CONF_PASSWORD: PASSWORD,
CONF_MAC: MACS[0],
},
unique_id="mocked_syno_dsm_entry",
)
entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(entry.entry_id)
source = await async_get_media_source(hass)
item = MediaSourceItem(hass, DOMAIN, "mocked_syno_dsm_entry/1", None)
result = await source.async_browse_media(item)
assert result
assert len(result.children) == 1
item = result.children[0]
assert isinstance(item, BrowseMedia)
assert item.identifier == "mocked_syno_dsm_entry/1/10_1298753/filename.jpg"
assert item.title == "filename.jpg"
assert item.media_class == MediaClass.IMAGE
assert item.media_content_type == "image/jpeg"
assert item.can_play
assert not item.can_expand
assert item.thumbnail == "http://my.thumbnail.url"
@pytest.mark.usefixtures("setup_media_source")
async def test_media_view(
hass: HomeAssistant, tmp_path: Path, dsm_with_photos: MagicMock
) -> None:
"""Test SynologyDsmMediaView returning albums."""
view = SynologyDsmMediaView(hass)
request = MockRequest(b"", DOMAIN)
# diskation not set uped
with pytest.raises(web.HTTPNotFound):
await view.get(request, "", "")
with patch(
"homeassistant.components.synology_dsm.common.SynologyDSM",
return_value=dsm_with_photos,
), patch("homeassistant.components.synology_dsm.PLATFORMS", return_value=[]):
entry = MockConfigEntry(
domain=DOMAIN,
data={
CONF_HOST: HOST,
CONF_PORT: PORT,
CONF_SSL: USE_SSL,
CONF_USERNAME: USERNAME,
CONF_PASSWORD: PASSWORD,
CONF_MAC: MACS[0],
},
unique_id="mocked_syno_dsm_entry",
)
entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(entry.entry_id)
with pytest.raises(web.HTTPNotFound):
await view.get(request, "", "10_1298753/filename")
# exception in download_item()
dsm_with_photos.photos.download_item = AsyncMock(
side_effect=SynologyDSMException("", None)
)
with pytest.raises(web.HTTPNotFound):
await view.get(request, "mocked_syno_dsm_entry", "10_1298753/filename.jpg")
# success
dsm_with_photos.photos.download_item = AsyncMock(return_value=b"xxxx")
tempfile.tempdir = tmp_path
result = await view.get(request, "mocked_syno_dsm_entry", "10_1298753/filename.jpg")
assert isinstance(result, web.Response)