Compare commits

..

6 Commits

Author SHA1 Message Date
Abílio Costa
e7994b3da1
Fix missing go2rtc dependency in non-docker setups (#143172) 2025-04-17 10:03:47 -10:00
Josef Zweck
b88bf74e13
Cleanup lamarzocco tests (#143176) 2025-04-17 20:53:47 +02:00
peteS-UK
8355727eb1
Fix for media content type case in Squeezebox (#143099) 2025-04-17 18:56:28 +02:00
Marc Mueller
c7290908cc
Update mypy-dev 1.16.0a8 (#143166) 2025-04-17 18:13:00 +02:00
Abílio Costa
1307cd4b10
Add bronze quality scale for Whirlpool (#142752) 2025-04-17 15:31:12 +01:00
Abílio Costa
c0b2193718
Use freezer for time change in Whirlpool config flow test (#143162) 2025-04-17 16:14:21 +02:00
15 changed files with 231 additions and 157 deletions

View File

@ -104,7 +104,7 @@ class LazyState(State):
return self._last_updated_ts return self._last_updated_ts
@cached_property @cached_property
def last_changed_timestamp(self) -> float: # type: ignore[override] def last_changed_timestamp(self) -> float:
"""Last changed timestamp.""" """Last changed timestamp."""
ts = self._last_changed_ts or self._last_updated_ts ts = self._last_changed_ts or self._last_updated_ts
if TYPE_CHECKING: if TYPE_CHECKING:
@ -112,7 +112,7 @@ class LazyState(State):
return ts return ts
@cached_property @cached_property
def last_reported_timestamp(self) -> float: # type: ignore[override] def last_reported_timestamp(self) -> float:
"""Last reported timestamp.""" """Last reported timestamp."""
ts = self._last_reported_ts or self._last_updated_ts ts = self._last_reported_ts or self._last_updated_ts
if TYPE_CHECKING: if TYPE_CHECKING:

View File

@ -22,34 +22,34 @@ from homeassistant.helpers.network import is_internal_request
from .const import UNPLAYABLE_TYPES from .const import UNPLAYABLE_TYPES
LIBRARY = [ LIBRARY = [
"Favorites", "favorites",
"Artists", "artists",
"Albums", "albums",
"Tracks", "tracks",
"Playlists", "playlists",
"Genres", "genres",
"New Music", "new music",
"Album Artists", "album artists",
"Apps", "apps",
"Radios", "radios",
] ]
MEDIA_TYPE_TO_SQUEEZEBOX: dict[str | MediaType, str] = { MEDIA_TYPE_TO_SQUEEZEBOX: dict[str | MediaType, str] = {
"Favorites": "favorites", "favorites": "favorites",
"Artists": "artists", "artists": "artists",
"Albums": "albums", "albums": "albums",
"Tracks": "titles", "tracks": "titles",
"Playlists": "playlists", "playlists": "playlists",
"Genres": "genres", "genres": "genres",
"New Music": "new music", "new music": "new music",
"Album Artists": "album artists", "album artists": "album artists",
MediaType.ALBUM: "album", MediaType.ALBUM: "album",
MediaType.ARTIST: "artist", MediaType.ARTIST: "artist",
MediaType.TRACK: "title", MediaType.TRACK: "title",
MediaType.PLAYLIST: "playlist", MediaType.PLAYLIST: "playlist",
MediaType.GENRE: "genre", MediaType.GENRE: "genre",
"Apps": "apps", MediaType.APPS: "apps",
"Radios": "radios", "radios": "radios",
} }
SQUEEZEBOX_ID_BY_TYPE: dict[str | MediaType, str] = { SQUEEZEBOX_ID_BY_TYPE: dict[str | MediaType, str] = {
@ -58,22 +58,20 @@ SQUEEZEBOX_ID_BY_TYPE: dict[str | MediaType, str] = {
MediaType.TRACK: "track_id", MediaType.TRACK: "track_id",
MediaType.PLAYLIST: "playlist_id", MediaType.PLAYLIST: "playlist_id",
MediaType.GENRE: "genre_id", MediaType.GENRE: "genre_id",
"Favorites": "item_id", "favorites": "item_id",
MediaType.APPS: "item_id", MediaType.APPS: "item_id",
} }
CONTENT_TYPE_MEDIA_CLASS: dict[str | MediaType, dict[str, MediaClass | str]] = { CONTENT_TYPE_MEDIA_CLASS: dict[str | MediaType, dict[str, MediaClass | str]] = {
"Favorites": {"item": MediaClass.DIRECTORY, "children": MediaClass.TRACK}, "favorites": {"item": MediaClass.DIRECTORY, "children": MediaClass.TRACK},
"Apps": {"item": MediaClass.DIRECTORY, "children": MediaClass.APP}, "radios": {"item": MediaClass.DIRECTORY, "children": MediaClass.APP},
"Radios": {"item": MediaClass.DIRECTORY, "children": MediaClass.APP}, "artists": {"item": MediaClass.DIRECTORY, "children": MediaClass.ARTIST},
"App": {"item": MediaClass.DIRECTORY, "children": MediaClass.TRACK}, "albums": {"item": MediaClass.DIRECTORY, "children": MediaClass.ALBUM},
"Artists": {"item": MediaClass.DIRECTORY, "children": MediaClass.ARTIST}, "tracks": {"item": MediaClass.DIRECTORY, "children": MediaClass.TRACK},
"Albums": {"item": MediaClass.DIRECTORY, "children": MediaClass.ALBUM}, "playlists": {"item": MediaClass.DIRECTORY, "children": MediaClass.PLAYLIST},
"Tracks": {"item": MediaClass.DIRECTORY, "children": MediaClass.TRACK}, "genres": {"item": MediaClass.DIRECTORY, "children": MediaClass.GENRE},
"Playlists": {"item": MediaClass.DIRECTORY, "children": MediaClass.PLAYLIST}, "new music": {"item": MediaClass.DIRECTORY, "children": MediaClass.ALBUM},
"Genres": {"item": MediaClass.DIRECTORY, "children": MediaClass.GENRE}, "album artists": {"item": MediaClass.DIRECTORY, "children": MediaClass.ARTIST},
"New Music": {"item": MediaClass.DIRECTORY, "children": MediaClass.ALBUM},
"Album Artists": {"item": MediaClass.DIRECTORY, "children": MediaClass.ARTIST},
MediaType.ALBUM: {"item": MediaClass.ALBUM, "children": MediaClass.TRACK}, MediaType.ALBUM: {"item": MediaClass.ALBUM, "children": MediaClass.TRACK},
MediaType.ARTIST: {"item": MediaClass.ARTIST, "children": MediaClass.ALBUM}, MediaType.ARTIST: {"item": MediaClass.ARTIST, "children": MediaClass.ALBUM},
MediaType.TRACK: {"item": MediaClass.TRACK, "children": ""}, MediaType.TRACK: {"item": MediaClass.TRACK, "children": ""},
@ -91,17 +89,15 @@ CONTENT_TYPE_TO_CHILD_TYPE: dict[
MediaType.PLAYLIST: MediaType.PLAYLIST, MediaType.PLAYLIST: MediaType.PLAYLIST,
MediaType.ARTIST: MediaType.ALBUM, MediaType.ARTIST: MediaType.ALBUM,
MediaType.GENRE: MediaType.ARTIST, MediaType.GENRE: MediaType.ARTIST,
"Artists": MediaType.ARTIST, "artists": MediaType.ARTIST,
"Albums": MediaType.ALBUM, "albums": MediaType.ALBUM,
"Tracks": MediaType.TRACK, "tracks": MediaType.TRACK,
"Playlists": MediaType.PLAYLIST, "playlists": MediaType.PLAYLIST,
"Genres": MediaType.GENRE, "genres": MediaType.GENRE,
"Favorites": None, # can only be determined after inspecting the item "favorites": None, # can only be determined after inspecting the item
"Apps": MediaClass.APP, "radios": MediaClass.APP,
"Radios": MediaClass.APP, "new music": MediaType.ALBUM,
"App": None, # can only be determined after inspecting the item "album artists": MediaType.ARTIST,
"New Music": MediaType.ALBUM,
"Album Artists": MediaType.ARTIST,
MediaType.APPS: MediaType.APP, MediaType.APPS: MediaType.APP,
MediaType.APP: MediaType.TRACK, MediaType.APP: MediaType.TRACK,
} }
@ -173,7 +169,7 @@ def _build_response_known_app(
def _build_response_favorites(item: dict[str, Any]) -> BrowseMedia: def _build_response_favorites(item: dict[str, Any]) -> BrowseMedia:
"""Build item for Favorites.""" """Build item for favorites."""
if "album_id" in item: if "album_id" in item:
return BrowseMedia( return BrowseMedia(
media_content_id=str(item["album_id"]), media_content_id=str(item["album_id"]),
@ -183,21 +179,21 @@ def _build_response_favorites(item: dict[str, Any]) -> BrowseMedia:
can_expand=True, can_expand=True,
can_play=True, can_play=True,
) )
if item["hasitems"] and not item["isaudio"]: if item.get("hasitems") and not item.get("isaudio"):
return BrowseMedia( return BrowseMedia(
media_content_id=item["id"], media_content_id=item["id"],
title=item["title"], title=item["title"],
media_content_type="Favorites", media_content_type="favorites",
media_class=CONTENT_TYPE_MEDIA_CLASS["Favorites"]["item"], media_class=CONTENT_TYPE_MEDIA_CLASS["favorites"]["item"],
can_expand=True, can_expand=True,
can_play=False, can_play=False,
) )
return BrowseMedia( return BrowseMedia(
media_content_id=item["id"], media_content_id=item["id"],
title=item["title"], title=item["title"],
media_content_type="Favorites", media_content_type="favorites",
media_class=CONTENT_TYPE_MEDIA_CLASS[MediaType.TRACK]["item"], media_class=CONTENT_TYPE_MEDIA_CLASS[MediaType.TRACK]["item"],
can_expand=item["hasitems"], can_expand=bool(item.get("hasitems")),
can_play=bool(item["isaudio"] and item.get("url")), can_play=bool(item["isaudio"] and item.get("url")),
) )
@ -220,7 +216,7 @@ def _get_item_thumbnail(
item_type, item["id"], artwork_track_id item_type, item["id"], artwork_track_id
) )
elif search_type in ["Apps", "Radios"]: elif search_type in ["apps", "radios"]:
item_thumbnail = player.generate_image_url(item["icon"]) item_thumbnail = player.generate_image_url(item["icon"])
if item_thumbnail is None: if item_thumbnail is None:
item_thumbnail = item.get("image_url") # will not be proxied by HA item_thumbnail = item.get("image_url") # will not be proxied by HA
@ -265,10 +261,10 @@ async def build_item_response(
for item in result["items"]: for item in result["items"]:
# Force the item id to a string in case it's numeric from some lms # Force the item id to a string in case it's numeric from some lms
item["id"] = str(item.get("id", "")) item["id"] = str(item.get("id", ""))
if search_type == "Favorites": if search_type == "favorites":
child_media = _build_response_favorites(item) child_media = _build_response_favorites(item)
elif search_type in ["Apps", "Radios"]: elif search_type in ["apps", "radios"]:
# item["cmd"] contains the name of the command to use with the cli for the app # item["cmd"] contains the name of the command to use with the cli for the app
# add the command to the dictionaries # add the command to the dictionaries
if item["title"] == "Search" or item.get("type") in UNPLAYABLE_TYPES: if item["title"] == "Search" or item.get("type") in UNPLAYABLE_TYPES:
@ -364,11 +360,11 @@ async def library_payload(
assert media_class["children"] is not None assert media_class["children"] is not None
library_info["children"].append( library_info["children"].append(
BrowseMedia( BrowseMedia(
title=item, title=item.title(),
media_class=media_class["children"], media_class=media_class["children"],
media_content_id=item, media_content_id=item,
media_content_type=item, media_content_type=item,
can_play=item not in ["Favorites", "Apps", "Radios"], can_play=item not in ["favorites", "apps", "radios"],
can_expand=True, can_expand=True,
) )
) )

View File

@ -446,6 +446,9 @@ class SqueezeBoxMediaPlayerEntity(SqueezeboxEntity, MediaPlayerEntity):
"""Send the play_media command to the media player.""" """Send the play_media command to the media player."""
index = None index = None
if media_type:
media_type = media_type.lower()
enqueue: MediaPlayerEnqueue | None = kwargs.get(ATTR_MEDIA_ENQUEUE) enqueue: MediaPlayerEnqueue | None = kwargs.get(ATTR_MEDIA_ENQUEUE)
if enqueue == MediaPlayerEnqueue.ADD: if enqueue == MediaPlayerEnqueue.ADD:
@ -617,6 +620,9 @@ class SqueezeBoxMediaPlayerEntity(SqueezeboxEntity, MediaPlayerEntity):
media_content_id, media_content_id,
) )
if media_content_type:
media_content_type = media_content_type.lower()
if media_content_type in [None, "library"]: if media_content_type in [None, "library"]:
return await library_payload(self.hass, self._player, self._browse_data) return await library_payload(self.hass, self._player, self._browse_data)

View File

@ -0,0 +1,92 @@
rules:
# Bronze
action-setup:
status: exempt
comment: |
The integration does not provide any additional actions.
appropriate-polling: done
brands: done
common-modules: done
config-flow-test-coverage: done
config-flow: done
dependency-transparency: done
docs-actions:
status: exempt
comment: |
This integration does not provide additional actions.
docs-high-level-description: done
docs-installation-instructions: done
docs-removal-instructions: done
entity-event-setup: done
entity-unique-id: done
has-entity-name: done
runtime-data: done
test-before-configure: done
test-before-setup:
status: todo
comment: |
When fetch_appliances fails, ConfigEntryNotReady should be raised.
unique-config-entry: done
# Silver
action-exceptions:
status: todo
comment: |
- The calls to the api can be changed to return bool, and services can then raise HomeAssistantError
- Current services raise ValueError and should raise ServiceValidationError instead.
config-entry-unloading: done
docs-configuration-parameters:
status: exempt
comment: Integration has no configuration parameters
docs-installation-parameters: todo
entity-unavailable: done
integration-owner: done
log-when-unavailable: todo
parallel-updates: todo
reauthentication-flow: done
test-coverage:
status: todo
comment: |
- Test helper init_integration() does not set a unique_id
- Merge test_setup_http_exception and test_setup_auth_account_locked
- The climate platform is at 94%
# Gold
devices: done
diagnostics: done
discovery-update-info:
status: exempt
comment: |
This integration is a cloud service and thus does not support discovery.
discovery:
status: exempt
comment: |
This integration is a cloud service and thus does not support discovery.
docs-data-update: todo
docs-examples: todo
docs-known-limitations: todo
docs-supported-devices: done
docs-supported-functions: done
docs-troubleshooting: todo
docs-use-cases: todo
dynamic-devices: todo
entity-category: done
entity-device-class:
status: todo
comment: The "unknown" state should not be part of the enum for the dispense level sensor.
entity-disabled-by-default: done
entity-translations: done
exception-translations: todo
icon-translations:
status: todo
comment: |
Time remaining sensor still has hardcoded icon.
reconfiguration-flow: todo
repair-issues:
status: exempt
comment: No known use cases for repair issues or flows, yet
stale-devices: todo
# Platinum
async-dependency: done
inject-websession: done
strict-typing: todo

View File

@ -1072,7 +1072,7 @@ class TemplateStateBase(State):
raise KeyError raise KeyError
@under_cached_property @under_cached_property
def entity_id(self) -> str: # type: ignore[override] def entity_id(self) -> str:
"""Wrap State.entity_id. """Wrap State.entity_id.
Intentionally does not collect state Intentionally does not collect state
@ -1128,7 +1128,7 @@ class TemplateStateBase(State):
return self._state.object_id return self._state.object_id
@property @property
def name(self) -> str: # type: ignore[override] def name(self) -> str:
"""Wrap State.name.""" """Wrap State.name."""
self._collect_state() self._collect_state()
return self._state.name return self._state.name

View File

@ -10,9 +10,10 @@
astroid==3.3.9 astroid==3.3.9
coverage==7.6.12 coverage==7.6.12
freezegun==1.5.1 freezegun==1.5.1
go2rtc-client==0.1.2
license-expression==30.4.1 license-expression==30.4.1
mock-open==1.4.0 mock-open==1.4.0
mypy-dev==1.16.0a7 mypy-dev==1.16.0a8
pre-commit==4.0.0 pre-commit==4.0.0
pydantic==2.11.3 pydantic==2.11.3
pylint==3.3.6 pylint==3.3.6

View File

@ -1100,7 +1100,6 @@ INTEGRATIONS_WITHOUT_QUALITY_SCALE_FILE = [
"weatherkit", "weatherkit",
"webmin", "webmin",
"wemo", "wemo",
"whirlpool",
"whois", "whois",
"wiffi", "wiffi",
"wilight", "wilight",

View File

@ -1,7 +1,7 @@
"""Lamarzocco session fixtures.""" """Lamarzocco session fixtures."""
from collections.abc import Generator from collections.abc import Generator
from unittest.mock import AsyncMock, MagicMock, patch from unittest.mock import MagicMock, patch
from bleak.backends.device import BLEDevice from bleak.backends.device import BLEDevice
from pylamarzocco.const import ModelName from pylamarzocco.const import ModelName
@ -22,15 +22,6 @@ from . import SERIAL_DICT, USER_INPUT, async_init_integration
from tests.common import MockConfigEntry, load_json_object_fixture from tests.common import MockConfigEntry, load_json_object_fixture
@pytest.fixture
def mock_setup_entry() -> Generator[AsyncMock]:
"""Override async_setup_entry."""
with patch(
"homeassistant.components.lamarzocco.async_setup_entry", return_value=True
) as mock_setup_entry:
yield mock_setup_entry
@pytest.fixture @pytest.fixture
def mock_config_entry( def mock_config_entry(
hass: HomeAssistant, mock_lamarzocco: MagicMock hass: HomeAssistant, mock_lamarzocco: MagicMock

View File

@ -18,7 +18,6 @@ from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_plat
async def test_binary_sensors( async def test_binary_sensors(
hass: HomeAssistant, hass: HomeAssistant,
mock_lamarzocco: MagicMock,
mock_config_entry: MockConfigEntry, mock_config_entry: MockConfigEntry,
entity_registry: er.EntityRegistry, entity_registry: er.EntityRegistry,
snapshot: SnapshotAssertion, snapshot: SnapshotAssertion,

View File

@ -27,6 +27,15 @@ from . import USER_INPUT, async_init_integration, get_bluetooth_service_info
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
@pytest.fixture(autouse=True)
def mock_setup_entry() -> Generator[AsyncMock]:
"""Override async_setup_entry."""
with patch(
"homeassistant.components.lamarzocco.async_setup_entry", return_value=True
) as mock_setup_entry:
yield mock_setup_entry
async def __do_successful_user_step( async def __do_successful_user_step(
hass: HomeAssistant, result: ConfigFlowResult, mock_cloud_client: MagicMock hass: HomeAssistant, result: ConfigFlowResult, mock_cloud_client: MagicMock
) -> ConfigFlowResult: ) -> ConfigFlowResult:
@ -47,25 +56,24 @@ async def __do_sucessful_machine_selection_step(
) -> None: ) -> None:
"""Successfully configure the machine selection step.""" """Successfully configure the machine selection step."""
result3 = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result2["flow_id"], result2["flow_id"],
{CONF_MACHINE: "GS012345"}, {CONF_MACHINE: "GS012345"},
) )
assert result3["type"] is FlowResultType.CREATE_ENTRY assert result["type"] is FlowResultType.CREATE_ENTRY
assert result3["title"] == "GS012345" assert result["title"] == "GS012345"
assert result3["data"] == { assert result["data"] == {
**USER_INPUT, **USER_INPUT,
CONF_TOKEN: None, CONF_TOKEN: None,
} }
assert result3["result"].unique_id == "GS012345" assert result["result"].unique_id == "GS012345"
async def test_form( async def test_form(
hass: HomeAssistant, hass: HomeAssistant,
mock_cloud_client: MagicMock, mock_cloud_client: MagicMock,
mock_setup_entry: Generator[AsyncMock],
) -> None: ) -> None:
"""Test we get the form.""" """Test we get the form."""
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
@ -75,13 +83,12 @@ async def test_form(
assert result["errors"] == {} assert result["errors"] == {}
assert result["step_id"] == "user" assert result["step_id"] == "user"
result2 = await __do_successful_user_step(hass, result, mock_cloud_client) result = await __do_successful_user_step(hass, result, mock_cloud_client)
await __do_sucessful_machine_selection_step(hass, result2) await __do_sucessful_machine_selection_step(hass, result)
async def test_form_abort_already_configured( async def test_form_abort_already_configured(
hass: HomeAssistant, hass: HomeAssistant,
mock_cloud_client: MagicMock,
mock_config_entry: MockConfigEntry, mock_config_entry: MockConfigEntry,
) -> None: ) -> None:
"""Test we abort if already configured.""" """Test we abort if already configured."""
@ -93,25 +100,25 @@ async def test_form_abort_already_configured(
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["errors"] == {} assert result["errors"] == {}
result2 = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
USER_INPUT, USER_INPUT,
) )
await hass.async_block_till_done() await hass.async_block_till_done()
assert result2["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result2["step_id"] == "machine_selection" assert result["step_id"] == "machine_selection"
result3 = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result2["flow_id"], result["flow_id"],
{ {
CONF_MACHINE: "GS012345", CONF_MACHINE: "GS012345",
}, },
) )
await hass.async_block_till_done() await hass.async_block_till_done()
assert result3["type"] is FlowResultType.ABORT assert result["type"] is FlowResultType.ABORT
assert result3["reason"] == "already_configured" assert result["reason"] == "already_configured"
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -124,7 +131,6 @@ async def test_form_abort_already_configured(
async def test_form_invalid_auth( async def test_form_invalid_auth(
hass: HomeAssistant, hass: HomeAssistant,
mock_cloud_client: MagicMock, mock_cloud_client: MagicMock,
mock_setup_entry: Generator[AsyncMock],
side_effect: Exception, side_effect: Exception,
error: str, error: str,
) -> None: ) -> None:
@ -135,25 +141,24 @@ async def test_form_invalid_auth(
DOMAIN, context={"source": SOURCE_USER} DOMAIN, context={"source": SOURCE_USER}
) )
result2 = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
USER_INPUT, USER_INPUT,
) )
assert result2["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result2["errors"] == {"base": error} assert result["errors"] == {"base": error}
assert len(mock_cloud_client.list_things.mock_calls) == 1 assert len(mock_cloud_client.list_things.mock_calls) == 1
# test recovery from failure # test recovery from failure
mock_cloud_client.list_things.side_effect = None mock_cloud_client.list_things.side_effect = None
result2 = await __do_successful_user_step(hass, result, mock_cloud_client) result = await __do_successful_user_step(hass, result, mock_cloud_client)
await __do_sucessful_machine_selection_step(hass, result2) await __do_sucessful_machine_selection_step(hass, result)
async def test_form_no_machines( async def test_form_no_machines(
hass: HomeAssistant, hass: HomeAssistant,
mock_cloud_client: MagicMock, mock_cloud_client: MagicMock,
mock_setup_entry: Generator[AsyncMock],
) -> None: ) -> None:
"""Test we don't have any devices.""" """Test we don't have any devices."""
@ -164,20 +169,20 @@ async def test_form_no_machines(
DOMAIN, context={"source": SOURCE_USER} DOMAIN, context={"source": SOURCE_USER}
) )
result2 = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
USER_INPUT, USER_INPUT,
) )
assert result2["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result2["errors"] == {"base": "no_machines"} assert result["errors"] == {"base": "no_machines"}
assert len(mock_cloud_client.list_things.mock_calls) == 1 assert len(mock_cloud_client.list_things.mock_calls) == 1
# test recovery from failure # test recovery from failure
mock_cloud_client.list_things.return_value = original_return mock_cloud_client.list_things.return_value = original_return
result2 = await __do_successful_user_step(hass, result, mock_cloud_client) result = await __do_successful_user_step(hass, result, mock_cloud_client)
await __do_sucessful_machine_selection_step(hass, result2) await __do_sucessful_machine_selection_step(hass, result)
async def test_reauth_flow( async def test_reauth_flow(
@ -194,14 +199,14 @@ async def test_reauth_flow(
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "reauth_confirm" assert result["step_id"] == "reauth_confirm"
result2 = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
{CONF_PASSWORD: "new_password"}, {CONF_PASSWORD: "new_password"},
) )
assert result2["type"] is FlowResultType.ABORT assert result["type"] is FlowResultType.ABORT
await hass.async_block_till_done() await hass.async_block_till_done()
assert result2["reason"] == "reauth_successful" assert result["reason"] == "reauth_successful"
assert len(mock_cloud_client.list_things.mock_calls) == 1 assert len(mock_cloud_client.list_things.mock_calls) == 1
assert mock_config_entry.data[CONF_PASSWORD] == "new_password" assert mock_config_entry.data[CONF_PASSWORD] == "new_password"
@ -210,7 +215,6 @@ async def test_reconfigure_flow(
hass: HomeAssistant, hass: HomeAssistant,
mock_cloud_client: MagicMock, mock_cloud_client: MagicMock,
mock_config_entry: MockConfigEntry, mock_config_entry: MockConfigEntry,
mock_setup_entry: Generator[AsyncMock],
) -> None: ) -> None:
"""Testing reconfgure flow.""" """Testing reconfgure flow."""
mock_config_entry.add_to_hass(hass) mock_config_entry.add_to_hass(hass)
@ -220,7 +224,7 @@ async def test_reconfigure_flow(
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "reconfigure" assert result["step_id"] == "reconfigure"
result2 = await __do_successful_user_step(hass, result, mock_cloud_client) result = await __do_successful_user_step(hass, result, mock_cloud_client)
service_info = get_bluetooth_service_info(ModelName.GS3_MP, "GS012345") service_info = get_bluetooth_service_info(ModelName.GS3_MP, "GS012345")
with ( with (
@ -229,24 +233,24 @@ async def test_reconfigure_flow(
return_value=[service_info], return_value=[service_info],
), ),
): ):
result3 = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result2["flow_id"], result["flow_id"],
{ {
CONF_MACHINE: "GS012345", CONF_MACHINE: "GS012345",
}, },
) )
await hass.async_block_till_done() await hass.async_block_till_done()
assert result3["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result3["step_id"] == "bluetooth_selection" assert result["step_id"] == "bluetooth_selection"
result4 = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result3["flow_id"], result["flow_id"],
{CONF_MAC: service_info.address}, {CONF_MAC: service_info.address},
) )
assert result4["type"] is FlowResultType.ABORT assert result["type"] is FlowResultType.ABORT
assert result4["reason"] == "reconfigure_successful" assert result["reason"] == "reconfigure_successful"
assert mock_config_entry.title == "My LaMarzocco" assert mock_config_entry.title == "My LaMarzocco"
assert mock_config_entry.data == { assert mock_config_entry.data == {
@ -259,7 +263,6 @@ async def test_bluetooth_discovery(
hass: HomeAssistant, hass: HomeAssistant,
mock_lamarzocco: MagicMock, mock_lamarzocco: MagicMock,
mock_cloud_client: MagicMock, mock_cloud_client: MagicMock,
mock_setup_entry: Generator[AsyncMock],
) -> None: ) -> None:
"""Test bluetooth discovery.""" """Test bluetooth discovery."""
service_info = get_bluetooth_service_info( service_info = get_bluetooth_service_info(
@ -274,14 +277,14 @@ async def test_bluetooth_discovery(
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user" assert result["step_id"] == "user"
result2 = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
USER_INPUT, USER_INPUT,
) )
assert result2["type"] is FlowResultType.CREATE_ENTRY assert result["type"] is FlowResultType.CREATE_ENTRY
assert result2["title"] == "GS012345" assert result["title"] == "GS012345"
assert result2["data"] == { assert result["data"] == {
**USER_INPUT, **USER_INPUT,
CONF_MAC: "aa:bb:cc:dd:ee:ff", CONF_MAC: "aa:bb:cc:dd:ee:ff",
CONF_TOKEN: "dummyToken", CONF_TOKEN: "dummyToken",
@ -291,8 +294,6 @@ async def test_bluetooth_discovery(
async def test_bluetooth_discovery_already_configured( async def test_bluetooth_discovery_already_configured(
hass: HomeAssistant, hass: HomeAssistant,
mock_lamarzocco: MagicMock, mock_lamarzocco: MagicMock,
mock_cloud_client: MagicMock,
mock_setup_entry: Generator[AsyncMock],
mock_config_entry: MockConfigEntry, mock_config_entry: MockConfigEntry,
) -> None: ) -> None:
"""Test bluetooth discovery.""" """Test bluetooth discovery."""
@ -312,7 +313,6 @@ async def test_bluetooth_discovery_errors(
hass: HomeAssistant, hass: HomeAssistant,
mock_lamarzocco: MagicMock, mock_lamarzocco: MagicMock,
mock_cloud_client: MagicMock, mock_cloud_client: MagicMock,
mock_setup_entry: Generator[AsyncMock],
) -> None: ) -> None:
"""Test bluetooth discovery errors.""" """Test bluetooth discovery errors."""
service_info = get_bluetooth_service_info( service_info = get_bluetooth_service_info(
@ -330,24 +330,24 @@ async def test_bluetooth_discovery_errors(
original_return = deepcopy(mock_cloud_client.list_things.return_value) original_return = deepcopy(mock_cloud_client.list_things.return_value)
mock_cloud_client.list_things.return_value[0].serial_number = "GS98765" mock_cloud_client.list_things.return_value[0].serial_number = "GS98765"
result2 = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
USER_INPUT, USER_INPUT,
) )
assert result2["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result2["errors"] == {"base": "machine_not_found"} assert result["errors"] == {"base": "machine_not_found"}
assert len(mock_cloud_client.list_things.mock_calls) == 1 assert len(mock_cloud_client.list_things.mock_calls) == 1
mock_cloud_client.list_things.return_value = original_return mock_cloud_client.list_things.return_value = original_return
result2 = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
USER_INPUT, USER_INPUT,
) )
assert result2["type"] is FlowResultType.CREATE_ENTRY assert result["type"] is FlowResultType.CREATE_ENTRY
assert result2["title"] == "GS012345" assert result["title"] == "GS012345"
assert result2["data"] == { assert result["data"] == {
**USER_INPUT, **USER_INPUT,
CONF_MAC: "aa:bb:cc:dd:ee:ff", CONF_MAC: "aa:bb:cc:dd:ee:ff",
CONF_TOKEN: None, CONF_TOKEN: None,
@ -357,8 +357,6 @@ async def test_bluetooth_discovery_errors(
async def test_dhcp_discovery( async def test_dhcp_discovery(
hass: HomeAssistant, hass: HomeAssistant,
mock_lamarzocco: MagicMock, mock_lamarzocco: MagicMock,
mock_cloud_client: MagicMock,
mock_setup_entry: Generator[AsyncMock],
) -> None: ) -> None:
"""Test dhcp discovery.""" """Test dhcp discovery."""
@ -375,12 +373,12 @@ async def test_dhcp_discovery(
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user" assert result["step_id"] == "user"
result2 = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
USER_INPUT, USER_INPUT,
) )
assert result2["type"] is FlowResultType.CREATE_ENTRY assert result["type"] is FlowResultType.CREATE_ENTRY
assert result2["data"] == { assert result["data"] == {
**USER_INPUT, **USER_INPUT,
CONF_ADDRESS: "aa:bb:cc:dd:ee:ff", CONF_ADDRESS: "aa:bb:cc:dd:ee:ff",
CONF_TOKEN: None, CONF_TOKEN: None,
@ -389,8 +387,6 @@ async def test_dhcp_discovery(
async def test_dhcp_discovery_abort_on_hostname_changed( async def test_dhcp_discovery_abort_on_hostname_changed(
hass: HomeAssistant, hass: HomeAssistant,
mock_lamarzocco: MagicMock,
mock_cloud_client: MagicMock,
mock_config_entry: MockConfigEntry, mock_config_entry: MockConfigEntry,
) -> None: ) -> None:
"""Test dhcp discovery aborts when hostname was changed manually.""" """Test dhcp discovery aborts when hostname was changed manually."""
@ -411,7 +407,6 @@ async def test_dhcp_discovery_abort_on_hostname_changed(
async def test_dhcp_already_configured_and_update( async def test_dhcp_already_configured_and_update(
hass: HomeAssistant, hass: HomeAssistant,
mock_lamarzocco: MagicMock, mock_lamarzocco: MagicMock,
mock_cloud_client: MagicMock,
mock_config_entry: MockConfigEntry, mock_config_entry: MockConfigEntry,
) -> None: ) -> None:
"""Test discovered IP address change.""" """Test discovered IP address change."""
@ -436,9 +431,7 @@ async def test_dhcp_already_configured_and_update(
async def test_options_flow( async def test_options_flow(
hass: HomeAssistant, hass: HomeAssistant,
mock_lamarzocco: MagicMock,
mock_config_entry: MockConfigEntry, mock_config_entry: MockConfigEntry,
mock_setup_entry: Generator[AsyncMock],
) -> None: ) -> None:
"""Test options flow.""" """Test options flow."""
await async_init_integration(hass, mock_config_entry) await async_init_integration(hass, mock_config_entry)
@ -449,7 +442,7 @@ async def test_options_flow(
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "init" assert result["step_id"] == "init"
result2 = await hass.config_entries.options.async_configure( result = await hass.config_entries.options.async_configure(
result["flow_id"], result["flow_id"],
user_input={ user_input={
CONF_USE_BLUETOOTH: False, CONF_USE_BLUETOOTH: False,
@ -457,7 +450,7 @@ async def test_options_flow(
) )
await hass.async_block_till_done() await hass.async_block_till_done()
assert result2["type"] is FlowResultType.CREATE_ENTRY assert result["type"] is FlowResultType.CREATE_ENTRY
assert result2["data"] == { assert result["data"] == {
CONF_USE_BLUETOOTH: False, CONF_USE_BLUETOOTH: False,
} }

View File

@ -35,7 +35,6 @@ from tests.common import MockConfigEntry
async def test_load_unload_config_entry( async def test_load_unload_config_entry(
hass: HomeAssistant, hass: HomeAssistant,
mock_config_entry: MockConfigEntry, mock_config_entry: MockConfigEntry,
mock_lamarzocco: MagicMock,
) -> None: ) -> None:
"""Test loading and unloading the integration.""" """Test loading and unloading the integration."""
await async_init_integration(hass, mock_config_entry) await async_init_integration(hass, mock_config_entry)
@ -111,7 +110,6 @@ async def test_invalid_auth(
async def test_v1_migration_fails( async def test_v1_migration_fails(
hass: HomeAssistant, hass: HomeAssistant,
mock_cloud_client: MagicMock,
mock_lamarzocco: MagicMock, mock_lamarzocco: MagicMock,
) -> None: ) -> None:
"""Test v1 -> v2 Migration.""" """Test v1 -> v2 Migration."""
@ -131,7 +129,6 @@ async def test_v1_migration_fails(
async def test_v2_migration( async def test_v2_migration(
hass: HomeAssistant, hass: HomeAssistant,
mock_cloud_client: MagicMock,
mock_lamarzocco: MagicMock, mock_lamarzocco: MagicMock,
) -> None: ) -> None:
"""Test v2 -> v3 Migration.""" """Test v2 -> v3 Migration."""
@ -256,7 +253,6 @@ async def test_websocket_closed_on_unload(
async def test_gateway_version_issue( async def test_gateway_version_issue(
hass: HomeAssistant, hass: HomeAssistant,
mock_config_entry: MockConfigEntry, mock_config_entry: MockConfigEntry,
mock_lamarzocco: MagicMock,
mock_cloud_client: MagicMock, mock_cloud_client: MagicMock,
version: str, version: str,
issue_exists: bool, issue_exists: bool,

View File

@ -25,7 +25,6 @@ from tests.common import MockConfigEntry, snapshot_platform
async def test_switches( async def test_switches(
hass: HomeAssistant, hass: HomeAssistant,
mock_lamarzocco: MagicMock,
mock_config_entry: MockConfigEntry, mock_config_entry: MockConfigEntry,
entity_registry: er.EntityRegistry, entity_registry: er.EntityRegistry,
snapshot: SnapshotAssertion, snapshot: SnapshotAssertion,

View File

@ -19,7 +19,6 @@ from tests.common import MockConfigEntry, snapshot_platform
async def test_update( async def test_update(
hass: HomeAssistant, hass: HomeAssistant,
mock_lamarzocco: MagicMock,
mock_config_entry: MockConfigEntry, mock_config_entry: MockConfigEntry,
entity_registry: er.EntityRegistry, entity_registry: er.EntityRegistry,
snapshot: SnapshotAssertion, snapshot: SnapshotAssertion,

View File

@ -65,21 +65,21 @@ async def test_async_browse_media_root(
assert response["success"] assert response["success"]
result = response["result"] result = response["result"]
for idx, item in enumerate(result["children"]): for idx, item in enumerate(result["children"]):
assert item["title"] == LIBRARY[idx] assert item["title"].lower() == LIBRARY[idx]
@pytest.mark.parametrize( @pytest.mark.parametrize(
("category", "child_count"), ("category", "child_count"),
[ [
("Favorites", 4), ("favorites", 4),
("Artists", 4), ("artists", 4),
("Albums", 4), ("albums", 4),
("Playlists", 4), ("playlists", 4),
("Genres", 4), ("genres", 4),
("New Music", 4), ("new music", 4),
("Album Artists", 4), ("album artists", 4),
("Apps", 3), ("apps", 3),
("Radios", 3), ("radios", 3),
], ],
) )
async def test_async_browse_media_with_subitems( async def test_async_browse_media_with_subitems(

View File

@ -3,6 +3,7 @@
from datetime import UTC, datetime, timedelta from datetime import UTC, datetime, timedelta
from unittest.mock import MagicMock from unittest.mock import MagicMock
from freezegun.api import FrozenDateTimeFactory
import pytest import pytest
from syrupy import SnapshotAssertion from syrupy import SnapshotAssertion
from whirlpool.washerdryer import MachineState from whirlpool.washerdryer import MachineState
@ -58,6 +59,7 @@ async def test_washer_dryer_time_sensor(
entity_id: str, entity_id: str,
mock_fixture: str, mock_fixture: str,
request: pytest.FixtureRequest, request: pytest.FixtureRequest,
freezer: FrozenDateTimeFactory,
) -> None: ) -> None:
"""Test Washer/Dryer end time sensors.""" """Test Washer/Dryer end time sensors."""
now = utcnow() now = utcnow()
@ -113,7 +115,8 @@ async def test_washer_dryer_time_sensor(
# Test that periodic updates call the API to fetch data # Test that periodic updates call the API to fetch data
mock_instance.fetch_data.reset_mock() mock_instance.fetch_data.reset_mock()
async_fire_time_changed(hass, utcnow() + SCAN_INTERVAL) freezer.tick(SCAN_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done() await hass.async_block_till_done()
mock_instance.fetch_data.assert_called_once() mock_instance.fetch_data.assert_called_once()