Add config_flow to shopping_list (#32388)

* Add config_flow to shopping_list

* Fix pylint unused import error

* Use _abort_if_unique_id_configured

* Remove SHOPPING_LIST const

* Use const.py for DOMAIN and CONF_TYPE

* Fix tests

* Remove unchanged variable _errors

* Revert CONF_TYPE (wrong usage)

* Use consts in test

* Remove import check

* Remove data={}

* Remove parameters and default values

* Re-add data={}, because it's needed

* Unique ID checks and reverts for default parameters

* remove pylint comment

* Remove block till done

* Address change requests

* Update homeassistant/components/shopping_list/strings.json

Co-Authored-By: Quentame <polletquentin74@me.com>

* Update homeassistant/components/shopping_list/strings.json

Co-Authored-By: Quentame <polletquentin74@me.com>

* Update tests/components/shopping_list/test_config_flow.py

Co-Authored-By: Quentame <polletquentin74@me.com>

* Update tests/components/shopping_list/test_config_flow.py

Co-Authored-By: Quentame <polletquentin74@me.com>

* Update tests/components/shopping_list/test_config_flow.py

Co-Authored-By: Quentame <polletquentin74@me.com>

* Update tests/components/shopping_list/test_config_flow.py

Co-Authored-By: Quentame <polletquentin74@me.com>

* Only test config_flow

* Generate translations

* Move data to end

* @asyncio.coroutine --> async def, yield from --> await

* @asyncio.coroutine --> async def, yield from --> await (tests)

* Remove init in config flow

* remove if not hass.config_entries.async_entries(DOMAIN)

* Add DOMAIN not in config

* Fix tests

* Update homeassistant/components/shopping_list/config_flow.py

Co-Authored-By: Paulus Schoutsen <paulus@home-assistant.io>

* Fix tests

* Update homeassistant/components/shopping_list/__init__.py

Co-Authored-By: Martin Hjelmare <marhje52@gmail.com>

Co-authored-by: Quentame <polletquentin74@me.com>
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
Tom Schneider 2020-03-06 19:17:30 +01:00 committed by GitHub
parent e1cc2acdf9
commit 8f2567f30d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 163 additions and 65 deletions

View File

@ -0,0 +1,14 @@
{
"config": {
"abort": {
"already_configured": "The shopping list is already configured."
},
"step": {
"user": {
"description": "Do you want to configure the shopping list?",
"title": "Shopping List"
}
},
"title": "Shopping List"
}
}

View File

@ -1,10 +1,10 @@
"""Support to manage a shopping list."""
import asyncio
import logging
import uuid
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.components import http, websocket_api
from homeassistant.components.http.data_validator import RequestDataValidator
from homeassistant.const import HTTP_BAD_REQUEST, HTTP_NOT_FOUND
@ -12,9 +12,10 @@ from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
from homeassistant.util.json import load_json, save_json
from .const import DOMAIN
ATTR_NAME = "name"
DOMAIN = "shopping_list"
_LOGGER = logging.getLogger(__name__)
CONFIG_SCHEMA = vol.Schema({DOMAIN: {}}, extra=vol.ALLOW_EXTRA)
EVENT = "shopping_list_updated"
@ -53,20 +54,32 @@ SCHEMA_WEBSOCKET_CLEAR_ITEMS = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend(
)
@asyncio.coroutine
def async_setup(hass, config):
async def async_setup(hass, config):
"""Initialize the shopping list."""
@asyncio.coroutine
def add_item_service(call):
if DOMAIN not in config:
return True
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_IMPORT}
)
)
return True
async def async_setup_entry(hass, config_entry):
"""Set up shopping list from config flow."""
async def add_item_service(call):
"""Add an item with `name`."""
data = hass.data[DOMAIN]
name = call.data.get(ATTR_NAME)
if name is not None:
data.async_add(name)
@asyncio.coroutine
def complete_item_service(call):
async def complete_item_service(call):
"""Mark the item provided via `name` as completed."""
data = hass.data[DOMAIN]
name = call.data.get(ATTR_NAME)
@ -80,7 +93,7 @@ def async_setup(hass, config):
data.async_update(item["id"], {"name": name, "complete": True})
data = hass.data[DOMAIN] = ShoppingData(hass)
yield from data.async_load()
await data.async_load()
hass.services.async_register(
DOMAIN, SERVICE_ADD_ITEM, add_item_service, schema=SERVICE_ITEM_SCHEMA
@ -206,8 +219,7 @@ class CreateShoppingListItemView(http.HomeAssistantView):
name = "api:shopping_list:item"
@RequestDataValidator(vol.Schema({vol.Required("name"): str}))
@asyncio.coroutine
def post(self, request, data):
async def post(self, request, data):
"""Create a new shopping list item."""
item = request.app["hass"].data[DOMAIN].async_add(data["name"])
request.app["hass"].bus.async_fire(EVENT)

View File

@ -0,0 +1,24 @@
"""Config flow to configure ShoppingList component."""
from homeassistant import config_entries
from .const import DOMAIN
class ShoppingListFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
"""Config flow for ShoppingList component."""
VERSION = 1
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH
async def async_step_user(self, user_input=None):
"""Handle a flow initialized by the user."""
# Check if already configured
await self.async_set_unique_id(DOMAIN)
self._abort_if_unique_id_configured()
if user_input is not None:
return self.async_create_entry(title="Shopping List", data=user_input)
return self.async_show_form(step_id="user")
async_step_import = async_step_user

View File

@ -0,0 +1,2 @@
"""All constants related to the shopping list component."""
DOMAIN = "shopping_list"

View File

@ -5,5 +5,6 @@
"requirements": [],
"dependencies": ["http"],
"codeowners": [],
"config_flow": true,
"quality_scale": "internal"
}

