soapbox/tests/test_app.py
Ole-Morten Duesund 6fef73793f Lisensier under AGPL-3.0-or-later og lenk til kildekoden i footeren
AGPL §13 krever at brukere av en nettverkstjeneste kan hente kildekoden;
temaet viser derfor en lenke til repoet på hver side.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JcEy43fNYpwg6K6oTKakWR
2026-08-26 13:13:25 +02:00

164 lines
6.2 KiB
Python

import re
from soapbox.app import create_app
from soapbox.config import load_config
from tests.conftest import csrf, login, png_bytes
def _create_post(client, prefix=""):
login(client, prefix=prefix)
r = client.post(f"{prefix}/admin/new", data={"csrf": csrf(client)})
assert r.status_code == 302
post_id = r.headers["Location"].split("/admin/edit/")[1]
return post_id
def test_login_required(client):
assert client.get("/admin/").status_code == 302
assert client.get("/admin/login").status_code == 200
assert client.get("/admin/static/admin.css").content_type.startswith("text/css")
assert client.get("/admin/static/editor.js").status_code == 200
def test_full_flow_owner(client, cfg):
post_id = _create_post(client)
assert post_id == "_owner/nytt-innlegg"
token = csrf(client)
# Last opp et bilde
r = client.post(
f"/admin/upload/{post_id}",
data={"file": (__import__("io").BytesIO(png_bytes()), "Mitt Bilde.PNG")},
headers={"X-CSRF": token},
content_type="multipart/form-data",
)
assert r.status_code == 200, r.data
assert r.json["filename"] == "mitt-bilde.png"
# Forhåndsvisning peker bildet til admin-media
r = client.post(
f"/admin/preview/{post_id}",
json={"body": "![](mitt-bilde.png) [x](/annet/)"},
headers={"X-CSRF": token},
)
assert f'src="/admin/media/{post_id}/mitt-bilde.png"' in r.text
assert 'href="../annet/"' in r.text
# Publiser: slug følger tittelen
body = "Hei **verden**\n\n![](mitt-bilde.png)\n\n[hjem](/)"
r = client.post(
f"/admin/edit/{post_id}",
data={"csrf": token, "title": "Grønt er skjønt", "body": body, "action": "publish"},
)
assert r.headers["Location"].endswith("/admin/edit/_owner/groent-er-skjoent")
assert (cfg.content_dir / "posts/groent-er-skjoent/index.md").exists()
assert (cfg.content_dir / "posts/groent-er-skjoent/mitt-bilde.png").exists()
# Statisk output
r = client.get("/groent-er-skjoent")
assert r.status_code == 301 and r.headers["Location"].endswith("/groent-er-skjoent/")
html = client.get("/groent-er-skjoent/").text
assert "<strong>verden</strong>" in html
assert 'href="../theme/style.css"' in html
assert 'href="../"' in html # intern lenke omskrevet til relativ
assert client.get("/groent-er-skjoent/mitt-bilde.png").status_code == 200
front = client.get("/").text
assert "Grønt er skjønt" in front
assert 'href="https://kode.naiv.no/olemd/soapbox"' in front # AGPL: lenke til kilden
feed = client.get("/feed.xml").text
assert "https://blog.example.no/groent-er-skjoent/" in feed
# Etter publisering fryses slug selv om tittelen endres
r = client.post(
"/admin/edit/_owner/groent-er-skjoent",
data={"csrf": token, "title": "Ny tittel", "body": body, "action": "save"},
)
assert r.headers["Location"].endswith("/admin/edit/_owner/groent-er-skjoent")
# Git-historikk
from soapbox.gitstore import log
lines = log(cfg.content_dir)
assert any("Publiserte: Grønt er skjønt" in line for line in lines)
assert all("eier:" in line for line in lines)
def test_guest_flow_and_isolation(client, cfg):
post_id = _create_post(client) # eierens utkast
client.post("/admin/logout", data={"csrf": csrf(client)})
login(client, "gjest1")
token = csrf(client)
# Gjest kan ikke røre eierens innlegg
assert client.get(f"/admin/edit/{post_id}").status_code == 403
assert client.get("/admin/users").status_code == 403
r = client.post("/admin/new", data={"csrf": token})
gid = r.headers["Location"].split("/admin/edit/")[1]
assert gid == "gjest1/nytt-innlegg"
client.post(
f"/admin/edit/{gid}",
data={"csrf": token, "title": "Post 1", "body": "hei", "action": "publish"},
)
assert (cfg.content_dir / "guests/gjest1/post-1/index.md").exists()
html = client.get("/gjest1/post-1/").text
assert 'href="../../theme/style.css"' in html
assert "Gjest En" in html
assert "Post 1" in client.get("/gjest1/").text
assert "Post 1" in client.get("/").text # gjesteinnlegg vises også på forsiden
# eierens utkast er ikke publisert
assert client.get("/nytt-innlegg/").status_code == 404
def test_login_next_no_open_redirect(client):
for bad in ("//evil.com", "/\\evil.com", "https://evil.com"):
r = client.post(
"/admin/login", data={"username": "eier", "password": "hemmelig123", "next": bad}
)
assert r.headers["Location"] == "/admin/", bad
r = client.post(
"/admin/login", data={"username": "eier", "password": "hemmelig123", "next": "/admin/users"}
)
assert r.headers["Location"] == "/admin/users"
def test_csrf_enforced(client):
login(client)
assert client.post("/admin/new", data={}).status_code == 403
def test_subpath_mount(tmp_path):
cfg = load_config({"SOAPBOX_DATA_DIR": str(tmp_path / "d"), "SOAPBOX_BASE_PATH": "/blog/"})
assert cfg.base_path == "/blog"
app = create_app(cfg)
app.config["TESTING"] = True
from soapbox import db
conn = db.connect(cfg.db_path)
db.create_user(conn, "eier", "hemmelig123", db.ROLE_OWNER)
conn.close()
client = app.test_client()
assert client.get("/admin/login").status_code == 404
assert client.get("/blog/admin/login").status_code == 200
login(client, prefix="/blog")
r = client.post("/blog/admin/new", data={"csrf": csrf(client, "/blog")})
assert r.headers["Location"].startswith("/blog/admin/edit/")
html = client.get("/blog/").text
assert 'href="theme/style.css"' in html
assert re.search(r'href="admin/"', html)
assert client.get("/blog/theme/style.css").status_code == 200
def test_no_inline_js_with_content(client, cfg):
"""Titler med anførselstegn skal ikke havne i en JS-kontekst (XSS)."""
post_id = _create_post(client)
token = csrf(client)
title = 'Ola\'s "test" <b>'
r = client.post(
f"/admin/edit/{post_id}",
data={"csrf": token, "title": title, "body": "x", "action": "save"},
)
html = client.get(r.headers["Location"]).text
assert "onsubmit=" not in html and "onclick=" not in html
assert "Ola&#39;s" in html