- XML escaping and namespaces, no priority, the image cap and file names - News window and cap, sources and languages, the publication name - IndexNow batches of 10 000, answer outcomes, keys and the host filter - HTML sitemap group order, tree order and paging - default slugs, the effective type setting, lastmod config, slot edges
127 lines
5.0 KiB
Go
127 lines
5.0 KiB
Go
package classes
|
|
|
|
import (
|
|
"bytes"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.golem15.com/golem15/sm-sitemap-plugin/models"
|
|
"git.golem15.com/golem15/summercms/modules/backpack"
|
|
"git.golem15.com/golem15/summercms/modules/compass"
|
|
)
|
|
|
|
// TestNewsLimits pins the News sitemap's window and cap (RESEARCH Pitfall
|
|
// 9): 48 hours and 1000 articles. The 47 h and 49 h boundaries on the
|
|
// database are TestNewsSitemap's.
|
|
func TestNewsLimits(t *testing.T) {
|
|
if NewsWindow != 48*time.Hour || MaxNewsEntries != 1000 || NewsFile != "sitemap-news.xml" {
|
|
t.Fatalf("window %s, cap %d, file %s", NewsWindow, MaxNewsEntries, NewsFile)
|
|
}
|
|
now := time.Date(2026, 11, 4, 12, 0, 0, 0, time.UTC)
|
|
for _, tc := range []struct {
|
|
age time.Duration
|
|
want bool
|
|
}{{0, true}, {47 * time.Hour, true}, {48 * time.Hour, true}, {48*time.Hour + time.Second, false}, {49 * time.Hour, false}} {
|
|
published := now.Add(-tc.age)
|
|
in := !published.Before(now.Add(-NewsWindow)) && !published.After(now)
|
|
if in != tc.want {
|
|
t.Errorf("published %s before now: in window %v, want %v", tc.age, in, tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestParseNewsSources pins the news_sources values: type:<key> and
|
|
// category:<type>:<id> (the last colon splits, so a type key may hold
|
|
// one); other forms and empty parts are ignored.
|
|
func TestParseNewsSources(t *testing.T) {
|
|
s := parseNewsSources([]string{"type:blog-post", "category:blog-category:7", "category:a:b:9", "type:", "category:x", "category::1", "category:x:", "other:1", ""})
|
|
if len(s.types) != 1 || !s.types["blog-post"] {
|
|
t.Fatalf("types = %v", s.types)
|
|
}
|
|
if len(s.parents) != 2 || !s.parents[models.Ref{Type: "blog-category", ID: "7"}] || !s.parents[models.Ref{Type: "a:b", ID: "9"}] {
|
|
t.Fatalf("parents = %v", s.parents)
|
|
}
|
|
if s.empty() || !parseNewsSources(nil).empty() || !parseNewsSources([]string{"bogus"}).empty() {
|
|
t.Fatal("empty() is wrong")
|
|
}
|
|
}
|
|
|
|
// TestNewsSourcesMatch pins P33-15: no source lists every article; else an
|
|
// article is listed by its type or by one of its parents.
|
|
func TestNewsSourcesMatch(t *testing.T) {
|
|
cat := models.Ref{Type: "blog-category", ID: "7"}
|
|
other := models.Ref{Type: "blog-category", ID: "8"}
|
|
all := parseNewsSources(nil)
|
|
byType := parseNewsSources([]string{"type:news"})
|
|
byCat := parseNewsSources([]string{"category:blog-category:7"})
|
|
for _, tc := range []struct {
|
|
name string
|
|
s newsSources
|
|
typ string
|
|
parents []models.Ref
|
|
want bool
|
|
}{
|
|
{"none listed", all, "blog-post", nil, true},
|
|
{"type listed", byType, "news", nil, true},
|
|
{"other type", byType, "blog-post", []models.Ref{cat}, false},
|
|
{"parent listed", byCat, "blog-post", []models.Ref{other, cat}, true},
|
|
{"other parent", byCat, "blog-post", []models.Ref{other}, false},
|
|
{"no parents", byCat, "blog-post", nil, false},
|
|
} {
|
|
if got := tc.s.matches(tc.typ, tc.parents); got != tc.want {
|
|
t.Errorf("%s: matches = %v, want %v", tc.name, got, tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestNewsLanguage pins the language codes Google reads.
|
|
func TestNewsLanguage(t *testing.T) {
|
|
for in, want := range map[string]string{
|
|
"pl": "pl", "pl-PL": "pl", "pl_PL": "pl", " EN ": "en", "en-GB": "en",
|
|
"zh": "zh-cn", "zh-Hans": "zh-cn", "zh-CN": "zh-cn", "zh-TW": "zh-tw", "zh_tw": "zh-tw", "zh-Hant": "zh-tw", "zh-Hant-HK": "zh-tw",
|
|
"": "",
|
|
} {
|
|
if got := NewsLanguage(in); got != want {
|
|
t.Errorf("NewsLanguage(%q) = %q, want %q", in, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestNewsPublication pins the publication name: the setting, else
|
|
// app.name, else nothing.
|
|
func TestNewsPublication(t *testing.T) {
|
|
cfg, err := compass.Open(compass.Options{Dir: t.TempDir(), Environ: []string{"SUMMER_APP__NAME=Acme Daily"}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
app := backpack.New(cfg)
|
|
named := " The Acme Times "
|
|
if got := NewsPublication(app, models.Settings{NewsPublication: &named}); got != "The Acme Times" {
|
|
t.Fatalf("with the setting: %q", got)
|
|
}
|
|
blank := " "
|
|
if got := NewsPublication(app, models.Settings{NewsPublication: &blank}); got != "Acme Daily" {
|
|
t.Fatalf("with a blank setting: %q, want app.name", got)
|
|
}
|
|
if got := NewsPublication(nil, models.Settings{}); got != "" {
|
|
t.Fatalf("without an app: %q", got)
|
|
}
|
|
}
|
|
|
|
// TestWriteNewsURLSet pins the News file: the news namespace, every
|
|
// element in order, escaping and the UTC publication date.
|
|
func TestWriteNewsURLSet(t *testing.T) {
|
|
at := time.Date(2026, 11, 4, 9, 0, 0, 0, time.FixedZone("CET", 3600))
|
|
var buf bytes.Buffer
|
|
if err := WriteNewsURLSet(&buf, []NewsEntry{{Loc: "https://example.test/a?b&c", Publication: "A & B", Language: "pl", Published: at, Title: "<Grzyby>"}}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
out := buf.String()
|
|
want := `<url><loc>https://example.test/a?b&c</loc><news:news><news:publication><news:name>A & B</news:name><news:language>pl</news:language></news:publication><news:publication_date>2026-11-04T08:00:00Z</news:publication_date><news:title><Grzyby></news:title></news:news></url>`
|
|
if !strings.Contains(out, `xmlns:news="`+NSNews+`"`) || !strings.Contains(out, want) {
|
|
t.Fatalf("news file:\n%s", out)
|
|
}
|
|
wellFormed(t, out)
|
|
}
|