- one settings tile opening the golem15.sitemap.settings singleton - Files tab (site address, file size, S1 files partial), Additional addresses repeater, Content types tab built from signpost at boot - golem15_sitemap_types; a type switched off leaves the files (reason type) - a save validates (errors.*) and dispatches a rebuild after commit
123 lines
4.6 KiB
Go
123 lines
4.6 KiB
Go
package models_test
|
||
|
||
import (
|
||
"errors"
|
||
"reflect"
|
||
"testing"
|
||
|
||
"git.golem15.com/golem15/sm-sitemap-plugin/models"
|
||
"git.golem15.com/golem15/summercms/modules/cabana"
|
||
"git.golem15.com/golem15/summercms/modules/lagoon"
|
||
)
|
||
|
||
// TestAllowedFrontendURL pins the one definition of a frontend_url that may
|
||
// be saved, imported and applied (T-17-15): empty, or an absolute http(s)
|
||
// URL with a host and without whitespace, control characters, a double
|
||
// quote, <, > or &.
|
||
func TestAllowedFrontendURL(t *testing.T) {
|
||
for _, u := range []string{
|
||
"",
|
||
"https://www.ex.pl",
|
||
"https://www.ex.pl/",
|
||
"https://www.ex.pl///",
|
||
"http://localhost:3000",
|
||
"HTTPS://WWW.EX.PL",
|
||
"https://www.ex.pl/app",
|
||
"https://www.ex.pl:8443/base/",
|
||
"https://[::1]:3000",
|
||
// The frontend URLs of the golden inputs.
|
||
"https://api.example.test",
|
||
"https://www.example.test/",
|
||
} {
|
||
if !models.AllowedFrontendURL(u) {
|
||
t.Errorf("AllowedFrontendURL(%q) = false, want true", u)
|
||
}
|
||
}
|
||
for _, u := range []string{
|
||
"0",
|
||
"javascript:alert(document.domain)//",
|
||
"JavaScript://www.ex.pl/%0aalert(1)",
|
||
`https://x"/><y a="`,
|
||
"https://www.ex.pl/?a=1&b=2",
|
||
"https://www.ex.pl <x>",
|
||
" https://www.ex.pl",
|
||
"https://www.ex.pl\n",
|
||
"https://www.ex.pl\t",
|
||
"https://www.ex.pl ",
|
||
"https://www.ex.pl\x7f",
|
||
"https://www.ex.pl\xff",
|
||
"//www.ex.pl",
|
||
"www.ex.pl",
|
||
"/path",
|
||
"https:///x",
|
||
"https://:8080",
|
||
"https:www.ex.pl",
|
||
"http://",
|
||
"https://",
|
||
"https://%zz",
|
||
"data:text/html,x",
|
||
"ftp://www.ex.pl",
|
||
} {
|
||
if models.AllowedFrontendURL(u) {
|
||
t.Errorf("AllowedFrontendURL(%q) = true, want false", u)
|
||
}
|
||
}
|
||
}
|
||
|
||
// TestSettingsValidate pins the save checks of the settings singleton
|
||
// (UI-SPEC C26, T-33-12): the check the controller runs before a save passes
|
||
// a null, empty or accepted site_url, a file size from 1 to 50 000 and
|
||
// additional addresses that are paths or http(s) URLs, and answers a
|
||
// validation error keyed by the field for any other value.
|
||
func TestSettingsValidate(t *testing.T) {
|
||
text := func(s string) *string { return &s }
|
||
ok := func(s models.Settings) models.Settings {
|
||
if s.FileSize == 0 {
|
||
s.FileSize = 1000
|
||
}
|
||
return s
|
||
}
|
||
extra := func(urls ...string) lagoon.Jsonable[[]models.ExtraURL] {
|
||
rows := []models.ExtraURL{}
|
||
for _, u := range urls {
|
||
rows = append(rows, models.ExtraURL{URL: u})
|
||
}
|
||
return lagoon.Jsonable[[]models.ExtraURL]{Data: rows, Valid: true}
|
||
}
|
||
for name, s := range map[string]models.Settings{
|
||
"null site_url": ok(models.Settings{}),
|
||
"empty site_url": ok(models.Settings{SiteURL: text("")}),
|
||
"site_url": ok(models.Settings{SiteURL: text("https://www.ex.pl/")}),
|
||
"file size 1": {FileSize: 1},
|
||
"file size 50 000": {FileSize: 50000},
|
||
"extra addresses": ok(models.Settings{ExtraURLs: extra("/", "/o-nas?x=1&y=2", "https://www.ex.pl/kontakt")}),
|
||
} {
|
||
if err := s.Validate(); err != nil {
|
||
t.Errorf("%s: Validate = %v, want nil", name, err)
|
||
}
|
||
}
|
||
for name, c := range map[string]struct {
|
||
s models.Settings
|
||
want map[string]any
|
||
}{
|
||
"javascript site_url": {ok(models.Settings{SiteURL: text("javascript:alert(document.domain)//")}), map[string]any{"site_url": []string{"golem15.sitemap::lang.errors.frontend_url"}}},
|
||
"attribute site_url": {ok(models.Settings{SiteURL: text(`https://x"/><y a="`)}), map[string]any{"site_url": []string{"golem15.sitemap::lang.errors.frontend_url"}}},
|
||
"path site_url": {ok(models.Settings{SiteURL: text("/x")}), map[string]any{"site_url": []string{"golem15.sitemap::lang.errors.frontend_url"}}},
|
||
"file size 0": {models.Settings{}, map[string]any{"file_size": []string{"golem15.sitemap::lang.errors.file_size"}}},
|
||
"file size 50 001": {models.Settings{FileSize: 50001}, map[string]any{"file_size": []string{"golem15.sitemap::lang.errors.file_size"}}},
|
||
"javascript address": {ok(models.Settings{ExtraURLs: extra("/", "javascript:alert(1)")}), map[string]any{"extra_urls.1.url": []string{"golem15.sitemap::lang.errors.url_scheme"}}},
|
||
"protocol-relative": {ok(models.Settings{ExtraURLs: extra("//evil.test/x")}), map[string]any{"extra_urls.0.url": []string{"golem15.sitemap::lang.errors.url_scheme"}}},
|
||
"address with a space": {ok(models.Settings{ExtraURLs: extra("/a b")}), map[string]any{"extra_urls.0.url": []string{"golem15.sitemap::lang.errors.url_scheme"}}},
|
||
} {
|
||
err := c.s.Validate()
|
||
var invalid *cabana.ValidationError
|
||
if !errors.As(err, &invalid) {
|
||
t.Errorf("%s: Validate = %v, want a *cabana.ValidationError", name, err)
|
||
continue
|
||
}
|
||
if !reflect.DeepEqual(invalid.Details, c.want) {
|
||
t.Errorf("%s: details = %v, want %v", name, invalid.Details, c.want)
|
||
}
|
||
}
|
||
}
|