munin/internal/markdown/markdown_test.go
Thomas Adamek 466f1e40af Workspaces, Änderungshistorie, Papierkorb, Nutzungsübersicht, Hilfe
Umsetzung der Ausbaustufen 2.1, 3.6, 3.8, 3.9, 4.1, 4.2, 4.4, 4.6,
5.1–5.4 (Details in AUSBAUSTUFEN.md).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 07:44:52 +02:00

90 lines
3 KiB
Go

package markdown
import (
"strings"
"testing"
)
// TestGFMErweiterungen: Tabellen, Durchstreichen, Checklisten und automatische
// Links funktionieren — in Einträgen genauso wie in der Hilfe (AUSBAUSTUFEN.md 5.3).
func TestGFMErweiterungen(t *testing.T) {
faelle := []struct {
name, quelle, erwartet string
}{
{"Tabelle", "| a | b |\n|---|---|\n| 1 | 2 |", "<table>"},
{"Durchstreichen", "~~veraltet~~", "<del>veraltet</del>"},
{"Checkliste offen", "- [ ] offen", `<input disabled="" type="checkbox">`},
{"Checkliste erledigt", "- [x] erledigt", `<input checked="" disabled="" type="checkbox">`},
{"automatischer Link", "siehe https://example.org", `<a href="https://example.org">`},
}
// Beide Renderer prüfen: Sie sollen sich nur in den Ankern unterscheiden.
renderer := map[string]func(string) (string, error){
"Render": Render,
"RenderMitAnkern": RenderMitAnkern,
}
for rName, render := range renderer {
for _, f := range faelle {
html, err := render(f.quelle)
if err != nil {
t.Fatalf("%s/%s: %v", rName, f.name, err)
}
if !strings.Contains(html, f.erwartet) {
t.Errorf("%s/%s: %q fehlt in %q", rName, f.name, f.erwartet, html)
}
}
}
}
// TestKeinRohHTML ist der wichtigste Test dieses Pakets: Die Weboberfläche setzt
// das Ergebnis ohne weiteres Escaping in die Seite. Das geht nur, solange hier
// weder Skripte noch gefährliche Link-Ziele durchkommen — auch nicht mit den
// GFM-Erweiterungen.
func TestKeinRohHTML(t *testing.T) {
boese := []string{
"<script>alert(1)</script>",
`<img src=x onerror="alert(1)">`,
"[klick](javascript:alert(1))",
"| a |\n|---|\n| <script>alert(1)</script> |",
"- [ ] <script>alert(1)</script>",
}
for _, quelle := range boese {
for _, render := range []func(string) (string, error){Render, RenderMitAnkern} {
html, _ := render(quelle)
if strings.Contains(html, "<script") || strings.Contains(html, "onerror") || strings.Contains(html, "javascript:") {
t.Errorf("gefährlicher Inhalt durchgelassen: %q → %q", quelle, html)
}
}
}
}
// TestAnker: Nur die Hilfe bekommt ids an den Überschriften, mit gefalteten
// Umlauten und eindeutig je Seite.
func TestAnker(t *testing.T) {
quelle := "## Zugänge verwalten\n\n## Protokoll\n\n## Protokoll\n\n## ???"
html, err := RenderMitAnkern(quelle)
if err != nil {
t.Fatal(err)
}
for _, id := range []string{`id="zugaenge-verwalten"`, `id="protokoll"`, `id="protokoll-1"`, `id="abschnitt"`} {
if !strings.Contains(html, id) {
t.Errorf("%s fehlt in %q", id, html)
}
}
// Ein zweiter Aufruf fängt wieder von vorn an — die vergebenen ids der
// ersten Seite dürfen nicht hinüberwirken.
nochmal, _ := RenderMitAnkern("## Protokoll")
if !strings.Contains(nochmal, `id="protokoll"`) {
t.Errorf("zweite Seite bekommt nicht die einfache id: %q", nochmal)
}
// Einträge: keine ids, damit Eintragstext nicht mit ids der Seite
// kollidieren kann.
eintrag, _ := Render(quelle)
if strings.Contains(eintrag, "id=") {
t.Errorf("Eintrag hat Überschrift-ids bekommen: %q", eintrag)
}
}