test: unit tests for the SEO classes

Pure tests of the classes package (no database): the resolver's value
chains with every layer present and absent, robots per directive, the
staging lock and drafts; the redirect graph (chains, flattened and
back-and-forth graphs, loops by exact, pattern and regex probes, the
10-hop cap); URL normalisation across every address rule; the robots
grammar over every directive combination; Google's robots.txt matcher
examples; templates and unknown placeholders; CSV export and import;
owner resolution; and every kind of the issue report.

go.mod: gorm.io/driver/postgres becomes a direct requirement (the tests'
dry-run database).
This commit is contained in:
2026-10-27 13:21:38 +01:00
parent 6d54fc7001
commit 9e45ddc848
11 changed files with 1275 additions and 1 deletions

75
classes/templates_test.go Normal file
View File

@@ -0,0 +1,75 @@
package classes
import (
"testing"
"git.golem15.com/golem15/sm-seo-plugin/models"
)
// TestTemplates: rendering fills the three placeholders, collapses spaces
// and trims separators an empty placeholder leaves; an unknown placeholder
// is refused when the template is saved.
func TestTemplates(t *testing.T) {
vars := TemplateVars{Title: "Grzyby leśne", Description: "O grzybach", Site: "Atlas"}
for _, tc := range []struct {
name, tpl string
vars TemplateVars
want string
}{
{"title and site", "%title% | %site%", vars, "Grzyby leśne | Atlas"},
{"description", "%description% – %site%", vars, "O grzybach – Atlas"},
{"every placeholder twice", "%title% %title% %site%", vars, "Grzyby leśne Grzyby leśne Atlas"},
{"an empty site leaves no separator", "%title% | %site%", TemplateVars{Title: "About"}, "About"},
{"an empty title leaves no separator", "%title% · %site%", TemplateVars{Site: "Atlas"}, "Atlas"},
{"separators of every kind are trimmed", "%title% — : , %site%", TemplateVars{Title: "X"}, "X"},
{"inner separators stay", "%title% | Blog | %site%", TemplateVars{Title: "X", Site: "Y"}, "X | Blog | Y"},
{"runs of spaces collapse", " %title% \t %site% ", vars, "Grzyby leśne Atlas"},
{"an empty template renders nothing", "", vars, ""},
{"a blank template renders nothing", " ", vars, ""},
{"text without placeholders", "Static title", vars, "Static title"},
{"an unknown placeholder renders as written", "%title% %author%", vars, "Grzyby leśne %author%"},
{"everything empty", "%title% | %site%", TemplateVars{}, ""},
} {
t.Run(tc.name, func(t *testing.T) {
if got := RenderTemplate(tc.tpl, tc.vars); got != tc.want {
t.Fatalf("RenderTemplate(%q) = %q, want %q", tc.tpl, got, tc.want)
}
})
}
for _, tc := range []struct {
tpl, unknown string
}{
{"%title% | %site%", ""},
{"%description%", ""},
{"", ""},
{"100% sure", ""},
{"%author% | %site%", "%author%"},
{"%title% %Title%", "%Title%"},
{"%%", "%%"},
{"%site_name%", "%site_name%"},
{"%title% %date% %page%", "%date%"},
} {
if got := models.UnknownPlaceholder(tc.tpl); got != tc.unknown {
t.Errorf("UnknownPlaceholder(%q) = %q, want %q", tc.tpl, got, tc.unknown)
}
problems := models.TemplateProblems(tc.tpl)
if tc.unknown == "" && problems != nil {
t.Errorf("TemplateProblems(%q) = %+v", tc.tpl, problems)
}
if tc.unknown != "" && (len(problems) != 1 || problems[0].Key != "golem15.seo::lang.settings.template_unknown" || problems[0].Params["placeholder"] != tc.unknown) {
t.Errorf("TemplateProblems(%q) = %+v", tc.tpl, problems)
}
}
refused := models.Settings{TitleTemplate: "%title% | %brand%", HomeTemplate: "%site% %x%"}.Problems()
if len(refused["title_template"]) != 1 || len(refused["home_template"]) != 1 {
t.Fatalf("settings problems = %+v", refused)
}
if ok := (models.Settings{TitleTemplate: "%title% | %site%", HomeTemplate: "%site%"}).Problems(); len(ok) != 0 {
t.Fatalf("valid templates refused: %+v", ok)
}
if len(models.KnownPlaceholders) != 3 || PlaceholderTitle != models.KnownPlaceholders[0] || PlaceholderDescription != models.KnownPlaceholders[1] || PlaceholderSite != models.KnownPlaceholders[2] {
t.Fatalf("the rendered and the validated placeholders differ: %v", models.KnownPlaceholders)
}
}