From 69c9167911a8449fef20951abe924a1c4528545c Mon Sep 17 00:00:00 2001 From: Daniel Schadt Date: Sun, 17 Jul 2022 20:56:38 +0200 Subject: add tests for archive download --- tests/conftest.py | 22 ++++++++++ tests/integration/test_browse.py | 91 ++++++++++++++++++++++++++++++++++++++++ tests/integration/test_upload.py | 27 +----------- tests/testutils.py | 20 +++++++++ tests/unit/test_util.py | 8 +--- tests/unit/views/test_browse.py | 14 +++++++ 6 files changed, 150 insertions(+), 32 deletions(-) create mode 100644 tests/integration/test_browse.py create mode 100644 tests/testutils.py create mode 100644 tests/unit/views/test_browse.py (limited to 'tests') diff --git a/tests/conftest.py b/tests/conftest.py index b8e3090..79f0245 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -141,3 +141,25 @@ def route_path(app_request): def get_route_path(*args, **kwargs): return app_request.route_path(*args, **kwargs) return get_route_path + + +@pytest.fixture() +def logged_in(dbsession, testapp, route_path): + """ + A fixture that represents a logged in state. + + This automatically creates a user and returns the created user. + + Returns the user that was logged in. + """ + user = models.User(email='foo@bar.com', is_verified=True) + user.set_password("foobar") + dbsession.add(user) + + login = testapp.get(route_path('login')) + form = login.form + form['email'] = 'foo@bar.com' + form['password'] = 'foobar' + response = form.submit() + assert response.status_code == 302 + return user diff --git a/tests/integration/test_browse.py b/tests/integration/test_browse.py new file mode 100644 index 0000000..cfd1d71 --- /dev/null +++ b/tests/integration/test_browse.py @@ -0,0 +1,91 @@ +import io +import zipfile +from contextlib import contextmanager +from datetime import datetime + +from sqlalchemy import select, func +from webtest import Upload + +from testutils import load_gpx_asset +from fietsboek import models +from fietsboek.models.track import Visibility + + +@contextmanager +def added_tracks(tm, dbsession, owner): + """Adds some tracks to the database session. + + This function should be used as a context manager and it ensures that the + added tracks are deleted again after the test, to make a clean slate for + the next test. + """ + # The normal transaction is "doomed", so we need to abort it, start a fresh + # one, and then explicitely commit it, otherwise we will not persist the + # objects to the database. + tm.abort() + + tracks = [] + with tm: + track = models.Track( + owner=owner, + title="Foobar", + visibility=Visibility.PUBLIC, + description="A foo'd track", + badges=[], + link_secret="foobar", + tagged_people=[], + ) + track.date = datetime(2022, 3, 14, 9, 26, 54) + track.gpx_data = load_gpx_asset("MyTourbook_1.gpx.gz") + dbsession.add(track) + tracks.append(track) + + track = models.Track( + owner=owner, + title="Barfoo", + visibility=Visibility.PUBLIC, + description="A bar'd track", + badges=[], + link_secret="barfoo", + tagged_people=[], + ) + track.date = datetime(2022, 10, 29, 13, 37, 11) + track.gpx_data = load_gpx_asset("Teasi_1.gpx.gz") + dbsession.add(track) + tracks.append(track) + + tm.begin() + tm.doom() + + try: + yield tracks + finally: + tm.abort() + with tm: + for track in tracks: + dbsession.delete(track) + tm.begin() + tm.doom() + + +def test_browse(testapp, dbsession, route_path, logged_in, tm): + # Ensure there are some tracks in the database + with added_tracks(tm, dbsession, logged_in): + # Now go to the browse page + browse = testapp.get(route_path('browse')) + + assert "Foobar" in browse.text + assert "Barfoo" in browse.text + + +def test_archive(testapp, dbsession, route_path, logged_in, tm): + with added_tracks(tm, dbsession, logged_in): + archive = testapp.get( + route_path('track-archive', _query=[("track_id[]", "1"), ("track_id[]", "2")]) + ) + result = io.BytesIO(archive.body) + + with zipfile.ZipFile(result, 'r') as zipped: + assert len(zipped.namelist()) == 2 + assert "track_1.gpx" in zipped.namelist() + assert "track_2.gpx" in zipped.namelist() diff --git a/tests/integration/test_upload.py b/tests/integration/test_upload.py index 651aedf..1903cbe 100644 --- a/tests/integration/test_upload.py +++ b/tests/integration/test_upload.py @@ -1,34 +1,9 @@ -import gzip -from pathlib import Path - -import pytest from sqlalchemy import select, func from webtest import Upload +from testutils import load_gpx_asset from fietsboek import models - -@pytest.fixture() -def logged_in(dbsession, testapp, route_path): - user = models.User(email='foo@bar.com', is_verified=True) - user.set_password("foobar") - dbsession.add(user) - - login = testapp.get(route_path('login')) - form = login.form - form['email'] = 'foo@bar.com' - form['password'] = 'foobar' - response = form.submit() - assert response.status_code == 302 - - -def load_gpx_asset(filename): - asset_dir = Path(__file__).parent.parent / 'assets' - test_file = asset_dir / filename - with gzip.open(test_file, 'rb') as fobj: - return fobj.read() - - def test_upload_forbidden(testapp, route_path): upload_form = testapp.get(route_path('upload'), status="4*") diff --git a/tests/testutils.py b/tests/testutils.py new file mode 100644 index 0000000..3ddbdbe --- /dev/null +++ b/tests/testutils.py @@ -0,0 +1,20 @@ +"""Various utility functions for testing.""" +import gzip +from pathlib import Path + + +def load_gpx_asset(filename): + """Load a GPX test asset. + + This looks in the tests/assets/ folder, reads and unzips the file and + returns its contents. + + :param filename: Name of the asset to load. + :type filename: str + :return: The content of the asset as bytes. + :rtype: bytes + """ + asset_dir = Path(__file__).parent / 'assets' + test_file = asset_dir / filename + with gzip.open(test_file, 'rb') as fobj: + return fobj.read() diff --git a/tests/unit/test_util.py b/tests/unit/test_util.py index 8f45611..13f4bfe 100644 --- a/tests/unit/test_util.py +++ b/tests/unit/test_util.py @@ -1,11 +1,10 @@ -import gzip from datetime import timedelta -from pathlib import Path import pytest import gpxpy from markupsafe import Markup +from testutils import load_gpx_asset from fietsboek import util @@ -60,10 +59,7 @@ def test_round_timedelta_to_multiple(delta, multiple, expected): ("MyTourbook_1.gpx.gz", timedelta(hours=2)), ]) def test_guess_gpx_timezone(gpx_file, offset): - asset_dir = Path(__file__).parent.parent / 'assets' - test_file = asset_dir / gpx_file - with gzip.open(test_file, 'rb') as fobj: - parsed_gpx = gpxpy.parse(fobj) + parsed_gpx = gpxpy.parse(load_gpx_asset(gpx_file)) timezone = util.guess_gpx_timezone(parsed_gpx) # Here we hope (and assume) that utcoffset is ignored. This is true for # datetime.timezone objects, but may not be for other datetime.tzinfo diff --git a/tests/unit/views/test_browse.py b/tests/unit/views/test_browse.py new file mode 100644 index 0000000..93eb0ae --- /dev/null +++ b/tests/unit/views/test_browse.py @@ -0,0 +1,14 @@ +from fietsboek.views.browse import Stream + + +class TestStream: + def test_write(self): + stream = Stream() + n = stream.write(b"foobar") + assert n == 6 + + def test_write_read(self): + stream = Stream() + stream.write(b"foo") + stream.write(b"bar") + assert stream.readall() == b"foobar" -- cgit v1.2.3