Return of the fire event and change state forms in debug interface

This commit is contained in:
Paulus Schoutsen 2013-11-04 20:16:27 -05:00
parent 3499814f7f
commit 5f4e9d33e0
2 changed files with 119 additions and 37 deletions

View File

@ -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<category>[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<event_type>\w+)'),
'_handle_post_api_events_event_type'),
'_handle_fire_event'),
# Statis files
('GET', re.compile(r'/static/(?P<file>[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("</table>")
# Change state form
write(("<br><form method='post' action='/change_state'>"
"<input type='hidden' name='api_password' value='{}'>"
"<b>Change state:</b><br>"
"Category: <input name='category'><br>"
"State: <input name='new_state'><br>"
"Attributes: <input name='attributes'><br>"
"<input type='submit' value='change state'></form>").format(
self.server.api_password))
# Describe event bus:
write(("<table><tr><th>Event</th><th>Listeners</th></tr>"))
for event_type, count in sorted(self.server.eventbus.listeners.items()):
write("<tr><td>{}</td><td>{}</td></tr>".format(event_type, count))
# Form to allow firing events
write("</table>")
# Form to allow firing events
write(("<br><form method='post' action='/fire_event'>"
"<input type='hidden' name='api_password' value='{}'>"
"<b>Fire event:</b><br>"
"Event type: <input name='event_type'><br>"
"Event data: <input name='event_data'><br>"
"<input type='submit' value='fire event'></form>").format(
self.server.api_password))
write("</body></html>")
# 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/<category>
"""
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/<event_type>
"""
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. """

View File

@ -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),