From cf96084ea3d161ca2607ff7d2f2edd54b4161627 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Thu, 4 Jul 2024 10:32:00 +0200 Subject: [PATCH] Improve type hints in generic tests (#121166) --- tests/components/generic/conftest.py | 28 +++--- tests/components/generic/test_camera.py | 28 +++--- tests/components/generic/test_config_flow.py | 96 +++++++++++++------- tests/components/generic/test_diagnostics.py | 5 +- 4 files changed, 99 insertions(+), 58 deletions(-) diff --git a/tests/components/generic/conftest.py b/tests/components/generic/conftest.py index 92a9298cbd5..34062aab954 100644 --- a/tests/components/generic/conftest.py +++ b/tests/components/generic/conftest.py @@ -1,7 +1,9 @@ """Test fixtures for the generic component.""" +from __future__ import annotations + from io import BytesIO -from unittest.mock import AsyncMock, Mock, patch +from unittest.mock import AsyncMock, MagicMock, Mock, _patch, patch from PIL import Image import pytest @@ -9,12 +11,14 @@ import respx from homeassistant import config_entries from homeassistant.components.generic.const import DOMAIN +from homeassistant.config_entries import ConfigFlowResult +from homeassistant.core import HomeAssistant from tests.common import MockConfigEntry @pytest.fixture(scope="package") -def fakeimgbytes_png(): +def fakeimgbytes_png() -> bytes: """Fake image in RAM for testing.""" buf = BytesIO() Image.new("RGB", (1, 1)).save(buf, format="PNG") @@ -22,7 +26,7 @@ def fakeimgbytes_png(): @pytest.fixture(scope="package") -def fakeimgbytes_jpg(): +def fakeimgbytes_jpg() -> bytes: """Fake image in RAM for testing.""" buf = BytesIO() # fake image in ram for testing. Image.new("RGB", (1, 1)).save(buf, format="jpeg") @@ -30,7 +34,7 @@ def fakeimgbytes_jpg(): @pytest.fixture(scope="package") -def fakeimgbytes_svg(): +def fakeimgbytes_svg() -> bytes: """Fake image in RAM for testing.""" return bytes( '', @@ -39,7 +43,7 @@ def fakeimgbytes_svg(): @pytest.fixture(scope="package") -def fakeimgbytes_gif(): +def fakeimgbytes_gif() -> bytes: """Fake image in RAM for testing.""" buf = BytesIO() # fake image in ram for testing. Image.new("RGB", (1, 1)).save(buf, format="gif") @@ -47,19 +51,19 @@ def fakeimgbytes_gif(): @pytest.fixture -def fakeimg_png(fakeimgbytes_png): +def fakeimg_png(fakeimgbytes_png: bytes) -> None: """Set up respx to respond to test url with fake image bytes.""" respx.get("http://127.0.0.1/testurl/1").respond(stream=fakeimgbytes_png) @pytest.fixture -def fakeimg_gif(fakeimgbytes_gif): +def fakeimg_gif(fakeimgbytes_gif: bytes) -> None: """Set up respx to respond to test url with fake image bytes.""" respx.get("http://127.0.0.1/testurl/1").respond(stream=fakeimgbytes_gif) @pytest.fixture(scope="package") -def mock_create_stream(): +def mock_create_stream() -> _patch[MagicMock]: """Mock create stream.""" mock_stream = Mock() mock_provider = Mock() @@ -75,7 +79,7 @@ def mock_create_stream(): @pytest.fixture -async def user_flow(hass): +async def user_flow(hass: HomeAssistant) -> ConfigFlowResult: """Initiate a user flow.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} @@ -87,7 +91,7 @@ async def user_flow(hass): @pytest.fixture(name="config_entry") -def config_entry_fixture(hass): +def config_entry_fixture(hass: HomeAssistant) -> MockConfigEntry: """Define a config entry fixture.""" entry = MockConfigEntry( domain=DOMAIN, @@ -112,7 +116,9 @@ def config_entry_fixture(hass): @pytest.fixture -async def setup_entry(hass, config_entry): +async def setup_entry( + hass: HomeAssistant, config_entry: MockConfigEntry +) -> MockConfigEntry: """Set up a config entry ready to be used in tests.""" await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/generic/test_camera.py b/tests/components/generic/test_camera.py index 72a7c32ba25..59ff513ccc9 100644 --- a/tests/components/generic/test_camera.py +++ b/tests/components/generic/test_camera.py @@ -73,7 +73,7 @@ async def help_setup_mock_config_entry( async def test_fetching_url( hass: HomeAssistant, hass_client: ClientSessionGenerator, - fakeimgbytes_png, + fakeimgbytes_png: bytes, caplog: pytest.LogCaptureFixture, ) -> None: """Test that it fetches the given url.""" @@ -132,7 +132,7 @@ async def test_image_caching( hass: HomeAssistant, hass_client: ClientSessionGenerator, freezer: FrozenDateTimeFactory, - fakeimgbytes_png, + fakeimgbytes_png: bytes, ) -> None: """Test that the image is cached and not fetched more often than the framerate indicates.""" respx.get("http://example.com").respond(stream=fakeimgbytes_png) @@ -197,7 +197,7 @@ async def test_image_caching( @respx.mock async def test_fetching_without_verify_ssl( - hass: HomeAssistant, hass_client: ClientSessionGenerator, fakeimgbytes_png + hass: HomeAssistant, hass_client: ClientSessionGenerator, fakeimgbytes_png: bytes ) -> None: """Test that it fetches the given url when ssl verify is off.""" respx.get("https://example.com").respond(stream=fakeimgbytes_png) @@ -221,7 +221,7 @@ async def test_fetching_without_verify_ssl( @respx.mock async def test_fetching_url_with_verify_ssl( - hass: HomeAssistant, hass_client: ClientSessionGenerator, fakeimgbytes_png + hass: HomeAssistant, hass_client: ClientSessionGenerator, fakeimgbytes_png: bytes ) -> None: """Test that it fetches the given url when ssl verify is explicitly on.""" respx.get("https://example.com").respond(stream=fakeimgbytes_png) @@ -247,8 +247,8 @@ async def test_fetching_url_with_verify_ssl( async def test_limit_refetch( hass: HomeAssistant, hass_client: ClientSessionGenerator, - fakeimgbytes_png, - fakeimgbytes_jpg, + fakeimgbytes_png: bytes, + fakeimgbytes_jpg: bytes, ) -> None: """Test that it fetches the given url.""" respx.get("http://example.com/0a").respond(stream=fakeimgbytes_png) @@ -319,7 +319,7 @@ async def test_stream_source( hass: HomeAssistant, hass_client: ClientSessionGenerator, hass_ws_client: WebSocketGenerator, - fakeimgbytes_png, + fakeimgbytes_png: bytes, ) -> None: """Test that the stream source is rendered.""" respx.get("http://example.com").respond(stream=fakeimgbytes_png) @@ -376,7 +376,7 @@ async def test_stream_source_error( hass: HomeAssistant, hass_client: ClientSessionGenerator, hass_ws_client: WebSocketGenerator, - fakeimgbytes_png, + fakeimgbytes_png: bytes, ) -> None: """Test that the stream source has an error.""" respx.get("http://example.com").respond(stream=fakeimgbytes_png) @@ -418,7 +418,7 @@ async def test_stream_source_error( @respx.mock async def test_setup_alternative_options( - hass: HomeAssistant, hass_ws_client: WebSocketGenerator, fakeimgbytes_png + hass: HomeAssistant, hass_ws_client: WebSocketGenerator, fakeimgbytes_png: bytes ) -> None: """Test that the stream source is setup with different config options.""" respx.get("https://example.com").respond(stream=fakeimgbytes_png) @@ -442,7 +442,7 @@ async def test_no_stream_source( hass: HomeAssistant, hass_client: ClientSessionGenerator, hass_ws_client: WebSocketGenerator, - fakeimgbytes_png, + fakeimgbytes_png: bytes, ) -> None: """Test a stream request without stream source option set.""" respx.get("https://example.com").respond(stream=fakeimgbytes_png) @@ -482,8 +482,8 @@ async def test_no_stream_source( async def test_camera_content_type( hass: HomeAssistant, hass_client: ClientSessionGenerator, - fakeimgbytes_svg, - fakeimgbytes_jpg, + fakeimgbytes_svg: bytes, + fakeimgbytes_jpg: bytes, ) -> None: """Test generic camera with custom content_type.""" urlsvg = "https://upload.wikimedia.org/wikipedia/commons/0/02/SVG_logo.svg" @@ -532,8 +532,8 @@ async def test_camera_content_type( async def test_timeout_cancelled( hass: HomeAssistant, hass_client: ClientSessionGenerator, - fakeimgbytes_png, - fakeimgbytes_jpg, + fakeimgbytes_png: bytes, + fakeimgbytes_jpg: bytes, ) -> None: """Test that timeouts and cancellations return last image.""" diff --git a/tests/components/generic/test_config_flow.py b/tests/components/generic/test_config_flow.py index 8c2d52f173b..c62142fb4d3 100644 --- a/tests/components/generic/test_config_flow.py +++ b/tests/components/generic/test_config_flow.py @@ -1,11 +1,13 @@ """Test The generic (IP Camera) config flow.""" +from __future__ import annotations + import contextlib import errno from http import HTTPStatus import os.path from pathlib import Path -from unittest.mock import AsyncMock, PropertyMock, patch +from unittest.mock import AsyncMock, MagicMock, PropertyMock, _patch, patch import httpx import pytest @@ -28,7 +30,7 @@ from homeassistant.components.stream import ( CONF_USE_WALLCLOCK_AS_TIMESTAMPS, ) from homeassistant.components.stream.worker import StreamWorkerError -from homeassistant.config_entries import ConfigEntryState +from homeassistant.config_entries import ConfigEntryState, ConfigFlowResult from homeassistant.const import ( CONF_AUTHENTICATION, CONF_NAME, @@ -68,10 +70,10 @@ TESTDATA_YAML = { @respx.mock async def test_form( hass: HomeAssistant, - fakeimgbytes_png, + fakeimgbytes_png: bytes, hass_client: ClientSessionGenerator, - user_flow, - mock_create_stream, + user_flow: ConfigFlowResult, + mock_create_stream: _patch[MagicMock], ) -> None: """Test the form with a normal set of settings.""" @@ -122,8 +124,9 @@ async def test_form( @respx.mock +@pytest.mark.usefixtures("fakeimg_png") async def test_form_only_stillimage( - hass: HomeAssistant, fakeimg_png, user_flow + hass: HomeAssistant, user_flow: ConfigFlowResult ) -> None: """Test we complete ok if the user wants still images only.""" result = await hass.config_entries.flow.async_init( @@ -164,7 +167,10 @@ async def test_form_only_stillimage( @respx.mock async def test_form_reject_still_preview( - hass: HomeAssistant, fakeimgbytes_png, mock_create_stream, user_flow + 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 still preview.""" respx.get("http://127.0.0.1/testurl/1").respond(stream=fakeimgbytes_png) @@ -184,11 +190,11 @@ async def test_form_reject_still_preview( @respx.mock +@pytest.mark.usefixtures("fakeimg_png") async def test_form_still_preview_cam_off( hass: HomeAssistant, - fakeimg_png, - mock_create_stream, - user_flow, + mock_create_stream: _patch[MagicMock], + user_flow: ConfigFlowResult, hass_client: ClientSessionGenerator, ) -> None: """Test camera errors are triggered during preview.""" @@ -213,8 +219,9 @@ async def test_form_still_preview_cam_off( @respx.mock +@pytest.mark.usefixtures("fakeimg_gif") async def test_form_only_stillimage_gif( - hass: HomeAssistant, fakeimg_gif, user_flow + hass: HomeAssistant, user_flow: ConfigFlowResult ) -> None: """Test we complete ok if the user wants a gif.""" data = TESTDATA.copy() @@ -237,7 +244,7 @@ async def test_form_only_stillimage_gif( @respx.mock async def test_form_only_svg_whitespace( - hass: HomeAssistant, fakeimgbytes_svg, user_flow + hass: HomeAssistant, fakeimgbytes_svg: bytes, user_flow: ConfigFlowResult ) -> None: """Test we complete ok if svg starts with whitespace, issue #68889.""" fakeimgbytes_wspace_svg = bytes(" \n ", encoding="utf-8") + fakeimgbytes_svg @@ -271,7 +278,7 @@ async def test_form_only_svg_whitespace( ], ) async def test_form_only_still_sample( - hass: HomeAssistant, user_flow, image_file + hass: HomeAssistant, user_flow: ConfigFlowResult, image_file ) -> None: """Test various sample images #69037.""" image_path = os.path.join(os.path.dirname(__file__), image_file) @@ -333,8 +340,8 @@ async def test_form_only_still_sample( ) async def test_still_template( hass: HomeAssistant, - user_flow, - fakeimgbytes_png, + user_flow: ConfigFlowResult, + fakeimgbytes_png: bytes, template, url, expected_result, @@ -359,8 +366,11 @@ async def test_still_template( @respx.mock +@pytest.mark.usefixtures("fakeimg_png") async def test_form_rtsp_mode( - hass: HomeAssistant, fakeimg_png, user_flow, mock_create_stream + hass: HomeAssistant, + user_flow: ConfigFlowResult, + mock_create_stream: _patch[MagicMock], ) -> None: """Test we complete ok if the user enters a stream url.""" data = TESTDATA.copy() @@ -399,7 +409,10 @@ async def test_form_rtsp_mode( async def test_form_only_stream( - hass: HomeAssistant, fakeimgbytes_jpg, user_flow, mock_create_stream + hass: HomeAssistant, + fakeimgbytes_jpg: bytes, + user_flow: ConfigFlowResult, + mock_create_stream: _patch[MagicMock], ) -> None: """Test we complete ok if the user wants stream only.""" data = TESTDATA.copy() @@ -435,7 +448,7 @@ async def test_form_only_stream( async def test_form_still_and_stream_not_provided( - hass: HomeAssistant, user_flow + hass: HomeAssistant, user_flow: ConfigFlowResult ) -> None: """Test we show a suitable error if neither still or stream URL are provided.""" result2 = await hass.config_entries.flow.async_configure( @@ -482,7 +495,11 @@ async def test_form_still_and_stream_not_provided( ], ) async def test_form_image_http_exceptions( - side_effect, expected_message, hass: HomeAssistant, user_flow, mock_create_stream + side_effect, + expected_message, + hass: HomeAssistant, + user_flow: ConfigFlowResult, + mock_create_stream: _patch[MagicMock], ) -> None: """Test we handle image http exceptions.""" respx.get("http://127.0.0.1/testurl/1").side_effect = [ @@ -502,7 +519,9 @@ async def test_form_image_http_exceptions( @respx.mock async def test_form_stream_invalidimage( - hass: HomeAssistant, user_flow, mock_create_stream + hass: HomeAssistant, + user_flow: ConfigFlowResult, + mock_create_stream: _patch[MagicMock], ) -> None: """Test we handle invalid image when a stream is specified.""" respx.get("http://127.0.0.1/testurl/1").respond(stream=b"invalid") @@ -519,7 +538,9 @@ async def test_form_stream_invalidimage( @respx.mock async def test_form_stream_invalidimage2( - hass: HomeAssistant, user_flow, mock_create_stream + hass: HomeAssistant, + user_flow: ConfigFlowResult, + mock_create_stream: _patch[MagicMock], ) -> None: """Test we handle invalid image when a stream is specified.""" respx.get("http://127.0.0.1/testurl/1").respond(content=None) @@ -536,7 +557,9 @@ async def test_form_stream_invalidimage2( @respx.mock async def test_form_stream_invalidimage3( - hass: HomeAssistant, user_flow, mock_create_stream + hass: HomeAssistant, + user_flow: ConfigFlowResult, + mock_create_stream: _patch[MagicMock], ) -> None: """Test we handle invalid image when a stream is specified.""" respx.get("http://127.0.0.1/testurl/1").respond(content=bytes([0xFF])) @@ -552,7 +575,10 @@ async def test_form_stream_invalidimage3( @respx.mock -async def test_form_stream_timeout(hass: HomeAssistant, fakeimg_png, user_flow) -> None: +@pytest.mark.usefixtures("fakeimg_png") +async def test_form_stream_timeout( + hass: HomeAssistant, user_flow: ConfigFlowResult +) -> None: """Test we handle invalid auth.""" with patch( "homeassistant.components.generic.config_flow.create_stream" @@ -571,8 +597,9 @@ async def test_form_stream_timeout(hass: HomeAssistant, fakeimg_png, user_flow) @respx.mock +@pytest.mark.usefixtures("fakeimg_png") async def test_form_stream_worker_error( - hass: HomeAssistant, fakeimg_png, user_flow + hass: HomeAssistant, user_flow: ConfigFlowResult ) -> None: """Test we handle a StreamWorkerError and pass the message through.""" with patch( @@ -589,7 +616,7 @@ async def test_form_stream_worker_error( @respx.mock async def test_form_stream_permission_error( - hass: HomeAssistant, fakeimgbytes_png, user_flow + hass: HomeAssistant, fakeimgbytes_png: bytes, user_flow: ConfigFlowResult ) -> None: """Test we handle permission error.""" respx.get("http://127.0.0.1/testurl/1").respond(stream=fakeimgbytes_png) @@ -606,8 +633,9 @@ async def test_form_stream_permission_error( @respx.mock +@pytest.mark.usefixtures("fakeimg_png") async def test_form_no_route_to_host( - hass: HomeAssistant, fakeimg_png, user_flow + hass: HomeAssistant, user_flow: ConfigFlowResult ) -> None: """Test we handle no route to host.""" with patch( @@ -623,8 +651,9 @@ async def test_form_no_route_to_host( @respx.mock +@pytest.mark.usefixtures("fakeimg_png") async def test_form_stream_io_error( - hass: HomeAssistant, fakeimg_png, user_flow + hass: HomeAssistant, user_flow: ConfigFlowResult ) -> None: """Test we handle no io error when setting up stream.""" with patch( @@ -640,7 +669,8 @@ async def test_form_stream_io_error( @respx.mock -async def test_form_oserror(hass: HomeAssistant, fakeimg_png, user_flow) -> None: +@pytest.mark.usefixtures("fakeimg_png") +async def test_form_oserror(hass: HomeAssistant, user_flow: ConfigFlowResult) -> None: """Test we handle OS error when setting up stream.""" with ( patch( @@ -657,7 +687,7 @@ async def test_form_oserror(hass: HomeAssistant, fakeimg_png, user_flow) -> None @respx.mock async def test_options_template_error( - hass: HomeAssistant, fakeimgbytes_png, mock_create_stream + hass: HomeAssistant, fakeimgbytes_png: bytes, mock_create_stream: _patch[MagicMock] ) -> None: """Test the options flow with a template error.""" respx.get("http://127.0.0.1/testurl/1").respond(stream=fakeimgbytes_png) @@ -755,7 +785,7 @@ async def test_slug(hass: HomeAssistant, caplog: pytest.LogCaptureFixture) -> No @respx.mock async def test_options_only_stream( - hass: HomeAssistant, fakeimgbytes_png, mock_create_stream + hass: HomeAssistant, fakeimgbytes_png: bytes, 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) @@ -792,7 +822,8 @@ async def test_options_only_stream( assert result3["data"][CONF_CONTENT_TYPE] == "image/jpeg" -async def test_unload_entry(hass: HomeAssistant, fakeimg_png) -> None: +@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) @@ -862,8 +893,9 @@ async def test_migrate_existing_ids( @respx.mock +@pytest.mark.usefixtures("fakeimg_png") async def test_use_wallclock_as_timestamps_option( - hass: HomeAssistant, fakeimg_png, mock_create_stream + hass: HomeAssistant, mock_create_stream: _patch[MagicMock] ) -> None: """Test the use_wallclock_as_timestamps option flow.""" diff --git a/tests/components/generic/test_diagnostics.py b/tests/components/generic/test_diagnostics.py index f68c3ba4bc6..80fa5fd4d4e 100644 --- a/tests/components/generic/test_diagnostics.py +++ b/tests/components/generic/test_diagnostics.py @@ -6,12 +6,15 @@ from homeassistant.components.diagnostics import REDACTED from homeassistant.components.generic.diagnostics import redact_url from homeassistant.core import HomeAssistant +from tests.common import MockConfigEntry from tests.components.diagnostics import get_diagnostics_for_config_entry from tests.typing import ClientSessionGenerator async def test_entry_diagnostics( - hass: HomeAssistant, hass_client: ClientSessionGenerator, setup_entry + hass: HomeAssistant, + hass_client: ClientSessionGenerator, + setup_entry: MockConfigEntry, ) -> None: """Test config entry diagnostics."""