173 lines
4.8 KiB
Go
173 lines
4.8 KiB
Go
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|||
|
|
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
|
||
|
|
|
||
|
|
package payloads
|
||
|
|
|
||
|
|
import (
|
||
|
|
"encoding/json"
|
||
|
|
"strings"
|
||
|
|
"testing"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"sourcedock.dev/petrbalvin/interpres/v2"
|
||
|
|
"sourcedock.dev/petrbalvin/volumen/internal/config"
|
||
|
|
"sourcedock.dev/petrbalvin/volumen/internal/frontmatter"
|
||
|
|
"sourcedock.dev/petrbalvin/volumen/internal/post"
|
||
|
|
)
|
||
|
|
|
||
|
|
const dtoSample = `+++
|
||
|
|
title = "Ahoj světe"
|
||
|
|
slug = "ahoj-svete"
|
||
|
|
lang = "cs"
|
||
|
|
author = "Petr"
|
||
|
|
tags = ["go", "blog"]
|
||
|
|
date = 2026-08-18
|
||
|
|
series = "Série"
|
||
|
|
series_order = 2
|
||
|
|
cover = "/media/cover.webp"
|
||
|
|
cover_alt = "alt"
|
||
|
|
cover_caption = "caption"
|
||
|
|
fediverse_creator = "@petr@mastodon.social"
|
||
|
|
|
||
|
|
[translations]
|
||
|
|
en = "hello-world"
|
||
|
|
+++
|
||
|
|
|
||
|
|
# Hlavička
|
||
|
|
|
||
|
|
Tělo.
|
||
|
|
`
|
||
|
|
|
||
|
|
func TestBuildSummary(t *testing.T) {
|
||
|
|
s := BuildSummary(parsePost(t, dtoSample))
|
||
|
|
if s.Slug != "ahoj-svete" {
|
||
|
|
t.Fatalf("slug = %q", s.Slug)
|
||
|
|
}
|
||
|
|
if s.URL != "/api/volumen/posts/ahoj-svete" {
|
||
|
|
t.Fatalf("url = %q", s.URL)
|
||
|
|
}
|
||
|
|
if s.ReadingTime != 1 {
|
||
|
|
t.Fatalf("reading_time = %d", s.ReadingTime)
|
||
|
|
}
|
||
|
|
if s.SeriesOrder == nil || *s.SeriesOrder != 2 {
|
||
|
|
t.Fatalf("series_order = %v", s.SeriesOrder)
|
||
|
|
}
|
||
|
|
if len(s.Tags) != 2 || s.Tags[0] != "go" {
|
||
|
|
t.Fatalf("tags = %v", s.Tags)
|
||
|
|
}
|
||
|
|
if s.Translations["en"] != "hello-world" {
|
||
|
|
t.Fatalf("translations = %v", s.Translations)
|
||
|
|
}
|
||
|
|
if s.CoverAlt != "alt" {
|
||
|
|
t.Fatalf("cover_alt = %q", s.CoverAlt)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestBuildSummaryEmptyPostOmitsOptionals(t *testing.T) {
|
||
|
|
s := BuildSummary(post.New(nil, "body"))
|
||
|
|
if s.Slug != "" || s.Title != "" || s.Date != "" || s.Series != "" {
|
||
|
|
t.Fatalf("optional fields should be empty: %+v", s)
|
||
|
|
}
|
||
|
|
if len(s.Tags) != 0 || len(s.Translations) != 0 {
|
||
|
|
t.Fatalf("collections should be empty: %+v", s)
|
||
|
|
}
|
||
|
|
if s.SeriesOrder != nil {
|
||
|
|
t.Fatalf("series_order = %v, want nil", s.SeriesOrder)
|
||
|
|
}
|
||
|
|
// Marshal and confirm the empty optionals are omitted entirely.
|
||
|
|
raw, err := json.Marshal(s)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("marshal: %v", err)
|
||
|
|
}
|
||
|
|
for _, key := range []string{`"slug"`, `"title"`, `"date"`, `"tags"`, `"translations"`, `"series_order"`, `"cover"`} {
|
||
|
|
if strings.Contains(string(raw), key) {
|
||
|
|
t.Fatalf("%s should be omitted: %s", key, raw)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if !strings.Contains(string(raw), `"excerpt":"body"`) {
|
||
|
|
t.Fatalf("excerpt missing: %s", raw)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestBuildDetail(t *testing.T) {
|
||
|
|
d, err := BuildDetail(parsePost(t, dtoSample), "https://example.com/")
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("BuildDetail: %v", err)
|
||
|
|
}
|
||
|
|
if d.HTML == "" || d.TOC == "" || d.Body == "" {
|
||
|
|
t.Fatalf("detail = %+v", d)
|
||
|
|
}
|
||
|
|
if d.Meta.URL != "https://example.com/ahoj-svete" {
|
||
|
|
t.Fatalf("meta url = %q", d.Meta.URL)
|
||
|
|
}
|
||
|
|
for _, want := range []string{
|
||
|
|
`"@context":"https://schema.org"`, `"@type":"Article"`,
|
||
|
|
`"headline":"Ahoj světe"`, `"inLanguage":"cs"`, `"datePublished":"2026-08-18"`,
|
||
|
|
} {
|
||
|
|
if !strings.Contains(d.Meta.JSONLD, want) {
|
||
|
|
t.Fatalf("json_ld missing %s: %s", want, d.Meta.JSONLD)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if d.Meta.OG["og:type"] != "article" || d.Meta.OG["og:image"] != "/media/cover.webp" {
|
||
|
|
t.Fatalf("og = %v", d.Meta.OG)
|
||
|
|
}
|
||
|
|
if d.Meta.Twitter["twitter:card"] != "summary_large_image" {
|
||
|
|
t.Fatalf("twitter = %v", d.Meta.Twitter)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestBuildMetaDescriptionFallback(t *testing.T) {
|
||
|
|
p := post.New(nil, "Body without an excerpt and with enough words to fill the description.")
|
||
|
|
meta, err := BuildMeta(p, "")
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("BuildMeta: %v", err)
|
||
|
|
}
|
||
|
|
if meta.OG["og:description"] == "" || meta.OG["og:description"] == nil {
|
||
|
|
t.Fatal("og:description empty")
|
||
|
|
}
|
||
|
|
if meta.URL != "/api/volumen/posts/" {
|
||
|
|
t.Fatalf("url = %q", meta.URL)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestBuildSiteShape(t *testing.T) {
|
||
|
|
cfg, err := config.Load(t.TempDir()+"/none.toml", config.Overrides{Port: -1})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("config: %v", err)
|
||
|
|
}
|
||
|
|
site := BuildSite(cfg)
|
||
|
|
if site.Title != config.DefaultSiteTitle || site.Language != "en" {
|
||
|
|
t.Fatalf("site = %+v", site)
|
||
|
|
}
|
||
|
|
if site.FediverseCreator != "" {
|
||
|
|
t.Fatalf("fediverse_creator = %q, want empty", site.FediverseCreator)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestPlainTextTruncation(t *testing.T) {
|
||
|
|
long := "<p>" + strings.Repeat("word ", 100) + "</p>"
|
||
|
|
plain := post.PlainText(long, 20)
|
||
|
|
// Trailing spaces are trimmed before the ellipsis, so the result is
|
||
|
|
// at most limit+1 runes long.
|
||
|
|
if len([]rune(plain)) > 21 || !strings.HasSuffix(plain, "…") {
|
||
|
|
t.Fatalf("plain = %q (%d runes)", plain, len([]rune(plain)))
|
||
|
|
}
|
||
|
|
if plain := post.PlainText("<b>short</b>", 200); plain != "short" {
|
||
|
|
t.Fatalf("plain = %q", plain)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestSeriesOrderPointer(t *testing.T) {
|
||
|
|
meta := frontmatter.NewMeta()
|
||
|
|
meta.Set("series_order", int64(3))
|
||
|
|
s := BuildSummary(post.New(meta, ""))
|
||
|
|
if s.SeriesOrder == nil || *s.SeriesOrder != 3 {
|
||
|
|
t.Fatalf("series_order = %v", s.SeriesOrder)
|
||
|
|
}
|
||
|
|
meta2 := frontmatter.NewMeta()
|
||
|
|
meta2.Set("date", interpres.LocalDate{Time: time.Date(2026, 1, 2, 0, 0, 0, 0, time.UTC)})
|
||
|
|
if got := BuildSummary(post.New(meta2, "")).Date; got != "2026-01-02" {
|
||
|
|
t.Fatalf("date = %q", got)
|
||
|
|
}
|
||
|
|
}
|