package classes import ( "bytes" "encoding/xml" "strings" "testing" "time" ) // TestWriteURLSet pins a sitemap file: the sitemap namespace, the xhtml // namespace only when an entry has alternates, escaped locs and // attributes, lastmod in UTC RFC 3339 and never a priority or changefreq, // and well-formed XML. func TestWriteURLSet(t *testing.T) { at := time.Date(2026, 11, 4, 18, 30, 0, 0, time.FixedZone("CET", 3600)) var buf bytes.Buffer err := WriteURLSet(&buf, []FileEntry{ {Loc: "https://example.test/a?x=1&y=<2>", LastMod: &at}, {Loc: "https://example.test/b", Alternates: []FileAlternate{{Hreflang: "en", Href: "https://example.test/b"}, {Hreflang: "x-default", Href: `https://example.test/b?"q"`}}}, }) if err != nil { t.Fatal(err) } out := buf.String() for _, want := range []string{ xml.Header, ``, `https://example.test/a?x=1&y=<2>2026-11-04T17:30:00Z`, ``, } { if !strings.Contains(out, want) { t.Fatalf("file lacks %s:\n%s", want, out) } } for _, never := range []string{"", ""} { if strings.Contains(out, never) { t.Fatalf("file has %s:\n%s", never, out) } } wellFormed(t, out) buf.Reset() if err := WriteURLSet(&buf, []FileEntry{{Loc: "https://example.test/"}}); err != nil { t.Fatal(err) } if strings.Contains(buf.String(), "xmlns:xhtml") || strings.Contains(buf.String(), "") { t.Fatalf("no alternates and no lastmod, yet:\n%s", buf.String()) } wellFormed(t, buf.String()) } // TestWriteIndex pins the index: one sitemap per file, lastmod only when // known. func TestWriteIndex(t *testing.T) { at := time.Date(2026, 1, 2, 3, 4, 5, 0, time.UTC) var buf bytes.Buffer if err := WriteIndex(&buf, []IndexFile{{Loc: "https://example.test/sitemap-pages-1.xml", LastMod: &at}, {Loc: "https://example.test/sitemap-posts-1.xml"}}); err != nil { t.Fatal(err) } out := buf.String() if !strings.Contains(out, ``) || !strings.Contains(out, "https://example.test/sitemap-pages-1.xml2026-01-02T03:04:05Z") || !strings.Contains(out, "https://example.test/sitemap-posts-1.xml") { t.Fatalf("index:\n%s", out) } wellFormed(t, out) } // TestWriteImageURLSet pins the image file: the image namespace, only // image:image and image:loc, escaping, and the MaxImagesPerURL cap. func TestWriteImageURLSet(t *testing.T) { many := make([]string, MaxImagesPerURL+5) for i := range many { many[i] = "https://cdn.example.test/i.jpg?n=" + strings.Repeat("1", i%3+1) } var buf bytes.Buffer if err := WriteImageURLSet(&buf, []ImageEntry{{Loc: "https://example.test/a", Images: []string{"https://cdn.example.test/c&d.webp"}}, {Loc: "https://example.test/b", Images: many}}); err != nil { t.Fatal(err) } out := buf.String() if !strings.Contains(out, `xmlns:image="`+NSImage+`"`) || !strings.Contains(out, "https://cdn.example.test/c&d.webp") { t.Fatalf("image file:\n%s", out) } if n := strings.Count(out, ""); n != 1+MaxImagesPerURL { t.Fatalf("%d images, want 1 + the %d cap", n, MaxImagesPerURL) } for _, never := range []string{"image:caption", "image:title", "image:license"} { if strings.Contains(out, never) { t.Fatalf("image file has %s", never) } } wellFormed(t, out) } // TestFileNames pins FileName and ParseFileName as inverses and the names // ParseFileName refuses. func TestFileNamesRoundTrip(t *testing.T) { for _, tc := range []struct { grp string n int }{{"pages", 1}, {"posts", 12}, {"news-2", 999999999}, {"a", 3}} { name := FileName(tc.grp, tc.n) grp, n, ok := ParseFileName(name) if !ok || grp != tc.grp || n != tc.n { t.Errorf("ParseFileName(%q) = %q %d %v", name, grp, n, ok) } } for _, bad := range []string{"sitemap.xml", "sitemap-pages-0.xml", "sitemap-pages-01.xml", "sitemap-Pages-1.xml", "sitemap--1.xml", "sitemap-pages-1.xml.gz", "sitemap-pages-1234567890.xml", "../sitemap-pages-1.xml", "sitemap-pages-1"} { if _, _, ok := ParseFileName(bad); ok { t.Errorf("ParseFileName(%q) accepted", bad) } } } // TestValidSlug pins the group slug rule. func TestValidSlug(t *testing.T) { for s, want := range map[string]bool{ "pages": true, "news-2": true, "0abc": true, strings.Repeat("a", 64): true, "": false, "-pages": false, "pages-": false, "Pages": false, "pa ges": false, "pa_ges": false, "żółw": false, strings.Repeat("a", 65): false, } { if got := ValidSlug(s); got != want { t.Errorf("ValidSlug(%q) = %v, want %v", s, got, want) } } } // wellFormed parses out as XML to its end. func wellFormed(t *testing.T, out string) { t.Helper() d := xml.NewDecoder(strings.NewReader(out)) for { _, err := d.Token() if err != nil { if err.Error() == "EOF" { return } t.Fatalf("not well-formed: %v\n%s", err, out) } } }