From 6e27e73474047bf759a390d856bd8e92339b728b Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 20 Nov 2017 21:44:22 -0800 Subject: [PATCH] Shopping list: add item HTTP API (#10674) * Shopping list: add item HTTP API * Fix order of decorators --- homeassistant/components/cloud/http_api.py | 12 +++---- homeassistant/components/shopping_list.py | 31 ++++++++++++---- tests/components/test_shopping_list.py | 41 ++++++++++++++++++++-- 3 files changed, 70 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/cloud/http_api.py b/homeassistant/components/cloud/http_api.py index d16df130c48..27fd6f604c0 100644 --- a/homeassistant/components/cloud/http_api.py +++ b/homeassistant/components/cloud/http_api.py @@ -65,12 +65,12 @@ class CloudLoginView(HomeAssistantView): url = '/api/cloud/login' name = 'api:cloud:login' - @asyncio.coroutine @_handle_cloud_errors @RequestDataValidator(vol.Schema({ vol.Required('email'): str, vol.Required('password'): str, })) + @asyncio.coroutine def post(self, request, data): """Handle login request.""" hass = request.app['hass'] @@ -92,8 +92,8 @@ class CloudLogoutView(HomeAssistantView): url = '/api/cloud/logout' name = 'api:cloud:logout' - @asyncio.coroutine @_handle_cloud_errors + @asyncio.coroutine def post(self, request): """Handle logout request.""" hass = request.app['hass'] @@ -129,12 +129,12 @@ class CloudRegisterView(HomeAssistantView): url = '/api/cloud/register' name = 'api:cloud:register' - @asyncio.coroutine @_handle_cloud_errors @RequestDataValidator(vol.Schema({ vol.Required('email'): str, vol.Required('password'): vol.All(str, vol.Length(min=6)), })) + @asyncio.coroutine def post(self, request, data): """Handle registration request.""" hass = request.app['hass'] @@ -153,12 +153,12 @@ class CloudConfirmRegisterView(HomeAssistantView): url = '/api/cloud/confirm_register' name = 'api:cloud:confirm_register' - @asyncio.coroutine @_handle_cloud_errors @RequestDataValidator(vol.Schema({ vol.Required('confirmation_code'): str, vol.Required('email'): str, })) + @asyncio.coroutine def post(self, request, data): """Handle registration confirmation request.""" hass = request.app['hass'] @@ -178,11 +178,11 @@ class CloudForgotPasswordView(HomeAssistantView): url = '/api/cloud/forgot_password' name = 'api:cloud:forgot_password' - @asyncio.coroutine @_handle_cloud_errors @RequestDataValidator(vol.Schema({ vol.Required('email'): str, })) + @asyncio.coroutine def post(self, request, data): """Handle forgot password request.""" hass = request.app['hass'] @@ -201,13 +201,13 @@ class CloudConfirmForgotPasswordView(HomeAssistantView): url = '/api/cloud/confirm_forgot_password' name = 'api:cloud:confirm_forgot_password' - @asyncio.coroutine @_handle_cloud_errors @RequestDataValidator(vol.Schema({ vol.Required('confirmation_code'): str, vol.Required('email'): str, vol.Required('new_password'): vol.All(str, vol.Length(min=6)) })) + @asyncio.coroutine def post(self, request, data): """Handle forgot password confirm request.""" hass = request.app['hass'] diff --git a/homeassistant/components/shopping_list.py b/homeassistant/components/shopping_list.py index 8b318d07946..8ec023057d1 100644 --- a/homeassistant/components/shopping_list.py +++ b/homeassistant/components/shopping_list.py @@ -38,6 +38,7 @@ def async_setup(hass, config): intent.async_register(hass, ListTopItemsIntent()) hass.http.register_view(ShoppingListView) + hass.http.register_view(CreateShoppingListItemView) hass.http.register_view(UpdateShoppingListItemView) hass.http.register_view(ClearCompletedItemsView) @@ -65,12 +66,14 @@ class ShoppingData: @callback def async_add(self, name): """Add a shopping list item.""" - self.items.append({ + item = { 'name': name, 'id': uuid.uuid4().hex, 'complete': False - }) + } + self.items.append(item) self.hass.async_add_job(self.save) + return item @callback def async_update(self, item_id, info): @@ -102,8 +105,7 @@ class ShoppingData: with open(path) as file: return json.loads(file.read()) - items = yield from self.hass.async_add_job(load) - self.items = items + self.items = yield from self.hass.async_add_job(load) def save(self): """Save the items.""" @@ -166,7 +168,7 @@ class ShoppingListView(http.HomeAssistantView): @callback def get(self, request): - """Retrieve if API is running.""" + """Retrieve shopping list items.""" return self.json(request.app['hass'].data[DOMAIN].items) @@ -178,7 +180,7 @@ class UpdateShoppingListItemView(http.HomeAssistantView): @callback def post(self, request, item_id): - """Retrieve if API is running.""" + """Update a shopping list item.""" data = yield from request.json() try: @@ -191,6 +193,23 @@ class UpdateShoppingListItemView(http.HomeAssistantView): return self.json_message('Item not found', HTTP_BAD_REQUEST) +class CreateShoppingListItemView(http.HomeAssistantView): + """View to retrieve shopping list content.""" + + url = '/api/shopping_list/item' + name = "api:shopping_list:item" + + @http.RequestDataValidator(vol.Schema({ + vol.Required('name'): str, + })) + @asyncio.coroutine + 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) + return self.json(item) + + class ClearCompletedItemsView(http.HomeAssistantView): """View to retrieve shopping list content.""" diff --git a/tests/components/test_shopping_list.py b/tests/components/test_shopping_list.py index 449eab65016..2e1a03c37d0 100644 --- a/tests/components/test_shopping_list.py +++ b/tests/components/test_shopping_list.py @@ -9,9 +9,11 @@ from homeassistant.helpers import intent @pytest.fixture(autouse=True) -def mock_shopping_list_save(): +def mock_shopping_list_io(): """Stub out the persistence.""" - with patch('homeassistant.components.shopping_list.ShoppingData.save'): + with patch('homeassistant.components.shopping_list.ShoppingData.save'), \ + patch('homeassistant.components.shopping_list.' + 'ShoppingData.async_load'): yield @@ -192,3 +194,38 @@ def test_api_clear_completed(hass, test_client): 'name': 'wine', 'complete': False } + + +@asyncio.coroutine +def test_api_create(hass, test_client): + """Test the API.""" + yield from async_setup_component(hass, 'shopping_list', {}) + + client = yield from test_client(hass.http.app) + resp = yield from client.post('/api/shopping_list/item', json={ + 'name': 'soda' + }) + + assert resp.status == 200 + data = yield from resp.json() + assert data['name'] == 'soda' + assert data['complete'] is False + + items = hass.data['shopping_list'].items + assert len(items) == 1 + assert items[0]['name'] == 'soda' + assert items[0]['complete'] is False + + +@asyncio.coroutine +def test_api_create_fail(hass, test_client): + """Test the API.""" + yield from async_setup_component(hass, 'shopping_list', {}) + + client = yield from test_client(hass.http.app) + resp = yield from client.post('/api/shopping_list/item', json={ + 'name': 1234 + }) + + assert resp.status == 400 + assert len(hass.data['shopping_list'].items) == 0