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: and // category:: (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: ""}}); err != nil { t.Fatal(err) } out := buf.String() want := `https://example.test/a?b&cA & Bpl2026-11-04T08:00:00Z<Grzyby>` if !strings.Contains(out, `xmlns:news="`+NSNews+`"`) || !strings.Contains(out, want) { t.Fatalf("news file:\n%s", out) } wellFormed(t, out) }