Files
sm-sitemap-plugin/classes/golden_test.go
Jakub Zych fe39e26ce0 feat(17-03): sitemap plugin tracer, /sitemap.xml byte-identical to PHP
- golem15.sitemap plugin: golem15_sitemap_definitions migration, Definition
  model (Fillable theme and data) with tolerant PHP-shaped item decoding and
  PHP toArray item encoding
- Url::to and buildUrl port, libxml-format serializer, generator for url and
  registry items
- GET /sitemap.xml serves the definition with theme default (404 without one,
  empty body without items)
- tests/php/gen-goldens.php and the tracer golden; TestSitemapGolden and the
  Postgres TestSitemapTracer
2026-10-12 16:53:09 +02:00

110 lines
3.0 KiB
Go

package classes_test
import (
"context"
"encoding/json"
"os"
"path/filepath"
"testing"
"time"
"git.golem15.com/golem15/sm-sitemap-plugin/classes"
"git.golem15.com/golem15/sm-sitemap-plugin/models"
"git.golem15.com/golem15/summercms/modules/pact"
)
// The goldens in testdata are written by tests/php/gen-goldens.php from the
// same JSON inputs with the PHP plugin's generation code and the vendored
// Winter URL generator; these tests feed the inputs through the Go port and
// compare bytes.
// goldenResolved is a registry item's resolve result in a golden input.
type goldenResolved struct {
URL string `json:"url"`
MTime *string `json:"mtime"`
Alternates [][2]string `json:"alternates"`
Items []goldenResolved `json:"items"`
}
func (g goldenResolved) toPact(t *testing.T) pact.ResolvedMenuItem {
t.Helper()
out := pact.ResolvedMenuItem{URL: g.URL}
if g.MTime != nil {
at, err := time.Parse(time.RFC3339, *g.MTime)
if err != nil {
t.Fatalf("mtime %q: %v", *g.MTime, err)
}
out.MTime = &at
}
for _, a := range g.Alternates {
out.Alternates = append(out.Alternates, pact.MenuItemAlternate{Locale: a[0], URL: a[1]})
}
for _, child := range g.Items {
out.Items = append(out.Items, child.toPact(t))
}
return out
}
// goldenCase is one sitemap case of a golden input.
type goldenCase struct {
Name string `json:"name"`
Root string `json:"root"`
Items models.DefinitionItems `json:"items"`
Types map[string]map[string]goldenResolved `json:"types"`
}
// fakeResolver answers Resolve from a golden case's types.
type fakeResolver struct {
t *testing.T
types map[string]map[string]goldenResolved
}
func (f fakeResolver) Type(key string) (pact.MenuItemType, bool) {
_, ok := f.types[key]
return pact.MenuItemType{Key: key}, ok
}
func (f fakeResolver) Resolve(_ context.Context, key, reference, _ string, _ bool, _ string) (*pact.ResolvedMenuItem, error) {
r, ok := f.types[key][reference]
if !ok {
return nil, nil
}
out := r.toPact(f.t)
return &out, nil
}
func renderCase(t *testing.T, c goldenCase) string {
t.Helper()
gen := &classes.Generator{Root: c.Root, Resolver: fakeResolver{t: t, types: c.Types}}
xml, err := gen.Generate(context.Background(), c.Items)
if err != nil {
t.Fatalf("generate %s: %v", c.Name, err)
}
return xml
}
func readTestdata(t *testing.T, name string) []byte {
t.Helper()
raw, err := os.ReadFile(filepath.Join("testdata", name))
if err != nil {
t.Fatal(err)
}
return raw
}
// TestSitemapGolden compares the Go sitemap with the PHP goldens.
func TestSitemapGolden(t *testing.T) {
t.Run("tracer", func(t *testing.T) {
var in struct {
Case goldenCase `json:"case"`
}
if err := json.Unmarshal(readTestdata(t, "tracer.json"), &in); err != nil {
t.Fatal(err)
}
got := renderCase(t, in.Case)
if want := string(readTestdata(t, "tracer.golden")); got != want {
t.Fatalf("tracer sitemap differs from the PHP golden\n got: %q\nwant: %q", got, want)
}
})
}