added pytest test suite (174 tests)

174 tests covering URL normalization, FTS5 query sanitization, SSRF/CSRF
guards, sharing-mode logic, DB schema and upsert paths, handler
end-to-end flows, and gateway body-size / mesh-whitelist guards. Each
recent bug-fix commit (6ffd38d, 1bc695f, 8dffd8c) has an explicit
regression test in test_regressions.py. One xfail documents a minor
latent bug in clean_url where port 80 is not stripped from upgraded
https URLs.
This commit is contained in:
lichenblankie 2026-04-24 15:03:29 -07:00
parent 55c6619ba3
commit 4d522ce62c
18 changed files with 1673 additions and 0 deletions

View file

@ -0,0 +1,174 @@
"""Tests for `handle_bulk_action`, edit flow, and the bulk-delete confirm step.
The bulk-delete confirmation flow is a data-loss guard added in commit
8dffd8c a stray POST without `confirmed=1` must render the confirmation
page instead of actually deleting.
"""
from db import get_db, return_db
from handlers import (
handle_bulk_action,
handle_edit_form,
handle_edit_submit,
handle_pages,
)
def _all_urls(seeded_db):
db = get_db()
try:
return {r["url"] for r in db.execute("SELECT url FROM pages").fetchall()}
finally:
return_db(db)
def _page_id(seeded_db, url):
db = get_db()
try:
return db.execute("SELECT id FROM pages WHERE url = ?", (url,)).fetchone()["id"]
finally:
return_db(db)
def test_bulk_delete_without_confirmed_renders_confirm_page(seeded_db, csrf_session):
"""Regression for 8dffd8c: bulk delete must NOT delete until confirmed=1 is set."""
pid = _page_id(seeded_db, "https://example.com/rust-intro")
urls_before = _all_urls(seeded_db)
resp = handle_bulk_action({
"ids": [str(pid)],
"action": ["delete"],
})
assert resp["status"] == 200
assert "confirm delete" in resp["body"].lower()
assert "Rust Intro" in resp["body"]
# Must still show a hidden confirmed=1 field in the follow-up form.
assert 'name="confirmed" value="1"' in resp["body"]
# Crucially: nothing should have been deleted.
assert _all_urls(seeded_db) == urls_before
def test_bulk_delete_with_confirmed_actually_deletes(seeded_db, csrf_session):
pid = _page_id(seeded_db, "https://example.com/rust-intro")
resp = handle_bulk_action({
"ids": [str(pid)],
"action": ["delete"],
"confirmed": ["1"],
})
# Confirmed delete redirects back to /pages.
assert resp["status"] in (302, 303)
urls = _all_urls(seeded_db)
assert "https://example.com/rust-intro" not in urls
# Other pages untouched.
assert "https://example.com/python-tips" in urls
def test_bulk_delete_with_no_ids_redirects(seeded_db, csrf_session):
resp = handle_bulk_action({
"ids": [],
"action": ["delete"],
"confirmed": ["1"],
})
assert resp["status"] in (302, 303)
assert _all_urls(seeded_db) == {
"https://example.com/rust-intro",
"https://example.com/python-tips",
"https://example.com/ocaml-why",
"https://news.example.org/mesh",
}
def test_bulk_delete_rejects_non_integer_ids(seeded_db, csrf_session):
resp = handle_bulk_action({
"ids": ["not-a-number"],
"action": ["delete"],
"confirmed": ["1"],
})
assert resp["status"] == 400
def test_bulk_retag_add_mode_merges_tags(seeded_db, csrf_session):
pid = _page_id(seeded_db, "https://example.com/python-tips")
handle_bulk_action({
"ids": [str(pid)],
"action": ["retag"],
"bulk_tags": ["scripting, tutorials"],
"tag_mode": ["add"],
})
db = get_db()
try:
rows = db.execute(
"SELECT t.name FROM tags t JOIN page_tags pt ON pt.tag_id = t.id "
"WHERE pt.page_id = ? ORDER BY t.name",
(pid,),
).fetchall()
finally:
return_db(db)
tags = [r["name"] for r in rows]
assert "python" in tags # existing kept
assert "scripting" in tags # new added
assert "tutorials" in tags
def test_bulk_retag_replace_mode_overwrites_tags(seeded_db, csrf_session):
pid = _page_id(seeded_db, "https://example.com/python-tips")
handle_bulk_action({
"ids": [str(pid)],
"action": ["retag"],
"bulk_tags": ["one, two"],
"tag_mode": ["replace"],
})
db = get_db()
try:
rows = db.execute(
"SELECT t.name FROM tags t JOIN page_tags pt ON pt.tag_id = t.id "
"WHERE pt.page_id = ?",
(pid,),
).fetchall()
finally:
return_db(db)
tags = {r["name"] for r in rows}
assert tags == {"one", "two"}
assert "python" not in tags
def test_edit_form_renders_current_values(seeded_db, csrf_session):
pid = _page_id(seeded_db, "https://example.com/rust-intro")
resp = handle_edit_form(pid)
assert resp["status"] == 200
assert "Rust Intro" in resp["body"]
# Existing tags should appear in the tag field.
assert "rust" in resp["body"]
def test_edit_form_404_for_unknown_page(temp_db, csrf_session):
resp = handle_edit_form(99999)
assert resp["status"] == 404
def test_edit_submit_updates_title_and_note(seeded_db, csrf_session):
pid = _page_id(seeded_db, "https://example.com/rust-intro")
handle_edit_submit(pid, {
"title": ["New Rust Title"],
"note": ["new annotation"],
"tags": ["rust, updated"],
})
db = get_db()
try:
row = db.execute("SELECT title, note FROM pages WHERE id = ?", (pid,)).fetchone()
finally:
return_db(db)
assert row["title"] == "New Rust Title"
assert row["note"] == "new annotation"
def test_handle_pages_lists_indexed_pages(seeded_db, csrf_session):
resp = handle_pages({})
assert resp["status"] == 200
# Every seeded page title appears on the list page.
for title in ("Rust Intro", "Python Tips", "Why OCaml", "Mesh Networking"):
assert title in resp["body"]