mirror of
https://github.com/home-assistant/core.git
synced 2025-07-24 21:57:51 +00:00
Use create_stream in generic camera config flow (#73237)
* Use create_stream in generic camera config flow
This commit is contained in:
parent
21cfbe875e
commit
b1f2e5f897
@ -3,7 +3,6 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import contextlib
|
import contextlib
|
||||||
from errno import EHOSTUNREACH, EIO
|
from errno import EHOSTUNREACH, EIO
|
||||||
from functools import partial
|
|
||||||
import io
|
import io
|
||||||
import logging
|
import logging
|
||||||
from types import MappingProxyType
|
from types import MappingProxyType
|
||||||
@ -11,7 +10,6 @@ from typing import Any
|
|||||||
|
|
||||||
import PIL
|
import PIL
|
||||||
from async_timeout import timeout
|
from async_timeout import timeout
|
||||||
import av
|
|
||||||
from httpx import HTTPStatusError, RequestError, TimeoutException
|
from httpx import HTTPStatusError, RequestError, TimeoutException
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
import yarl
|
import yarl
|
||||||
@ -19,9 +17,10 @@ import yarl
|
|||||||
from homeassistant.components.stream import (
|
from homeassistant.components.stream import (
|
||||||
CONF_RTSP_TRANSPORT,
|
CONF_RTSP_TRANSPORT,
|
||||||
CONF_USE_WALLCLOCK_AS_TIMESTAMPS,
|
CONF_USE_WALLCLOCK_AS_TIMESTAMPS,
|
||||||
|
HLS_PROVIDER,
|
||||||
RTSP_TRANSPORTS,
|
RTSP_TRANSPORTS,
|
||||||
SOURCE_TIMEOUT,
|
SOURCE_TIMEOUT,
|
||||||
convert_stream_options,
|
create_stream,
|
||||||
)
|
)
|
||||||
from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow
|
from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
@ -198,6 +197,11 @@ async def async_test_stream(hass, info) -> dict[str, str]:
|
|||||||
"""Verify that the stream is valid before we create an entity."""
|
"""Verify that the stream is valid before we create an entity."""
|
||||||
if not (stream_source := info.get(CONF_STREAM_SOURCE)):
|
if not (stream_source := info.get(CONF_STREAM_SOURCE)):
|
||||||
return {}
|
return {}
|
||||||
|
# Import from stream.worker as stream cannot reexport from worker
|
||||||
|
# without forcing the av dependency on default_config
|
||||||
|
# pylint: disable=import-outside-toplevel
|
||||||
|
from homeassistant.components.stream.worker import StreamWorkerError
|
||||||
|
|
||||||
if not isinstance(stream_source, template_helper.Template):
|
if not isinstance(stream_source, template_helper.Template):
|
||||||
stream_source = template_helper.Template(stream_source, hass)
|
stream_source = template_helper.Template(stream_source, hass)
|
||||||
try:
|
try:
|
||||||
@ -205,42 +209,21 @@ async def async_test_stream(hass, info) -> dict[str, str]:
|
|||||||
except TemplateError as err:
|
except TemplateError as err:
|
||||||
_LOGGER.warning("Problem rendering template %s: %s", stream_source, err)
|
_LOGGER.warning("Problem rendering template %s: %s", stream_source, err)
|
||||||
return {CONF_STREAM_SOURCE: "template_error"}
|
return {CONF_STREAM_SOURCE: "template_error"}
|
||||||
|
stream_options: dict[str, bool | str] = {}
|
||||||
|
if rtsp_transport := info.get(CONF_RTSP_TRANSPORT):
|
||||||
|
stream_options[CONF_RTSP_TRANSPORT] = rtsp_transport
|
||||||
|
if info.get(CONF_USE_WALLCLOCK_AS_TIMESTAMPS):
|
||||||
|
stream_options[CONF_USE_WALLCLOCK_AS_TIMESTAMPS] = True
|
||||||
try:
|
try:
|
||||||
# For RTSP streams, prefer TCP. This code is duplicated from
|
stream = create_stream(hass, stream_source, stream_options, "test_stream")
|
||||||
# homeassistant.components.stream.__init__.py:create_stream()
|
hls_provider = stream.add_provider(HLS_PROVIDER)
|
||||||
# It may be possible & better to call create_stream() directly.
|
await stream.start()
|
||||||
stream_options: dict[str, bool | str] = {}
|
if not await hls_provider.part_recv(timeout=SOURCE_TIMEOUT):
|
||||||
if rtsp_transport := info.get(CONF_RTSP_TRANSPORT):
|
hass.async_create_task(stream.stop())
|
||||||
stream_options[CONF_RTSP_TRANSPORT] = rtsp_transport
|
return {CONF_STREAM_SOURCE: "timeout"}
|
||||||
if info.get(CONF_USE_WALLCLOCK_AS_TIMESTAMPS):
|
await stream.stop()
|
||||||
stream_options[CONF_USE_WALLCLOCK_AS_TIMESTAMPS] = True
|
except StreamWorkerError as err:
|
||||||
pyav_options = convert_stream_options(stream_options)
|
return {CONF_STREAM_SOURCE: str(err)}
|
||||||
if isinstance(stream_source, str) and stream_source[:7] == "rtsp://":
|
|
||||||
pyav_options = {
|
|
||||||
"rtsp_flags": "prefer_tcp",
|
|
||||||
"stimeout": "5000000",
|
|
||||||
**pyav_options,
|
|
||||||
}
|
|
||||||
_LOGGER.debug("Attempting to open stream %s", stream_source)
|
|
||||||
container = await hass.async_add_executor_job(
|
|
||||||
partial(
|
|
||||||
av.open,
|
|
||||||
stream_source,
|
|
||||||
options=pyav_options,
|
|
||||||
timeout=SOURCE_TIMEOUT,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
_ = container.streams.video[0]
|
|
||||||
except (av.error.FileNotFoundError): # pylint: disable=c-extension-no-member
|
|
||||||
return {CONF_STREAM_SOURCE: "stream_file_not_found"}
|
|
||||||
except (av.error.HTTPNotFoundError): # pylint: disable=c-extension-no-member
|
|
||||||
return {CONF_STREAM_SOURCE: "stream_http_not_found"}
|
|
||||||
except (av.error.TimeoutError): # pylint: disable=c-extension-no-member
|
|
||||||
return {CONF_STREAM_SOURCE: "timeout"}
|
|
||||||
except av.error.HTTPUnauthorizedError: # pylint: disable=c-extension-no-member
|
|
||||||
return {CONF_STREAM_SOURCE: "stream_unauthorised"}
|
|
||||||
except (KeyError, IndexError):
|
|
||||||
return {CONF_STREAM_SOURCE: "stream_no_video"}
|
|
||||||
except PermissionError:
|
except PermissionError:
|
||||||
return {CONF_STREAM_SOURCE: "stream_not_permitted"}
|
return {CONF_STREAM_SOURCE: "stream_not_permitted"}
|
||||||
except OSError as err:
|
except OSError as err:
|
||||||
|
@ -12,9 +12,7 @@
|
|||||||
"timeout": "Timeout while loading URL",
|
"timeout": "Timeout while loading URL",
|
||||||
"stream_no_route_to_host": "Could not find host while trying to connect to stream",
|
"stream_no_route_to_host": "Could not find host while trying to connect to stream",
|
||||||
"stream_io_error": "Input/Output error while trying to connect to stream. Wrong RTSP transport protocol?",
|
"stream_io_error": "Input/Output error while trying to connect to stream. Wrong RTSP transport protocol?",
|
||||||
"stream_unauthorised": "Authorisation failed while trying to connect to stream",
|
"stream_not_permitted": "Operation not permitted while trying to connect to stream. Wrong RTSP transport protocol?"
|
||||||
"stream_not_permitted": "Operation not permitted while trying to connect to stream. Wrong RTSP transport protocol?",
|
|
||||||
"stream_no_video": "Stream has no video"
|
|
||||||
},
|
},
|
||||||
"abort": {
|
"abort": {
|
||||||
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]",
|
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]",
|
||||||
@ -78,15 +76,11 @@
|
|||||||
"unable_still_load": "[%key:component::generic::config::error::unable_still_load%]",
|
"unable_still_load": "[%key:component::generic::config::error::unable_still_load%]",
|
||||||
"no_still_image_or_stream_url": "[%key:component::generic::config::error::no_still_image_or_stream_url%]",
|
"no_still_image_or_stream_url": "[%key:component::generic::config::error::no_still_image_or_stream_url%]",
|
||||||
"invalid_still_image": "[%key:component::generic::config::error::invalid_still_image%]",
|
"invalid_still_image": "[%key:component::generic::config::error::invalid_still_image%]",
|
||||||
"stream_file_not_found": "[%key:component::generic::config::error::stream_file_not_found%]",
|
|
||||||
"stream_http_not_found": "[%key:component::generic::config::error::stream_http_not_found%]",
|
|
||||||
"template_error": "[%key:component::generic::config::error::template_error%]",
|
"template_error": "[%key:component::generic::config::error::template_error%]",
|
||||||
"timeout": "[%key:component::generic::config::error::timeout%]",
|
"timeout": "[%key:component::generic::config::error::timeout%]",
|
||||||
"stream_no_route_to_host": "[%key:component::generic::config::error::stream_no_route_to_host%]",
|
"stream_no_route_to_host": "[%key:component::generic::config::error::stream_no_route_to_host%]",
|
||||||
"stream_io_error": "[%key:component::generic::config::error::stream_io_error%]",
|
"stream_io_error": "[%key:component::generic::config::error::stream_io_error%]",
|
||||||
"stream_unauthorised": "[%key:component::generic::config::error::stream_unauthorised%]",
|
"stream_not_permitted": "[%key:component::generic::config::error::stream_not_permitted%]"
|
||||||
"stream_not_permitted": "[%key:component::generic::config::error::stream_not_permitted%]",
|
|
||||||
"stream_no_video": "[%key:component::generic::config::error::stream_no_video%]"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"""Test fixtures for the generic component."""
|
"""Test fixtures for the generic component."""
|
||||||
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from unittest.mock import Mock, patch
|
from unittest.mock import AsyncMock, Mock, patch
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
import pytest
|
import pytest
|
||||||
@ -59,14 +59,20 @@ def fakeimg_gif(fakeimgbytes_gif):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="package")
|
@pytest.fixture(scope="package")
|
||||||
def mock_av_open():
|
def mock_create_stream():
|
||||||
"""Fake container object with .streams.video[0] != None."""
|
"""Mock create stream."""
|
||||||
fake = Mock()
|
mock_stream = Mock()
|
||||||
fake.streams.video = ["fakevid"]
|
mock_provider = Mock()
|
||||||
return patch(
|
mock_provider.part_recv = AsyncMock()
|
||||||
"homeassistant.components.generic.config_flow.av.open",
|
mock_provider.part_recv.return_value = True
|
||||||
return_value=fake,
|
mock_stream.add_provider.return_value = mock_provider
|
||||||
|
mock_stream.start = AsyncMock()
|
||||||
|
mock_stream.stop = AsyncMock()
|
||||||
|
fake_create_stream = patch(
|
||||||
|
"homeassistant.components.generic.config_flow.create_stream",
|
||||||
|
return_value=mock_stream,
|
||||||
)
|
)
|
||||||
|
return fake_create_stream
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
@ -17,26 +17,25 @@ from tests.common import AsyncMock, Mock
|
|||||||
|
|
||||||
|
|
||||||
@respx.mock
|
@respx.mock
|
||||||
async def test_fetching_url(hass, hass_client, fakeimgbytes_png, mock_av_open):
|
async def test_fetching_url(hass, hass_client, fakeimgbytes_png):
|
||||||
"""Test that it fetches the given url."""
|
"""Test that it fetches the given url."""
|
||||||
respx.get("http://example.com").respond(stream=fakeimgbytes_png)
|
respx.get("http://example.com").respond(stream=fakeimgbytes_png)
|
||||||
|
|
||||||
with mock_av_open:
|
await async_setup_component(
|
||||||
await async_setup_component(
|
hass,
|
||||||
hass,
|
"camera",
|
||||||
"camera",
|
{
|
||||||
{
|
"camera": {
|
||||||
"camera": {
|
"name": "config_test",
|
||||||
"name": "config_test",
|
"platform": "generic",
|
||||||
"platform": "generic",
|
"still_image_url": "http://example.com",
|
||||||
"still_image_url": "http://example.com",
|
"username": "user",
|
||||||
"username": "user",
|
"password": "pass",
|
||||||
"password": "pass",
|
"authentication": "basic",
|
||||||
"authentication": "basic",
|
}
|
||||||
}
|
},
|
||||||
},
|
)
|
||||||
)
|
await hass.async_block_till_done()
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
|
||||||
client = await hass_client()
|
client = await hass_client()
|
||||||
|
|
||||||
@ -179,30 +178,27 @@ async def test_limit_refetch(hass, hass_client, fakeimgbytes_png, fakeimgbytes_j
|
|||||||
|
|
||||||
|
|
||||||
@respx.mock
|
@respx.mock
|
||||||
async def test_stream_source(
|
async def test_stream_source(hass, hass_client, hass_ws_client, fakeimgbytes_png):
|
||||||
hass, hass_client, hass_ws_client, fakeimgbytes_png, mock_av_open
|
|
||||||
):
|
|
||||||
"""Test that the stream source is rendered."""
|
"""Test that the stream source is rendered."""
|
||||||
respx.get("http://example.com").respond(stream=fakeimgbytes_png)
|
respx.get("http://example.com").respond(stream=fakeimgbytes_png)
|
||||||
respx.get("http://example.com/0a").respond(stream=fakeimgbytes_png)
|
respx.get("http://example.com/0a").respond(stream=fakeimgbytes_png)
|
||||||
|
|
||||||
hass.states.async_set("sensor.temp", "0")
|
hass.states.async_set("sensor.temp", "0")
|
||||||
with mock_av_open:
|
assert await async_setup_component(
|
||||||
assert await async_setup_component(
|
hass,
|
||||||
hass,
|
"camera",
|
||||||
"camera",
|
{
|
||||||
{
|
"camera": {
|
||||||
"camera": {
|
"name": "config_test",
|
||||||
"name": "config_test",
|
"platform": "generic",
|
||||||
"platform": "generic",
|
"still_image_url": "http://example.com",
|
||||||
"still_image_url": "http://example.com",
|
"stream_source": 'http://example.com/{{ states.sensor.temp.state + "a" }}',
|
||||||
"stream_source": 'http://example.com/{{ states.sensor.temp.state + "a" }}',
|
"limit_refetch_to_url_change": True,
|
||||||
"limit_refetch_to_url_change": True,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
)
|
},
|
||||||
assert await async_setup_component(hass, "stream", {})
|
)
|
||||||
await hass.async_block_till_done()
|
assert await async_setup_component(hass, "stream", {})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
hass.states.async_set("sensor.temp", "5")
|
hass.states.async_set("sensor.temp", "5")
|
||||||
|
|
||||||
@ -227,29 +223,26 @@ async def test_stream_source(
|
|||||||
|
|
||||||
|
|
||||||
@respx.mock
|
@respx.mock
|
||||||
async def test_stream_source_error(
|
async def test_stream_source_error(hass, hass_client, hass_ws_client, fakeimgbytes_png):
|
||||||
hass, hass_client, hass_ws_client, fakeimgbytes_png, mock_av_open
|
|
||||||
):
|
|
||||||
"""Test that the stream source has an error."""
|
"""Test that the stream source has an error."""
|
||||||
respx.get("http://example.com").respond(stream=fakeimgbytes_png)
|
respx.get("http://example.com").respond(stream=fakeimgbytes_png)
|
||||||
|
|
||||||
with mock_av_open:
|
assert await async_setup_component(
|
||||||
assert await async_setup_component(
|
hass,
|
||||||
hass,
|
"camera",
|
||||||
"camera",
|
{
|
||||||
{
|
"camera": {
|
||||||
"camera": {
|
"name": "config_test",
|
||||||
"name": "config_test",
|
"platform": "generic",
|
||||||
"platform": "generic",
|
"still_image_url": "http://example.com",
|
||||||
"still_image_url": "http://example.com",
|
# Does not exist
|
||||||
# Does not exist
|
"stream_source": 'http://example.com/{{ states.sensor.temp.state + "a" }}',
|
||||||
"stream_source": 'http://example.com/{{ states.sensor.temp.state + "a" }}',
|
"limit_refetch_to_url_change": True,
|
||||||
"limit_refetch_to_url_change": True,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
)
|
},
|
||||||
assert await async_setup_component(hass, "stream", {})
|
)
|
||||||
await hass.async_block_till_done()
|
assert await async_setup_component(hass, "stream", {})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.camera.Stream.endpoint_url",
|
"homeassistant.components.camera.Stream.endpoint_url",
|
||||||
@ -275,30 +268,27 @@ async def test_stream_source_error(
|
|||||||
|
|
||||||
|
|
||||||
@respx.mock
|
@respx.mock
|
||||||
async def test_setup_alternative_options(
|
async def test_setup_alternative_options(hass, hass_ws_client, fakeimgbytes_png):
|
||||||
hass, hass_ws_client, fakeimgbytes_png, mock_av_open
|
|
||||||
):
|
|
||||||
"""Test that the stream source is setup with different config options."""
|
"""Test that the stream source is setup with different config options."""
|
||||||
respx.get("https://example.com").respond(stream=fakeimgbytes_png)
|
respx.get("https://example.com").respond(stream=fakeimgbytes_png)
|
||||||
|
|
||||||
with mock_av_open:
|
assert await async_setup_component(
|
||||||
assert await async_setup_component(
|
hass,
|
||||||
hass,
|
"camera",
|
||||||
"camera",
|
{
|
||||||
{
|
"camera": {
|
||||||
"camera": {
|
"name": "config_test",
|
||||||
"name": "config_test",
|
"platform": "generic",
|
||||||
"platform": "generic",
|
"still_image_url": "https://example.com",
|
||||||
"still_image_url": "https://example.com",
|
"authentication": "digest",
|
||||||
"authentication": "digest",
|
"username": "user",
|
||||||
"username": "user",
|
"password": "pass",
|
||||||
"password": "pass",
|
"stream_source": "rtsp://example.com:554/rtsp/",
|
||||||
"stream_source": "rtsp://example.com:554/rtsp/",
|
"rtsp_transport": "udp",
|
||||||
"rtsp_transport": "udp",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
)
|
},
|
||||||
await hass.async_block_till_done()
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
assert hass.states.get("camera.config_test")
|
assert hass.states.get("camera.config_test")
|
||||||
|
|
||||||
|
|
||||||
@ -346,7 +336,7 @@ async def test_no_stream_source(hass, hass_client, hass_ws_client, fakeimgbytes_
|
|||||||
|
|
||||||
@respx.mock
|
@respx.mock
|
||||||
async def test_camera_content_type(
|
async def test_camera_content_type(
|
||||||
hass, hass_client, fakeimgbytes_svg, fakeimgbytes_jpg, mock_av_open
|
hass, hass_client, fakeimgbytes_svg, fakeimgbytes_jpg
|
||||||
):
|
):
|
||||||
"""Test generic camera with custom content_type."""
|
"""Test generic camera with custom content_type."""
|
||||||
urlsvg = "https://upload.wikimedia.org/wikipedia/commons/0/02/SVG_logo.svg"
|
urlsvg = "https://upload.wikimedia.org/wikipedia/commons/0/02/SVG_logo.svg"
|
||||||
@ -372,20 +362,18 @@ async def test_camera_content_type(
|
|||||||
"verify_ssl": True,
|
"verify_ssl": True,
|
||||||
}
|
}
|
||||||
|
|
||||||
with mock_av_open:
|
result1 = await hass.config_entries.flow.async_init(
|
||||||
result1 = await hass.config_entries.flow.async_init(
|
"generic",
|
||||||
"generic",
|
data=cam_config_jpg,
|
||||||
data=cam_config_jpg,
|
context={"source": SOURCE_IMPORT, "unique_id": 12345},
|
||||||
context={"source": SOURCE_IMPORT, "unique_id": 12345},
|
)
|
||||||
)
|
await hass.async_block_till_done()
|
||||||
await hass.async_block_till_done()
|
result2 = await hass.config_entries.flow.async_init(
|
||||||
with mock_av_open:
|
"generic",
|
||||||
result2 = await hass.config_entries.flow.async_init(
|
data=cam_config_svg,
|
||||||
"generic",
|
context={"source": SOURCE_IMPORT, "unique_id": 54321},
|
||||||
data=cam_config_svg,
|
)
|
||||||
context={"source": SOURCE_IMPORT, "unique_id": 54321},
|
await hass.async_block_till_done()
|
||||||
)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
|
||||||
assert result1["type"] == "create_entry"
|
assert result1["type"] == "create_entry"
|
||||||
assert result2["type"] == "create_entry"
|
assert result2["type"] == "create_entry"
|
||||||
@ -457,21 +445,20 @@ async def test_timeout_cancelled(hass, hass_client, fakeimgbytes_png, fakeimgbyt
|
|||||||
assert await resp.read() == fakeimgbytes_png
|
assert await resp.read() == fakeimgbytes_png
|
||||||
|
|
||||||
|
|
||||||
async def test_no_still_image_url(hass, hass_client, mock_av_open):
|
async def test_no_still_image_url(hass, hass_client):
|
||||||
"""Test that the component can grab images from stream with no still_image_url."""
|
"""Test that the component can grab images from stream with no still_image_url."""
|
||||||
with mock_av_open:
|
assert await async_setup_component(
|
||||||
assert await async_setup_component(
|
hass,
|
||||||
hass,
|
"camera",
|
||||||
"camera",
|
{
|
||||||
{
|
"camera": {
|
||||||
"camera": {
|
"name": "config_test",
|
||||||
"name": "config_test",
|
"platform": "generic",
|
||||||
"platform": "generic",
|
"stream_source": "rtsp://example.com:554/rtsp/",
|
||||||
"stream_source": "rtsp://example.com:554/rtsp/",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
)
|
},
|
||||||
await hass.async_block_till_done()
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
client = await hass_client()
|
client = await hass_client()
|
||||||
|
|
||||||
@ -503,23 +490,22 @@ async def test_no_still_image_url(hass, hass_client, mock_av_open):
|
|||||||
assert await resp.read() == b"stream_keyframe_image"
|
assert await resp.read() == b"stream_keyframe_image"
|
||||||
|
|
||||||
|
|
||||||
async def test_frame_interval_property(hass, mock_av_open):
|
async def test_frame_interval_property(hass):
|
||||||
"""Test that the frame interval is calculated and returned correctly."""
|
"""Test that the frame interval is calculated and returned correctly."""
|
||||||
|
|
||||||
with mock_av_open:
|
await async_setup_component(
|
||||||
await async_setup_component(
|
hass,
|
||||||
hass,
|
"camera",
|
||||||
"camera",
|
{
|
||||||
{
|
"camera": {
|
||||||
"camera": {
|
"name": "config_test",
|
||||||
"name": "config_test",
|
"platform": "generic",
|
||||||
"platform": "generic",
|
"stream_source": "rtsp://example.com:554/rtsp/",
|
||||||
"stream_source": "rtsp://example.com:554/rtsp/",
|
"framerate": 5,
|
||||||
"framerate": 5,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
)
|
},
|
||||||
await hass.async_block_till_done()
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
request = Mock()
|
request = Mock()
|
||||||
with patch(
|
with patch(
|
||||||
|
@ -2,9 +2,8 @@
|
|||||||
|
|
||||||
import errno
|
import errno
|
||||||
import os.path
|
import os.path
|
||||||
from unittest.mock import patch
|
from unittest.mock import AsyncMock, patch
|
||||||
|
|
||||||
import av
|
|
||||||
import httpx
|
import httpx
|
||||||
import pytest
|
import pytest
|
||||||
import respx
|
import respx
|
||||||
@ -23,6 +22,7 @@ from homeassistant.components.stream import (
|
|||||||
CONF_RTSP_TRANSPORT,
|
CONF_RTSP_TRANSPORT,
|
||||||
CONF_USE_WALLCLOCK_AS_TIMESTAMPS,
|
CONF_USE_WALLCLOCK_AS_TIMESTAMPS,
|
||||||
)
|
)
|
||||||
|
from homeassistant.components.stream.worker import StreamWorkerError
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_AUTHENTICATION,
|
CONF_AUTHENTICATION,
|
||||||
CONF_NAME,
|
CONF_NAME,
|
||||||
@ -57,10 +57,10 @@ TESTDATA_YAML = {
|
|||||||
|
|
||||||
|
|
||||||
@respx.mock
|
@respx.mock
|
||||||
async def test_form(hass, fakeimg_png, mock_av_open, user_flow):
|
async def test_form(hass, fakeimg_png, user_flow, mock_create_stream):
|
||||||
"""Test the form with a normal set of settings."""
|
"""Test the form with a normal set of settings."""
|
||||||
|
|
||||||
with mock_av_open as mock_setup:
|
with mock_create_stream as mock_setup:
|
||||||
result2 = await hass.config_entries.flow.async_configure(
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
user_flow["flow_id"],
|
user_flow["flow_id"],
|
||||||
TESTDATA,
|
TESTDATA,
|
||||||
@ -211,12 +211,12 @@ async def test_still_template(
|
|||||||
|
|
||||||
|
|
||||||
@respx.mock
|
@respx.mock
|
||||||
async def test_form_rtsp_mode(hass, fakeimg_png, mock_av_open, user_flow):
|
async def test_form_rtsp_mode(hass, fakeimg_png, user_flow, mock_create_stream):
|
||||||
"""Test we complete ok if the user enters a stream url."""
|
"""Test we complete ok if the user enters a stream url."""
|
||||||
with mock_av_open as mock_setup:
|
data = TESTDATA.copy()
|
||||||
data = TESTDATA.copy()
|
data[CONF_RTSP_TRANSPORT] = "tcp"
|
||||||
data[CONF_RTSP_TRANSPORT] = "tcp"
|
data[CONF_STREAM_SOURCE] = "rtsp://127.0.0.1/testurl/2"
|
||||||
data[CONF_STREAM_SOURCE] = "rtsp://127.0.0.1/testurl/2"
|
with mock_create_stream as mock_setup:
|
||||||
result2 = await hass.config_entries.flow.async_configure(
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
user_flow["flow_id"], data
|
user_flow["flow_id"], data
|
||||||
)
|
)
|
||||||
@ -240,7 +240,7 @@ async def test_form_rtsp_mode(hass, fakeimg_png, mock_av_open, user_flow):
|
|||||||
assert len(mock_setup.mock_calls) == 1
|
assert len(mock_setup.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
async def test_form_only_stream(hass, mock_av_open, fakeimgbytes_jpg):
|
async def test_form_only_stream(hass, fakeimgbytes_jpg, mock_create_stream):
|
||||||
"""Test we complete ok if the user wants stream only."""
|
"""Test we complete ok if the user wants stream only."""
|
||||||
await setup.async_setup_component(hass, "persistent_notification", {})
|
await setup.async_setup_component(hass, "persistent_notification", {})
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
@ -249,7 +249,7 @@ async def test_form_only_stream(hass, mock_av_open, fakeimgbytes_jpg):
|
|||||||
data = TESTDATA.copy()
|
data = TESTDATA.copy()
|
||||||
data.pop(CONF_STILL_IMAGE_URL)
|
data.pop(CONF_STILL_IMAGE_URL)
|
||||||
data[CONF_STREAM_SOURCE] = "rtsp://user:pass@127.0.0.1/testurl/2"
|
data[CONF_STREAM_SOURCE] = "rtsp://user:pass@127.0.0.1/testurl/2"
|
||||||
with mock_av_open as mock_setup:
|
with mock_create_stream as mock_setup:
|
||||||
result3 = await hass.config_entries.flow.async_configure(
|
result3 = await hass.config_entries.flow.async_configure(
|
||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
data,
|
data,
|
||||||
@ -294,13 +294,13 @@ async def test_form_still_and_stream_not_provided(hass, user_flow):
|
|||||||
|
|
||||||
|
|
||||||
@respx.mock
|
@respx.mock
|
||||||
async def test_form_image_timeout(hass, mock_av_open, user_flow):
|
async def test_form_image_timeout(hass, user_flow, mock_create_stream):
|
||||||
"""Test we handle invalid image timeout."""
|
"""Test we handle invalid image timeout."""
|
||||||
respx.get("http://127.0.0.1/testurl/1").side_effect = [
|
respx.get("http://127.0.0.1/testurl/1").side_effect = [
|
||||||
httpx.TimeoutException,
|
httpx.TimeoutException,
|
||||||
]
|
]
|
||||||
|
|
||||||
with mock_av_open:
|
with mock_create_stream:
|
||||||
result2 = await hass.config_entries.flow.async_configure(
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
user_flow["flow_id"],
|
user_flow["flow_id"],
|
||||||
TESTDATA,
|
TESTDATA,
|
||||||
@ -312,10 +312,10 @@ async def test_form_image_timeout(hass, mock_av_open, user_flow):
|
|||||||
|
|
||||||
|
|
||||||
@respx.mock
|
@respx.mock
|
||||||
async def test_form_stream_invalidimage(hass, mock_av_open, user_flow):
|
async def test_form_stream_invalidimage(hass, user_flow, mock_create_stream):
|
||||||
"""Test we handle invalid image when a stream is specified."""
|
"""Test we handle invalid image when a stream is specified."""
|
||||||
respx.get("http://127.0.0.1/testurl/1").respond(stream=b"invalid")
|
respx.get("http://127.0.0.1/testurl/1").respond(stream=b"invalid")
|
||||||
with mock_av_open:
|
with mock_create_stream:
|
||||||
result2 = await hass.config_entries.flow.async_configure(
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
user_flow["flow_id"],
|
user_flow["flow_id"],
|
||||||
TESTDATA,
|
TESTDATA,
|
||||||
@ -327,10 +327,10 @@ async def test_form_stream_invalidimage(hass, mock_av_open, user_flow):
|
|||||||
|
|
||||||
|
|
||||||
@respx.mock
|
@respx.mock
|
||||||
async def test_form_stream_invalidimage2(hass, mock_av_open, user_flow):
|
async def test_form_stream_invalidimage2(hass, user_flow, mock_create_stream):
|
||||||
"""Test we handle invalid image when a stream is specified."""
|
"""Test we handle invalid image when a stream is specified."""
|
||||||
respx.get("http://127.0.0.1/testurl/1").respond(content=None)
|
respx.get("http://127.0.0.1/testurl/1").respond(content=None)
|
||||||
with mock_av_open:
|
with mock_create_stream:
|
||||||
result2 = await hass.config_entries.flow.async_configure(
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
user_flow["flow_id"],
|
user_flow["flow_id"],
|
||||||
TESTDATA,
|
TESTDATA,
|
||||||
@ -342,10 +342,10 @@ async def test_form_stream_invalidimage2(hass, mock_av_open, user_flow):
|
|||||||
|
|
||||||
|
|
||||||
@respx.mock
|
@respx.mock
|
||||||
async def test_form_stream_invalidimage3(hass, mock_av_open, user_flow):
|
async def test_form_stream_invalidimage3(hass, user_flow, mock_create_stream):
|
||||||
"""Test we handle invalid image when a stream is specified."""
|
"""Test we handle invalid image when a stream is specified."""
|
||||||
respx.get("http://127.0.0.1/testurl/1").respond(content=bytes([0xFF]))
|
respx.get("http://127.0.0.1/testurl/1").respond(content=bytes([0xFF]))
|
||||||
with mock_av_open:
|
with mock_create_stream:
|
||||||
result2 = await hass.config_entries.flow.async_configure(
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
user_flow["flow_id"],
|
user_flow["flow_id"],
|
||||||
TESTDATA,
|
TESTDATA,
|
||||||
@ -356,43 +356,17 @@ async def test_form_stream_invalidimage3(hass, mock_av_open, user_flow):
|
|||||||
assert result2["errors"] == {"still_image_url": "invalid_still_image"}
|
assert result2["errors"] == {"still_image_url": "invalid_still_image"}
|
||||||
|
|
||||||
|
|
||||||
@respx.mock
|
|
||||||
async def test_form_stream_file_not_found(hass, fakeimg_png, user_flow):
|
|
||||||
"""Test we handle file not found."""
|
|
||||||
with patch(
|
|
||||||
"homeassistant.components.generic.config_flow.av.open",
|
|
||||||
side_effect=av.error.FileNotFoundError(0, 0),
|
|
||||||
):
|
|
||||||
result2 = await hass.config_entries.flow.async_configure(
|
|
||||||
user_flow["flow_id"],
|
|
||||||
TESTDATA,
|
|
||||||
)
|
|
||||||
assert result2["type"] == "form"
|
|
||||||
assert result2["errors"] == {"stream_source": "stream_file_not_found"}
|
|
||||||
|
|
||||||
|
|
||||||
@respx.mock
|
|
||||||
async def test_form_stream_http_not_found(hass, fakeimg_png, user_flow):
|
|
||||||
"""Test we handle invalid auth."""
|
|
||||||
with patch(
|
|
||||||
"homeassistant.components.generic.config_flow.av.open",
|
|
||||||
side_effect=av.error.HTTPNotFoundError(0, 0),
|
|
||||||
):
|
|
||||||
result2 = await hass.config_entries.flow.async_configure(
|
|
||||||
user_flow["flow_id"],
|
|
||||||
TESTDATA,
|
|
||||||
)
|
|
||||||
assert result2["type"] == "form"
|
|
||||||
assert result2["errors"] == {"stream_source": "stream_http_not_found"}
|
|
||||||
|
|
||||||
|
|
||||||
@respx.mock
|
@respx.mock
|
||||||
async def test_form_stream_timeout(hass, fakeimg_png, user_flow):
|
async def test_form_stream_timeout(hass, fakeimg_png, user_flow):
|
||||||
"""Test we handle invalid auth."""
|
"""Test we handle invalid auth."""
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.generic.config_flow.av.open",
|
"homeassistant.components.generic.config_flow.create_stream"
|
||||||
side_effect=av.error.TimeoutError(0, 0),
|
) as create_stream:
|
||||||
):
|
create_stream.return_value.start = AsyncMock()
|
||||||
|
create_stream.return_value.add_provider.return_value.part_recv = AsyncMock()
|
||||||
|
create_stream.return_value.add_provider.return_value.part_recv.return_value = (
|
||||||
|
False
|
||||||
|
)
|
||||||
result2 = await hass.config_entries.flow.async_configure(
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
user_flow["flow_id"],
|
user_flow["flow_id"],
|
||||||
TESTDATA,
|
TESTDATA,
|
||||||
@ -402,32 +376,18 @@ async def test_form_stream_timeout(hass, fakeimg_png, user_flow):
|
|||||||
|
|
||||||
|
|
||||||
@respx.mock
|
@respx.mock
|
||||||
async def test_form_stream_unauthorised(hass, fakeimg_png, user_flow):
|
async def test_form_stream_worker_error(hass, fakeimg_png, user_flow):
|
||||||
"""Test we handle invalid auth."""
|
"""Test we handle a StreamWorkerError and pass the message through."""
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.generic.config_flow.av.open",
|
"homeassistant.components.generic.config_flow.create_stream",
|
||||||
side_effect=av.error.HTTPUnauthorizedError(0, 0),
|
side_effect=StreamWorkerError("Some message"),
|
||||||
):
|
):
|
||||||
result2 = await hass.config_entries.flow.async_configure(
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
user_flow["flow_id"],
|
user_flow["flow_id"],
|
||||||
TESTDATA,
|
TESTDATA,
|
||||||
)
|
)
|
||||||
assert result2["type"] == "form"
|
assert result2["type"] == "form"
|
||||||
assert result2["errors"] == {"stream_source": "stream_unauthorised"}
|
assert result2["errors"] == {"stream_source": "Some message"}
|
||||||
|
|
||||||
|
|
||||||
@respx.mock
|
|
||||||
async def test_form_stream_novideo(hass, fakeimg_png, user_flow):
|
|
||||||
"""Test we handle invalid stream."""
|
|
||||||
with patch(
|
|
||||||
"homeassistant.components.generic.config_flow.av.open", side_effect=KeyError()
|
|
||||||
):
|
|
||||||
result2 = await hass.config_entries.flow.async_configure(
|
|
||||||
user_flow["flow_id"],
|
|
||||||
TESTDATA,
|
|
||||||
)
|
|
||||||
assert result2["type"] == "form"
|
|
||||||
assert result2["errors"] == {"stream_source": "stream_no_video"}
|
|
||||||
|
|
||||||
|
|
||||||
@respx.mock
|
@respx.mock
|
||||||
@ -435,7 +395,7 @@ async def test_form_stream_permission_error(hass, fakeimgbytes_png, user_flow):
|
|||||||
"""Test we handle permission error."""
|
"""Test we handle permission error."""
|
||||||
respx.get("http://127.0.0.1/testurl/1").respond(stream=fakeimgbytes_png)
|
respx.get("http://127.0.0.1/testurl/1").respond(stream=fakeimgbytes_png)
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.generic.config_flow.av.open",
|
"homeassistant.components.generic.config_flow.create_stream",
|
||||||
side_effect=PermissionError(),
|
side_effect=PermissionError(),
|
||||||
):
|
):
|
||||||
result2 = await hass.config_entries.flow.async_configure(
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
@ -450,7 +410,7 @@ async def test_form_stream_permission_error(hass, fakeimgbytes_png, user_flow):
|
|||||||
async def test_form_no_route_to_host(hass, fakeimg_png, user_flow):
|
async def test_form_no_route_to_host(hass, fakeimg_png, user_flow):
|
||||||
"""Test we handle no route to host."""
|
"""Test we handle no route to host."""
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.generic.config_flow.av.open",
|
"homeassistant.components.generic.config_flow.create_stream",
|
||||||
side_effect=OSError(errno.EHOSTUNREACH, "No route to host"),
|
side_effect=OSError(errno.EHOSTUNREACH, "No route to host"),
|
||||||
):
|
):
|
||||||
result2 = await hass.config_entries.flow.async_configure(
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
@ -465,7 +425,7 @@ async def test_form_no_route_to_host(hass, fakeimg_png, user_flow):
|
|||||||
async def test_form_stream_io_error(hass, fakeimg_png, user_flow):
|
async def test_form_stream_io_error(hass, fakeimg_png, user_flow):
|
||||||
"""Test we handle no io error when setting up stream."""
|
"""Test we handle no io error when setting up stream."""
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.generic.config_flow.av.open",
|
"homeassistant.components.generic.config_flow.create_stream",
|
||||||
side_effect=OSError(errno.EIO, "Input/output error"),
|
side_effect=OSError(errno.EIO, "Input/output error"),
|
||||||
):
|
):
|
||||||
result2 = await hass.config_entries.flow.async_configure(
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
@ -480,7 +440,7 @@ async def test_form_stream_io_error(hass, fakeimg_png, user_flow):
|
|||||||
async def test_form_oserror(hass, fakeimg_png, user_flow):
|
async def test_form_oserror(hass, fakeimg_png, user_flow):
|
||||||
"""Test we handle OS error when setting up stream."""
|
"""Test we handle OS error when setting up stream."""
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.generic.config_flow.av.open",
|
"homeassistant.components.generic.config_flow.create_stream",
|
||||||
side_effect=OSError("Some other OSError"),
|
side_effect=OSError("Some other OSError"),
|
||||||
), pytest.raises(OSError):
|
), pytest.raises(OSError):
|
||||||
await hass.config_entries.flow.async_configure(
|
await hass.config_entries.flow.async_configure(
|
||||||
@ -490,7 +450,7 @@ async def test_form_oserror(hass, fakeimg_png, user_flow):
|
|||||||
|
|
||||||
|
|
||||||
@respx.mock
|
@respx.mock
|
||||||
async def test_options_template_error(hass, fakeimgbytes_png, mock_av_open):
|
async def test_options_template_error(hass, fakeimgbytes_png, mock_create_stream):
|
||||||
"""Test the options flow with a template error."""
|
"""Test the options flow with a template error."""
|
||||||
respx.get("http://127.0.0.1/testurl/1").respond(stream=fakeimgbytes_png)
|
respx.get("http://127.0.0.1/testurl/1").respond(stream=fakeimgbytes_png)
|
||||||
respx.get("http://127.0.0.1/testurl/2").respond(stream=fakeimgbytes_png)
|
respx.get("http://127.0.0.1/testurl/2").respond(stream=fakeimgbytes_png)
|
||||||
@ -503,18 +463,18 @@ async def test_options_template_error(hass, fakeimgbytes_png, mock_av_open):
|
|||||||
options=TESTDATA,
|
options=TESTDATA,
|
||||||
)
|
)
|
||||||
|
|
||||||
with mock_av_open:
|
mock_entry.add_to_hass(hass)
|
||||||
mock_entry.add_to_hass(hass)
|
await hass.config_entries.async_setup(mock_entry.entry_id)
|
||||||
await hass.config_entries.async_setup(mock_entry.entry_id)
|
await hass.async_block_till_done()
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
|
||||||
result = await hass.config_entries.options.async_init(mock_entry.entry_id)
|
result = await hass.config_entries.options.async_init(mock_entry.entry_id)
|
||||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||||
assert result["step_id"] == "init"
|
assert result["step_id"] == "init"
|
||||||
|
|
||||||
# try updating the still image url
|
# try updating the still image url
|
||||||
data = TESTDATA.copy()
|
data = TESTDATA.copy()
|
||||||
data[CONF_STILL_IMAGE_URL] = "http://127.0.0.1/testurl/2"
|
data[CONF_STILL_IMAGE_URL] = "http://127.0.0.1/testurl/2"
|
||||||
|
with mock_create_stream:
|
||||||
result2 = await hass.config_entries.options.async_configure(
|
result2 = await hass.config_entries.options.async_configure(
|
||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
user_input=data,
|
user_input=data,
|
||||||
@ -541,12 +501,12 @@ async def test_options_template_error(hass, fakeimgbytes_png, mock_av_open):
|
|||||||
result4["flow_id"],
|
result4["flow_id"],
|
||||||
user_input=data,
|
user_input=data,
|
||||||
)
|
)
|
||||||
assert result5.get("type") == data_entry_flow.RESULT_TYPE_FORM
|
assert result5.get("type") == data_entry_flow.RESULT_TYPE_FORM
|
||||||
assert result5["errors"] == {"stream_source": "template_error"}
|
assert result5["errors"] == {"stream_source": "template_error"}
|
||||||
|
|
||||||
|
|
||||||
@respx.mock
|
@respx.mock
|
||||||
async def test_options_only_stream(hass, fakeimgbytes_png, mock_av_open):
|
async def test_options_only_stream(hass, fakeimgbytes_png, mock_create_stream):
|
||||||
"""Test the options flow without a still_image_url."""
|
"""Test the options flow without a still_image_url."""
|
||||||
respx.get("http://127.0.0.1/testurl/2").respond(stream=fakeimgbytes_png)
|
respx.get("http://127.0.0.1/testurl/2").respond(stream=fakeimgbytes_png)
|
||||||
data = TESTDATA.copy()
|
data = TESTDATA.copy()
|
||||||
@ -558,36 +518,35 @@ async def test_options_only_stream(hass, fakeimgbytes_png, mock_av_open):
|
|||||||
data={},
|
data={},
|
||||||
options=data,
|
options=data,
|
||||||
)
|
)
|
||||||
with mock_av_open:
|
mock_entry.add_to_hass(hass)
|
||||||
mock_entry.add_to_hass(hass)
|
await hass.config_entries.async_setup(mock_entry.entry_id)
|
||||||
await hass.config_entries.async_setup(mock_entry.entry_id)
|
await hass.async_block_till_done()
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
|
||||||
result = await hass.config_entries.options.async_init(mock_entry.entry_id)
|
result = await hass.config_entries.options.async_init(mock_entry.entry_id)
|
||||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||||
assert result["step_id"] == "init"
|
assert result["step_id"] == "init"
|
||||||
|
|
||||||
# try updating the config options
|
# try updating the config options
|
||||||
|
with mock_create_stream:
|
||||||
result3 = await hass.config_entries.options.async_configure(
|
result3 = await hass.config_entries.options.async_configure(
|
||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
user_input=data,
|
user_input=data,
|
||||||
)
|
)
|
||||||
assert result3["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
assert result3["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||||
assert result3["data"][CONF_CONTENT_TYPE] == "image/jpeg"
|
assert result3["data"][CONF_CONTENT_TYPE] == "image/jpeg"
|
||||||
|
|
||||||
|
|
||||||
# These below can be deleted after deprecation period is finished.
|
# These below can be deleted after deprecation period is finished.
|
||||||
@respx.mock
|
@respx.mock
|
||||||
async def test_import(hass, fakeimg_png, mock_av_open):
|
async def test_import(hass, fakeimg_png):
|
||||||
"""Test configuration.yaml import used during migration."""
|
"""Test configuration.yaml import used during migration."""
|
||||||
with mock_av_open:
|
result = await hass.config_entries.flow.async_init(
|
||||||
result = await hass.config_entries.flow.async_init(
|
DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data=TESTDATA_YAML
|
||||||
DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data=TESTDATA_YAML
|
)
|
||||||
)
|
# duplicate import should be aborted
|
||||||
# duplicate import should be aborted
|
result2 = await hass.config_entries.flow.async_init(
|
||||||
result2 = await hass.config_entries.flow.async_init(
|
DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data=TESTDATA_YAML
|
||||||
DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data=TESTDATA_YAML
|
)
|
||||||
)
|
|
||||||
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||||
assert result["title"] == "Yaml Defined Name"
|
assert result["title"] == "Yaml Defined Name"
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
@ -599,7 +558,7 @@ async def test_import(hass, fakeimg_png, mock_av_open):
|
|||||||
# These above can be deleted after deprecation period is finished.
|
# These above can be deleted after deprecation period is finished.
|
||||||
|
|
||||||
|
|
||||||
async def test_unload_entry(hass, fakeimg_png, mock_av_open):
|
async def test_unload_entry(hass, fakeimg_png):
|
||||||
"""Test unloading the generic IP Camera entry."""
|
"""Test unloading the generic IP Camera entry."""
|
||||||
mock_entry = MockConfigEntry(domain=DOMAIN, options=TESTDATA)
|
mock_entry = MockConfigEntry(domain=DOMAIN, options=TESTDATA)
|
||||||
mock_entry.add_to_hass(hass)
|
mock_entry.add_to_hass(hass)
|
||||||
@ -669,7 +628,9 @@ async def test_migrate_existing_ids(hass) -> None:
|
|||||||
|
|
||||||
|
|
||||||
@respx.mock
|
@respx.mock
|
||||||
async def test_use_wallclock_as_timestamps_option(hass, fakeimg_png, mock_av_open):
|
async def test_use_wallclock_as_timestamps_option(
|
||||||
|
hass, fakeimg_png, mock_create_stream
|
||||||
|
):
|
||||||
"""Test the use_wallclock_as_timestamps option flow."""
|
"""Test the use_wallclock_as_timestamps option flow."""
|
||||||
|
|
||||||
mock_entry = MockConfigEntry(
|
mock_entry = MockConfigEntry(
|
||||||
@ -679,19 +640,18 @@ async def test_use_wallclock_as_timestamps_option(hass, fakeimg_png, mock_av_ope
|
|||||||
options=TESTDATA,
|
options=TESTDATA,
|
||||||
)
|
)
|
||||||
|
|
||||||
with mock_av_open:
|
mock_entry.add_to_hass(hass)
|
||||||
mock_entry.add_to_hass(hass)
|
await hass.config_entries.async_setup(mock_entry.entry_id)
|
||||||
await hass.config_entries.async_setup(mock_entry.entry_id)
|
await hass.async_block_till_done()
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
|
||||||
result = await hass.config_entries.options.async_init(
|
|
||||||
mock_entry.entry_id, context={"show_advanced_options": True}
|
|
||||||
)
|
|
||||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
|
||||||
assert result["step_id"] == "init"
|
|
||||||
|
|
||||||
|
result = await hass.config_entries.options.async_init(
|
||||||
|
mock_entry.entry_id, context={"show_advanced_options": True}
|
||||||
|
)
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||||
|
assert result["step_id"] == "init"
|
||||||
|
with mock_create_stream:
|
||||||
result2 = await hass.config_entries.options.async_configure(
|
result2 = await hass.config_entries.options.async_configure(
|
||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
user_input={CONF_USE_WALLCLOCK_AS_TIMESTAMPS: True, **TESTDATA},
|
user_input={CONF_USE_WALLCLOCK_AS_TIMESTAMPS: True, **TESTDATA},
|
||||||
)
|
)
|
||||||
assert result2["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
assert result2["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||||
|
Loading…
x
Reference in New Issue
Block a user