1
0
mirror of https://github.com/home-assistant/core.git synced 2025-12-24 21:06:19 +00:00

Use create_stream in generic camera config flow (#73237)

* Use create_stream in generic camera config flow
This commit is contained in:
uvjustin
2022-06-11 15:38:43 +10:00
committed by GitHub
parent 21cfbe875e
commit b1f2e5f897
5 changed files with 220 additions and 291 deletions

View File

@@ -2,9 +2,8 @@
import errno
import os.path
from unittest.mock import patch
from unittest.mock import AsyncMock, patch
import av
import httpx
import pytest
import respx
@@ -23,6 +22,7 @@ from homeassistant.components.stream import (
CONF_RTSP_TRANSPORT,
CONF_USE_WALLCLOCK_AS_TIMESTAMPS,
)
from homeassistant.components.stream.worker import StreamWorkerError
from homeassistant.const import (
CONF_AUTHENTICATION,
CONF_NAME,
@@ -57,10 +57,10 @@ TESTDATA_YAML = {
@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."""
with mock_av_open as mock_setup:
with mock_create_stream as mock_setup:
result2 = await hass.config_entries.flow.async_configure(
user_flow["flow_id"],
TESTDATA,
@@ -211,12 +211,12 @@ async def test_still_template(
@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."""
with mock_av_open as mock_setup:
data = TESTDATA.copy()
data[CONF_RTSP_TRANSPORT] = "tcp"
data[CONF_STREAM_SOURCE] = "rtsp://127.0.0.1/testurl/2"
data = TESTDATA.copy()
data[CONF_RTSP_TRANSPORT] = "tcp"
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(
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
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."""
await setup.async_setup_component(hass, "persistent_notification", {})
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.pop(CONF_STILL_IMAGE_URL)
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(
result["flow_id"],
data,
@@ -294,13 +294,13 @@ async def test_form_still_and_stream_not_provided(hass, user_flow):
@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."""
respx.get("http://127.0.0.1/testurl/1").side_effect = [
httpx.TimeoutException,
]
with mock_av_open:
with mock_create_stream:
result2 = await hass.config_entries.flow.async_configure(
user_flow["flow_id"],
TESTDATA,
@@ -312,10 +312,10 @@ async def test_form_image_timeout(hass, mock_av_open, user_flow):
@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."""
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(
user_flow["flow_id"],
TESTDATA,
@@ -327,10 +327,10 @@ async def test_form_stream_invalidimage(hass, mock_av_open, user_flow):
@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."""
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(
user_flow["flow_id"],
TESTDATA,
@@ -342,10 +342,10 @@ async def test_form_stream_invalidimage2(hass, mock_av_open, user_flow):
@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."""
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(
user_flow["flow_id"],
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"}
@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
async def test_form_stream_timeout(hass, fakeimg_png, user_flow):
"""Test we handle invalid auth."""
with patch(
"homeassistant.components.generic.config_flow.av.open",
side_effect=av.error.TimeoutError(0, 0),
):
"homeassistant.components.generic.config_flow.create_stream"
) 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(
user_flow["flow_id"],
TESTDATA,
@@ -402,32 +376,18 @@ async def test_form_stream_timeout(hass, fakeimg_png, user_flow):
@respx.mock
async def test_form_stream_unauthorised(hass, fakeimg_png, user_flow):
"""Test we handle invalid auth."""
async def test_form_stream_worker_error(hass, fakeimg_png, user_flow):
"""Test we handle a StreamWorkerError and pass the message through."""
with patch(
"homeassistant.components.generic.config_flow.av.open",
side_effect=av.error.HTTPUnauthorizedError(0, 0),
"homeassistant.components.generic.config_flow.create_stream",
side_effect=StreamWorkerError("Some message"),
):
result2 = await hass.config_entries.flow.async_configure(
user_flow["flow_id"],
TESTDATA,
)
assert result2["type"] == "form"
assert result2["errors"] == {"stream_source": "stream_unauthorised"}
@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"}
assert result2["errors"] == {"stream_source": "Some message"}
@respx.mock
@@ -435,7 +395,7 @@ async def test_form_stream_permission_error(hass, fakeimgbytes_png, user_flow):
"""Test we handle permission error."""
respx.get("http://127.0.0.1/testurl/1").respond(stream=fakeimgbytes_png)
with patch(
"homeassistant.components.generic.config_flow.av.open",
"homeassistant.components.generic.config_flow.create_stream",
side_effect=PermissionError(),
):
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):
"""Test we handle no route to host."""
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"),
):
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):
"""Test we handle no io error when setting up stream."""
with patch(
"homeassistant.components.generic.config_flow.av.open",
"homeassistant.components.generic.config_flow.create_stream",
side_effect=OSError(errno.EIO, "Input/output error"),
):
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):
"""Test we handle OS error when setting up stream."""
with patch(
"homeassistant.components.generic.config_flow.av.open",
"homeassistant.components.generic.config_flow.create_stream",
side_effect=OSError("Some other OSError"),
), pytest.raises(OSError):
await hass.config_entries.flow.async_configure(
@@ -490,7 +450,7 @@ async def test_form_oserror(hass, fakeimg_png, user_flow):
@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."""
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)
@@ -503,18 +463,18 @@ async def test_options_template_error(hass, fakeimgbytes_png, mock_av_open):
options=TESTDATA,
)
with mock_av_open:
mock_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_entry.entry_id)
await hass.async_block_till_done()
mock_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_entry.entry_id)
await hass.async_block_till_done()
result = await hass.config_entries.options.async_init(mock_entry.entry_id)
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)
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "init"
# try updating the still image url
data = TESTDATA.copy()
data[CONF_STILL_IMAGE_URL] = "http://127.0.0.1/testurl/2"
# try updating the still image url
data = TESTDATA.copy()
data[CONF_STILL_IMAGE_URL] = "http://127.0.0.1/testurl/2"
with mock_create_stream:
result2 = await hass.config_entries.options.async_configure(
result["flow_id"],
user_input=data,
@@ -541,12 +501,12 @@ async def test_options_template_error(hass, fakeimgbytes_png, mock_av_open):
result4["flow_id"],
user_input=data,
)
assert result5.get("type") == data_entry_flow.RESULT_TYPE_FORM
assert result5["errors"] == {"stream_source": "template_error"}
assert result5.get("type") == data_entry_flow.RESULT_TYPE_FORM
assert result5["errors"] == {"stream_source": "template_error"}
@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."""
respx.get("http://127.0.0.1/testurl/2").respond(stream=fakeimgbytes_png)
data = TESTDATA.copy()
@@ -558,36 +518,35 @@ async def test_options_only_stream(hass, fakeimgbytes_png, mock_av_open):
data={},
options=data,
)
with mock_av_open:
mock_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_entry.entry_id)
await hass.async_block_till_done()
mock_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_entry.entry_id)
await hass.async_block_till_done()
result = await hass.config_entries.options.async_init(mock_entry.entry_id)
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)
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
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(
result["flow_id"],
user_input=data,
)
assert result3["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert result3["data"][CONF_CONTENT_TYPE] == "image/jpeg"
assert result3["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert result3["data"][CONF_CONTENT_TYPE] == "image/jpeg"
# These below can be deleted after deprecation period is finished.
@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."""
with mock_av_open:
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data=TESTDATA_YAML
)
# duplicate import should be aborted
result2 = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data=TESTDATA_YAML
)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data=TESTDATA_YAML
)
# duplicate import should be aborted
result2 = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data=TESTDATA_YAML
)
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert result["title"] == "Yaml Defined Name"
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.
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."""
mock_entry = MockConfigEntry(domain=DOMAIN, options=TESTDATA)
mock_entry.add_to_hass(hass)
@@ -669,7 +628,9 @@ async def test_migrate_existing_ids(hass) -> None:
@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."""
mock_entry = MockConfigEntry(
@@ -679,19 +640,18 @@ async def test_use_wallclock_as_timestamps_option(hass, fakeimg_png, mock_av_ope
options=TESTDATA,
)
with mock_av_open:
mock_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_entry.entry_id)
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"
mock_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_entry.entry_id)
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"
with mock_create_stream:
result2 = await hass.config_entries.options.async_configure(
result["flow_id"],
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