View File

@ -0,0 +1,14 @@
{
"config": {
"title": "Shopping List",
"step": {
"user": {
"title": "Shopping List",
"description": "Do you want to configure the shopping list?"
}
},
"abort": {
"already_configured": "The shopping list is already configured."
}
}
}

View File

@ -82,6 +82,7 @@ FLOWS = [
"samsungtv",
"sense",
"sentry",
"shopping_list",
"simplisafe",
"smartthings",
"smhi",

View File

@ -1,10 +1,10 @@
"""Shopping list test helpers."""
from unittest.mock import patch
from asynctest import patch
import pytest
from homeassistant.components.shopping_list import intent as sl_intent
from homeassistant.setup import async_setup_component
from tests.common import MockConfigEntry
@pytest.fixture(autouse=True)
@ -19,5 +19,10 @@ def mock_shopping_list_io():
@pytest.fixture
async def sl_setup(hass):
"""Set up the shopping list."""
assert await async_setup_component(hass, "shopping_list", {})
entry = MockConfigEntry(domain="shopping_list")
entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(entry.entry_id)
await sl_intent.async_setup_intents(hass)

View File

@ -0,0 +1,36 @@
"""Test config flow."""
from homeassistant import data_entry_flow
from homeassistant.components.shopping_list.const import DOMAIN
from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER
async def test_import(hass):
"""Test entry will be imported."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_IMPORT}, data={}
)
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
async def test_user(hass):
"""Test we can start a config flow."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
)
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "user"
async def test_user_confirm(hass):
"""Test we can finish a config flow."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}, data={}
)
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert result["result"].data == {}

View File

@ -1,36 +1,33 @@
"""Test shopping list component."""
import asyncio
from homeassistant.components.websocket_api.const import TYPE_RESULT
from homeassistant.helpers import intent
@asyncio.coroutine
def test_add_item(hass, sl_setup):
async def test_add_item(hass, sl_setup):
"""Test adding an item intent."""
response = yield from intent.async_handle(
response = await intent.async_handle(
hass, "test", "HassShoppingListAddItem", {"item": {"value": "beer"}}
)
assert response.speech["plain"]["speech"] == "I've added beer to your shopping list"
@asyncio.coroutine
def test_recent_items_intent(hass, sl_setup):
async def test_recent_items_intent(hass, sl_setup):
"""Test recent items."""
yield from intent.async_handle(
await intent.async_handle(
hass, "test", "HassShoppingListAddItem", {"item": {"value": "beer"}}
)
yield from intent.async_handle(
await intent.async_handle(
hass, "test", "HassShoppingListAddItem", {"item": {"value": "wine"}}
)
yield from intent.async_handle(
await intent.async_handle(
hass, "test", "HassShoppingListAddItem", {"item": {"value": "soda"}}
)
response = yield from intent.async_handle(hass, "test", "HassShoppingListLastItems")
response = await intent.async_handle(hass, "test", "HassShoppingListLastItems")
assert (
response.speech["plain"]["speech"]
@ -38,22 +35,21 @@ def test_recent_items_intent(hass, sl_setup):
)
@asyncio.coroutine
def test_deprecated_api_get_all(hass, hass_client, sl_setup):
async def test_deprecated_api_get_all(hass, hass_client, sl_setup):
"""Test the API."""
yield from intent.async_handle(
await intent.async_handle(
hass, "test", "HassShoppingListAddItem", {"item": {"value": "beer"}}
)
yield from intent.async_handle(
await intent.async_handle(
hass, "test", "HassShoppingListAddItem", {"item": {"value": "wine"}}
)
client = yield from hass_client()
resp = yield from client.get("/api/shopping_list")
client = await hass_client()
resp = await client.get("/api/shopping_list")
assert resp.status == 200
data = yield from resp.json()
data = await resp.json()
assert len(data) == 2
assert data[0]["name"] == "beer"
assert not data[0]["complete"]
@ -88,35 +84,34 @@ async def test_ws_get_items(hass, hass_ws_client, sl_setup):
assert not data[1]["complete"]
@asyncio.coroutine
def test_deprecated_api_update(hass, hass_client, sl_setup):
async def test_deprecated_api_update(hass, hass_client, sl_setup):
"""Test the API."""
yield from intent.async_handle(
await intent.async_handle(
hass, "test", "HassShoppingListAddItem", {"item": {"value": "beer"}}
)
yield from intent.async_handle(
await intent.async_handle(
hass, "test", "HassShoppingListAddItem", {"item": {"value": "wine"}}
)
beer_id = hass.data["shopping_list"].items[0]["id"]
wine_id = hass.data["shopping_list"].items[1]["id"]
client = yield from hass_client()
resp = yield from client.post(
client = await hass_client()
resp = await client.post(
"/api/shopping_list/item/{}".format(beer_id), json={"name": "soda"}
)
assert resp.status == 200
data = yield from resp.json()
data = await resp.json()
assert data == {"id": beer_id, "name": "soda", "complete": False}
resp = yield from client.post(
resp = await client.post(
"/api/shopping_list/item/{}".format(wine_id), json={"complete": True}
)
assert resp.status == 200
data = yield from resp.json()
data = await resp.json()
assert data == {"id": wine_id, "name": "wine", "complete": True}
beer, wine = hass.data["shopping_list"].items
@ -166,23 +161,20 @@ async def test_ws_update_item(hass, hass_ws_client, sl_setup):
assert wine == {"id": wine_id, "name": "wine", "complete": True}
@asyncio.coroutine
def test_api_update_fails(hass, hass_client, sl_setup):
async def test_api_update_fails(hass, hass_client, sl_setup):
"""Test the API."""
yield from intent.async_handle(
await intent.async_handle(
hass, "test", "HassShoppingListAddItem", {"item": {"value": "beer"}}
)
client = yield from hass_client()
resp = yield from client.post(
"/api/shopping_list/non_existing", json={"name": "soda"}
)
client = await hass_client()
resp = await client.post("/api/shopping_list/non_existing", json={"name": "soda"})
assert resp.status == 404
beer_id = hass.data["shopping_list"].items[0]["id"]
resp = yield from client.post(
resp = await client.post(
"/api/shopping_list/item/{}".format(beer_id), json={"name": 123}
)
@ -212,29 +204,28 @@ async def test_ws_update_item_fail(hass, hass_ws_client, sl_setup):
assert msg["success"] is False
@asyncio.coroutine
def test_deprecated_api_clear_completed(hass, hass_client, sl_setup):
async def test_deprecated_api_clear_completed(hass, hass_client, sl_setup):
"""Test the API."""
yield from intent.async_handle(
await intent.async_handle(
hass, "test", "HassShoppingListAddItem", {"item": {"value": "beer"}}
)
yield from intent.async_handle(
await intent.async_handle(
hass, "test", "HassShoppingListAddItem", {"item": {"value": "wine"}}
)
beer_id = hass.data["shopping_list"].items[0]["id"]
wine_id = hass.data["shopping_list"].items[1]["id"]
client = yield from hass_client()
client = await hass_client()
# Mark beer as completed
resp = yield from client.post(
resp = await client.post(
"/api/shopping_list/item/{}".format(beer_id), json={"complete": True}
)
assert resp.status == 200
resp = yield from client.post("/api/shopping_list/clear_completed")
resp = await client.post("/api/shopping_list/clear_completed")
assert resp.status == 200
items = hass.data["shopping_list"].items
@ -272,15 +263,14 @@ async def test_ws_clear_items(hass, hass_ws_client, sl_setup):
assert items[0] == {"id": wine_id, "name": "wine", "complete": False}
@asyncio.coroutine
def test_deprecated_api_create(hass, hass_client, sl_setup):
async def test_deprecated_api_create(hass, hass_client, sl_setup):
"""Test the API."""
client = yield from hass_client()
resp = yield from client.post("/api/shopping_list/item", json={"name": "soda"})
client = await hass_client()
resp = await client.post("/api/shopping_list/item", json={"name": "soda"})
assert resp.status == 200
data = yield from resp.json()
data = await resp.json()
assert data["name"] == "soda"
assert data["complete"] is False
@ -290,12 +280,11 @@ def test_deprecated_api_create(hass, hass_client, sl_setup):
assert items[0]["complete"] is False
@asyncio.coroutine
def test_deprecated_api_create_fail(hass, hass_client, sl_setup):
async def test_deprecated_api_create_fail(hass, hass_client, sl_setup):
"""Test the API."""
client = yield from hass_client()
resp = yield from client.post("/api/shopping_list/item", json={"name": 1234})
client = await hass_client()
resp = await client.post("/api/shopping_list/item", json={"name": 1234})
assert resp.status == 400
assert len(hass.data["shopping_list"].items) == 0