aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/bootstrap/test_new_instance.py90
-rw-r--r--tests/conftest.py4
-rw-r--r--tests/integration/test_browse.py118
-rw-r--r--tests/integration/test_pdf.py59
-rw-r--r--tests/playwright/conftest.py6
-rw-r--r--tests/playwright/test_transformers.py19
-rw-r--r--tests/unit/test_pdf.py58
-rw-r--r--tests/unit/test_util.py25
8 files changed, 319 insertions, 60 deletions
diff --git a/tests/bootstrap/test_new_instance.py b/tests/bootstrap/test_new_instance.py
index dc3076e..05076f4 100644
--- a/tests/bootstrap/test_new_instance.py
+++ b/tests/bootstrap/test_new_instance.py
@@ -2,6 +2,7 @@
script, as described in the documentation.
"""
+import configparser
import contextlib
import logging
import os
@@ -10,6 +11,8 @@ import subprocess
import venv
from pathlib import Path
+import sqlalchemy
+
LOGGER = logging.getLogger(__name__)
REPO_BASE = Path(__file__).parent.parent.parent
@@ -51,31 +54,66 @@ def create_config(config_name: Path):
Path("data").mkdir()
+def cleanup_database(config_name: Path):
+ """Connects to the database and ensures everything is reset.
+
+ :param config_name: Path to the config file.
+ """
+ if not config_name.exists():
+ return
+
+ parser = configparser.ConfigParser()
+ parser["DEFAULT"]["here"] = str(config_name.parent)
+ parser.read(config_name)
+
+ db_url = parser["app:main"]["sqlalchemy.url"]
+ engine = sqlalchemy.create_engine(db_url)
+
+ match engine.name:
+ case "sqlite":
+ pass
+ case "postgresql":
+ with engine.connect() as connection:
+ connection.execute(sqlalchemy.text("DROP SCHEMA public CASCADE;"))
+ connection.execute(sqlalchemy.text("CREATE SCHEMA public;"))
+ connection.commit()
+
+
def test_setup_via_fietsupdate(tmpdir):
with chdir(tmpdir):
- # We create a new temporary virtual environment with a fresh install, just
- # to be sure there's as little interference as possible.
- LOGGER.info("Installing Fietsboek into clean env")
- binaries_path = install_fietsboek(tmpdir / "venv")
-
- LOGGER.info("Creating a test configuration")
- create_config(Path("testing.ini"))
-
- # Try to run the migrations
- subprocess.check_call(
- [binaries_path / "fietsupdate", "update", "-c", "testing.ini", "-f"]
- )
-
- # Also try to add an administrator
- subprocess.check_call([
- binaries_path / "fietsctl",
- "user",
- "add",
- "-c", "testing.ini",
- "--email", "foobar@example.com",
- "--name", "Foo Bar",
- "--password", "raboof",
- "--admin",
- ])
-
- assert True
+ try:
+ # We create a new temporary virtual environment with a fresh install, just
+ # to be sure there's as little interference as possible.
+ LOGGER.info("Installing Fietsboek into clean env")
+ binaries_path = install_fietsboek(tmpdir / "venv")
+
+ LOGGER.info("Installing additional SQL engines")
+ subprocess.check_call(
+ [binaries_path / "pip", "install", "psycopg2"]
+ )
+
+ LOGGER.info("Creating a test configuration")
+ create_config(Path("testing.ini"))
+
+ # Try to run the migrations
+ subprocess.check_call(
+ [binaries_path / "fietsupdate", "update", "-c", "testing.ini", "-f"]
+ )
+
+ # Also try to add an administrator
+ subprocess.check_call([
+ binaries_path / "fietsctl",
+ "user",
+ "add",
+ "-c", "testing.ini",
+ "--email", "foobar@example.com",
+ "--name", "Foo Bar",
+ "--password", "raboof",
+ "--admin",
+ ])
+
+ assert True
+ finally:
+ # Clean up the database. This is important with anything but SQLite, as
+ # the tables would otherwise persist and interfere with the other tests.
+ cleanup_database(Path("testing.ini"))
diff --git a/tests/conftest.py b/tests/conftest.py
index cd74b0b..b49dad2 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -52,6 +52,7 @@ def dbengine(app_settings, ini_file):
yield engine
+ engine.dispose()
Base.metadata.drop_all(bind=engine)
alembic.command.stamp(alembic_cfg, None, purge=True)
@@ -82,6 +83,7 @@ def _cleanup_data(app_settings):
def app(app_settings, dbengine, tmp_path_factory):
app_settings["fietsboek.data_dir"] = str(tmp_path_factory.mktemp("data"))
logging.getLogger().setLevel(logging.DEBUG)
+ logging.getLogger("matplotlib").setLevel(logging.INFO)
return main({}, dbengine=dbengine, **app_settings)
@pytest.fixture
@@ -189,7 +191,7 @@ def logged_in(testapp, route_path, dbsession, tm):
tm.abort()
with tm:
- user = models.User(email='foo@barre.com', is_verified=True)
+ user = models.User(name="Feu Barre", email='foo@barre.com', is_verified=True)
user.set_password("foobar")
dbsession.add(user)
dbsession.flush()
diff --git a/tests/integration/test_browse.py b/tests/integration/test_browse.py
index 875821d..1b96e2e 100644
--- a/tests/integration/test_browse.py
+++ b/tests/integration/test_browse.py
@@ -3,8 +3,10 @@ import zipfile
from contextlib import contextmanager
from datetime import datetime
+from sqlalchemy import delete, inspect
+
from testutils import load_gpx_asset
-from fietsboek import models
+from fietsboek import convert, models
from fietsboek.models.track import Visibility
@@ -21,7 +23,10 @@ def added_tracks(tm, dbsession, owner, data_manager):
# objects to the database.
tm.abort()
+ path = convert.smart_convert(load_gpx_asset("Teasi_1.gpx.gz")).path()
+
tracks = []
+ track_ids = []
with tm:
track = models.Track(
owner=owner,
@@ -35,8 +40,10 @@ def added_tracks(tm, dbsession, owner, data_manager):
track.date = datetime(2022, 3, 14, 9, 26, 54)
dbsession.add(track)
dbsession.flush()
- data_manager.initialize(track.id).compress_gpx(load_gpx_asset("MyTourbook_1.gpx.gz"))
+ track.fast_set_path(path)
+ data_manager.initialize(track.id)
tracks.append(track)
+ track_ids.append(track.id)
track = models.Track(
owner=owner,
@@ -50,14 +57,18 @@ def added_tracks(tm, dbsession, owner, data_manager):
track.date = datetime(2022, 10, 29, 13, 37, 11)
dbsession.add(track)
dbsession.flush()
- data_manager.initialize(track.id).compress_gpx(load_gpx_asset("Teasi_1.gpx.gz"))
+ track.fast_set_path(path)
+ track.ensure_cache(path)
+ dbsession.add(track.cache)
+ data_manager.initialize(track.id)
tracks.append(track)
+ track_ids.append(track.id)
tm.begin()
tm.doom()
try:
- yield tracks
+ yield track_ids
finally:
tm.abort()
with tm:
@@ -67,26 +78,113 @@ def added_tracks(tm, dbsession, owner, data_manager):
tm.doom()
+@contextmanager
+def a_lot_of_tracks(tm, dbsession, owner, data_manager):
+ """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()
+
+ gpx_data = load_gpx_asset("MyTourbook_1.gpx.gz")
+ skel = convert.smart_convert(gpx_data)
+ path = skel.path()
+
+ tracks = []
+ track_ids = []
+ with tm:
+ for index in range(50):
+ track = models.Track(
+ owner=owner,
+ title=f"Traxi {index}",
+ visibility=Visibility.PUBLIC,
+ description="One of many",
+ badges=[],
+ link_secret="foobar",
+ tagged_people=[],
+ )
+ track.date = datetime(2022 - index, 3, 14, 9, 26, 59)
+ dbsession.add(track)
+ dbsession.flush()
+ track.fast_set_path(path)
+ track.ensure_cache(path)
+ dbsession.add(track.cache)
+ tracks.append(track)
+ track_ids.append(track.id)
+ data_manager.initialize(track.id)
+
+ tm.begin()
+ tm.doom()
+
+ try:
+ yield track_ids
+ finally:
+ tm.abort()
+ table = inspect(models.track.TrackPoint).tables[0]
+ with tm:
+ for track_id in track_ids:
+ dbsession.execute(delete(table).where(table.c.track_id == track_id))
+ dbsession.execute(
+ delete(models.TrackCache).where(models.TrackCache.track_id == track_id)
+ )
+ dbsession.execute(delete(models.Track).where(models.Track.id == track_id))
+ tm.begin()
+ tm.doom()
+
+
def test_browse(testapp, dbsession, route_path, logged_in, tm, data_manager):
# pylint: disable=too-many-positional-arguments
# Ensure there are some tracks in the database
with added_tracks(tm, dbsession, logged_in, data_manager):
# Now go to the browse page
- browse = testapp.get(route_path('browse'))
+ browse = testapp.get(route_path("browse"))
assert "Foobar" in browse.text
assert "Barfoo" in browse.text
+def test_browse_paged(testapp, dbsession, route_path, logged_in, tm, data_manager):
+ # pylint: disable=too-many-positional-arguments
+ with a_lot_of_tracks(tm, dbsession, logged_in, data_manager):
+ page_1 = testapp.get(route_path("browse", _query=[("page", 1)]))
+ assert "Traxi 0" in page_1.text
+ assert "Traxi 10" in page_1.text
+ assert "Traxi 20" not in page_1.text
+ assert "Traxi 30" not in page_1.text
+ assert "Traxi 40" not in page_1.text
+
+ page_2 = testapp.get(route_path("browse", _query=[("page", 2)]))
+ assert "Traxi 0" not in page_2.text
+ assert "Traxi 10" not in page_2.text
+ assert "Traxi 20" in page_2.text
+ assert "Traxi 30" in page_2.text
+ assert "Traxi 40" not in page_2.text
+
+ page_3 = testapp.get(route_path("browse", _query=[("page", 3)]))
+ assert "Traxi 0" not in page_3.text
+ assert "Traxi 10" not in page_3.text
+ assert "Traxi 20" not in page_3.text
+ assert "Traxi 30" not in page_3.text
+ assert "Traxi 40" in page_3.text
+
+
def test_archive(testapp, dbsession, route_path, logged_in, tm, data_manager):
# pylint: disable=too-many-positional-arguments
- with added_tracks(tm, dbsession, logged_in, data_manager):
+ with added_tracks(tm, dbsession, logged_in, data_manager) as tracks:
archive = testapp.get(
- route_path('track-archive', _query=[("track_id[]", "1"), ("track_id[]", "2")])
+ route_path(
+ "track-archive",
+ _query=[("track_id[]", tracks[0]), ("track_id[]", tracks[1])],
+ )
)
result = io.BytesIO(archive.body)
- with zipfile.ZipFile(result, 'r') as zipped:
+ 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()
+ assert f"track_{tracks[0]}.gpx" in zipped.namelist()
+ assert f"track_{tracks[1]}.gpx" in zipped.namelist()
diff --git a/tests/integration/test_pdf.py b/tests/integration/test_pdf.py
new file mode 100644
index 0000000..29cda02
--- /dev/null
+++ b/tests/integration/test_pdf.py
@@ -0,0 +1,59 @@
+from contextlib import contextmanager
+from datetime import datetime
+
+from testutils import load_gpx_asset
+from fietsboek import convert, models
+from fietsboek.models.track import Visibility
+
+
+@contextmanager
+def a_track(tm, dbsession, owner, data_manager):
+ """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()
+
+ with tm:
+ track = models.Track(
+ owner=owner,
+ title="Goober",
+ visibility=Visibility.PUBLIC,
+ description="A bar'd track",
+ badges=[],
+ link_secret="raboof",
+ tagged_people=[],
+ )
+ track.date = datetime(2027, 3, 14, 9, 26, 54)
+ track.set_path(convert.smart_convert(load_gpx_asset("MyTourbook_1.gpx.gz")).path())
+ dbsession.add(track)
+ dbsession.flush()
+ data_manager.initialize(track.id)
+ track_id = track.id
+
+ tm.begin()
+ tm.doom()
+
+ try:
+ yield track_id
+ finally:
+ tm.abort()
+ with tm:
+ dbsession.delete(track)
+ data_manager.purge(track_id)
+ tm.begin()
+ tm.doom()
+
+
+def test_pdf(testapp, dbsession, route_path, logged_in, tm, data_manager):
+ # pylint: disable=too-many-positional-arguments
+ # Ensure there are some tracks in the database
+ with a_track(tm, dbsession, logged_in, data_manager) as track_id:
+ pdf = testapp.get(route_path("track-pdf", track_id=track_id))
+
+ assert pdf
diff --git a/tests/playwright/conftest.py b/tests/playwright/conftest.py
index e914b01..adf5ef3 100644
--- a/tests/playwright/conftest.py
+++ b/tests/playwright/conftest.py
@@ -57,7 +57,11 @@ def dbaccess(app):
through and the running WSGI app cannot read them.
"""
session_factory = app.registry["dbsession_factory"]
- return session_factory()
+ factory = session_factory()
+
+ yield factory
+
+ factory.close()
class Helper:
diff --git a/tests/playwright/test_transformers.py b/tests/playwright/test_transformers.py
index fc89afb..d4e3456 100644
--- a/tests/playwright/test_transformers.py
+++ b/tests/playwright/test_transformers.py
@@ -26,7 +26,7 @@ def test_transformer_zero_elevation_disabled(page: Page, playwright_helper, tmp_
# Expect early (here and in the other tests) to ensure that the backend has
# caught up with executing the transformer. Otherwise it might happen that
# we read the database while the request is not finished yet.
- expect(page.locator("#detailsUphill")).to_contain_text("167.7 m")
+ expect(page.locator("#detailsUphill")).to_contain_text("167.79 m")
new_track_id = int(page.url.rsplit("/", 1)[1])
track = dbaccess.execute(select(models.Track).filter_by(id=new_track_id)).scalar_one()
@@ -90,7 +90,7 @@ def test_transformer_steep_slope_disabled(page: Page, playwright_helper, tmp_pat
page.locator(".btn", has_text="Upload").click()
- expect(page.locator("#detailsUphill")).to_contain_text("61.54 m")
+ expect(page.locator("#detailsUphill")).to_contain_text("64.4 m")
new_track_id = int(page.url.rsplit("/", 1)[1])
track = dbaccess.execute(select(models.Track).filter_by(id=new_track_id)).scalar_one()
@@ -111,11 +111,11 @@ def test_transformer_steep_slope_enabled(page: Page, playwright_helper, tmp_path
page.locator(".btn", has_text="Upload").click()
- expect(page.locator("#detailsUphill")).to_contain_text("1.2 m")
+ expect(page.locator("#detailsUphill")).to_contain_text("2.4 m")
new_track_id = int(page.url.rsplit("/", 1)[1])
track = dbaccess.execute(select(models.Track).filter_by(id=new_track_id)).scalar_one()
- assert track.cache.uphill < 2
+ assert track.cache.uphill < 3
def test_transformer_steep_slope_edited(page: Page, playwright_helper, tmp_path, dbaccess):
@@ -137,14 +137,14 @@ def test_transformer_steep_slope_edited(page: Page, playwright_helper, tmp_path,
page.locator(".btn", has_text="Save").click()
- expect(page.locator("#detailsUphill")).to_contain_text("1.2 m")
+ expect(page.locator("#detailsUphill")).to_contain_text("2.4 m")
track_id = int(page.url.rsplit("/", 1)[1])
track = dbaccess.execute(select(models.Track).filter_by(id=track_id)).scalar_one()
- assert track.cache.uphill < 2
+ assert track.cache.uphill < 3
-def test_transformer_elevation_jump_enabled(page: Page, playwright_helper, tmp_path, data_manager):
+def test_transformer_elevation_jump_enabled(page: Page, playwright_helper, tmp_path, dbaccess):
playwright_helper.login()
page.goto("/")
@@ -161,9 +161,10 @@ def test_transformer_elevation_jump_enabled(page: Page, playwright_helper, tmp_p
page.locator(".alert", has_text="Upload successful").wait_for()
new_track_id = int(page.url.rsplit("/", 1)[1])
- data = data_manager.open(new_track_id)
- gpx = gpxpy.parse(data.decompress_gpx())
+ gpx = gpxpy.parse(
+ dbaccess.execute(select(models.Track).filter_by(id=new_track_id)).scalar_one().gpx_xml()
+ )
points = iter(gpx.walk(only_points=True))
next(points)
for prev_point, point in zip(gpx.walk(only_points=True), points):
diff --git a/tests/unit/test_pdf.py b/tests/unit/test_pdf.py
new file mode 100644
index 0000000..aafa717
--- /dev/null
+++ b/tests/unit/test_pdf.py
@@ -0,0 +1,58 @@
+import pytest
+
+from fietsboek import pdf
+
+
+@pytest.mark.parametrize("value, expected", [
+ ('', '""'),
+ ('a', '"\\u{61}"'),
+ ('FOO', '"\\u{46}\\u{4f}\\u{4f}"'),
+ ('äß', '"\\u{e4}\\u{df}"'),
+ ('"', '"\\u{22}"'),
+ ("'", '"\\u{27}"'),
+])
+def test_typst_string(value, expected):
+ assert pdf.typst_string(value) == expected
+
+
+@pytest.mark.parametrize("value, expected", [
+ ("foo", "foo"),
+ ("*foo*", "\\*foo\\*"),
+ ("#strong[foo]", "\\#strong\\[foo\\]"),
+ ("= foo", "\\= foo"),
+ ("par 1\n\npar 2", "par 1\n\npar 2"),
+])
+def test_typst_escape(value, expected):
+ assert pdf.typst_escape(value) == expected
+
+
+@pytest.mark.parametrize("md_source, typst_source", [
+ ("*foo*", "#emph[foo]\n\n"),
+ ("**foo**", "#strong[foo]\n\n"),
+ ("***foo***", "#strong[#emph[foo]]\n\n"),
+ ("[Teksd](https://link)",
+ '#link("\\u{68}\\u{74}\\u{74}\\u{70}\\u{73}\\u{3a}'
+ '\\u{2f}\\u{2f}\\u{6c}\\u{69}\\u{6e}\\u{6b}")[Teksd]\n\n'),
+ ("""\
+# Uperschrift
+
+Teksd""", """\
+#heading(level: 1)[Uperschrift]
+Teksd\n\n"""),
+ ("""\
+* Eitem 1
+* Eitem 2""", """\
+#list(
+[Eitem 1],
+[Eitem 2],
+)"""),
+ ("""\
+1. Eitem 1
+1. Eitem 2""", """\
+#enum(
+[Eitem 1],
+[Eitem 2],
+)"""),
+])
+def test_md_to_typst(md_source, typst_source):
+ assert pdf.md_to_typst(md_source) == typst_source
diff --git a/tests/unit/test_util.py b/tests/unit/test_util.py
index 6dc8e7d..cc92058 100644
--- a/tests/unit/test_util.py
+++ b/tests/unit/test_util.py
@@ -71,19 +71,6 @@ def test_guess_gpx_timezone(gpx_file, offset):
assert timezone.utcoffset(None) == offset
-@pytest.mark.parametrize('gpx_file', [
- 'Teasi_1.gpx.gz',
- 'MyTourbook_1.gpx.gz',
- 'Synthetic_WT2.gpx.gz',
- 'Synthetic_BRouter_1.gpx.gz',
-])
-def test_tour_metadata(gpx_file):
- # Here we simply make sure that we do not crash the metadata extraction
- # function.
- gpx_data = load_gpx_asset(gpx_file)
- assert util.tour_metadata(gpx_data) is not None
-
-
@pytest.mark.parametrize('mps, kph', [(1, 3.6), (10, 36)])
def test_mps_to_kph(mps, kph):
assert util.mps_to_kph(mps) == pytest.approx(kph, 0.1)
@@ -112,3 +99,15 @@ def test_tile_url(app_request):
assert "{y}" in route_url
assert "{z}" in route_url
assert "bobby" in route_url
+
+
+@pytest.mark.parametrize("value, expected", [
+ ("", b""),
+ ("foo", b"foo"),
+ ("<foo>", b"&#x3c;foo&#x3e;"),
+ ("foo bar", b"foo bar"),
+ ("</gpx>", b"&#x3c;&#x2f;gpx&#x3e;"),
+ ("äÖß", b"&#xe4;&#xd6;&#xdf;"),
+])
+def test_xml_escape(value, expected):
+ assert util.xml_escape(value) == expected