Files
pablan/backend/tests/test_help_import.py
ProfessorNovaandClaude Opus 5 784b76baf7 Pablan, as it stands
Self-hosted knowledge management for SMEs: a split-screen Markdown editor
whose sections an LLM refines while you write, and RAG question answering
over the documents that result. FastAPI + Postgres/pgvector on the back,
SvelteKit on the front, everything OpenAI-compatible and self-hostable.

Squashed into a single commit; the development history stays local.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CA43ZJda8Rbp2hKXNy8f6b
2026-09-04 09:21:37 +02:00

115 lines
3.4 KiB
Python

"""Built-in help documents: imported from files, never editable in the UI."""
import pytest
from httpx import AsyncClient
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.help_import import (
HelpImportError,
import_help_documents,
parse_help_document,
)
from app.models import Document, Job, User
pytestmark = pytest.mark.usefixtures("fake_embed")
HELP_FILE = """---
key: test-hilfe
title: "Testhilfe"
---
# Testhilfe
So funktioniert es.
"""
def test_parse_splits_frontmatter_from_body() -> None:
key, title, body = parse_help_document(HELP_FILE)
assert (key, title) == ("test-hilfe", "Testhilfe")
assert body.startswith("# Testhilfe")
def test_parse_rejects_a_file_without_frontmatter() -> None:
with pytest.raises(HelpImportError):
parse_help_document("# Just markdown")
async def _import(db: AsyncSession, tmp_path, monkeypatch, text: str) -> int:
(tmp_path / "hilfe.md").write_text(text)
monkeypatch.setenv("PABLAN_HELP_DIR", str(tmp_path))
from app.config import get_settings
get_settings.cache_clear()
try:
return await import_help_documents(db)
finally:
get_settings.cache_clear()
async def test_import_is_idempotent_and_reindexes_only_on_change(
db: AsyncSession, tmp_path, monkeypatch: pytest.MonkeyPatch
) -> None:
assert await _import(db, tmp_path, monkeypatch, HELP_FILE) == 1
document = (
await db.execute(select(Document).where(Document.is_builtin.is_(True)))
).scalar_one()
assert document.title == "Testhilfe"
assert document.status.value == "published"
assert document.visibility.value == "public"
assert document.author_id is None
# Unchanged: no second document, no new index job.
assert await _import(db, tmp_path, monkeypatch, HELP_FILE) == 0
# Changed: content is refreshed in place and re-indexed.
assert (
await _import(
db, tmp_path, monkeypatch, HELP_FILE.replace("So funktioniert es.", "Neu.")
)
== 1
)
documents = (
(await db.execute(select(Document).where(Document.is_builtin.is_(True))))
.scalars()
.all()
)
assert len(documents) == 1
assert "Neu." in documents[0].content_md
jobs = (await db.execute(select(Job))).scalars().all()
assert len(jobs) == 2 # one per actual change
async def test_help_documents_cannot_be_edited_or_deleted(
client: AsyncClient,
db: AsyncSession,
seeded_admin: User,
tmp_path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Not even an admin may edit them — the next deploy would overwrite it."""
await _import(db, tmp_path, monkeypatch, HELP_FILE)
document = (
await db.execute(select(Document).where(Document.is_builtin.is_(True)))
).scalar_one()
login = await client.post(
"/api/auth/login", json={"email": "florian@test.dev", "password": "secret123"}
)
assert login.status_code == 200
listed = (await client.get("/api/documents")).json()["items"]
entry = next(row for row in listed if row["id"] == str(document.id))
assert entry["is_builtin"] is True
assert entry["can_edit"] is False
patched = await client.patch(
f"/api/documents/{document.id}", json={"title": "Gekapert"}
)
assert patched.status_code == 409
assert patched.json()["code"] == "builtin_readonly"
deleted = await client.delete(f"/api/documents/{document.id}")
assert deleted.status_code == 409