Files
sm-seo-plugin/classes/robots_value_test.go
Jakub Zych 923d0f9c3a fix: the robots element follows the Go grammar and keeps a value it cannot parse (WR-24)
parseRobots accepted 2027-02-30 (Date.parse rolls it over) and refused
max-snippet:+5, both unlike classes/robots_value.go. A value it could not
parse showed every group as Inherit and the first click serialised the
empty model, dropping the stored directives. Dates are now checked as
calendar dates, a sign is accepted, and an unparsed value is kept with a
note, the controls off and an explicit Reset.
2026-10-27 15:43:32 +01:00

145 lines
4.7 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)
}
}
}
// TestRobotsValueSigned: a signed number is accepted and stored without
// its plus sign, as the element's grammar reads it (WR-24).
func TestRobotsValueSigned(t *testing.T) {
if got, err := NormalizeRobots("max-snippet:+5"); err != nil || got != "max-snippet:5" {
t.Fatalf("NormalizeRobots(max-snippet:+5) = %q, %v", got, err)
}
if _, err := NormalizeRobots("unavailable_after:2028-02-29"); err != nil {
t.Fatalf("leap day: %v", err)
}
}
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)
}
}
}