Allow unregistering a push subscription (#2921)

* Allow unregistering a push subscription

* Update frontend

* ps - HTML5 tests DRY 🍾
This commit is contained in:
Paulus Schoutsen
2016-08-21 16:01:24 -07:00
committed by GitHub
parent d2f7b3c7db
commit 7598de90cb
10 changed files with 179 additions and 44 deletions

View File

@@ -1,8 +1,8 @@
"""DO NOT MODIFY. Auto-generated by script/fingerprint_frontend."""
FINGERPRINTS = {
"core.js": "b4ee3a700ef5549a36b436611e27d3a9",
"frontend.html": "203371247fdba69b4d4d92fd707a459a",
"core.js": "7901b14f238956024a19139d6c479d68",
"frontend.html": "b33df7a012ea6d2aaf353c4466d6554a",
"mdi.html": "710b84acc99b32514f52291aba9cd8e8",
"panels/ha-panel-dev-event.html": "3cc881ae8026c0fba5aa67d334a3ab2b",
"panels/ha-panel-dev-info.html": "34e2df1af32e60fffcafe7e008a92169",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -182,6 +182,30 @@ class HTML5PushRegistrationView(HomeAssistantView):
return self.json_message('Push notification subscriber registered.')
def delete(self, request):
"""Delete a registration."""
subscription = request.json.get(ATTR_SUBSCRIPTION)
found = None
for key, registration in self.registrations.items():
if registration.get(ATTR_SUBSCRIPTION) == subscription:
found = key
break
if not found:
# If not found, unregistering was already done. Return 200
return self.json_message('Registration not found.')
reg = self.registrations.pop(found)
if not _save_config(self.json_path, self.registrations):
self.registrations[found] = reg
return self.json_message('Error saving registration.',
HTTP_INTERNAL_SERVER_ERROR)
return self.json_message('Push notification subscriber unregistered.')
class HTML5PushCallbackView(HomeAssistantView):
"""Accepts push registrations from a browser."""

View File

@@ -8,6 +8,34 @@ from werkzeug.test import EnvironBuilder
from homeassistant.components.http import request_class
from homeassistant.components.notify import html5
SUBSCRIPTION_1 = {
'browser': 'chrome',
'subscription': {
'endpoint': 'https://google.com',
'keys': {'auth': 'auth', 'p256dh': 'p256dh'}
},
}
SUBSCRIPTION_2 = {
'browser': 'firefox',
'subscription': {
'endpoint': 'https://example.com',
'keys': {
'auth': 'bla',
'p256dh': 'bla',
},
},
}
SUBSCRIPTION_3 = {
'browser': 'chrome',
'subscription': {
'endpoint': 'https://example.com/not_exist',
'keys': {
'auth': 'bla',
'p256dh': 'bla',
},
},
}
class TestHtml5Notify(object):
"""Tests for HTML5 notify platform."""
@@ -40,13 +68,7 @@ class TestHtml5Notify(object):
hass = MagicMock()
data = {
'device': {
'browser': 'chrome',
'subscription': {
'endpoint': 'https://google.com',
'keys': {'auth': 'auth', 'p256dh': 'p256dh'}
},
}
'device': SUBSCRIPTION_1
}
with tempfile.NamedTemporaryFile() as fp:
@@ -63,10 +85,7 @@ class TestHtml5Notify(object):
assert len(mock_wp.mock_calls) == 2
# WebPusher constructor
assert mock_wp.mock_calls[0][1][0] == {'endpoint':
'https://google.com',
'keys': {'auth': 'auth',
'p256dh': 'p256dh'}}
assert mock_wp.mock_calls[0][1][0] == SUBSCRIPTION_1['subscription']
# Call to send
payload = json.loads(mock_wp.mock_calls[1][1][0])
@@ -92,22 +111,13 @@ class TestHtml5Notify(object):
assert view.json_path == fp.name
assert view.registrations == {}
builder = EnvironBuilder(method='POST', data=json.dumps({
'browser': 'chrome',
'subscription': {'endpoint': 'https://google.com',
'keys': {'auth': 'auth',
'p256dh': 'p256dh'}},
}))
builder = EnvironBuilder(method='POST',
data=json.dumps(SUBSCRIPTION_1))
Request = request_class()
resp = view.post(Request(builder.get_environ()))
expected = {
'unnamed device': {
'browser': 'chrome',
'subscription': {'endpoint': 'https://google.com',
'keys': {'auth': 'auth',
'p256dh': 'p256dh'}},
},
'unnamed device': SUBSCRIPTION_1,
}
assert resp.status_code == 200, resp.response
@@ -154,6 +164,116 @@ class TestHtml5Notify(object):
resp = view.post(Request(builder.get_environ()))
assert resp.status_code == 400, resp.response
def test_unregistering_device_view(self):
"""Test that the HTML unregister view works."""
hass = MagicMock()
config = {
'some device': SUBSCRIPTION_1,
'other device': SUBSCRIPTION_2,
}
with tempfile.NamedTemporaryFile() as fp:
hass.config.path.return_value = fp.name
fp.write(json.dumps(config).encode('utf-8'))
fp.flush()
service = html5.get_service(hass, {})
assert service is not None
# assert hass.called
assert len(hass.mock_calls) == 3
view = hass.mock_calls[1][1][0]
assert view.json_path == fp.name
assert view.registrations == config
builder = EnvironBuilder(method='DELETE', data=json.dumps({
'subscription': SUBSCRIPTION_1['subscription'],
}))
Request = request_class()
resp = view.delete(Request(builder.get_environ()))
config.pop('some device')
assert resp.status_code == 200, resp.response
assert view.registrations == config
with open(fp.name) as fpp:
assert json.load(fpp) == config
def test_unregistering_device_view_handles_unknown_subscription(self):
"""Test that the HTML unregister view handles unknown subscriptions."""
hass = MagicMock()
config = {
'some device': SUBSCRIPTION_1,
'other device': SUBSCRIPTION_2,
}
with tempfile.NamedTemporaryFile() as fp:
hass.config.path.return_value = fp.name
fp.write(json.dumps(config).encode('utf-8'))
fp.flush()
service = html5.get_service(hass, {})
assert service is not None
# assert hass.called
assert len(hass.mock_calls) == 3
view = hass.mock_calls[1][1][0]
assert view.json_path == fp.name
assert view.registrations == config
builder = EnvironBuilder(method='DELETE', data=json.dumps({
'subscription': SUBSCRIPTION_3['subscription']
}))
Request = request_class()
resp = view.delete(Request(builder.get_environ()))
assert resp.status_code == 200, resp.response
assert view.registrations == config
with open(fp.name) as fpp:
assert json.load(fpp) == config
def test_unregistering_device_view_handles_json_safe_error(self):
"""Test that the HTML unregister view handles JSON write errors."""
hass = MagicMock()
config = {
'some device': SUBSCRIPTION_1,
'other device': SUBSCRIPTION_2,
}
with tempfile.NamedTemporaryFile() as fp:
hass.config.path.return_value = fp.name
fp.write(json.dumps(config).encode('utf-8'))
fp.flush()
service = html5.get_service(hass, {})
assert service is not None
# assert hass.called
assert len(hass.mock_calls) == 3
view = hass.mock_calls[1][1][0]
assert view.json_path == fp.name
assert view.registrations == config
builder = EnvironBuilder(method='DELETE', data=json.dumps({
'subscription': SUBSCRIPTION_1['subscription'],
}))
Request = request_class()
with patch('homeassistant.components.notify.html5._save_config',
return_value=False):
resp = view.delete(Request(builder.get_environ()))
assert resp.status_code == 500, resp.response
assert view.registrations == config
with open(fp.name) as fpp:
assert json.load(fpp) == config
def test_callback_view_no_jwt(self):
"""Test that the notification callback view works without JWT."""
hass = MagicMock()
@@ -185,13 +305,7 @@ class TestHtml5Notify(object):
hass = MagicMock()
data = {
'device': {
'browser': 'chrome',
'subscription': {
'endpoint': 'https://google.com',
'keys': {'auth': 'auth', 'p256dh': 'p256dh'}
},
}
'device': SUBSCRIPTION_1,
}
with tempfile.NamedTemporaryFile() as fp:
@@ -211,11 +325,8 @@ class TestHtml5Notify(object):
assert len(mock_wp.mock_calls) == 2
# WebPusher constructor
assert mock_wp.mock_calls[0][1][0] == {'endpoint':
'https://google.com',
'keys': {'auth': 'auth',
'p256dh':
'p256dh'}}
assert mock_wp.mock_calls[0][1][0] == \
SUBSCRIPTION_1['subscription']
# Call to send
push_payload = json.loads(mock_wp.mock_calls[1][1][0])