combo/tests/test_pwa.py

272 lines
11 KiB
Python

import base64
import os
import mock
import pytest
from webtest import Upload
import pywebpush
from django.conf import settings
from django.core.files import File
from django.urls import reverse
from django.template import Context, Template
from django.test import override_settings
from django.test.client import RequestFactory
from django.utils.six import BytesIO
from combo.apps.notifications.models import Notification
from combo.apps.pwa.models import PushSubscription, PwaSettings, PwaNavigationEntry
from combo.data.models import Page
from .test_manager import login
pytestmark = pytest.mark.django_db
def test_manifest_json(app):
assert app.get('/manifest.json', status=200).json['name'] == 'Compte Citoyen'
with override_settings(TEMPLATE_VARS={'global_title': 'FooBar'}):
assert app.get('/manifest.json', status=200).json['name'] == 'FooBar'
def test_service_worker(app):
app.get('/service-worker.js', status=200)
resp = app.get('/service-worker-registration.js', status=200)
assert 'applicationServerPublicKey = null' in resp.text
pwa_settings = PwaSettings.singleton()
pwa_settings.push_notifications = True
pwa_settings.save()
resp = app.get('/service-worker-registration.js', status=200)
assert 'applicationServerPublicKey = "' in resp.text
# check legacy settings are still supported
with override_settings(
PWA_VAPID_PUBLIK_KEY="BFzvUdXB...",
PWA_VAPID_PRIVATE_KEY="4WbCnBF...",
PWA_VAPID_CLAIMS = {'sub': 'mailto:admin@entrouvert.com'}):
resp = app.get('/service-worker-registration.js', status=200)
assert 'applicationServerPublicKey = "BFzvUdXB..."' in resp.text
def test_webpush_subscription(app, john_doe, jane_doe):
app.post_json(reverse('pwa-subscribe-push'), params={'sample': 'content'}, status=403)
app.get(reverse('pwa-subscribe-push'), status=403)
app = login(app, john_doe.username, john_doe.username)
app.post_json(reverse('pwa-subscribe-push'), params={'sample': 'content'}, status=200)
assert PushSubscription.objects.count() == 1
app.post_json(reverse('pwa-subscribe-push'), params={'sample': 'content2'}, status=200)
assert PushSubscription.objects.count() == 2
app = login(app, jane_doe.username, jane_doe.username)
app.post_json(reverse('pwa-subscribe-push'), params={'sample': 'content'}, status=200)
assert PushSubscription.objects.count() == 3
app = login(app, john_doe.username, john_doe.username)
app.post_json(reverse('pwa-subscribe-push'), params=None, status=200)
assert PushSubscription.objects.count() == 1
app = login(app, john_doe.username, john_doe.username)
resp = app.post(reverse('pwa-subscribe-push'), params='', status=400)
assert 'bad json' in resp.text
def test_webpush_notification(app, john_doe):
PushSubscription.objects.all().delete()
app = login(app, john_doe.username, john_doe.username)
app.post_json(reverse('pwa-subscribe-push'), params={'sample': 'content'}, status=200)
pwa_settings = PwaSettings.singleton()
pwa_settings.push_notifications = False
pwa_settings.save()
with mock.patch('pywebpush.webpush') as webpush:
notification = Notification.notify(john_doe, 'test', body='hello world')
assert webpush.call_count == 0
pwa_settings.push_notifications = True
pwa_settings.save()
with mock.patch('pywebpush.webpush') as webpush:
notification = Notification.notify(john_doe, 'test', body='hello world')
assert webpush.call_count == 1
assert webpush.call_args[1]['subscription_info'] == {'sample': 'content'}
# check legacy settings are still supported
with override_settings(
PWA_VAPID_PUBLIK_KEY="BFzvUdXB...",
PWA_VAPID_PRIVATE_KEY="4WbCnBF...",
PWA_VAPID_CLAIMS = {'sub': 'mailto:admin@entrouvert.com'}):
with mock.patch('pywebpush.webpush') as webpush:
notification = Notification.notify(john_doe, 'test', body='hello world')
assert webpush.call_count == 1
assert webpush.call_args[1]['subscription_info'] == {'sample': 'content'}
assert webpush.call_args[1]['vapid_private_key'] == settings.PWA_VAPID_PRIVATE_KEY
assert webpush.call_args[1]['vapid_claims'] == settings.PWA_VAPID_CLAIMS
def test_no_pwa_manager(app, admin_user):
app = login(app)
resp = app.get('/manage/', status=200)
assert not '/manage/pwa/' in resp.text
def test_pwa_manager(app, admin_user):
app = login(app)
with override_settings(TEMPLATE_VARS={'pwa_display': 'standalone'}):
resp = app.get('/manage/', status=200)
assert '/manage/pwa/' in resp.text
resp = app.get('/manage/pwa/')
resp.form['offline_text'] = 'You are offline.'
assert resp.form['offline_retry_button'].checked
resp.form['offline_retry_button'].checked = False
resp = resp.form.submit().follow()
assert resp.form['offline_text'].value == 'You are offline.'
assert resp.form['offline_retry_button'].checked is False
resp = app.get('/manage/pwa/')
resp = resp.click('Add a navigation entry')
resp.form['label'] = 'Hello'
resp.form['url'] = 'https://www.example.net'
resp = resp.form.submit().follow()
assert PwaNavigationEntry.objects.all().count() == 1
page = Page(title='test', slug='test')
page.save()
resp = resp.click('Add a navigation entry')
resp.form['link_page'] = page.id
resp = resp.form.submit().follow()
assert PwaNavigationEntry.objects.all().count() == 2
for i in range(3):
resp = resp.click('Add a navigation entry')
resp.form['label'] = 'Hello %s' % i
resp.form['url'] = 'https://www.example.net'
resp = resp.form.submit().follow()
# max 5 items
assert 'Add a navigation entry' not in resp.text
# reorder items, reverse them all
entries = PwaNavigationEntry.objects.all()
app.get('/manage/pwa/navigation/order/?new-order=%s' %
','.join(reversed([str(x.id) for x in entries])))
entries = PwaNavigationEntry.objects.all()
assert entries[0].label == 'Hello 2'
# remove first item
resp = app.get('/manage/pwa/')
resp = resp.click(href='delete', index=0)
resp = resp.form.submit().follow()
assert 'Hello 2' not in resp.text
assert 'Add a navigation entry' in resp.text
# rename item
resp = resp.click('Hello 1')
resp.form['label'] = 'Hello 12'
resp = resp.form.submit().follow()
assert PwaNavigationEntry.objects.all()[0].label == 'Hello 12'
# check error handling
resp = resp.click('Hello 12')
resp.form['label'] = ''
resp.form['url'] = ''
resp = resp.form.submit()
assert 'A label is required' in resp.text
assert 'An URL is required' in resp.text
resp.form['url'] = 'foobar'
resp.form['link_page'] = page.id
resp = resp.form.submit()
assert 'An URL cannot be specified' in resp.text
# add an icon
resp = app.get('/manage/pwa/')
resp.form['application_icon'] = Upload('test.png',
base64.decodebytes(b'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQAAAAA3bvkkAAAACklEQVQI12NoAAAAggCB3UNq9AAAAABJRU5ErkJggg=='),
'image/png')
resp = resp.form.submit().follow()
assert PwaSettings.singleton().application_icon.name == 'pwa/test.png'
# make sure another application name was not saved
assert not PwaSettings.singleton().application_name
resp = app.get('/manage/pwa/')
assert resp.form['application_name'].value == 'Compte Citoyen'
resp.form['application_name'] = 'Test'
resp = resp.form.submit().follow()
assert PwaSettings.singleton().application_name == 'Test'
# make sure using the default application name switches back to
# global_title/compte citoyen.
resp.form['application_name'] = 'Compte Citoyen'
resp = resp.form.submit().follow()
assert not PwaSettings.singleton().application_name
# try an icon in an invalid format
resp.form['application_icon'] = Upload('test.txt', b'hello', 'text/plain')
resp = resp.form.submit()
assert 'The application icon must be in JPEG or PNG format' in resp
assert PwaSettings.singleton().application_icon.name == 'pwa/test.png'
def test_pwa_offline_page(app):
PwaSettings.objects.all().delete()
resp = app.get('/__pwa__/offline/')
assert 'You are currently offline.' in resp.text
assert 'Retry' in resp.text
pwa_settings = PwaSettings.singleton()
pwa_settings.offline_text = 'You are offline.'
pwa_settings.offline_retry_button = False
pwa_settings.save()
resp = app.get('/__pwa__/offline/')
assert 'You are offline.' in resp.text
assert 'Retry' not in resp.text
def test_pwa_navigation_templatetag(app):
page = Page(title='One', slug='one')
page.save()
entry1 = PwaNavigationEntry(label='a', url='/', notification_count=True,
use_user_name_as_label=True, order=0)
entry2 = PwaNavigationEntry(link_page=page, order=1, icon=File(BytesIO(b'te\30st'), 'test.png'))
entry1.save()
entry2.save()
t = Template('{% load pwa %}{% pwa_navigation %}')
assert t.render(Context({})) == ''
with override_settings(TEMPLATE_VARS={'pwa_display': 'standalone'}):
request = RequestFactory().get('/')
nav = t.render(Context({'request': request}))
assert '<span>a</span>' in nav
assert '<span>One</span>' in nav
assert nav.count('background-image') == 1
assert nav.count('data-notification-count-url=') == 1
assert nav.count('data-pwa-user-name=""') == 1
nav = t.render(Context({'request': request, 'render_skeleton': True}))
assert 'data-pwa-user-name="{% block placeholder-user-name %}' in nav
def test_pwa_application_icon(app, admin_user):
app = login(app)
with override_settings(TEMPLATE_VARS={'pwa_display': 'standalone'}):
PwaSettings.objects.all().delete()
resp = app.get('/manage/pwa/')
resp.form['application_icon'] = Upload('test.png',
base64.decodebytes(b'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQAAAAA3bvkkAAAACklEQVQI12NoAAAAggCB3UNq9AAAAABJRU5ErkJggg=='),
'image/png')
resp = resp.form.submit().follow()
resp = app.get('/manifest.json', status=200)
assert len(resp.json['icons']) == 5
# make sure largest icon is referenced in service worker
resp2 = app.get('/service-worker.js', status=200)
assert resp.json['icons'][-1]['src'].split('/')[-1] in resp.text
def test_pwa_application_name(app, admin_user):
app = login(app)
assert app.get('/manifest.json', status=200).json['name'] == 'Compte Citoyen'
pwa_settings = PwaSettings.singleton()
pwa_settings.application_name = 'My wonderful app'
pwa_settings.save()
assert app.get('/manifest.json', status=200).json['name'] == 'My wonderful app'