test(backend): pure-logic pytest harness, run in an isolated test image
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.
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
"""normalize_title: de-shout / de-clutter video titles without mangling non-English text."""
|
||||
from app.titles import normalize_title
|
||||
|
||||
|
||||
def test_empty_and_whitespace_pass_through_unchanged():
|
||||
assert normalize_title(None) is None
|
||||
assert normalize_title("") == ""
|
||||
assert normalize_title(" ") == " "
|
||||
|
||||
|
||||
def test_plain_title_is_left_alone():
|
||||
assert normalize_title("How I built a boat") == "How I built a boat"
|
||||
|
||||
|
||||
def test_shouting_title_is_de_capsed_and_first_letter_kept_upper():
|
||||
# >=2 all-caps runs and >=50% caps → treated as shouting: title-case the long words.
|
||||
out = normalize_title("THIS IS A HUGE ANNOUNCEMENT")
|
||||
assert out is not None
|
||||
assert out == out[0].upper() + out[1:] # first alpha char stays capitalized
|
||||
assert "HUGE" not in out and "ANNOUNCEMENT" not in out
|
||||
|
||||
|
||||
def test_trailing_hashtags_are_stripped():
|
||||
assert normalize_title("Great cooking video #food #yummy") == "Great cooking video"
|
||||
|
||||
|
||||
def test_emoji_and_symbols_are_dropped():
|
||||
assert "🔥" not in (normalize_title("Fire recipe 🔥🔥") or "")
|
||||
|
||||
|
||||
def test_repeated_punctuation_is_collapsed():
|
||||
assert normalize_title("Wait what!!!!") == "Wait what!"
|
||||
|
||||
|
||||
def test_accented_non_english_letters_survive():
|
||||
# Siftlode is trilingual — Hungarian/German letters must not be ASCII-folded away.
|
||||
for title in ["Gulyásleves főzés", "Über die Straße"]:
|
||||
out = normalize_title(title) or ""
|
||||
for ch in "áöüőűé":
|
||||
if ch in title:
|
||||
assert ch in out
|
||||
Reference in New Issue
Block a user