test(17-06): sitemap parity unit tests
- TestURLTo (every Url::to golden case, BackendURL, URLPath), TestXMLEscaping (PHP XmlEscapingTest as behaviour), TestTiers (each default rule, first match, off, bad pattern fails Boot), TestRewrite, TestHooks, TestURLCap, TestManifest (fail-safe reads, key normalization, dates), TestFillable (PHP MassAssignmentTest), TestDefinitionItemJSON, TestBackendURL (trusted forwarded proto) - TestImportWinter on Postgres: maps and drops with report, --theme, --dry-run, --force, settings upsert, refusals
This commit is contained in:
145
models/definition_test.go
Normal file
145
models/definition_test.go
Normal file
@@ -0,0 +1,145 @@
|
||||
package models_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.golem15.com/golem15/sm-sitemap-plugin/models"
|
||||
"git.golem15.com/golem15/summercms/modules/lagoon"
|
||||
)
|
||||
|
||||
// TestFillable ports the PHP MassAssignmentTest (SITE-001) as behaviour:
|
||||
// Definition.Fillable is exactly theme and data, and a fill with extra keys
|
||||
// (id, timestamps, unknown keys) writes only those two.
|
||||
func TestFillable(t *testing.T) {
|
||||
if got := (models.Definition{}).Fillable(); !slices.Equal(got, []string{"theme", "data"}) {
|
||||
t.Fatalf("Fillable = %v, want [theme data]", got)
|
||||
}
|
||||
def := models.Definition{ID: 4}
|
||||
requested := map[string]any{
|
||||
"id": 99,
|
||||
"theme": "site",
|
||||
"data": `[{"type":"url","url":"/a"}]`,
|
||||
"created_at": "2020-01-01 00:00:00",
|
||||
"updated_at": "2020-01-01 00:00:00",
|
||||
"is_admin": true,
|
||||
}
|
||||
for _, production := range []bool{true, false} {
|
||||
d := def
|
||||
if err := lagoon.Fill(&d, d.Fillable(), requested, production); err != nil {
|
||||
t.Fatalf("Fill: %v", err)
|
||||
}
|
||||
if d.ID != 4 || d.CreatedAt != nil || d.UpdatedAt != nil || models.Text(d.Theme) != "site" {
|
||||
t.Fatalf("filled definition = %+v", d)
|
||||
}
|
||||
if items := d.Items(); len(items) != 1 || models.Text(items[0].URL) != "/a" {
|
||||
t.Fatalf("filled data = %+v", items)
|
||||
}
|
||||
}
|
||||
if (models.Definition{}).TableName() != "golem15_sitemap_definitions" || (models.Definition{}).MorphName() != `Golem15\Sitemap\Models\Definition` {
|
||||
t.Fatal("table or morph name changed")
|
||||
}
|
||||
if rules := (models.Definition{}).Rules(); len(rules) != 0 {
|
||||
t.Fatalf("Rules = %v", rules)
|
||||
}
|
||||
if got := (models.Settings{}).Fillable(); slices.Contains(got, "id") || len(got) != 9 {
|
||||
t.Fatalf("Settings.Fillable = %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
// TestDefinitionItemJSON pins the tolerant decoding of PHP-stored items
|
||||
// (strings, numbers, booleans and null for text fields, PHP truthiness for
|
||||
// nesting, unknown keys ignored, a missing type is url, a null item a
|
||||
// default url item, an object-keyed list in key order) and the PHP toArray
|
||||
// encoding (key set and order, cmsPage always null, null text kept).
|
||||
func TestDefinitionItemJSON(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
raw string
|
||||
want string
|
||||
}{
|
||||
{`{"type":"url","url":"/a","priority":"0.5","changefreq":"daily","nesting":"0","reference":null,"urlPattern":null,"cmsPage":"x","code":"c","exists":false,"fillable":["a"]}`,
|
||||
`type=url url=/a ref=<nil> pattern=<nil> nesting=false priority=0.5 changefreq=daily`},
|
||||
{`{"url":"/b"}`, `type=url url=/b ref=<nil> pattern=<nil> nesting=false priority=<nil> changefreq=<nil>`},
|
||||
{`{"type":null}`, `type= url=<nil> ref=<nil> pattern=<nil> nesting=false priority=<nil> changefreq=<nil>`},
|
||||
{`{"type":"journal-post","reference":7,"priority":0.5,"changefreq":true,"nesting":1}`,
|
||||
`type=journal-post url=<nil> ref=7 pattern=<nil> nesting=true priority=0.5 changefreq=1`},
|
||||
{`{"type":"x","priority":1.0,"reference":12.50,"changefreq":false,"nesting":"1"}`,
|
||||
`type=x url=<nil> ref=12.5 pattern=<nil> nesting=true priority=1 changefreq=`},
|
||||
{`{"type":"x","nesting":"false"}`, `type=x url=<nil> ref=<nil> pattern=<nil> nesting=true priority=<nil> changefreq=<nil>`},
|
||||
{`{"type":"x","nesting":""}`, `type=x url=<nil> ref=<nil> pattern=<nil> nesting=false priority=<nil> changefreq=<nil>`},
|
||||
{`{"type":"x","nesting":null,"urlPattern":"/p/:slug"}`, `type=x url=<nil> ref=<nil> pattern=/p/:slug nesting=false priority=<nil> changefreq=<nil>`},
|
||||
{`{"type":"x","reference":12345678901234}`, `type=x url=<nil> ref=12345678901234 pattern=<nil> nesting=false priority=<nil> changefreq=<nil>`},
|
||||
{`null`, `type=url url=<nil> ref=<nil> pattern=<nil> nesting=false priority=<nil> changefreq=<nil>`},
|
||||
} {
|
||||
var item models.DefinitionItem
|
||||
if err := json.Unmarshal([]byte(tc.raw), &item); err != nil {
|
||||
t.Fatalf("%s: %v", tc.raw, err)
|
||||
}
|
||||
if got := describe(item); got != tc.want {
|
||||
t.Errorf("%s\n got %s\nwant %s", tc.raw, got, tc.want)
|
||||
}
|
||||
}
|
||||
for _, raw := range []string{`{"type":["x"]}`, `{"url":{"a":1}}`, `{"nesting":[1]}`, `[1]`, `"x"`} {
|
||||
var item models.DefinitionItem
|
||||
if err := json.Unmarshal([]byte(raw), &item); err == nil {
|
||||
t.Errorf("%s decoded without an error", raw)
|
||||
}
|
||||
}
|
||||
|
||||
// A list, an object keyed like a PHP array with gaps, null and empty.
|
||||
var list models.DefinitionItems
|
||||
if err := json.Unmarshal([]byte(`{"3":{"url":"/c"},"1":{"url":"/a"},"x":{"url":"/x"}}`), &list); err != nil || len(list) != 3 ||
|
||||
models.Text(list[0].URL) != "/c" || models.Text(list[1].URL) != "/a" || models.Text(list[2].URL) != "/x" {
|
||||
t.Fatalf("object list = %+v %v", list, err)
|
||||
}
|
||||
for raw, n := range map[string]int{`[]`: 0, `null`: 0, `[{"url":"/a"},null]`: 2} {
|
||||
var l models.DefinitionItems
|
||||
if err := json.Unmarshal([]byte(raw), &l); err != nil || len(l) != n {
|
||||
t.Fatalf("%s = %+v %v", raw, l, err)
|
||||
}
|
||||
}
|
||||
var bad models.DefinitionItems
|
||||
if err := json.Unmarshal([]byte(`"items"`), &bad); err == nil {
|
||||
t.Fatal("a string decoded as an item list")
|
||||
}
|
||||
|
||||
// Encoding: the PHP toArray key set and order.
|
||||
pattern := "/p/:slug"
|
||||
item := models.DefinitionItem{Type: "journal-post", Reference: str("7"), URLPattern: &pattern, Nesting: true, Priority: str("0.5")}
|
||||
raw, err := json.Marshal(item)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if want := `{"changefreq":null,"priority":"0.5","nesting":true,"type":"journal-post","url":null,"reference":"7","cmsPage":null,"urlPattern":"/p/:slug"}`; string(raw) != want {
|
||||
t.Fatalf("Marshal = %s\nwant %s", raw, want)
|
||||
}
|
||||
if raw, _ := json.Marshal(models.DefinitionItems(nil)); string(raw) != "[]" {
|
||||
t.Fatalf("nil list = %s", raw)
|
||||
}
|
||||
var back models.DefinitionItem
|
||||
if err := json.Unmarshal(raw, &back); err != nil || describe(back) != describe(item) {
|
||||
t.Fatalf("round trip = %s, %v", describe(back), err)
|
||||
}
|
||||
if models.Text(nil) != "" || models.Text(str("x")) != "x" {
|
||||
t.Fatal("Text")
|
||||
}
|
||||
if s, err := models.DecodeText(json.RawMessage(`1e3`)); err != nil || *s != "1000" {
|
||||
t.Fatalf("DecodeText(1e3) = %v %v", s, err)
|
||||
}
|
||||
}
|
||||
|
||||
func str(s string) *string { return &s }
|
||||
|
||||
func describe(d models.DefinitionItem) string {
|
||||
p := func(s *string) string {
|
||||
if s == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
return *s
|
||||
}
|
||||
return strings.TrimSpace(fmt.Sprintf("type=%s url=%s ref=%s pattern=%s nesting=%v priority=%s changefreq=%s",
|
||||
d.Type, p(d.URL), p(d.Reference), p(d.URLPattern), d.Nesting, p(d.Priority), p(d.Changefreq)))
|
||||
}
|
||||
Reference in New Issue
Block a user