From 33dbf70313e443fc7cbeb0566ecb96eb88b9bb77 Mon Sep 17 00:00:00 2001 From: npeter83 Date: Tue, 21 Jul 2026 03:29:24 +0200 Subject: [PATCH] test(backend): pure-logic pytest harness, run in an isolated test image MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The backend had zero tests. This adds 33, all pure (no DB, no network), plus the harness to run them in the gate: - backend/Dockerfile.test: the app's deps + pinned pytest/ruff, code bind-mounted at run time so it always tests the working tree. Isolated from the prod Dockerfile, so the published image never carries test tooling. - requirements-dev.txt: pytest==8.4.2, ruff==0.15.21 (the version the gate already runs) — the backend lane is now pinned, not just host-global. - tests: normalize_title (de-shout, hashtag strip, emoji drop, trilingual letters survive); storage sanitize/rel_path/download_filename and the safe_abs_path traversal guard (../ and absolute-path escapes rejected — verified by mutation); links HMAC grants (round-trip, wrong-token, tampered sig/exp, expiry) + is_expired; a DB-free app-assembly smoke (every router wires up, OpenAPI generates). DB-backed router smoke (auth/feed/downloads) needs a test Postgres + migrations — deferred. useCardPager needs hook-test infra (jsdom/renderHook) — deferred. --- backend/Dockerfile.test | 12 +++++ backend/pytest.ini | 6 +++ backend/requirements-dev.txt | 6 +++ backend/tests/test_links.py | 53 +++++++++++++++++++ backend/tests/test_smoke.py | 26 ++++++++++ backend/tests/test_storage.py | 95 +++++++++++++++++++++++++++++++++++ backend/tests/test_titles.py | 41 +++++++++++++++ 7 files changed, 239 insertions(+) create mode 100644 backend/Dockerfile.test create mode 100644 backend/pytest.ini create mode 100644 backend/requirements-dev.txt create mode 100644 backend/tests/test_links.py create mode 100644 backend/tests/test_smoke.py create mode 100644 backend/tests/test_storage.py create mode 100644 backend/tests/test_titles.py diff --git a/backend/Dockerfile.test b/backend/Dockerfile.test new file mode 100644 index 0000000..6bffc2f --- /dev/null +++ b/backend/Dockerfile.test @@ -0,0 +1,12 @@ +# Test-only image — the app's Python deps + pytest/ruff, and NOTHING else. Code is bind-mounted at +# run time (not baked), so pytest always sees the working tree, and this file never touches the prod +# Dockerfile, so the published image can never carry test tooling. Same python + same pinned +# requirements.txt as the runtime stage, so tests run against the prod dependency set. Rebuilt only +# when a requirements file changes (the pip layer is cache-keyed on them); otherwise a no-op. +# +# docker build -f backend/Dockerfile.test -t siftlode-test:local backend +# docker run --rm -v "/backend:/app" -w /app siftlode-test:local python -m pytest +FROM python:3.12-slim +WORKDIR /app +COPY requirements.txt requirements-dev.txt ./ +RUN pip install --no-cache-dir -r requirements.txt -r requirements-dev.txt diff --git a/backend/pytest.ini b/backend/pytest.ini new file mode 100644 index 0000000..d052618 --- /dev/null +++ b/backend/pytest.ini @@ -0,0 +1,6 @@ +[pytest] +# Run from backend/ (the image's /app). `pythonpath = .` puts the app package on sys.path so tests +# import it as `app.*`, matching runtime. The suite is pure-logic only (no DB/network) — see tests/. +testpaths = tests +pythonpath = . +addopts = -q diff --git a/backend/requirements-dev.txt b/backend/requirements-dev.txt new file mode 100644 index 0000000..f07b152 --- /dev/null +++ b/backend/requirements-dev.txt @@ -0,0 +1,6 @@ +# Test + lint tooling — NEVER in the prod image (installed only in the Dockerfile's `dev` stage, +# which `siftlode publish` does not target). Pinned so the gate is reproducible: the same ruff the +# `siftlode check` backend lane runs, and pytest for the pure-logic suite. httpx is already an app +# dependency (requirements.txt), so its TestClient needs nothing extra here. +pytest==8.4.2 +ruff==0.15.21 diff --git a/backend/tests/test_links.py b/backend/tests/test_links.py new file mode 100644 index 0000000..ef8a94b --- /dev/null +++ b/backend/tests/test_links.py @@ -0,0 +1,53 @@ +"""Signed-grant HMAC for password-protected share links: a grant proves the viewer unlocked a +token, rides in the file URL (a