diff --git a/tests/components/generic/conftest.py b/tests/components/generic/conftest.py index cdea83b599c..96cdfe41d0d 100644 --- a/tests/components/generic/conftest.py +++ b/tests/components/generic/conftest.py @@ -4,7 +4,7 @@ from __future__ import annotations from collections.abc import Generator from io import BytesIO -from unittest.mock import AsyncMock, MagicMock, Mock, _patch, patch +from unittest.mock import AsyncMock, MagicMock, Mock, patch from PIL import Image import pytest @@ -54,11 +54,15 @@ def fakeimgbytes_gif() -> bytes: @pytest.fixture def fakeimg_png(fakeimgbytes_png: bytes) -> Generator[None]: """Set up respx to respond to test url with fake image bytes.""" - respx.get("http://127.0.0.1/testurl/1", name="fake_img").respond( + respx.get("http://127.0.0.1/testurl/1", name="fake_img1").respond( + stream=fakeimgbytes_png + ) + respx.get("http://127.0.0.1/testurl/2", name="fake_img2").respond( stream=fakeimgbytes_png ) yield - respx.pop("fake_img") + respx.pop("fake_img1") + respx.pop("fake_img2") @pytest.fixture @@ -71,8 +75,8 @@ def fakeimg_gif(fakeimgbytes_gif: bytes) -> Generator[None]: respx.pop("fake_img") -@pytest.fixture -def mock_create_stream(hass: HomeAssistant) -> _patch[MagicMock]: +@pytest.fixture(name="mock_create_stream") +def mock_create_stream(hass: HomeAssistant) -> Generator[AsyncMock]: """Mock create stream.""" mock_stream = MagicMock() mock_stream.hass = hass @@ -83,14 +87,25 @@ def mock_create_stream(hass: HomeAssistant) -> _patch[MagicMock]: mock_stream.start = AsyncMock() mock_stream.stop = AsyncMock() mock_stream.endpoint_url.return_value = "http://127.0.0.1/nothing" - return patch( + with patch( "homeassistant.components.generic.config_flow.create_stream", return_value=mock_stream, - ) + ) as mock_create_stream: + yield mock_create_stream @pytest.fixture -async def user_flow(hass: HomeAssistant) -> ConfigFlowResult: +def mock_setup_entry() -> Generator[AsyncMock]: + """Mock setup entry.""" + with patch( + "homeassistant.components.generic.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + yield mock_setup_entry + + +@pytest.fixture(name="user_flow") +async def user_flow_fixture(hass: HomeAssistant) -> ConfigFlowResult: """Initiate a user flow.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} @@ -126,8 +141,8 @@ def config_entry_fixture(hass: HomeAssistant) -> MockConfigEntry: return entry -@pytest.fixture -async def setup_entry( +@pytest.fixture(name="setup_entry") +async def setup_entry_fixture( hass: HomeAssistant, config_entry: MockConfigEntry ) -> MockConfigEntry: """Set up a config entry ready to be used in tests.""" diff --git a/tests/components/generic/test_config_flow.py b/tests/components/generic/test_config_flow.py index 9eee49619b5..19af6cd7a09 100644 --- a/tests/components/generic/test_config_flow.py +++ b/tests/components/generic/test_config_flow.py @@ -2,6 +2,7 @@ from __future__ import annotations +from collections.abc import Generator import contextlib import errno from http import HTTPStatus @@ -9,12 +10,10 @@ import os.path from pathlib import Path from unittest.mock import AsyncMock, MagicMock, PropertyMock, _patch, patch -from freezegun.api import FrozenDateTimeFactory import httpx import pytest import respx -from homeassistant import config_entries from homeassistant.components.camera import async_get_image from homeassistant.components.generic.config_flow import slug from homeassistant.components.generic.const import ( @@ -30,7 +29,7 @@ from homeassistant.components.stream import ( CONF_RTSP_TRANSPORT, CONF_USE_WALLCLOCK_AS_TIMESTAMPS, ) -from homeassistant.config_entries import ConfigEntryState, ConfigFlowResult +from homeassistant.config_entries import ConfigFlowResult from homeassistant.const import ( CONF_AUTHENTICATION, CONF_NAME, @@ -44,7 +43,7 @@ from homeassistant.data_entry_flow import FlowResultType from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity_registry as er -from tests.common import MockConfigEntry, async_fire_time_changed +from tests.common import MockConfigEntry from tests.typing import ClientSessionGenerator, WebSocketGenerator TESTDATA = { @@ -69,52 +68,47 @@ TESTDATA_YAML = { @respx.mock +@pytest.mark.usefixtures("fakeimg_png") async def test_form( hass: HomeAssistant, fakeimgbytes_png: bytes, hass_client: ClientSessionGenerator, user_flow: ConfigFlowResult, mock_create_stream: _patch[MagicMock], + mock_setup_entry: _patch[MagicMock], hass_ws_client: WebSocketGenerator, ) -> None: """Test the form with a normal set of settings.""" - respx.get("http://127.0.0.1/testurl/1").respond(stream=fakeimgbytes_png) - with ( - mock_create_stream as mock_setup, - patch( - "homeassistant.components.generic.async_setup_entry", return_value=True - ) as mock_setup_entry, - ): - result1 = await hass.config_entries.flow.async_configure( - user_flow["flow_id"], - TESTDATA, - ) - assert result1["type"] is FlowResultType.FORM - assert result1["step_id"] == "user_confirm" + result1 = await hass.config_entries.flow.async_configure( + user_flow["flow_id"], + TESTDATA, + ) + assert result1["type"] is FlowResultType.FORM + assert result1["step_id"] == "user_confirm" - # HA should now be serving a WS connection for a preview stream. - ws_client = await hass_ws_client() - flow_id = user_flow["flow_id"] - await ws_client.send_json_auto_id( - { - "type": "generic_camera/start_preview", - "flow_id": flow_id, - }, - ) - json = await ws_client.receive_json() + # HA should now be serving a WS connection for a preview stream. + ws_client = await hass_ws_client() + flow_id = user_flow["flow_id"] + await ws_client.send_json_auto_id( + { + "type": "generic_camera/start_preview", + "flow_id": flow_id, + }, + ) + json = await ws_client.receive_json() - client = await hass_client() - still_preview_url = json["event"]["attributes"]["still_url"] - # Check the preview image works. - resp = await client.get(still_preview_url) - assert resp.status == HTTPStatus.OK - assert await resp.read() == fakeimgbytes_png + client = await hass_client() + still_preview_url = json["event"]["attributes"]["still_url"] + # Check the preview image works. + resp = await client.get(still_preview_url) + assert resp.status == HTTPStatus.OK + assert await resp.read() == fakeimgbytes_png - result2 = await hass.config_entries.flow.async_configure( - result1["flow_id"], - user_input={CONF_CONFIRMED_OK: True}, - ) + result2 = await hass.config_entries.flow.async_configure( + result1["flow_id"], + user_input={CONF_CONFIRMED_OK: True}, + ) assert result2["type"] is FlowResultType.CREATE_ENTRY assert result2["title"] == "127_0_0_1" assert result2["options"] == { @@ -131,36 +125,29 @@ async def test_form( # Check that the preview image is disabled after. resp = await client.get(still_preview_url) assert resp.status == HTTPStatus.NOT_FOUND - assert len(mock_setup.mock_calls) == 1 - assert len(mock_setup_entry.mock_calls) == 1 @respx.mock @pytest.mark.usefixtures("fakeimg_png") async def test_form_only_stillimage( - hass: HomeAssistant, user_flow: ConfigFlowResult + hass: HomeAssistant, + user_flow: ConfigFlowResult, + mock_setup_entry: _patch[MagicMock], ) -> None: """Test we complete ok if the user wants still images only.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) - assert result["type"] is FlowResultType.FORM - assert result["errors"] == {} - data = TESTDATA.copy() data.pop(CONF_STREAM_SOURCE) - with patch("homeassistant.components.generic.async_setup_entry", return_value=True): - result1 = await hass.config_entries.flow.async_configure( - user_flow["flow_id"], - data, - ) - await hass.async_block_till_done() - assert result1["type"] is FlowResultType.FORM - assert result1["step_id"] == "user_confirm" - result2 = await hass.config_entries.flow.async_configure( - result1["flow_id"], - user_input={CONF_CONFIRMED_OK: True}, - ) + result1 = await hass.config_entries.flow.async_configure( + user_flow["flow_id"], + data, + ) + await hass.async_block_till_done() + assert result1["type"] is FlowResultType.FORM + assert result1["step_id"] == "user_confirm" + result2 = await hass.config_entries.flow.async_configure( + result1["flow_id"], + user_input={CONF_CONFIRMED_OK: True}, + ) assert result2["type"] is FlowResultType.CREATE_ENTRY assert result2["title"] == "127_0_0_1" assert result2["options"] == { @@ -177,19 +164,17 @@ async def test_form_only_stillimage( @respx.mock +@pytest.mark.usefixtures("fakeimg_png") async def test_form_reject_preview( hass: HomeAssistant, - fakeimgbytes_png: bytes, mock_create_stream: _patch[MagicMock], user_flow: ConfigFlowResult, ) -> None: """Test we go back to the config screen if the user rejects the preview.""" - respx.get("http://127.0.0.1/testurl/1").respond(stream=fakeimgbytes_png) - with mock_create_stream: - result1 = await hass.config_entries.flow.async_configure( - user_flow["flow_id"], - TESTDATA, - ) + result1 = await hass.config_entries.flow.async_configure( + user_flow["flow_id"], + TESTDATA, + ) assert result1["type"] is FlowResultType.FORM assert result1["step_id"] == "user_confirm" result2 = await hass.config_entries.flow.async_configure( @@ -215,7 +200,6 @@ async def test_form_still_preview_cam_off( "homeassistant.components.generic.camera.GenericCamera.is_on", new_callable=PropertyMock(return_value=False), ), - mock_create_stream, ): result1 = await hass.config_entries.flow.async_configure( user_flow["flow_id"], @@ -246,47 +230,50 @@ async def test_form_still_preview_cam_off( @respx.mock @pytest.mark.usefixtures("fakeimg_gif") async def test_form_only_stillimage_gif( - hass: HomeAssistant, user_flow: ConfigFlowResult + hass: HomeAssistant, + user_flow: ConfigFlowResult, + mock_setup_entry: _patch[MagicMock], ) -> None: """Test we complete ok if the user wants a gif.""" data = TESTDATA.copy() data.pop(CONF_STREAM_SOURCE) - with patch("homeassistant.components.generic.async_setup_entry", return_value=True): - result1 = await hass.config_entries.flow.async_configure( - user_flow["flow_id"], - data, - ) - assert result1["type"] is FlowResultType.FORM - assert result1["step_id"] == "user_confirm" - result2 = await hass.config_entries.flow.async_configure( - result1["flow_id"], - user_input={CONF_CONFIRMED_OK: True}, - ) - await hass.async_block_till_done() + result1 = await hass.config_entries.flow.async_configure( + user_flow["flow_id"], + data, + ) + assert result1["type"] is FlowResultType.FORM + assert result1["step_id"] == "user_confirm" + result2 = await hass.config_entries.flow.async_configure( + result1["flow_id"], + user_input={CONF_CONFIRMED_OK: True}, + ) + await hass.async_block_till_done() assert result2["type"] is FlowResultType.CREATE_ENTRY assert result2["options"][CONF_CONTENT_TYPE] == "image/gif" @respx.mock async def test_form_only_svg_whitespace( - hass: HomeAssistant, fakeimgbytes_svg: bytes, user_flow: ConfigFlowResult + hass: HomeAssistant, + fakeimgbytes_svg: bytes, + user_flow: ConfigFlowResult, + mock_setup_entry: _patch[MagicMock], ) -> None: """Test we complete ok if svg starts with whitespace, issue #68889.""" fakeimgbytes_wspace_svg = bytes(" \n ", encoding="utf-8") + fakeimgbytes_svg respx.get("http://127.0.0.1/testurl/1").respond(stream=fakeimgbytes_wspace_svg) data = TESTDATA.copy() data.pop(CONF_STREAM_SOURCE) - with patch("homeassistant.components.generic.async_setup_entry", return_value=True): - result1 = await hass.config_entries.flow.async_configure( - user_flow["flow_id"], - data, - ) - assert result1["type"] is FlowResultType.FORM - assert result1["step_id"] == "user_confirm" - result2 = await hass.config_entries.flow.async_configure( - result1["flow_id"], - user_input={CONF_CONFIRMED_OK: True}, - ) + result1 = await hass.config_entries.flow.async_configure( + user_flow["flow_id"], + data, + ) + assert result1["type"] is FlowResultType.FORM + assert result1["step_id"] == "user_confirm" + result2 = await hass.config_entries.flow.async_configure( + result1["flow_id"], + user_input={CONF_CONFIRMED_OK: True}, + ) await hass.async_block_till_done() assert result2["type"] is FlowResultType.CREATE_ENTRY @@ -303,7 +290,7 @@ async def test_form_only_svg_whitespace( ], ) async def test_form_only_still_sample( - hass: HomeAssistant, user_flow: ConfigFlowResult, image_file + hass: HomeAssistant, user_flow: ConfigFlowResult, image_file, mock_setup_entry ) -> None: """Test various sample images #69037.""" image_path = os.path.join(os.path.dirname(__file__), image_file) @@ -311,18 +298,17 @@ async def test_form_only_still_sample( respx.get("http://127.0.0.1/testurl/1").respond(stream=image_bytes) data = TESTDATA.copy() data.pop(CONF_STREAM_SOURCE) - with patch("homeassistant.components.generic.async_setup_entry", return_value=True): - result1 = await hass.config_entries.flow.async_configure( - user_flow["flow_id"], - data, - ) - assert result1["type"] is FlowResultType.FORM - assert result1["step_id"] == "user_confirm" - result2 = await hass.config_entries.flow.async_configure( - result1["flow_id"], - user_input={CONF_CONFIRMED_OK: True}, - ) - await hass.async_block_till_done() + result1 = await hass.config_entries.flow.async_configure( + user_flow["flow_id"], + data, + ) + assert result1["type"] is FlowResultType.FORM + assert result1["step_id"] == "user_confirm" + result2 = await hass.config_entries.flow.async_configure( + result1["flow_id"], + user_input={CONF_CONFIRMED_OK: True}, + ) + await hass.async_block_till_done() assert result2["type"] is FlowResultType.CREATE_ENTRY @@ -363,10 +349,11 @@ async def test_form_only_still_sample( ), ], ) -async def test_still_template( +async def test_form_still_template( hass: HomeAssistant, user_flow: ConfigFlowResult, fakeimgbytes_png: bytes, + mock_setup_entry: Generator[AsyncMock], template, url, expected_result, @@ -380,12 +367,11 @@ async def test_still_template( data = TESTDATA.copy() data.pop(CONF_STREAM_SOURCE) data[CONF_STILL_IMAGE_URL] = template - with patch("homeassistant.components.generic.async_setup_entry", return_value=True): - result2 = await hass.config_entries.flow.async_configure( - user_flow["flow_id"], - data, - ) - await hass.async_block_till_done() + result2 = await hass.config_entries.flow.async_configure( + user_flow["flow_id"], + data, + ) + await hass.async_block_till_done() assert result2["step_id"] == expected_result assert result2.get("errors") == expected_errors @@ -396,24 +382,19 @@ async def test_form_rtsp_mode( hass: HomeAssistant, user_flow: ConfigFlowResult, mock_create_stream: _patch[MagicMock], + mock_setup_entry: _patch[MagicMock], ) -> None: """Test we complete ok if the user enters a stream url.""" 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, - patch("homeassistant.components.generic.async_setup_entry", return_value=True), - ): - result1 = await hass.config_entries.flow.async_configure( - user_flow["flow_id"], data - ) - assert result1["type"] is FlowResultType.FORM - assert result1["step_id"] == "user_confirm" - result2 = await hass.config_entries.flow.async_configure( - result1["flow_id"], - user_input={CONF_CONFIRMED_OK: True}, - ) + result1 = await hass.config_entries.flow.async_configure(user_flow["flow_id"], data) + assert result1["type"] is FlowResultType.FORM + assert result1["step_id"] == "user_confirm" + result2 = await hass.config_entries.flow.async_configure( + result1["flow_id"], + user_input={CONF_CONFIRMED_OK: True}, + ) assert result2["type"] is FlowResultType.CREATE_ENTRY assert result2["title"] == "127_0_0_1" assert result2["options"] == { @@ -428,8 +409,6 @@ async def test_form_rtsp_mode( CONF_VERIFY_SSL: False, } - assert len(mock_setup.mock_calls) == 1 - async def test_form_only_stream( hass: HomeAssistant, @@ -441,18 +420,16 @@ async def test_form_only_stream( data = TESTDATA.copy() data.pop(CONF_STILL_IMAGE_URL) data[CONF_STREAM_SOURCE] = "rtsp://user:pass@127.0.0.1/testurl/2" - with mock_create_stream: - result1 = await hass.config_entries.flow.async_configure( - user_flow["flow_id"], - data, - ) + result1 = await hass.config_entries.flow.async_configure( + user_flow["flow_id"], + data, + ) assert result1["type"] is FlowResultType.FORM - with mock_create_stream: - result2 = await hass.config_entries.flow.async_configure( - result1["flow_id"], - user_input={CONF_CONFIRMED_OK: True}, - ) + result2 = await hass.config_entries.flow.async_configure( + result1["flow_id"], + user_input={CONF_CONFIRMED_OK: True}, + ) assert result2["title"] == "127_0_0_1" assert result2["options"] == { @@ -528,15 +505,11 @@ async def test_form_image_http_exceptions( mock_create_stream: _patch[MagicMock], ) -> None: """Test we handle image http exceptions.""" - respx.get("http://127.0.0.1/testurl/1").side_effect = [ - side_effect, - ] - - with mock_create_stream: - result2 = await hass.config_entries.flow.async_configure( - user_flow["flow_id"], - TESTDATA, - ) + respx.get("http://127.0.0.1/testurl/1").side_effect = [side_effect] + result2 = await hass.config_entries.flow.async_configure( + user_flow["flow_id"], + TESTDATA, + ) assert result2["type"] is FlowResultType.FORM assert result2["errors"] == expected_message @@ -550,11 +523,10 @@ async def test_form_stream_invalidimage( ) -> None: """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_create_stream: - result2 = await hass.config_entries.flow.async_configure( - user_flow["flow_id"], - TESTDATA, - ) + result2 = await hass.config_entries.flow.async_configure( + user_flow["flow_id"], + TESTDATA, + ) assert result2["type"] is FlowResultType.FORM assert result2["errors"] == {"still_image_url": "invalid_still_image"} @@ -568,11 +540,10 @@ async def test_form_stream_invalidimage2( ) -> None: """Test we handle invalid image when a stream is specified.""" respx.get("http://127.0.0.1/testurl/1").respond(content=None) - with mock_create_stream: - result2 = await hass.config_entries.flow.async_configure( - user_flow["flow_id"], - TESTDATA, - ) + result2 = await hass.config_entries.flow.async_configure( + user_flow["flow_id"], + TESTDATA, + ) assert result2["type"] is FlowResultType.FORM assert result2["errors"] == {"still_image_url": "unable_still_load_no_image"} @@ -586,11 +557,10 @@ async def test_form_stream_invalidimage3( ) -> None: """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_create_stream: - result2 = await hass.config_entries.flow.async_configure( - user_flow["flow_id"], - TESTDATA, - ) + result2 = await hass.config_entries.flow.async_configure( + user_flow["flow_id"], + TESTDATA, + ) assert result2["type"] is FlowResultType.FORM assert result2["errors"] == {"still_image_url": "invalid_still_image"} @@ -599,23 +569,22 @@ async def test_form_stream_invalidimage3( @respx.mock @pytest.mark.usefixtures("fakeimg_png") async def test_form_stream_timeout( - hass: HomeAssistant, user_flow: ConfigFlowResult + hass: HomeAssistant, + user_flow: ConfigFlowResult, + mock_create_stream: _patch[MagicMock], ) -> None: """Test we handle invalid auth.""" - with patch( - "homeassistant.components.generic.config_flow.create_stream" - ) as create_stream: - create_stream.return_value.start = AsyncMock() - create_stream.return_value.stop = AsyncMock() - create_stream.return_value.hass = hass - 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, - ) + mock_create_stream.return_value.start = AsyncMock() + mock_create_stream.return_value.stop = AsyncMock() + mock_create_stream.return_value.hass = hass + mock_create_stream.return_value.add_provider.return_value.part_recv = AsyncMock() + mock_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, + ) assert result2["type"] is FlowResultType.FORM assert result2["errors"] == {"stream_source": "timeout"} @@ -661,11 +630,11 @@ async def test_form_stream_other_error(hass: HomeAssistant, user_flow) -> None: @respx.mock +@pytest.mark.usefixtures("fakeimg_png") async def test_form_stream_permission_error( - hass: HomeAssistant, fakeimgbytes_png: bytes, user_flow: ConfigFlowResult + hass: HomeAssistant, user_flow: ConfigFlowResult ) -> None: """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.create_stream", side_effect=PermissionError(), @@ -732,116 +701,73 @@ async def test_form_oserror(hass: HomeAssistant, user_flow: ConfigFlowResult) -> @respx.mock -async def test_form_stream_preview_auto_timeout( - hass: HomeAssistant, - user_flow: ConfigFlowResult, - mock_create_stream: _patch[MagicMock], - freezer: FrozenDateTimeFactory, - fakeimgbytes_png: bytes, -) -> None: - """Test that the stream preview times out after 10mins.""" - respx.get("http://fred_flintstone:bambam@127.0.0.1/testurl/2").respond( - stream=fakeimgbytes_png - ) - data = TESTDATA.copy() - data.pop(CONF_STILL_IMAGE_URL) - - with mock_create_stream as mock_stream: - result1 = await hass.config_entries.flow.async_configure( - user_flow["flow_id"], - data, - ) - assert result1["type"] is FlowResultType.FORM - assert result1["step_id"] == "user_confirm" - - freezer.tick(600 + 12) - async_fire_time_changed(hass) - await hass.async_block_till_done() - - mock_str = mock_stream.return_value - mock_str.start.assert_awaited_once() - - -@respx.mock +@pytest.mark.usefixtures("fakeimg_png") async def test_options_template_error( - hass: HomeAssistant, fakeimgbytes_png: bytes, mock_create_stream: _patch[MagicMock] + hass: HomeAssistant, + mock_create_stream: _patch[MagicMock], + config_entry: MockConfigEntry, ) -> None: """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) - - mock_entry = MockConfigEntry( - title="Test Camera", - domain=DOMAIN, - data={}, - options=TESTDATA, - ) - - 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) + result = await hass.config_entries.options.async_init(config_entry.entry_id) assert result["type"] is FlowResultType.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" - with mock_create_stream: - result2 = await hass.config_entries.options.async_configure( - result["flow_id"], - user_input=data, - ) - assert result2["type"] is FlowResultType.FORM - assert result2["step_id"] == "user_confirm" + result2 = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input=data, + ) + assert result2["type"] is FlowResultType.FORM + assert result2["step_id"] == "user_confirm" - result2a = await hass.config_entries.options.async_configure( - result2["flow_id"], user_input={CONF_CONFIRMED_OK: True} - ) - assert result2a["type"] is FlowResultType.CREATE_ENTRY + result2a = await hass.config_entries.options.async_configure( + result2["flow_id"], user_input={CONF_CONFIRMED_OK: True} + ) + assert result2a["type"] is FlowResultType.CREATE_ENTRY - result3 = await hass.config_entries.options.async_init(mock_entry.entry_id) - assert result3["type"] is FlowResultType.FORM - assert result3["step_id"] == "init" + result3 = await hass.config_entries.options.async_init(config_entry.entry_id) + assert result3["type"] is FlowResultType.FORM + assert result3["step_id"] == "init" - # verify that an invalid template reports the correct UI error. - data[CONF_STILL_IMAGE_URL] = "http://127.0.0.1/testurl/{{1/0}}" - result4 = await hass.config_entries.options.async_configure( - result3["flow_id"], - user_input=data, - ) - assert result4.get("type") is FlowResultType.FORM - assert result4["errors"] == {"still_image_url": "template_error"} + # verify that an invalid template reports the correct UI error. + data[CONF_STILL_IMAGE_URL] = "http://127.0.0.1/testurl/{{1/0}}" + result4 = await hass.config_entries.options.async_configure( + result3["flow_id"], + user_input=data, + ) + assert result4.get("type") is FlowResultType.FORM + assert result4["errors"] == {"still_image_url": "template_error"} - # verify that an invalid template reports the correct UI error. - data[CONF_STILL_IMAGE_URL] = "http://127.0.0.1/testurl/1" - data[CONF_STREAM_SOURCE] = "http://127.0.0.2/testurl/{{1/0}}" - result5 = await hass.config_entries.options.async_configure( - result4["flow_id"], - user_input=data, - ) + # verify that an invalid template reports the correct UI error. + data[CONF_STILL_IMAGE_URL] = "http://127.0.0.1/testurl/1" + data[CONF_STREAM_SOURCE] = "http://127.0.0.2/testurl/{{1/0}}" + result5 = await hass.config_entries.options.async_configure( + result4["flow_id"], + user_input=data, + ) - assert result5.get("type") is FlowResultType.FORM - assert result5["errors"] == {"stream_source": "template_error"} + assert result5.get("type") is FlowResultType.FORM + assert result5["errors"] == {"stream_source": "template_error"} - # verify that an relative stream url is rejected. - data[CONF_STILL_IMAGE_URL] = "http://127.0.0.1/testurl/1" - data[CONF_STREAM_SOURCE] = "relative/stream.mjpeg" - result6 = await hass.config_entries.options.async_configure( - result5["flow_id"], - user_input=data, - ) - assert result6.get("type") is FlowResultType.FORM - assert result6["errors"] == {"stream_source": "relative_url"} + # verify that an relative stream url is rejected. + data[CONF_STILL_IMAGE_URL] = "http://127.0.0.1/testurl/1" + data[CONF_STREAM_SOURCE] = "relative/stream.mjpeg" + result6 = await hass.config_entries.options.async_configure( + result5["flow_id"], + user_input=data, + ) + assert result6.get("type") is FlowResultType.FORM + assert result6["errors"] == {"stream_source": "relative_url"} - # verify that an malformed stream url is rejected. - data[CONF_STILL_IMAGE_URL] = "http://127.0.0.1/testurl/1" - data[CONF_STREAM_SOURCE] = "http://example.com:45:56" - result7 = await hass.config_entries.options.async_configure( - result6["flow_id"], - user_input=data, - ) + # verify that an malformed stream url is rejected. + data[CONF_STILL_IMAGE_URL] = "http://127.0.0.1/testurl/1" + data[CONF_STREAM_SOURCE] = "http://example.com:45:56" + result7 = await hass.config_entries.options.async_configure( + result6["flow_id"], + user_input=data, + ) assert result7.get("type") is FlowResultType.FORM assert result7["errors"] == {"stream_source": "malformed_url"} @@ -861,11 +787,13 @@ async def test_slug(hass: HomeAssistant, caplog: pytest.LogCaptureFixture) -> No @respx.mock +@pytest.mark.usefixtures("fakeimg_png") async def test_options_only_stream( - hass: HomeAssistant, fakeimgbytes_png: bytes, mock_create_stream: _patch[MagicMock] + hass: HomeAssistant, + mock_setup_entry: _patch[MagicMock], + mock_create_stream: _patch[MagicMock], ) -> None: """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() data.pop(CONF_STILL_IMAGE_URL) @@ -883,11 +811,10 @@ async def test_options_only_stream( assert result["step_id"] == "init" # try updating the config options - with mock_create_stream: - result2 = await hass.config_entries.options.async_configure( - result["flow_id"], - user_input=data, - ) + result2 = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input=data, + ) assert result2["type"] is FlowResultType.FORM assert result2["step_id"] == "user_confirm" @@ -900,6 +827,7 @@ async def test_options_only_stream( async def test_options_still_and_stream_not_provided( hass: HomeAssistant, + mock_setup_entry: _patch[MagicMock], ) -> None: """Test we show a suitable error if neither still or stream URL are provided.""" data = TESTDATA.copy() @@ -929,7 +857,7 @@ async def test_options_still_and_stream_not_provided( @respx.mock @pytest.mark.usefixtures("fakeimg_png") -async def test_form_options_permission_error( +async def test_options_permission_error( hass: HomeAssistant, config_entry: MockConfigEntry ) -> None: """Test we handle a PermissionError and pass the message through.""" @@ -947,43 +875,6 @@ async def test_form_options_permission_error( assert result2["errors"] == {"stream_source": "stream_not_permitted"} -@pytest.mark.usefixtures("fakeimg_png") -async def test_unload_entry(hass: HomeAssistant) -> None: - """Test unloading the generic IP Camera entry.""" - mock_entry = MockConfigEntry(domain=DOMAIN, options=TESTDATA) - mock_entry.add_to_hass(hass) - - await hass.config_entries.async_setup(mock_entry.entry_id) - await hass.async_block_till_done() - assert mock_entry.state is ConfigEntryState.LOADED - - await hass.config_entries.async_unload(mock_entry.entry_id) - await hass.async_block_till_done() - assert mock_entry.state is ConfigEntryState.NOT_LOADED - - -async def test_reload_on_title_change(hass: HomeAssistant) -> None: - """Test the integration gets reloaded when the title is updated.""" - - test_data = TESTDATA_OPTIONS - test_data[CONF_CONTENT_TYPE] = "image/png" - mock_entry = MockConfigEntry( - domain=DOMAIN, unique_id="54321", options=test_data, title="My Title" - ) - mock_entry.add_to_hass(hass) - - await hass.config_entries.async_setup(mock_entry.entry_id) - await hass.async_block_till_done() - assert mock_entry.state is ConfigEntryState.LOADED - assert hass.states.get("camera.my_title").attributes["friendly_name"] == "My Title" - - hass.config_entries.async_update_entry(mock_entry, title="New Title") - assert mock_entry.title == "New Title" - await hass.async_block_till_done() - - assert hass.states.get("camera.my_title").attributes["friendly_name"] == "New Title" - - async def test_migrate_existing_ids( hass: HomeAssistant, entity_registry: er.EntityRegistry ) -> None: @@ -1019,40 +910,26 @@ async def test_migrate_existing_ids( @respx.mock @pytest.mark.usefixtures("fakeimg_png") -async def test_use_wallclock_as_timestamps_option( +async def test_options_use_wallclock_as_timestamps( hass: HomeAssistant, mock_create_stream: _patch[MagicMock], hass_client: ClientSessionGenerator, hass_ws_client: WebSocketGenerator, fakeimgbytes_png: bytes, + config_entry: MockConfigEntry, + mock_setup_entry: _patch[MagicMock], ) -> None: """Test the use_wallclock_as_timestamps option flow.""" - respx.get("http://127.0.0.1/testurl/1").respond(stream=fakeimgbytes_png) - mock_entry = MockConfigEntry( - title="Test Camera", - domain=DOMAIN, - data={}, - options=TESTDATA, - ) - - 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} + config_entry.entry_id, context={"show_advanced_options": True} ) assert result["type"] is FlowResultType.FORM assert result["step_id"] == "init" - with ( - patch("homeassistant.components.generic.async_setup_entry", return_value=True), - mock_create_stream, - ): - result2 = await hass.config_entries.options.async_configure( - result["flow_id"], - user_input={CONF_USE_WALLCLOCK_AS_TIMESTAMPS: True, **TESTDATA}, - ) + result2 = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={CONF_USE_WALLCLOCK_AS_TIMESTAMPS: True, **TESTDATA}, + ) assert result2["type"] is FlowResultType.FORM ws_client = await hass_ws_client() @@ -1079,14 +956,10 @@ async def test_use_wallclock_as_timestamps_option( ) assert result3["type"] is FlowResultType.FORM assert result3["step_id"] == "init" - with ( - patch("homeassistant.components.generic.async_setup_entry", return_value=True), - mock_create_stream, - ): - result4 = await hass.config_entries.options.async_configure( - result3["flow_id"], - user_input={CONF_USE_WALLCLOCK_AS_TIMESTAMPS: True, **TESTDATA}, - ) + result4 = await hass.config_entries.options.async_configure( + result3["flow_id"], + user_input={CONF_USE_WALLCLOCK_AS_TIMESTAMPS: True, **TESTDATA}, + ) assert result4["type"] is FlowResultType.FORM assert result4["step_id"] == "user_confirm" result5 = await hass.config_entries.options.async_configure( diff --git a/tests/components/generic/test_init.py b/tests/components/generic/test_init.py new file mode 100644 index 00000000000..faa00ee9144 --- /dev/null +++ b/tests/components/generic/test_init.py @@ -0,0 +1,37 @@ +"""Define tests for the generic (IP camera) integration.""" + +import pytest + +from homeassistant.config_entries import ConfigEntryState +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry + + +@pytest.mark.usefixtures("fakeimg_png") +async def test_unload_entry(hass: HomeAssistant, setup_entry: MockConfigEntry) -> None: + """Test unloading the generic IP Camera entry.""" + assert setup_entry.state is ConfigEntryState.LOADED + + await hass.config_entries.async_unload(setup_entry.entry_id) + await hass.async_block_till_done() + assert setup_entry.state is ConfigEntryState.NOT_LOADED + + +async def test_reload_on_title_change( + hass: HomeAssistant, setup_entry: MockConfigEntry +) -> None: + """Test the integration gets reloaded when the title is updated.""" + assert setup_entry.state is ConfigEntryState.LOADED + assert ( + hass.states.get("camera.test_camera").attributes["friendly_name"] + == "Test Camera" + ) + + hass.config_entries.async_update_entry(setup_entry, title="New Title") + assert setup_entry.title == "New Title" + await hass.async_block_till_done() + + assert ( + hass.states.get("camera.test_camera").attributes["friendly_name"] == "New Title" + )