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).
134 lines
4.3 KiB
Go
134 lines
4.3 KiB
Go
package classes
|
|
|
|
import (
|
|
"errors"
|
|
"math/rand"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// robotsCombinations enumerates every combination of the C5 grammar's
|
|
// directives (with representative arguments).
|
|
func robotsCombinations() []Robots {
|
|
ptr := func(n int) *int { return &n }
|
|
var out []Robots
|
|
for _, index := range []string{"", "index", "noindex"} {
|
|
for _, follow := range []string{"", "follow", "nofollow"} {
|
|
for flags := 0; flags < 8; flags++ {
|
|
for _, snippet := range []*int{nil, ptr(-1), ptr(0), ptr(160)} {
|
|
for _, image := range []string{"", "none", "standard", "large"} {
|
|
for _, video := range []*int{nil, ptr(-1), ptr(30)} {
|
|
for _, after := range []string{"", "2027-01-31"} {
|
|
out = append(out, Robots{
|
|
Index: index, Follow: follow,
|
|
NoArchive: flags&1 != 0, NoSnippet: flags&2 != 0, NoImageIndex: flags&4 != 0,
|
|
MaxSnippet: snippet, MaxImagePreview: image, MaxVideoPreview: video,
|
|
UnavailableAfter: after,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func TestRobotsValueRoundTrip(t *testing.T) {
|
|
rng := rand.New(rand.NewSource(32))
|
|
combos := robotsCombinations()
|
|
if len(combos) != 3*3*8*4*4*3*2 {
|
|
t.Fatalf("%d combinations", len(combos))
|
|
}
|
|
for _, want := range combos {
|
|
value := want.String()
|
|
got, err := ParseRobots(value)
|
|
if err != nil {
|
|
t.Fatalf("ParseRobots(%q): %v", value, err)
|
|
}
|
|
if got.String() != value {
|
|
t.Fatalf("round trip %q -> %q", value, got.String())
|
|
}
|
|
if got.Header() != strings.ReplaceAll(value, ",", ", ") {
|
|
t.Fatalf("Header(%q) = %q", value, got.Header())
|
|
}
|
|
if got.Empty() != (value == "") {
|
|
t.Fatalf("Empty(%q) = %v", value, got.Empty())
|
|
}
|
|
// Any order, spaces and case read the same value back in the
|
|
// fixed order.
|
|
parts := strings.Split(value, ",")
|
|
rng.Shuffle(len(parts), func(i, j int) { parts[i], parts[j] = parts[j], parts[i] })
|
|
for i := range parts {
|
|
if i%2 == 0 {
|
|
parts[i] = " " + strings.ToUpper(parts[i]) + " "
|
|
}
|
|
}
|
|
if value != "" {
|
|
norm, err := NormalizeRobots(strings.Join(parts, ","))
|
|
if err != nil || norm != value {
|
|
t.Fatalf("NormalizeRobots(%q) = %q, %v; want %q", strings.Join(parts, ","), norm, err, value)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRobotsValueAllInheritIsEmpty(t *testing.T) {
|
|
for _, value := range []string{"", " ", "\t"} {
|
|
r, err := ParseRobots(value)
|
|
if err != nil || !r.Empty() || r.String() != "" {
|
|
t.Fatalf("ParseRobots(%q) = %+v, %v", value, r, err)
|
|
}
|
|
}
|
|
if s := (Robots{}).String(); s != "" {
|
|
t.Fatalf("all-inherit serialises to %q", s)
|
|
}
|
|
}
|
|
|
|
func TestRobotsValueMalformed(t *testing.T) {
|
|
for _, value := range []string{
|
|
",", "index,", ",noindex", "index,,follow",
|
|
"index,noindex", "follow,nofollow", "noindex,noindex", "noarchive,noarchive",
|
|
"max-snippet:1,max-snippet:2",
|
|
"index:1", "nofollow:yes", "noarchive:1",
|
|
"max-snippet", "max-snippet:", "max-snippet:abc", "max-snippet:-2", "max-snippet:1.5",
|
|
"max-video-preview", "max-video-preview:-5",
|
|
"max-image-preview", "max-image-preview:huge", "max-image-preview:",
|
|
"unavailable_after", "unavailable_after:2027-13-01", "unavailable_after:2027-02-30",
|
|
"unavailable_after:27-01-01", "unavailable_after:2027-1-1",
|
|
"none", "all", "noodp", "indexx",
|
|
} {
|
|
if _, err := ParseRobots(value); !errors.Is(err, ErrRobotsValue) {
|
|
t.Errorf("ParseRobots(%q) = %v, want ErrRobotsValue", value, err)
|
|
}
|
|
if _, err := NormalizeRobots(value); err == nil {
|
|
t.Errorf("NormalizeRobots(%q) accepted", value)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRobotsValueInherit(t *testing.T) {
|
|
parse := func(v string) Robots {
|
|
t.Helper()
|
|
r, err := ParseRobots(v)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return r
|
|
}
|
|
for _, tc := range []struct{ upper, lower, want string }{
|
|
{"", "", ""},
|
|
{"noindex", "index,nofollow", "noindex,nofollow"},
|
|
{"", "noindex,follow", "noindex,follow"},
|
|
{"index,follow", "noindex,nofollow,noarchive", "index,follow,noarchive"},
|
|
{"max-snippet:10", "max-snippet:50,max-image-preview:large", "max-snippet:10,max-image-preview:large"},
|
|
{"unavailable_after:2027-01-01", "unavailable_after:2028-01-01,max-video-preview:5", "max-video-preview:5,unavailable_after:2027-01-01"},
|
|
{"nosnippet", "noimageindex", "nosnippet,noimageindex"},
|
|
} {
|
|
if got := parse(tc.upper).Inherit(parse(tc.lower)).String(); got != tc.want {
|
|
t.Errorf("%q over %q = %q, want %q", tc.upper, tc.lower, got, tc.want)
|
|
}
|
|
}
|
|
}
|