diff --git a/homeassistant/components/shopping_list/__init__.py b/homeassistant/components/shopping_list/__init__.py index 97c6ed135c36..e60acf4b3778 100644 --- a/homeassistant/components/shopping_list/__init__.py +++ b/homeassistant/components/shopping_list/__init__.py @@ -325,8 +325,7 @@ class ShoppingData: ) return self.items - @callback - def async_reorder( + async def async_reorder( self, item_ids: list[str], context: Context | None = None ) -> None: """Reorder items.""" @@ -351,7 +350,7 @@ class ShoppingData: ) new_items.append(value) self.items = new_items - self.hass.async_add_executor_job(self.save) + await self.hass.async_add_executor_job(self.save) self._async_notify() self.hass.bus.async_fire( EVENT_SHOPPING_LIST_UPDATED, @@ -388,7 +387,7 @@ class ShoppingData: ) -> None: """Sort items by name.""" self.items = sorted(self.items, key=lambda item: item["name"], reverse=reverse) # type: ignore[arg-type,return-value] - self.hass.async_add_executor_job(self.save) + await self.hass.async_add_executor_job(self.save) self._async_notify() self.hass.bus.async_fire( EVENT_SHOPPING_LIST_UPDATED, @@ -591,7 +590,8 @@ async def websocket_handle_clear( vol.Required("item_ids"): [str], } ) -def websocket_handle_reorder( +@websocket_api.async_response +async def websocket_handle_reorder( hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict[str, Any], @@ -599,7 +599,9 @@ def websocket_handle_reorder( """Handle reordering shopping_list items.""" msg_id = msg.pop("id") try: - hass.data[DOMAIN].async_reorder(msg.pop("item_ids"), connection.context(msg)) + await hass.data[DOMAIN].async_reorder( + msg.pop("item_ids"), connection.context(msg) + ) except NoMatchingShoppingListItem: connection.send_error( msg_id, diff --git a/tests/components/shopping_list/conftest.py b/tests/components/shopping_list/conftest.py index dd1b690e1e3e..69d214efe18c 100644 --- a/tests/components/shopping_list/conftest.py +++ b/tests/components/shopping_list/conftest.py @@ -1,6 +1,8 @@ """Shopping list test helpers.""" -from unittest.mock import patch +from collections.abc import Generator +from contextlib import suppress +import os import pytest @@ -11,13 +13,13 @@ from tests.common import MockConfigEntry @pytest.fixture(autouse=True) -def mock_shopping_list_io(): - """Stub out the persistence.""" - with ( - patch("homeassistant.components.shopping_list.ShoppingData.save"), - patch("homeassistant.components.shopping_list.ShoppingData.async_load"), - ): +def wipe_shopping_list_store(hass: HomeAssistant) -> Generator[None]: + """Wipe shopping list store after test.""" + try: yield + finally: + with suppress(FileNotFoundError): + os.remove(hass.config.path(".shopping_list.json")) @pytest.fixture @@ -27,7 +29,7 @@ def mock_config_entry() -> MockConfigEntry: @pytest.fixture -async def sl_setup(hass: HomeAssistant, mock_config_entry: MockConfigEntry): +async def sl_setup(hass: HomeAssistant, mock_config_entry: MockConfigEntry) -> None: """Set up the shopping list.""" mock_config_entry.add_to_hass(hass) diff --git a/tests/components/shopping_list/snapshots/test_init.ambr b/tests/components/shopping_list/snapshots/test_init.ambr new file mode 100644 index 000000000000..0e30e9bf0590 --- /dev/null +++ b/tests/components/shopping_list/snapshots/test_init.ambr @@ -0,0 +1,717 @@ +# serializer version: 1 +# name: test_add_item + list([ + dict({ + 'complete': False, + 'id': '', + 'name': 'beer', + }), + ]) +# --- +# name: test_add_item_service + list([ + dict({ + 'complete': False, + 'id': '', + 'name': 'beer', + }), + ]) +# --- +# name: test_api_update_fails + list([ + dict({ + 'complete': False, + 'id': '', + 'name': 'beer', + }), + ]) +# --- +# name: test_api_update_fails.1 + list([ + dict({ + 'complete': False, + 'id': '', + 'name': 'beer', + }), + ]) +# --- +# name: test_api_update_fails.2 + list([ + dict({ + 'complete': False, + 'id': '', + 'name': 'beer', + }), + ]) +# --- +# name: test_clear_completed_items + list([ + dict({ + 'complete': False, + 'id': '', + 'name': 'beer', + }), + dict({ + 'complete': False, + 'id': '', + 'name': 'cheese', + }), + ]) +# --- +# name: test_clear_completed_items.1 + list([ + dict({ + 'complete': True, + 'id': '', + 'name': 'beer', + }), + dict({ + 'complete': True, + 'id': '', + 'name': 'cheese', + }), + ]) +# --- +# name: test_clear_completed_items.2 + list([ + ]) +# --- +# name: test_clear_completed_items_service + list([ + dict({ + 'complete': False, + 'id': '', + 'name': 'beer', + }), + ]) +# --- +# name: test_clear_completed_items_service.1 + list([ + dict({ + 'complete': True, + 'id': '', + 'name': 'beer', + }), + ]) +# --- +# name: test_clear_completed_items_service.2 + list([ + ]) +# --- +# name: test_deprecated_api_clear_completed + list([ + dict({ + 'complete': False, + 'id': '', + 'name': 'beer', + }), + dict({ + 'complete': False, + 'id': '', + 'name': 'wine', + }), + ]) +# --- +# name: test_deprecated_api_clear_completed.1 + list([ + dict({ + 'complete': True, + 'id': '', + 'name': 'beer', + }), + dict({ + 'complete': False, + 'id': '', + 'name': 'wine', + }), + ]) +# --- +# name: test_deprecated_api_clear_completed.2 + list([ + dict({ + 'complete': False, + 'id': '', + 'name': 'wine', + }), + ]) +# --- +# name: test_deprecated_api_create + list([ + dict({ + 'complete': False, + 'id': '', + 'name': 'soda', + }), + ]) +# --- +# name: test_deprecated_api_create_fail + '' +# --- +# name: test_deprecated_api_get_all + list([ + dict({ + 'complete': False, + 'id': '', + 'name': 'beer', + }), + dict({ + 'complete': False, + 'id': '', + 'name': 'wine', + }), + ]) +# --- +# name: test_deprecated_api_get_all.1 + list([ + dict({ + 'complete': False, + 'id': '', + 'name': 'beer', + }), + dict({ + 'complete': False, + 'id': '', + 'name': 'wine', + }), + ]) +# --- +# name: test_deprecated_api_update + list([ + dict({ + 'complete': False, + 'id': '', + 'name': 'beer', + }), + dict({ + 'complete': False, + 'id': '', + 'name': 'wine', + }), + ]) +# --- +# name: test_deprecated_api_update.1 + list([ + dict({ + 'complete': False, + 'id': '', + 'name': 'soda', + }), + dict({ + 'complete': False, + 'id': '', + 'name': 'wine', + }), + ]) +# --- +# name: test_deprecated_api_update.2 + list([ + dict({ + 'complete': False, + 'id': '', + 'name': 'soda', + }), + dict({ + 'complete': True, + 'id': '', + 'name': 'wine', + }), + ]) +# --- +# name: test_recent_items_intent + list([ + dict({ + 'complete': False, + 'id': '', + 'name': 'beer', + }), + dict({ + 'complete': False, + 'id': '', + 'name': 'wine', + }), + dict({ + 'complete': False, + 'id': '', + 'name': 'soda', + }), + ]) +# --- +# name: test_recent_items_intent.1 + list([ + dict({ + 'complete': False, + 'id': '', + 'name': 'beer', + }), + dict({ + 'complete': False, + 'id': '', + 'name': 'wine', + }), + dict({ + 'complete': False, + 'id': '', + 'name': 'soda', + }), + ]) +# --- +# name: test_remove_item + list([ + dict({ + 'complete': False, + 'id': '', + 'name': 'beer', + }), + dict({ + 'complete': False, + 'id': '', + 'name': 'cheese', + }), + ]) +# --- +# name: test_remove_item.1 + list([ + dict({ + 'complete': False, + 'id': '', + 'name': 'cheese', + }), + ]) +# --- +# name: test_remove_item.2 + list([ + dict({ + 'complete': False, + 'id': '', + 'name': 'cheese', + }), + ]) +# --- +# name: test_remove_item_service + list([ + dict({ + 'complete': False, + 'id': '', + 'name': 'beer', + }), + dict({ + 'complete': False, + 'id': '', + 'name': 'cheese', + }), + ]) +# --- +# name: test_remove_item_service.1 + list([ + dict({ + 'complete': False, + 'id': '', + 'name': 'cheese', + }), + ]) +# --- +# name: test_sort_list_service + list([ + dict({ + 'complete': False, + 'id': '', + 'name': 'zzz', + }), + dict({ + 'complete': False, + 'id': '', + 'name': 'ddd', + }), + dict({ + 'complete': False, + 'id': '', + 'name': 'aaa', + }), + ]) +# --- +# name: test_sort_list_service.1 + list([ + dict({ + 'complete': False, + 'id': '', + 'name': 'aaa', + }), + dict({ + 'complete': False, + 'id': '', + 'name': 'ddd', + }), + dict({ + 'complete': False, + 'id': '', + 'name': 'zzz', + }), + ]) +# --- +# name: test_sort_list_service.2 + list([ + dict({ + 'complete': False, + 'id': '', + 'name': 'zzz', + }), + dict({ + 'complete': False, + 'id': '', + 'name': 'ddd', + }), + dict({ + 'complete': False, + 'id': '', + 'name': 'aaa', + }), + ]) +# --- +# name: test_update_list + list([ + dict({ + 'complete': False, + 'id': '', + 'name': 'beer', + }), + dict({ + 'complete': False, + 'id': '', + 'name': 'cheese', + }), + ]) +# --- +# name: test_update_list.1 + list([ + dict({ + 'complete': False, + 'id': '', + 'name': 'dupe', + }), + dict({ + 'complete': False, + 'id': '', + 'name': 'dupe', + }), + ]) +# --- +# name: test_ws_add_item + list([ + dict({ + 'complete': False, + 'id': '', + 'name': 'soda', + }), + ]) +# --- +# name: test_ws_add_item_fail + '' +# --- +# name: test_ws_clear_items + list([ + dict({ + 'complete': False, + 'id': '', + 'name': 'beer', + }), + dict({ + 'complete': False, + 'id': '', + 'name': 'wine', + }), + ]) +# --- +# name: test_ws_clear_items.1 + list([ + dict({ + 'complete': True, + 'id': '', + 'name': 'beer', + }), + dict({ + 'complete': False, + 'id': '', + 'name': 'wine', + }), + ]) +# --- +# name: test_ws_clear_items.2 + list([ + dict({ + 'complete': False, + 'id': '', + 'name': 'wine', + }), + ]) +# --- +# name: test_ws_get_items + list([ + dict({ + 'complete': False, + 'id': '', + 'name': 'beer', + }), + dict({ + 'complete': False, + 'id': '', + 'name': 'wine', + }), + ]) +# --- +# name: test_ws_get_items.1 + list([ + dict({ + 'complete': False, + 'id': '', + 'name': 'beer', + }), + dict({ + 'complete': False, + 'id': '', + 'name': 'wine', + }), + ]) +# --- +# name: test_ws_remove_item + list([ + dict({ + 'complete': False, + 'id': '', + 'name': 'soda', + }), + dict({ + 'complete': False, + 'id': '', + 'name': 'cheese', + }), + ]) +# --- +# name: test_ws_remove_item.1 + list([ + dict({ + 'complete': False, + 'id': '', + 'name': 'cheese', + }), + ]) +# --- +# name: test_ws_remove_item_fail + list([ + dict({ + 'complete': False, + 'id': '', + 'name': 'soda', + }), + ]) +# --- +# name: test_ws_remove_item_fail.1 + list([ + dict({ + 'complete': False, + 'id': '', + 'name': 'soda', + }), + ]) +# --- +# name: test_ws_reorder_items + list([ + dict({ + 'complete': False, + 'id': '', + 'name': 'beer', + }), + dict({ + 'complete': False, + 'id': '', + 'name': 'wine', + }), + dict({ + 'complete': False, + 'id': '', + 'name': 'apple', + }), + ]) +# --- +# name: test_ws_reorder_items.1 + list([ + dict({ + 'complete': False, + 'id': '', + 'name': 'wine', + }), + dict({ + 'complete': False, + 'id': '', + 'name': 'apple', + }), + dict({ + 'complete': False, + 'id': '', + 'name': 'beer', + }), + ]) +# --- +# name: test_ws_reorder_items.2 + list([ + dict({ + 'complete': True, + 'id': '', + 'name': 'wine', + }), + dict({ + 'complete': False, + 'id': '', + 'name': 'apple', + }), + dict({ + 'complete': False, + 'id': '', + 'name': 'beer', + }), + ]) +# --- +# name: test_ws_reorder_items.3 + list([ + dict({ + 'complete': False, + 'id': '', + 'name': 'apple', + }), + dict({ + 'complete': False, + 'id': '', + 'name': 'beer', + }), + dict({ + 'complete': True, + 'id': '', + 'name': 'wine', + }), + ]) +# --- +# name: test_ws_reorder_items_failure + list([ + dict({ + 'complete': False, + 'id': '', + 'name': 'beer', + }), + dict({ + 'complete': False, + 'id': '', + 'name': 'wine', + }), + dict({ + 'complete': False, + 'id': '', + 'name': 'apple', + }), + ]) +# --- +# name: test_ws_reorder_items_failure.1 + list([ + dict({ + 'complete': False, + 'id': '', + 'name': 'beer', + }), + dict({ + 'complete': False, + 'id': '', + 'name': 'wine', + }), + dict({ + 'complete': False, + 'id': '', + 'name': 'apple', + }), + ]) +# --- +# name: test_ws_reorder_items_failure.2 + list([ + dict({ + 'complete': False, + 'id': '', + 'name': 'beer', + }), + dict({ + 'complete': False, + 'id': '', + 'name': 'wine', + }), + dict({ + 'complete': False, + 'id': '', + 'name': 'apple', + }), + ]) +# --- +# name: test_ws_update_item + list([ + dict({ + 'complete': False, + 'id': '', + 'name': 'beer', + }), + dict({ + 'complete': False, + 'id': '', + 'name': 'wine', + }), + ]) +# --- +# name: test_ws_update_item.1 + list([ + dict({ + 'complete': False, + 'id': '', + 'name': 'soda', + }), + dict({ + 'complete': False, + 'id': '', + 'name': 'wine', + }), + ]) +# --- +# name: test_ws_update_item.2 + list([ + dict({ + 'complete': False, + 'id': '', + 'name': 'soda', + }), + dict({ + 'complete': True, + 'id': '', + 'name': 'wine', + }), + ]) +# --- +# name: test_ws_update_item_fail + list([ + dict({ + 'complete': False, + 'id': '', + 'name': 'beer', + }), + ]) +# --- +# name: test_ws_update_item_fail.1 + list([ + dict({ + 'complete': False, + 'id': '', + 'name': 'beer', + }), + ]) +# --- +# name: test_ws_update_item_fail.2 + list([ + dict({ + 'complete': False, + 'id': '', + 'name': 'beer', + }), + ]) +# --- diff --git a/tests/components/shopping_list/test_init.py b/tests/components/shopping_list/test_init.py index 276602f794ea..644f28c64d20 100644 --- a/tests/components/shopping_list/test_init.py +++ b/tests/components/shopping_list/test_init.py @@ -1,8 +1,11 @@ """Test shopping list component.""" from http import HTTPStatus +import json +from pathlib import Path import pytest +from syrupy.assertion import SnapshotAssertion from homeassistant.components.shopping_list import NoMatchingShoppingListItem from homeassistant.components.shopping_list.const import ( @@ -28,7 +31,23 @@ from tests.common import async_capture_events from tests.typing import ClientSessionGenerator, WebSocketGenerator -async def test_add_item(hass: HomeAssistant, sl_setup) -> None: +def assert_shopping_list_data(hass: HomeAssistant, snapshot: SnapshotAssertion) -> None: + """Assert shopping list data matches snapshot.""" + path = Path(hass.config.path(".shopping_list.json")) + if not path.exists(): + assert snapshot == "" + else: + shopping_list_data = json.loads(path.read_text(encoding="utf-8")) + for item in shopping_list_data: + if "id" not in item: + continue + item["id"] = "" + assert shopping_list_data == snapshot + + +async def test_add_item( + hass: HomeAssistant, sl_setup: None, snapshot: SnapshotAssertion +) -> None: """Test adding an item intent.""" response = await intent.async_handle( @@ -39,10 +58,13 @@ async def test_add_item(hass: HomeAssistant, sl_setup) -> None: # Response text is now handled by default conversation agent assert response.response_type == intent.IntentResponseType.ACTION_DONE + assert_shopping_list_data(hass, snapshot) -async def test_remove_item(hass: HomeAssistant, sl_setup) -> None: - """Test removiung list items.""" +async def test_remove_item( + hass: HomeAssistant, sl_setup: None, snapshot: SnapshotAssertion +) -> None: + """Test removing list items.""" await intent.async_handle( hass, "test", "HassShoppingListAddItem", {"item": {"value": "beer"}} ) @@ -50,12 +72,14 @@ async def test_remove_item(hass: HomeAssistant, sl_setup) -> None: await intent.async_handle( hass, "test", "HassShoppingListAddItem", {"item": {"value": "cheese"}} ) + assert_shopping_list_data(hass, snapshot) assert len(hass.data[DOMAIN].items) == 2 # Remove a single item item_id = hass.data[DOMAIN].items[0]["id"] await hass.data[DOMAIN].async_remove(item_id) + assert_shopping_list_data(hass, snapshot) assert len(hass.data[DOMAIN].items) == 1 @@ -65,9 +89,12 @@ async def test_remove_item(hass: HomeAssistant, sl_setup) -> None: # Trying to remove the same item twice should fail with pytest.raises(NoMatchingShoppingListItem): await hass.data[DOMAIN].async_remove(item_id) + assert_shopping_list_data(hass, snapshot) -async def test_update_list(hass: HomeAssistant, sl_setup) -> None: +async def test_update_list( + hass: HomeAssistant, sl_setup: None, snapshot: SnapshotAssertion +) -> None: """Test updating all list items.""" await intent.async_handle( hass, "test", "HassShoppingListAddItem", {"item": {"value": "beer"}} @@ -76,6 +103,7 @@ async def test_update_list(hass: HomeAssistant, sl_setup) -> None: await intent.async_handle( hass, "test", "HassShoppingListAddItem", {"item": {"value": "cheese"}} ) + assert_shopping_list_data(hass, snapshot) # Update a single attribute, other attributes shouldn't change await hass.data[DOMAIN].async_update_list({"complete": True}) @@ -90,6 +118,7 @@ async def test_update_list(hass: HomeAssistant, sl_setup) -> None: # Update multiple attributes await hass.data[DOMAIN].async_update_list({"name": "dupe", "complete": False}) + assert_shopping_list_data(hass, snapshot) beer = hass.data[DOMAIN].items[0] assert beer["name"] == "dupe" @@ -100,7 +129,9 @@ async def test_update_list(hass: HomeAssistant, sl_setup) -> None: assert cheese["complete"] is False -async def test_clear_completed_items(hass: HomeAssistant, sl_setup) -> None: +async def test_clear_completed_items( + hass: HomeAssistant, sl_setup: None, snapshot: SnapshotAssertion +) -> None: """Test clear completed list items.""" await intent.async_handle( hass, @@ -112,18 +143,23 @@ async def test_clear_completed_items(hass: HomeAssistant, sl_setup) -> None: await intent.async_handle( hass, "test", "HassShoppingListAddItem", {"item": {"value": "cheese"}} ) + assert_shopping_list_data(hass, snapshot) assert len(hass.data[DOMAIN].items) == 2 # Update a single attribute, other attributes shouldn't change await hass.data[DOMAIN].async_update_list({"complete": True}) + assert_shopping_list_data(hass, snapshot) await hass.data[DOMAIN].async_clear_completed() + assert_shopping_list_data(hass, snapshot) assert len(hass.data[DOMAIN].items) == 0 -async def test_recent_items_intent(hass: HomeAssistant, sl_setup) -> None: +async def test_recent_items_intent( + hass: HomeAssistant, sl_setup: None, snapshot: SnapshotAssertion +) -> None: """Test recent items.""" await intent.async_handle( @@ -135,8 +171,10 @@ async def test_recent_items_intent(hass: HomeAssistant, sl_setup) -> None: await intent.async_handle( hass, "test", "HassShoppingListAddItem", {"item": {"value": "soda"}} ) + assert_shopping_list_data(hass, snapshot) response = await intent.async_handle(hass, "test", "HassShoppingListLastItems") + assert_shopping_list_data(hass, snapshot) assert ( response.speech["plain"]["speech"] @@ -145,7 +183,10 @@ async def test_recent_items_intent(hass: HomeAssistant, sl_setup) -> None: async def test_deprecated_api_get_all( - hass: HomeAssistant, hass_client: ClientSessionGenerator, sl_setup + hass: HomeAssistant, + hass_client: ClientSessionGenerator, + sl_setup: None, + snapshot: SnapshotAssertion, ) -> None: """Test the API.""" @@ -155,9 +196,11 @@ async def test_deprecated_api_get_all( await intent.async_handle( hass, "test", "HassShoppingListAddItem", {"item": {"value": "wine"}} ) + assert_shopping_list_data(hass, snapshot) client = await hass_client() resp = await client.get("/api/shopping_list") + assert_shopping_list_data(hass, snapshot) assert resp.status == HTTPStatus.OK data = await resp.json() @@ -169,7 +212,10 @@ async def test_deprecated_api_get_all( async def test_ws_get_items( - hass: HomeAssistant, hass_ws_client: WebSocketGenerator, sl_setup + hass: HomeAssistant, + hass_ws_client: WebSocketGenerator, + sl_setup: None, + snapshot: SnapshotAssertion, ) -> None: """Test get shopping_list items websocket command.""" @@ -179,6 +225,7 @@ async def test_ws_get_items( await intent.async_handle( hass, "test", "HassShoppingListAddItem", {"item": {"value": "wine"}} ) + assert_shopping_list_data(hass, snapshot) client = await hass_ws_client(hass) events = async_capture_events(hass, EVENT_SHOPPING_LIST_UPDATED) @@ -187,6 +234,7 @@ async def test_ws_get_items( msg = await client.receive_json() assert msg["success"] is True assert len(events) == 0 + assert_shopping_list_data(hass, snapshot) assert msg["id"] == 5 assert msg["type"] == TYPE_RESULT @@ -200,7 +248,10 @@ async def test_ws_get_items( async def test_deprecated_api_update( - hass: HomeAssistant, hass_client: ClientSessionGenerator, sl_setup + hass: HomeAssistant, + hass_client: ClientSessionGenerator, + sl_setup: None, + snapshot: SnapshotAssertion, ) -> None: """Test the API.""" @@ -210,6 +261,7 @@ async def test_deprecated_api_update( await intent.async_handle( hass, "test", "HassShoppingListAddItem", {"item": {"value": "wine"}} ) + assert_shopping_list_data(hass, snapshot) beer_id = hass.data["shopping_list"].items[0]["id"] wine_id = hass.data["shopping_list"].items[1]["id"] @@ -219,6 +271,7 @@ async def test_deprecated_api_update( resp = await client.post( f"/api/shopping_list/item/{beer_id}", json={"name": "soda"} ) + assert_shopping_list_data(hass, snapshot) assert resp.status == HTTPStatus.OK assert len(events) == 1 @@ -228,6 +281,7 @@ async def test_deprecated_api_update( resp = await client.post( f"/api/shopping_list/item/{wine_id}", json={"complete": True} ) + assert_shopping_list_data(hass, snapshot) assert resp.status == HTTPStatus.OK assert len(events) == 2 @@ -240,7 +294,10 @@ async def test_deprecated_api_update( async def test_ws_update_item( - hass: HomeAssistant, hass_ws_client: WebSocketGenerator, sl_setup + hass: HomeAssistant, + hass_ws_client: WebSocketGenerator, + sl_setup: None, + snapshot: SnapshotAssertion, ) -> None: """Test update shopping_list item websocket command.""" await intent.async_handle( @@ -249,6 +306,7 @@ async def test_ws_update_item( await intent.async_handle( hass, "test", "HassShoppingListAddItem", {"item": {"value": "wine"}} ) + assert_shopping_list_data(hass, snapshot) beer_id = hass.data["shopping_list"].items[0]["id"] wine_id = hass.data["shopping_list"].items[1]["id"] @@ -267,6 +325,7 @@ async def test_ws_update_item( data = msg["result"] assert data == {"id": beer_id, "name": "soda", "complete": False} assert len(events) == 1 + assert_shopping_list_data(hass, snapshot) await client.send_json( { @@ -281,6 +340,7 @@ async def test_ws_update_item( data = msg["result"] assert data == {"id": wine_id, "name": "wine", "complete": True} assert len(events) == 2 + assert_shopping_list_data(hass, snapshot) beer, wine = hass.data["shopping_list"].items assert beer == {"id": beer_id, "name": "soda", "complete": False} @@ -288,34 +348,44 @@ async def test_ws_update_item( async def test_api_update_fails( - hass: HomeAssistant, hass_client: ClientSessionGenerator, sl_setup + hass: HomeAssistant, + hass_client: ClientSessionGenerator, + sl_setup: None, + snapshot: SnapshotAssertion, ) -> None: """Test the API.""" await intent.async_handle( hass, "test", "HassShoppingListAddItem", {"item": {"value": "beer"}} ) + assert_shopping_list_data(hass, snapshot) client = await hass_client() events = async_capture_events(hass, EVENT_SHOPPING_LIST_UPDATED) resp = await client.post("/api/shopping_list/non_existing", json={"name": "soda"}) + assert_shopping_list_data(hass, snapshot) assert resp.status == HTTPStatus.NOT_FOUND assert len(events) == 0 beer_id = hass.data["shopping_list"].items[0]["id"] resp = await client.post(f"/api/shopping_list/item/{beer_id}", json={"name": 123}) + assert_shopping_list_data(hass, snapshot) assert resp.status == HTTPStatus.BAD_REQUEST async def test_ws_update_item_fail( - hass: HomeAssistant, hass_ws_client: WebSocketGenerator, sl_setup + hass: HomeAssistant, + hass_ws_client: WebSocketGenerator, + sl_setup: None, + snapshot: SnapshotAssertion, ) -> None: """Test failure of update shopping_list item websocket command.""" await intent.async_handle( hass, "test", "HassShoppingListAddItem", {"item": {"value": "beer"}} ) + assert_shopping_list_data(hass, snapshot) client = await hass_ws_client(hass) events = async_capture_events(hass, EVENT_SHOPPING_LIST_UPDATED) await client.send_json( @@ -331,15 +401,20 @@ async def test_ws_update_item_fail( data = msg["error"] assert data == {"code": "item_not_found", "message": "Item not found"} assert len(events) == 0 + assert_shopping_list_data(hass, snapshot) await client.send_json({"id": 6, "type": "shopping_list/items/update", "name": 123}) msg = await client.receive_json() assert msg["success"] is False assert len(events) == 0 + assert_shopping_list_data(hass, snapshot) async def test_deprecated_api_clear_completed( - hass: HomeAssistant, hass_client: ClientSessionGenerator, sl_setup + hass: HomeAssistant, + hass_client: ClientSessionGenerator, + sl_setup: None, + snapshot: SnapshotAssertion, ) -> None: """Test the API.""" @@ -349,6 +424,7 @@ async def test_deprecated_api_clear_completed( await intent.async_handle( hass, "test", "HassShoppingListAddItem", {"item": {"value": "wine"}} ) + assert_shopping_list_data(hass, snapshot) beer_id = hass.data["shopping_list"].items[0]["id"] wine_id = hass.data["shopping_list"].items[1]["id"] @@ -362,10 +438,12 @@ async def test_deprecated_api_clear_completed( ) assert resp.status == HTTPStatus.OK assert len(events) == 1 + assert_shopping_list_data(hass, snapshot) resp = await client.post("/api/shopping_list/clear_completed") assert resp.status == HTTPStatus.OK assert len(events) == 2 + assert_shopping_list_data(hass, snapshot) items = hass.data["shopping_list"].items assert len(items) == 1 @@ -374,7 +452,10 @@ async def test_deprecated_api_clear_completed( async def test_ws_clear_items( - hass: HomeAssistant, hass_ws_client: WebSocketGenerator, sl_setup + hass: HomeAssistant, + hass_ws_client: WebSocketGenerator, + sl_setup: None, + snapshot: SnapshotAssertion, ) -> None: """Test clearing shopping_list items websocket command.""" await intent.async_handle( @@ -383,6 +464,7 @@ async def test_ws_clear_items( await intent.async_handle( hass, "test", "HassShoppingListAddItem", {"item": {"value": "wine"}} ) + assert_shopping_list_data(hass, snapshot) beer_id = hass.data["shopping_list"].items[0]["id"] wine_id = hass.data["shopping_list"].items[1]["id"] client = await hass_ws_client(hass) @@ -398,6 +480,7 @@ async def test_ws_clear_items( msg = await client.receive_json() assert msg["success"] is True assert len(events) == 1 + assert_shopping_list_data(hass, snapshot) await client.send_json({"id": 6, "type": "shopping_list/items/clear"}) msg = await client.receive_json() @@ -406,16 +489,21 @@ async def test_ws_clear_items( assert len(items) == 1 assert items[0] == {"id": wine_id, "name": "wine", "complete": False} assert len(events) == 2 + assert_shopping_list_data(hass, snapshot) async def test_deprecated_api_create( - hass: HomeAssistant, hass_client: ClientSessionGenerator, sl_setup + hass: HomeAssistant, + hass_client: ClientSessionGenerator, + sl_setup: None, + snapshot: SnapshotAssertion, ) -> None: """Test the API.""" client = await hass_client() events = async_capture_events(hass, EVENT_SHOPPING_LIST_UPDATED) resp = await client.post("/api/shopping_list/item", json={"name": "soda"}) + assert_shopping_list_data(hass, snapshot) assert resp.status == HTTPStatus.OK data = await resp.json() @@ -430,13 +518,17 @@ async def test_deprecated_api_create( async def test_deprecated_api_create_fail( - hass: HomeAssistant, hass_client: ClientSessionGenerator, sl_setup + hass: HomeAssistant, + hass_client: ClientSessionGenerator, + sl_setup: None, + snapshot: SnapshotAssertion, ) -> None: """Test the API.""" client = await hass_client() events = async_capture_events(hass, EVENT_SHOPPING_LIST_UPDATED) resp = await client.post("/api/shopping_list/item", json={"name": 1234}) + assert_shopping_list_data(hass, snapshot) assert resp.status == HTTPStatus.BAD_REQUEST assert len(hass.data["shopping_list"].items) == 0 @@ -444,7 +536,10 @@ async def test_deprecated_api_create_fail( async def test_ws_add_item( - hass: HomeAssistant, hass_ws_client: WebSocketGenerator, sl_setup + hass: HomeAssistant, + hass_ws_client: WebSocketGenerator, + sl_setup: None, + snapshot: SnapshotAssertion, ) -> None: """Test adding shopping_list item websocket command.""" client = await hass_ws_client(hass) @@ -456,6 +551,7 @@ async def test_ws_add_item( assert data["name"] == "soda" assert data["complete"] is False assert len(events) == 1 + assert_shopping_list_data(hass, snapshot) items = hass.data["shopping_list"].items assert len(items) == 1 @@ -464,7 +560,10 @@ async def test_ws_add_item( async def test_ws_add_item_fail( - hass: HomeAssistant, hass_ws_client: WebSocketGenerator, sl_setup + hass: HomeAssistant, + hass_ws_client: WebSocketGenerator, + sl_setup: None, + snapshot: SnapshotAssertion, ) -> None: """Test adding shopping_list item failure websocket command.""" client = await hass_ws_client(hass) @@ -474,10 +573,14 @@ async def test_ws_add_item_fail( assert msg["success"] is False assert len(events) == 0 assert len(hass.data["shopping_list"].items) == 0 + assert_shopping_list_data(hass, snapshot) async def test_ws_remove_item( - hass: HomeAssistant, hass_ws_client: WebSocketGenerator, sl_setup + hass: HomeAssistant, + hass_ws_client: WebSocketGenerator, + sl_setup: None, + snapshot: SnapshotAssertion, ) -> None: """Test removing shopping_list item websocket command.""" client = await hass_ws_client(hass) @@ -490,6 +593,7 @@ async def test_ws_remove_item( ) msg = await client.receive_json() assert len(events) == 2 + assert_shopping_list_data(hass, snapshot) items = hass.data["shopping_list"].items assert len(items) == 2 @@ -500,6 +604,7 @@ async def test_ws_remove_item( msg = await client.receive_json() assert len(events) == 3 assert msg["success"] is True + assert_shopping_list_data(hass, snapshot) items = hass.data["shopping_list"].items assert len(items) == 1 @@ -507,22 +612,30 @@ async def test_ws_remove_item( async def test_ws_remove_item_fail( - hass: HomeAssistant, hass_ws_client: WebSocketGenerator, sl_setup + hass: HomeAssistant, + hass_ws_client: WebSocketGenerator, + sl_setup: None, + snapshot: SnapshotAssertion, ) -> None: """Test removing shopping_list item failure websocket command.""" client = await hass_ws_client(hass) events = async_capture_events(hass, EVENT_SHOPPING_LIST_UPDATED) await client.send_json({"id": 5, "type": "shopping_list/items/add", "name": "soda"}) msg = await client.receive_json() + assert_shopping_list_data(hass, snapshot) await client.send_json({"id": 6, "type": "shopping_list/items/remove"}) msg = await client.receive_json() assert msg["success"] is False assert len(events) == 1 assert len(hass.data["shopping_list"].items) == 1 + assert_shopping_list_data(hass, snapshot) async def test_ws_reorder_items( - hass: HomeAssistant, hass_ws_client: WebSocketGenerator, sl_setup + hass: HomeAssistant, + hass_ws_client: WebSocketGenerator, + sl_setup: None, + snapshot: SnapshotAssertion, ) -> None: """Test reordering shopping_list items websocket command.""" await intent.async_handle( @@ -534,6 +647,7 @@ async def test_ws_reorder_items( await intent.async_handle( hass, "test", "HassShoppingListAddItem", {"item": {"value": "apple"}} ) + assert_shopping_list_data(hass, snapshot) beer_id = hass.data["shopping_list"].items[0]["id"] wine_id = hass.data["shopping_list"].items[1]["id"] @@ -566,6 +680,7 @@ async def test_ws_reorder_items( "name": "beer", "complete": False, } + assert_shopping_list_data(hass, snapshot) # Mark wine as completed. await client.send_json( @@ -578,6 +693,7 @@ async def test_ws_reorder_items( ) _ = await client.receive_json() assert len(events) == 2 + assert_shopping_list_data(hass, snapshot) await client.send_json( { @@ -604,10 +720,14 @@ async def test_ws_reorder_items( "name": "wine", "complete": True, } + assert_shopping_list_data(hass, snapshot) async def test_ws_reorder_items_failure( - hass: HomeAssistant, hass_ws_client: WebSocketGenerator, sl_setup + hass: HomeAssistant, + hass_ws_client: WebSocketGenerator, + sl_setup: None, + snapshot: SnapshotAssertion, ) -> None: """Test reordering shopping_list items websocket command.""" await intent.async_handle( @@ -619,6 +739,7 @@ async def test_ws_reorder_items_failure( await intent.async_handle( hass, "test", "HassShoppingListAddItem", {"item": {"value": "apple"}} ) + assert_shopping_list_data(hass, snapshot) beer_id = hass.data["shopping_list"].items[0]["id"] wine_id = hass.data["shopping_list"].items[1]["id"] @@ -639,6 +760,7 @@ async def test_ws_reorder_items_failure( assert msg["success"] is False assert msg["error"]["code"] == ERR_NOT_FOUND assert len(events) == 0 + assert_shopping_list_data(hass, snapshot) # Testing not sending all unchecked item ids. await client.send_json( @@ -652,9 +774,12 @@ async def test_ws_reorder_items_failure( assert msg["success"] is False assert msg["error"]["code"] == ERR_INVALID_FORMAT assert len(events) == 0 + assert_shopping_list_data(hass, snapshot) -async def test_add_item_service(hass: HomeAssistant, sl_setup) -> None: +async def test_add_item_service( + hass: HomeAssistant, sl_setup: None, snapshot: SnapshotAssertion +) -> None: """Test adding shopping_list item service.""" events = async_capture_events(hass, EVENT_SHOPPING_LIST_UPDATED) await hass.services.async_call( @@ -665,9 +790,12 @@ async def test_add_item_service(hass: HomeAssistant, sl_setup) -> None: ) assert len(hass.data[DOMAIN].items) == 1 assert len(events) == 1 + assert_shopping_list_data(hass, snapshot) -async def test_remove_item_service(hass: HomeAssistant, sl_setup) -> None: +async def test_remove_item_service( + hass: HomeAssistant, sl_setup: None, snapshot: SnapshotAssertion +) -> None: """Test removing shopping_list item service.""" events = async_capture_events(hass, EVENT_SHOPPING_LIST_UPDATED) await hass.services.async_call( @@ -684,6 +812,7 @@ async def test_remove_item_service(hass: HomeAssistant, sl_setup) -> None: ) assert len(hass.data[DOMAIN].items) == 2 assert len(events) == 2 + assert_shopping_list_data(hass, snapshot) await hass.services.async_call( DOMAIN, @@ -694,9 +823,12 @@ async def test_remove_item_service(hass: HomeAssistant, sl_setup) -> None: assert len(hass.data[DOMAIN].items) == 1 assert hass.data[DOMAIN].items[0]["name"] == "cheese" assert len(events) == 3 + assert_shopping_list_data(hass, snapshot) -async def test_clear_completed_items_service(hass: HomeAssistant, sl_setup) -> None: +async def test_clear_completed_items_service( + hass: HomeAssistant, sl_setup: None, snapshot: SnapshotAssertion +) -> None: """Test clearing completed shopping_list items service.""" events = async_capture_events(hass, EVENT_SHOPPING_LIST_UPDATED) await hass.services.async_call( @@ -707,6 +839,7 @@ async def test_clear_completed_items_service(hass: HomeAssistant, sl_setup) -> N ) assert len(hass.data[DOMAIN].items) == 1 assert len(events) == 1 + assert_shopping_list_data(hass, snapshot) events = async_capture_events(hass, EVENT_SHOPPING_LIST_UPDATED) await hass.services.async_call( @@ -717,6 +850,7 @@ async def test_clear_completed_items_service(hass: HomeAssistant, sl_setup) -> N ) assert len(hass.data[DOMAIN].items) == 1 assert len(events) == 1 + assert_shopping_list_data(hass, snapshot) events = async_capture_events(hass, EVENT_SHOPPING_LIST_UPDATED) await hass.services.async_call( @@ -727,9 +861,12 @@ async def test_clear_completed_items_service(hass: HomeAssistant, sl_setup) -> N ) assert len(hass.data[DOMAIN].items) == 0 assert len(events) == 1 + assert_shopping_list_data(hass, snapshot) -async def test_sort_list_service(hass: HomeAssistant, sl_setup) -> None: +async def test_sort_list_service( + hass: HomeAssistant, sl_setup: None, snapshot: SnapshotAssertion +) -> None: """Test sort_all service.""" for name in ("zzz", "ddd", "aaa"): @@ -739,6 +876,7 @@ async def test_sort_list_service(hass: HomeAssistant, sl_setup) -> None: {ATTR_NAME: name}, blocking=True, ) + assert_shopping_list_data(hass, snapshot) # sort ascending events = async_capture_events(hass, EVENT_SHOPPING_LIST_UPDATED) @@ -748,6 +886,7 @@ async def test_sort_list_service(hass: HomeAssistant, sl_setup) -> None: {ATTR_REVERSE: False}, blocking=True, ) + assert_shopping_list_data(hass, snapshot) assert hass.data[DOMAIN].items[0][ATTR_NAME] == "aaa" assert hass.data[DOMAIN].items[1][ATTR_NAME] == "ddd" @@ -761,6 +900,7 @@ async def test_sort_list_service(hass: HomeAssistant, sl_setup) -> None: {ATTR_REVERSE: True}, blocking=True, ) + assert_shopping_list_data(hass, snapshot) assert hass.data[DOMAIN].items[0][ATTR_NAME] == "zzz" assert hass.data[DOMAIN].items[1][ATTR_NAME] == "ddd"