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:
121
classes/normalize_test.go
Normal file
121
classes/normalize_test.go
Normal file
@@ -0,0 +1,121 @@
|
||||
package classes
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"git.golem15.com/golem15/sm-seo-plugin/models"
|
||||
)
|
||||
|
||||
// TestNormalizeMatrix: every combination of the address rules on every
|
||||
// input form reaches its final address in one hop (normalising the result
|
||||
// changes nothing), never touches the query, keeps the root and does not
|
||||
// modify its input.
|
||||
func TestNormalizeMatrix(t *testing.T) {
|
||||
inputs := []string{
|
||||
"/", "/a", "/a/", "/A/b", "//a//b/", "/a/index.html", "/A/Index.PHP", "/index.htm",
|
||||
"/sitemap.xml", "/files/Doc.PDF/", "/%C5%81%C3%B3d%C5%BA/Abc/", "/a?Page=2&x=Y", "/a#frag",
|
||||
"http://example.test", "http://example.test/A//B/", "https://www.example.test/a/index.html?q=1",
|
||||
"https://WWW.Example.Test/Blog/", "http://www.example.test/",
|
||||
}
|
||||
var all []NormalizeOptions
|
||||
for _, host := range []string{models.HostKeep, models.HostWWW, models.HostBare} {
|
||||
for _, slash := range []string{models.SlashKeep, models.SlashAdd, models.SlashDrop} {
|
||||
for _, lower := range []bool{false, true} {
|
||||
all = append(all, NormalizeOptions{Host: host, Slash: slash, Lowercase: lower})
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, opts := range all {
|
||||
for _, in := range inputs {
|
||||
u, err := url.Parse(in)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
before := *u
|
||||
once, _ := Normalize(u, opts)
|
||||
if *u != before {
|
||||
t.Fatalf("%+v: Normalize(%s) modified its input", opts, in)
|
||||
}
|
||||
twice, changed := Normalize(once, opts)
|
||||
if changed || twice.String() != once.String() {
|
||||
t.Fatalf("%+v: %s -> %s -> %s (changed %v): not one hop", opts, in, once, twice, changed)
|
||||
}
|
||||
if once.RawQuery != u.RawQuery {
|
||||
t.Fatalf("%+v: %s: query %q became %q", opts, in, u.RawQuery, once.RawQuery)
|
||||
}
|
||||
if once.Fragment != "" {
|
||||
t.Fatalf("%+v: %s kept its fragment", opts, in)
|
||||
}
|
||||
if once.Host != "" && once.Scheme != "https" {
|
||||
t.Fatalf("%+v: %s scheme %s", opts, in, once.Scheme)
|
||||
}
|
||||
if u.Path == "/" && once.Path != "/" {
|
||||
t.Fatalf("%+v: the root became %s", opts, once.Path)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeMatrixExpectations(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
in string
|
||||
opts NormalizeOptions
|
||||
want string
|
||||
}{
|
||||
{"/A//B/index.html", NormalizeOptions{Host: models.HostKeep, Slash: models.SlashKeep}, "/A/B"},
|
||||
{"/A//B/index.html", NormalizeOptions{Slash: models.SlashAdd, Lowercase: true}, "/a/b/"},
|
||||
{"/a/b///", NormalizeOptions{Slash: models.SlashDrop}, "/a/b"},
|
||||
{"/a/b///", NormalizeOptions{Slash: models.SlashKeep}, "/a/b/"},
|
||||
{"/report.PDF", NormalizeOptions{Slash: models.SlashAdd, Lowercase: true}, "/report.pdf"},
|
||||
{"/v1.2/page", NormalizeOptions{Slash: models.SlashAdd}, "/v1.2/page/"},
|
||||
{"http://EXAMPLE.test/a", NormalizeOptions{Host: models.HostKeep}, "https://example.test/a"},
|
||||
{"http://www.example.test/a/", NormalizeOptions{Host: models.HostBare, Slash: models.SlashDrop}, "https://example.test/a"},
|
||||
{"https://example.test", NormalizeOptions{Host: models.HostWWW, Slash: models.SlashAdd}, "https://www.example.test/"},
|
||||
{"/%c5%81/%C5%82X", NormalizeOptions{Lowercase: true}, "/%C5%81/%C5%82x"},
|
||||
} {
|
||||
u, _ := url.Parse(tc.in)
|
||||
got, _ := Normalize(u, tc.opts)
|
||||
if got.String() != tc.want {
|
||||
t.Errorf("Normalize(%s, %+v) = %s, want %s", tc.in, tc.opts, got, tc.want)
|
||||
}
|
||||
}
|
||||
if got, changed := Normalize(nil, NormalizeOptions{}); got != nil || changed {
|
||||
t.Fatal("Normalize(nil) answered a URL")
|
||||
}
|
||||
if got := NormalizeOptionsOf(models.Settings{URLHost: models.HostWWW, URLSlash: models.SlashAdd, URLLowercase: true}); got != (NormalizeOptions{Host: models.HostWWW, Slash: models.SlashAdd, Lowercase: true}) {
|
||||
t.Fatalf("NormalizeOptionsOf = %+v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeHelpers(t *testing.T) {
|
||||
for in, want := range map[string]string{"/": "/", "//": "/", "/a//b": "/a/b", "///a///": "/a/", "/a/b": "/a/b"} {
|
||||
if got := collapseSlashes(in); got != want {
|
||||
t.Errorf("collapseSlashes(%q) = %q", in, got)
|
||||
}
|
||||
}
|
||||
if got := lowerASCII("/ŁÓDŹ/ABC-z"); got != "/ŁÓdŹ/abc-z" {
|
||||
t.Errorf("lowerASCII = %q", got)
|
||||
}
|
||||
for in, want := range map[string]string{"/a/b.c": "b.c", "/a/": "", "x": "x", "/": ""} {
|
||||
if got := lastSegment(in); got != want {
|
||||
t.Errorf("lastSegment(%q) = %q", in, got)
|
||||
}
|
||||
}
|
||||
for _, tc := range []struct {
|
||||
site, host string
|
||||
same bool
|
||||
}{
|
||||
{"https://example.test", "example.test", true},
|
||||
{"https://example.test", "WWW.example.test", true},
|
||||
{"https://www.example.test", "example.test", true},
|
||||
{"https://example.test", "evil.test", false},
|
||||
{"https://example.test", "", false},
|
||||
{"", "example.test", false},
|
||||
{"https://example.test", "example.test.evil", false},
|
||||
} {
|
||||
if got := sameSite(tc.site, tc.host); got != tc.same {
|
||||
t.Errorf("sameSite(%q, %q) = %v", tc.site, tc.host, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user