Files
sm-faq-plugin/controllers/api/faqs_test.go
Jakub Zych 919cf06eca test(17-06): FAQ plugin unit tests
- TestFaqContentDecode, TestFaqModel and TestFaqSerialize (Docker-free):
  content as list, JSON text or row-keyed object, required export
  fields, the model contract, output sanitizing, null description,
  question text and the slug shape
- TestFaqAPI and TestFaqImport (Postgres): active duplicate behind an
  inactive lower id, default locale per is_default, a disabled default,
  malformed slugs, keyed content order, malformed rows aborting before
  any write
2026-10-12 19:42:42 +02:00

56 lines
2.2 KiB
Go

package api
import (
"bytes"
"encoding/json"
"strings"
"testing"
"git.golem15.com/golem15/sm-faq-plugin/models"
"git.golem15.com/golem15/summercms/modules/lagoon"
)
// TestFaqSerialize pins the D-17 response object without a database: the
// description and every answer pass through the sanitizer on output, clean
// HTML byte for byte; a null description stays null; questions are plain
// text (numbers formatted, null empty); content is never null; and the
// slug pattern the handler accepts.
func TestFaqSerialize(t *testing.T) {
desc := `<p>About.</p><img src="/x.png" onerror="alert(1)">`
faq := models.FAQ{Name: "Shipping", Slug: "shipping", Description: &desc, Content: lagoon.Jsonable[models.Content]{Valid: true, Data: models.Content{
{"question": "How long?", "answer": "<p>Two <strong>days</strong> &amp; more.</p>"},
{"question": 42.0, "answer": `<p onclick="x()">Here</p><script>alert(1)</script>`},
{"question": nil, "answer": nil},
}}}
out := serialize(faq, "en")
var buf bytes.Buffer
enc := json.NewEncoder(&buf)
enc.SetEscapeHTML(false)
if err := enc.Encode(out); err != nil {
t.Fatal(err)
}
raw := strings.TrimSuffix(buf.String(), "\n")
want := `{"name":"Shipping","slug":"shipping","locale":"en","description":"<p>About.</p><img src=\"/x.png\">","content":[` +
`{"question":"How long?","answer":"<p>Two <strong>days</strong> &amp; more.</p>"},` +
`{"question":"42","answer":"<p>Here</p>"},` +
`{"question":"","answer":""}]}`
if raw != want {
t.Fatalf("serialized:\n%s\nwant:\n%s", raw, want)
}
if strings.Contains(raw, "onerror") || strings.Contains(raw, "onclick") || strings.Contains(raw, "<script") {
t.Fatal("unsafe HTML served")
}
bare := serialize(models.FAQ{Name: "Empty", Slug: "empty"}, "pl")
if bare.Description != nil || bare.Content == nil || len(bare.Content) != 0 || bare.Locale != "pl" {
t.Fatalf("bare = %+v", bare)
}
for slug, ok := range map[string]bool{
"shipping": true, "a": true, "faq-2": true, "2024-faq": true,
"Shipping": false, "-faq": false, "faq_2": false, "faq.json": false, "zażółć": false, "": false, "a b": false,
} {
if slugPattern.MatchString(slug) != ok {
t.Errorf("slug %q accepted = %v", slug, !ok)
}
}
}