diff --git a/homeassistant/httpinterface.py b/homeassistant/httpinterface.py index 51152e87251..44b71d23177 100644 --- a/homeassistant/httpinterface.py +++ b/homeassistant/httpinterface.py @@ -90,10 +90,11 @@ HTTP_METHOD_NOT_ALLOWED = 405 HTTP_UNPROCESSABLE_ENTITY = 422 URL_ROOT = "/" +URL_CHANGE_STATE = "/change_state" +URL_FIRE_EVENT = "/fire_event" URL_API_STATES = "/api/states" URL_API_STATES_CATEGORY = "/api/states/{}" - URL_API_EVENTS = "/api/events" URL_API_EVENTS_EVENT = "/api/events/{}" @@ -137,6 +138,8 @@ class RequestHandler(BaseHTTPRequestHandler): PATHS = [ # debug interface ('GET', '/', '_handle_get_root'), + ('POST', re.compile(r'/change_state'), '_handle_change_state'), + ('POST', re.compile(r'/fire_event'), '_handle_fire_event'), # /states ('GET', '/api/states', '_handle_get_api_states'), @@ -145,12 +148,12 @@ class RequestHandler(BaseHTTPRequestHandler): '_handle_get_api_states_category'), ('POST', re.compile(r'/api/states/(?P[a-zA-Z\._0-9]+)'), - '_handle_post_api_states_category'), + '_handle_change_state'), # /events ('GET', '/api/events', '_handle_get_api_events'), ('POST', re.compile(r'/api/events/(?P\w+)'), - '_handle_post_api_events_event_type'), + '_handle_fire_event'), # Statis files ('GET', re.compile(r'/static/(?P[a-zA-Z\._\-0-9\/]+)'), @@ -222,7 +225,6 @@ class RequestHandler(BaseHTTPRequestHandler): else: self.send_response(HTTP_NOT_FOUND) - def do_GET(self): # pylint: disable=invalid-name """ GET request handler. """ self._handle_request('GET') @@ -306,43 +308,50 @@ class RequestHandler(BaseHTTPRequestHandler): write("") + # Change state form + write(("
" + "" + "Change state:
" + "Category:
" + "State:
" + "Attributes:
" + "
").format( + self.server.api_password)) + # Describe event bus: write(("")) for event_type, count in sorted(self.server.eventbus.listeners.items()): write("".format(event_type, count)) - # Form to allow firing events write("
EventListeners
{}{}
") + # Form to allow firing events + write(("
" + "" + "Fire event:
" + "Event type:
" + "Event data:
" + "
").format( + self.server.api_password)) + + write("") - # pylint: disable=unused-argument - def _handle_get_api_states(self, path_match, data): - """ Returns the categories which state is being tracked. """ - self._write_json({'categories': self.server.statemachine.categories}) - - # pylint: disable=unused-argument - def _handle_get_api_states_category(self, path_match, data): - """ Returns the state of a specific category. """ - category = path_match.group('category') - - state = self.server.statemachine.get_state(category) - - if state: - state['category'] = category - - self._write_json(state) - - else: - # If category does not exist - self._message("State does not exist.", HTTP_UNPROCESSABLE_ENTITY) - # pylint: disable=invalid-name - def _handle_post_api_states_category(self, path_match, data): - """ Handles updating the state of a category. """ + def _handle_change_state(self, path_match, data): + """ Handles updating the state of a category. + + This handles the following paths: + /change_state + /api/states/ + """ try: - category = path_match.group('category') + try: + category = path_match.group('category') + except IndexError: + # If group 'category' does not exist in path_match + category = data['category'][0] new_state = data['new_state'][0] @@ -379,16 +388,21 @@ class RequestHandler(BaseHTTPRequestHandler): self._message("Invalid JSON for attributes", HTTP_UNPROCESSABLE_ENTITY) - def _handle_get_api_events(self, path_match, data): - """ Handles getting overview of event listeners. """ - self._write_json({'listeners': self.server.eventbus.listeners}) - # pylint: disable=invalid-name - def _handle_post_api_events_event_type(self, path_match, data): - """ Handles firing of an event. """ - event_type = path_match.group('event_type') + def _handle_fire_event(self, path_match, data): + """ Handles firing of an event. + This handles the following paths: + /fire_event + /api/events/ + """ try: + try: + event_type = path_match.group('event_type') + except IndexError: + # If group event_type does not exist in path_match + event_type = data['event_type'][0] + try: event_data = json.loads(data['event_data'][0]) except KeyError: @@ -399,11 +413,40 @@ class RequestHandler(BaseHTTPRequestHandler): self._message("Event {} fired.".format(event_type)) + except KeyError: + # Occurs if event_type does not exist in data + self._message("No event_type received.", HTTP_BAD_REQUEST) + except ValueError: # Occurs during error parsing json self._message("Invalid JSON for event_data", HTTP_UNPROCESSABLE_ENTITY) + # pylint: disable=unused-argument + def _handle_get_api_states(self, path_match, data): + """ Returns the categories which state is being tracked. """ + self._write_json({'categories': self.server.statemachine.categories}) + + # pylint: disable=unused-argument + def _handle_get_api_states_category(self, path_match, data): + """ Returns the state of a specific category. """ + category = path_match.group('category') + + state = self.server.statemachine.get_state(category) + + if state: + state['category'] = category + + self._write_json(state) + + else: + # If category does not exist + self._message("State does not exist.", HTTP_UNPROCESSABLE_ENTITY) + + def _handle_get_api_events(self, path_match, data): + """ Handles getting overview of event listeners. """ + self._write_json({'listeners': self.server.eventbus.listeners}) + def _handle_get_static(self, path_match, data): """ Returns a static file. """ req_file = util.sanitize_filename(path_match.group('file')) @@ -430,9 +473,11 @@ class RequestHandler(BaseHTTPRequestHandler): """ Helper method to return a message to the caller. """ if self.use_json: self._write_json({'message': message}, status_code=status_code) - else: + elif status_code == HTTP_OK: self.server.flash_message = message self._redirect('/') + else: + self.send_error(status_code, message) def _redirect(self, location): """ Helper method to redirect caller. """ diff --git a/homeassistant/test.py b/homeassistant/test.py index 5e9fca09601..cc042a1f89b 100644 --- a/homeassistant/test.py +++ b/homeassistant/test.py @@ -85,6 +85,43 @@ class TestHTTPInterface(unittest.TestCase): self.assertEqual(req.status_code, 401) + def test_debug_change_state(self): + """ Test if we can change a state from the debug interface. """ + self.statemachine.set_state("test", "not_to_be_set_state") + + requests.post(_url(hah.URL_CHANGE_STATE), + data={"category": "test", + "new_state":"debug_state_change2", + "api_password":API_PASSWORD}) + + self.assertEqual(self.statemachine.get_state("test")['state'], + "debug_state_change2") + + def test_debug_fire_event(self): + """ Test if we can fire an event from the debug interface. """ + test_value = [] + + def listener(event): # pylint: disable=unused-argument + """ Helper method that will verify that our event got called and + that test if our data came through. """ + if "test" in event.data: + test_value.append(1) + + self.eventbus.listen_once("test_event_with_data", listener) + + requests.post( + _url(hah.URL_FIRE_EVENT), + data={"event_type": "test_event_with_data", + "event_data":'{"test": 1}', + "api_password":API_PASSWORD}) + + # Allow the event to take place + time.sleep(1) + + self.assertEqual(len(test_value), 1) + + + def test_api_list_state_categories(self): """ Test if the debug interface allows us to list state categories. """ req = requests.get(_url(hah.URL_API_STATES),