Use the compressed URL only for the QR code, and always present the full URL to the user/browser.

This commit is contained in:
Phil Elson 2021-03-25 11:36:56 +01:00
parent eae08e81f9
commit e6fb1b9374
7 changed files with 80 additions and 33 deletions

View file

@ -88,8 +88,6 @@ class ConcentrationModel(BaseRequestHandler):
try:
form = model_generator.FormData.from_dict(requested_model_config)
except (KeyboardInterrupt, SystemExit):
raise
except Exception as err:
if self.settings.get("debug", False):
import traceback
@ -137,7 +135,11 @@ class CompressedCalculatorFormInputs(BaseRequestHandler):
def get(self, compressed_args: str):
# Convert a base64 zlib encoded shortened URL into a non compressed
# URL, and redirect.
args = zlib.decompress(base64.b64decode(compressed_args)).decode()
try:
args = zlib.decompress(base64.b64decode(compressed_args)).decode()
except Exception as err: # noqa
self.set_status(400)
return self.finish("Invalid calculator data: it seems incomplete. Was there an error copying & pasting the URL?")
self.redirect(f'/calculator?{args}')
@ -156,7 +158,7 @@ def make_app(debug=False, prefix='/calculator'):
calculator_static_dir = Path(__file__).absolute().parent / 'static'
urls = [
(r'/?', LandingPage),
(r'/c/(.*)', CompressedCalculatorFormInputs),
(r'/_c/(.*)', CompressedCalculatorFormInputs),
(r'/static/(.*)', StaticFileHandler, {'path': static_dir}),
(prefix + r'/?', CalculatorForm),
(prefix + r'/report', ConcentrationModel),

View file

@ -77,16 +77,11 @@ def generate_qr_code(prefix, form: FormData):
args = urllib.parse.urlencode(form_dict)
# Then zlib compress + base64 encode the string. To be inverted by the
# /c/ endpoint.
qr_url = prefix + "/c/" + base64.b64encode(
# /_c/ endpoint.
qr_url = prefix + "/_c/" + base64.b64encode(
zlib.compress(args.encode())
).decode()
# We show the human-friendly URL when hovering over the link, but for now
# let's keep it the same as the QR URL to ensure everything continues to work
# as expected (it takes more effort to validate QR barcodes than it does links).
# url = prefix + "/calculator/?" + args
url = qr_url
url = prefix + "/calculator?" + args
qr = qrcode.QRCode(
version=1,
@ -101,6 +96,7 @@ def generate_qr_code(prefix, form: FormData):
return {
'image': img2base64(_img2bytes(img)),
'link': url,
'qr_url': qr_url,
}

View file

@ -0,0 +1,13 @@
import pytest
from cara.apps.calculator import model_generator
@pytest.fixture
def baseline_form_data():
return model_generator.baseline_raw_form_data()
@pytest.fixture
def baseline_form(baseline_form_data):
return model_generator.FormData.from_dict(baseline_form_data)

View file

@ -6,15 +6,6 @@ from cara import models
from cara import data
import numpy as np
@pytest.fixture
def baseline_form_data():
return model_generator.baseline_raw_form_data()
@pytest.fixture
def baseline_form(baseline_form_data):
return model_generator.FormData.from_dict(baseline_form_data)
def test_model_from_dict(baseline_form_data):
form = model_generator.FormData.from_dict(baseline_form_data)

View file

@ -1,19 +1,8 @@
import pytest
from cara.apps.calculator import model_generator
from cara.apps.calculator import report_generator
@pytest.fixture
def baseline_form_data():
return model_generator.baseline_raw_form_data()
@pytest.fixture
def baseline_form(baseline_form_data):
return model_generator.FormData.from_dict(baseline_form_data)
def test_generate_report(baseline_form):
model = baseline_form.build_model()
@ -32,4 +21,4 @@ def test_generate_report(baseline_form):
],
)
def test_readable_minutes(test_input, expected):
assert report_generator.readable_minutes(test_input) == expected
assert report_generator.readable_minutes(test_input) == expected

View file

@ -0,0 +1,54 @@
import pytest
from cara.apps.calculator import make_app
from cara.apps.calculator.report_generator import generate_qr_code
@pytest.fixture
def app():
return make_app()
async def test_homepage(http_server_client):
response = await http_server_client.fetch('/')
assert response.code == 200
async def test_calculator(http_server_client):
# Both with and without a trailing slash.
response = await http_server_client.fetch('/calculator')
assert response.code == 200
response = await http_server_client.fetch('/calculator/')
assert response.code == 200
async def test_qrcode_urls(http_server_client, baseline_form):
prefix = 'proto://hostname/prefix'
qr_data = generate_qr_code(prefix, baseline_form)
expected = f'{prefix}/calculator?activity_type=office&air_changes=0.0'
assert qr_data['link'].startswith(expected)
# We should get a 200 for the link.
response = await http_server_client.fetch(qr_data['link'].replace(prefix, ''))
assert response.code == 200
# And a 302 for the QR url itself. The redirected URL should be the same as
# in the link.
assert qr_data['qr_url'].startswith(prefix)
response = await http_server_client.fetch(
qr_data['qr_url'].replace(prefix, ''),
max_redirects=0,
raise_error=False,
)
assert response.code == 302
assert response.headers['Location'] == qr_data['link'].replace(prefix, '')
async def test_invalid_compressed_url(http_server_client, baseline_form):
response = await http_server_client.fetch(
'/_c/invalid-data',
max_redirects=0,
raise_error=False,
)
assert response.code == 400

View file

@ -71,6 +71,8 @@ setup(
'all': [req for reqs in REQUIREMENTS.values() for req in reqs],
},
package_data={'cara': [
'apps/templates/*.j2',
'apps/calculator/templates/*.j2',
'apps/calculator/*',
'apps/calculator/*/*',
'apps/calculator/*/*/*'