munin/internal/entry/entry_test.go
2026-09-15 20:36:26 +02:00

163 lines
5.5 KiB
Go

package entry
import (
"strings"
"testing"
)
// TestRenderHTMLEscapesRawHTML ist ein Sicherheitstest.
//
// Das gerenderte Ergebnis wird in der Weboberfläche ohne weiteres Escaping
// eingesetzt. Der Schutz davor, dass jemand über einen Eintrag ein <script>
// einschleust, hängt damit allein daran, dass goldmark kein Roh-HTML durchreicht.
// Wird diese Einstellung je gelockert, schlägt dieser Test an.
func TestRenderHTMLEscapesRawHTML(t *testing.T) {
boesartig := []string{
"<script>alert(1)</script>",
"<img src=x onerror=alert(1)>",
"<iframe src=\"https://fremd.example\"></iframe>",
}
for _, eingabe := range boesartig {
html, err := RenderHTML(eingabe, "/p/test/e/")
if err != nil {
t.Fatalf("RenderHTML(%q): %v", eingabe, err)
}
if strings.Contains(html, "<script") || strings.Contains(html, "onerror") || strings.Contains(html, "<iframe") {
t.Errorf("Roh-HTML kam durch: %q → %q", eingabe, html)
}
}
}
// TestRenderHTMLResolvesLinks prüft, dass [[bezeichner]] zu einem Link wird.
func TestRenderHTMLResolvesLinks(t *testing.T) {
html, err := RenderHTML("Siehe [[server-zugang]] für Details.", "/p/test/e/")
if err != nil {
t.Fatal(err)
}
if !strings.Contains(html, `href="/p/test/e/server-zugang"`) {
t.Errorf("der Verweis wurde nicht aufgelöst: %s", html)
}
}
// TestRenderHTMLKeepsMarkdown prüft, dass gewöhnliches Markdown funktioniert.
func TestRenderHTMLKeepsMarkdown(t *testing.T) {
html, err := RenderHTML("# Überschrift\n\n- Punkt eins\n- Punkt zwei\n\n`code`", "/p/test/e/")
if err != nil {
t.Fatal(err)
}
for _, erwartet := range []string{"<h1", "<ul>", "<li>", "<code>"} {
if !strings.Contains(html, erwartet) {
t.Errorf("%s fehlt im Ergebnis: %s", erwartet, html)
}
}
}
func TestRenderPlainStripsLinkSyntax(t *testing.T) {
got := RenderPlain("Siehe [[server-zugang]] für Details.")
if strings.Contains(got, "[[") {
t.Errorf("die Verweis-Klammern blieben stehen: %q", got)
}
if !strings.Contains(got, "server-zugang") {
t.Errorf("der Bezeichner ging verloren: %q", got)
}
}
// ── Textausschnitte ──────────────────────────────────────────────────────────
func TestSnippetAroundMatch(t *testing.T) {
text := strings.Repeat("Vorlauf. ", 40) + "GESUCHT " + strings.Repeat("Nachlauf. ", 40)
got := Snippet(text, "GESUCHT")
if !strings.Contains(got, "GESUCHT") {
t.Error("der Treffer fehlt im Ausschnitt")
}
// Der Ausschnitt soll kurz sein — er dient dem Einordnen, nicht dem Lesen.
if len(got) > 400 {
t.Errorf("Ausschnitt ist %d Zeichen lang, das ist zu viel", len(got))
}
// Vorne und hinten wurde gekürzt, das muss sichtbar sein.
if !strings.HasPrefix(got, "…") || !strings.HasSuffix(got, "…") {
t.Errorf("die Auslassungszeichen fehlen: %q", got)
}
}
func TestSnippetWithoutMatch(t *testing.T) {
// Kein wörtlicher Treffer — etwa weil der Fund über die Präfix-Suche kam.
got := Snippet("Ein kurzer Text ohne den Suchbegriff.", "deploy")
if got == "" {
t.Error("ohne Treffer kam gar kein Ausschnitt zurück")
}
if !strings.Contains(got, "kurzer Text") {
t.Errorf("es wurde nicht der Textanfang genommen: %q", got)
}
}
func TestSnippetShortText(t *testing.T) {
got := Snippet("Kurz.", "Kurz")
if got != "Kurz." {
t.Errorf("Snippet = %q, erwartet %q", got, "Kurz.")
}
}
// TestHighlightSnippetEscapesFirst ist ein Sicherheitstest: Der Inhalt muss
// escaped werden, BEVOR die Hervorhebung eingesetzt wird. Andersherum könnte ein
// Eintrag eigenes HTML in die Trefferliste einschleusen.
func TestHighlightSnippetEscapesFirst(t *testing.T) {
got := HighlightSnippet("Ein <script>alert(1)</script> im Text", "script")
if strings.Contains(got, "<script>") {
t.Errorf("unescaptes HTML im Ausschnitt: %q", got)
}
// Die spitzen Klammern des Inhalts müssen escaped sein. Geprüft wird auf
// "&lt;" statt auf "&lt;script&gt;", weil die Hervorhebung mitten im
// escapten Text steht: "&lt;<mark>script</mark>&gt;".
if !strings.Contains(got, "&lt;") || !strings.Contains(got, "&gt;") {
t.Errorf("der Inhalt wurde nicht escaped: %q", got)
}
// Die Hervorhebung selbst muss echtes HTML sein.
if !strings.Contains(got, "<mark>") {
t.Errorf("der Treffer wurde nicht hervorgehoben: %q", got)
}
}
func TestHighlightSnippetWithoutQuery(t *testing.T) {
got := HighlightSnippet("Text mit <Klammern>", "")
if strings.Contains(got, "<mark>") {
t.Error("ohne Suchbegriff wurde etwas hervorgehoben")
}
if strings.Contains(got, "<Klammern>") {
t.Errorf("der Inhalt wurde nicht escaped: %q", got)
}
}
// ── Titel raten ──────────────────────────────────────────────────────────────
func TestTitleFromContent(t *testing.T) {
cases := map[string]string{
"# Deployment\n\nText": "Deployment",
"## Unterüberschrift\nX": "Unterüberschrift",
"\n\nErste echte Zeile\nX": "Erste echte Zeile",
"": "Ohne Titel",
" \n \n": "Ohne Titel",
}
for eingabe, erwartet := range cases {
if got := TitleFromContent(eingabe); got != erwartet {
t.Errorf("TitleFromContent(%q) = %q, erwartet %q", eingabe, got, erwartet)
}
}
}
func TestTitleFromContentIsShortened(t *testing.T) {
lang := strings.Repeat("Wort ", 100)
got := TitleFromContent(lang)
if len(got) > 130 {
t.Errorf("der Titel ist %d Zeichen lang — er wurde nicht gekürzt", len(got))
}
}