Molodetz blogrol volgens DPP-template

This commit is contained in:
2026-10-05 09:36:20 +02:00
commit f290a4c766
277 changed files with 15758 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
# retoor <retoor@molodetz.nl>
+50
View File
@@ -0,0 +1,50 @@
# retoor <retoor@molodetz.nl>
JSON = {"Accept": "application/json"}
def _create(admin, **overrides):
payload = {"title": "Een echte notitie", "body": "Tekst met **nadruk** en `code`.", "topic": "rol", "status": "draft"}
payload.update(overrides)
response = admin.post("/admin/posts", json=payload, headers=JSON)
assert response.status_code == 200, response.text
return response.json()["data"]
def test_create_draft_is_hidden_then_published(admin, anon):
data = _create(admin, title="Concept dat later live gaat")
assert anon.get(f"/posts/{data['slug']}").status_code == 404
assert admin.post(f"/admin/posts/{data['uid']}/publish", headers=JSON).status_code == 200
page = anon.get(f"/posts/{data['slug']}")
assert page.status_code == 200
assert "<strong>nadruk</strong>" in page.text
def test_edit_post(admin, anon):
data = _create(admin, title="Te bewerken notitie", status="published")
response = admin.post(f"/admin/posts/{data['uid']}", json={"title": "Bewerkte notitie", "body": "Nieuwe tekst hier.", "topic": "standaard", "status": "published"}, headers=JSON)
assert response.status_code == 200, response.text
posts = anon.get("/standaard", headers=JSON).json()["posts"]
assert any(post["title"] == "Bewerkte notitie" for post in posts)
def test_delete_goes_to_trash(admin, anon):
data = _create(admin, title="Weg ermee notitie", status="published")
assert admin.post(f"/admin/posts/{data['uid']}/delete", headers=JSON).status_code == 200
assert anon.get(f"/posts/{data['slug']}").status_code == 404
trash = admin.get("/admin/trash", headers=JSON).json()["items"]
assert any(item["uid"] == data["uid"] for item in trash)
def test_validation_error(admin):
response = admin.post("/admin/posts", json={"title": "x"}, headers=JSON)
assert response.status_code == 422
def test_html_form_post(admin):
response = admin.post("/admin/posts", data={"title": "Via formulier", "body": "Formuliertekst hier.", "topic": "rol", "status": "draft"})
assert response.status_code in (302, 303)
def test_member_cannot_write(member):
response = member.post("/admin/posts", json={"title": "Niet toegestaan", "body": "Tekst hier.", "topic": "rol"}, headers=JSON)
assert response.status_code == 403
+35
View File
@@ -0,0 +1,35 @@
# retoor <retoor@molodetz.nl>
from tests.conftest import ADMIN_PASSWORD, ADMIN_USERNAME
def test_login_wrong_password(anon):
response = anon.post("/auth/login", data={"username": ADMIN_USERNAME, "password": "verkeerd-wachtwoord"})
assert response.status_code in (400, 401, 200)
assert "session" not in "".join(response.cookies.keys()).lower() or response.status_code != 302
def test_login_and_logout(anon):
response = anon.post("/auth/login", data={"username": ADMIN_USERNAME, "password": ADMIN_PASSWORD})
assert response.status_code in (302, 303)
assert anon.get("/admin").status_code == 200
cookie = response.headers.get("set-cookie", "").lower()
assert "httponly" in cookie and "samesite=lax" in cookie
assert anon.post("/auth/logout").status_code in (302, 303)
assert anon.get("/admin").status_code in (302, 303, 401)
def test_admin_redirects_anonymous(anon):
response = anon.get("/admin")
assert response.status_code in (302, 303)
assert "/auth/login" in response.headers["location"]
def test_api_key_header(admin, anon):
key = admin.get("/profile/api-key", headers={"Accept": "application/json"}).json()["api_key"]
assert anon.get("/admin", headers={"X-API-KEY": key, "Accept": "application/json"}).status_code == 200
assert anon.get("/admin", headers={"Authorization": f"Bearer {key}", "Accept": "application/json"}).status_code == 200
def test_no_jwt_anywhere(admin):
response = admin.get("/profile/api-key", headers={"Accept": "application/json"})
assert response.json()["api_key"].count(".") == 0
+30
View File
@@ -0,0 +1,30 @@
# retoor <retoor@molodetz.nl>
import pytest
from molodetz.docs_api import all_endpoints
ENDPOINTS = [item for item in all_endpoints() if "logout" not in item["path"]]
JSON = {"Accept": "application/json"}
def _call(client, item):
path = item["probe_path"]
if item["method"] == "GET":
return client.get(path, headers=JSON)
return client.post(path, json=item["probe_body"] or {}, headers=JSON)
@pytest.mark.parametrize("item", ENDPOINTS, ids=lambda item: f"{item['method']} {item['path']}")
def test_documented_auth_matches_reality(item, anon, member, admin):
anon_status = _call(anon, item).status_code
member_status = _call(member, item).status_code
admin_status = _call(admin, item).status_code
if item["auth"] == "public":
assert anon_status not in (401, 403)
elif item["auth"] == "member":
assert anon_status in (401, 303, 302)
assert member_status not in (401, 403)
else:
assert anon_status in (401, 303, 302)
assert member_status == 403
assert admin_status not in (401, 403)
+35
View File
@@ -0,0 +1,35 @@
# retoor <retoor@molodetz.nl>
JSON = {"Accept": "application/json"}
def test_join_request_is_stored_and_visible_to_admin(anon, admin):
response = anon.post("/binnen", data={"name": "Schrijver Een", "contact": "schrijver@example.invalid", "repo_url": "https://example.com/repo", "message": "Ik wil meeschrijven.", "website": ""})
assert response.status_code in (302, 303)
assert "ok=1" in response.headers["location"]
rows = admin.get("/admin/joins", headers=JSON).json()["requests"]
row = next(row for row in rows if row["name"] == "Schrijver Een")
assert row["contact"] == "schrijver@example.invalid"
assert row["repo_url"].startswith("https://example.com/repo")
status = admin.post(f"/admin/joins/{row['uid']}/status", json={"status": "contacted"}, headers=JSON)
assert status.status_code == 200
def test_join_json_api(anon):
response = anon.post("/binnen", json={"name": "Schrijver Twee", "contact": "@twee"}, headers=JSON)
assert response.status_code == 200
assert response.json()["ok"] is True
def test_honeypot_is_not_stored(anon, admin):
anon.post("/binnen", data={"name": "Robot Bot", "contact": "bot@example.invalid", "website": "spam"})
rows = admin.get("/admin/joins", headers=JSON).json()["requests"]
assert not any(row["name"] == "Robot Bot" for row in rows)
def test_join_validation(anon):
response = anon.post("/binnen", json={"name": "x", "contact": ""}, headers=JSON)
assert response.status_code == 422
def test_member_cannot_view_joins(member):
assert member.get("/admin/joins", headers=JSON).status_code == 403
+93
View File
@@ -0,0 +1,93 @@
# retoor <retoor@molodetz.nl>
import re
import pytest
from defusedxml import ElementTree
PUBLIC_PAGES = ["/", "/rol", "/standaard", "/flyers", "/memes", "/mensen", "/mensen/retoor", "/binnen", "/docs", "/docs/welkom", "/docs/api/inhoud", "/docs/search?q=markdown", "/voorwaarden", "/privacy", "/auth/login"]
JSON_PAGES = ["/", "/rol", "/standaard", "/flyers", "/memes", "/mensen", "/binnen"]
@pytest.mark.parametrize("path", PUBLIC_PAGES)
def test_public_pages_return_200(anon, path):
response = anon.get(path)
assert response.status_code == 200, path
assert "text/html" in response.headers["content-type"]
@pytest.mark.parametrize("path", JSON_PAGES)
def test_json_faces(anon, path):
response = anon.get(path, headers={"Accept": "application/json"})
assert response.status_code == 200
assert response.headers["content-type"].startswith("application/json")
assert isinstance(response.json(), dict)
def test_home_is_molodetz(anon):
html = anon.get("/").text
assert "Molodetz" in html
for label in ("Rol", "Standaard", "Flyers", "Memes", "Mensen", "Binnen"):
assert f">{label}</a>" in html
assert "wa.me" not in html and "whatsapp" not in html.lower()
def test_no_bare_static_urls_in_rendered_html(anon):
for path in ("/", "/flyers", "/memes", "/rol"):
html = anon.get(path).text
assert not re.search(r"""(src|href)="/static/(?!v|uploads/)""", html), path
def test_versioned_static_asset(anon):
html = anon.get("/").text
match = re.search(r'href="(/static/v[^"]+/css/base\.css)"', html)
assert match
assert anon.get(match.group(1)).status_code == 200
def test_seeded_posts_and_placeholders(anon):
rol = anon.get("/rol", headers={"Accept": "application/json"}).json()["posts"]
seeds = {"Een zes is geen ambitie.", "Af is een eigenschap.", "De assistent is geen senior.", "Geen cursus."}
assert seeds <= {post["title"] for post in rol}
assert all(post["is_placeholder"] for post in rol if post["title"] in seeds)
standaard = anon.get("/standaard", headers={"Accept": "application/json"}).json()["posts"]
keuzes = next(post for post in standaard if post["title"] == "Eenendertig keuzes. Jij hebt er nul.")
assert keuzes["is_placeholder"] is False
def test_post_page(anon):
posts = anon.get("/rol", headers={"Accept": "application/json"}).json()["posts"]
post = next(post for post in posts if post["title"] == "Geen cursus.")
response = anon.get(f"/posts/{post['slug']}")
assert response.status_code == 200
assert "Placeholder" in response.text
def test_galleries_have_images(anon):
for path, minimum in (("/flyers", 4), ("/memes", 10)):
html = anon.get(path).text
assert html.count("data-lightbox") >= minimum, path
def test_mensen_lists_real_accounts_only(anon):
people = anon.get("/mensen", headers={"Accept": "application/json"}).json()
names = [person["username"] for person in people.get("people", [])]
assert names[0] == "retoor"
assert set(names) <= {"retoor", "lezer"}
def test_unknown_page_is_404(anon):
response = anon.get("/bestaat-niet-xyz")
assert response.status_code == 404
assert anon.get("/bestaat-niet-xyz", headers={"Accept": "application/json"}).json()["error"]
def test_robots_and_sitemap(anon):
assert "Sitemap:" in anon.get("/robots.txt").text
sitemap = anon.get("/sitemap.xml")
root = ElementTree.fromstring(sitemap.content)
assert root.tag.endswith("urlset")
assert len(list(root)) >= 5
def test_health(anon):
assert anon.get("/health").json()["status"] == "ok"