Soapbox: statisk blogg med mobilvennlig markdown-editor
Flask-app som genererer statiske sider til public/ og serverer dem sammen med et admin-grensesnitt (/admin). Innhold ligger som markdown og bilder i et git-repo (data/content), brukere i sqlite. - Editor med toolbar, live server-side forhåndsvisning, bildeopplasting via knapp, dra-og-slipp og lim inn. Bilder nedskaleres og EXIF fjernes. - Hovedbruker publiserer under /<slug>/, gjester under /<bruker>/<slug>/. Slug lages fra tittelen og fryses ved publisering. - Alle interne lenker er relative, så siden kan flyttes mellom domener og sub-paths (SOAPBOX_BASE_PATH) uten rebuild. - Tema 'green' med CSS-variabler, mørk modus og WCAG-kontrast. - Containerfile (python:alpine + git), compose.yaml og Caddyfile-eksempel. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JcEy43fNYpwg6K6oTKakWR
This commit is contained in:
commit
7ab2320be6
35 changed files with 2726 additions and 0 deletions
56
tests/test_core.py
Normal file
56
tests/test_core.py
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
from datetime import UTC, datetime
|
||||
|
||||
from soapbox import db
|
||||
from soapbox.content import dump_frontmatter, parse_frontmatter
|
||||
from soapbox.images import process_image
|
||||
from soapbox.render import relative_to, render_markdown
|
||||
from soapbox.slug import slugify, unique_slug
|
||||
from tests.conftest import png_bytes
|
||||
|
||||
|
||||
def test_slugify_norwegian():
|
||||
assert slugify("Grønt er skjønt: blåbær & øl!") == "groent-er-skjoent-blaabaer-oel"
|
||||
assert slugify(" ") == "post"
|
||||
assert unique_slug("a", {"a", "a-2"}) == "a-3"
|
||||
|
||||
|
||||
def test_frontmatter_roundtrip():
|
||||
meta = {"title": 'Tittel: med "anførsel"', "date": "2026-01-01T00:00:00+00:00", "draft": True}
|
||||
text = dump_frontmatter(meta, "# Hei\n\nbrødtekst")
|
||||
parsed, body = parse_frontmatter(text)
|
||||
assert parsed == meta
|
||||
assert body.strip() == "# Hei\n\nbrødtekst"
|
||||
|
||||
|
||||
def test_relative_paths():
|
||||
assert relative_to("", "post1/") == "post1/"
|
||||
assert relative_to("post1/", "") == "../"
|
||||
assert relative_to("gjest/post1/", "post2/") == "../../post2/"
|
||||
assert relative_to("gjest/post1/", "theme/style.css") == "../../theme/style.css"
|
||||
|
||||
|
||||
def test_markdown_safe_and_relative():
|
||||
html = render_markdown("<script>x</script>\n\n[a](/post2/#x) ", "g/p/")
|
||||
assert "<script>" not in html
|
||||
assert 'href="../../post2/#x"' in html
|
||||
assert 'src="b.jpg"' in html
|
||||
assert "//example.com" in render_markdown("[x](//example.com)")
|
||||
|
||||
|
||||
def test_image_downscale():
|
||||
data, ext = process_image(png_bytes((3000, 1500)), 2000)
|
||||
import io
|
||||
|
||||
from PIL import Image
|
||||
|
||||
img = Image.open(io.BytesIO(data))
|
||||
assert ext == ".png" and img.size == (2000, 1000)
|
||||
|
||||
|
||||
def test_password_hashing(tmp_path):
|
||||
conn = db.connect(tmp_path / "x.sqlite")
|
||||
db.create_user(conn, "a", "passord123", db.ROLE_OWNER)
|
||||
assert db.authenticate(conn, "a", "passord123")
|
||||
assert db.authenticate(conn, "a", "feil") is None
|
||||
assert db.has_owner(conn)
|
||||
assert datetime.now(UTC) # sanity: UTC alias tilgjengelig
|
||||
Loading…
Add table
Add a link
Reference in a new issue