- on a string or url field that is not numeric or integer, min and max compare utf8.RuneCountInString (Laravel's mb_strlen) and answer with the lagoon::validation min.string / max.string messages - a min or max argument that is not a whole number is the unrecognized-rule error, whatever the value - rule strings without string or url keep their results (pinned by a test); between is unchanged
137 lines
5.1 KiB
Go
137 lines
5.1 KiB
Go
package cabana
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
"testing/fstest"
|
|
|
|
"git.golem15.com/golem15/summercms/modules/lagoon"
|
|
"git.golem15.com/golem15/summercms/modules/pact"
|
|
)
|
|
|
|
// stringURLSettings is a settings model whose rules use the Laravel string
|
|
// and url tokens the way plugin settings models do (title, feed URL,
|
|
// language code).
|
|
type stringURLSettings struct {
|
|
ID uint `gorm:"column:id;primaryKey"`
|
|
Title *string `gorm:"column:title"`
|
|
Website *string `gorm:"column:website"`
|
|
Language string `gorm:"column:language"`
|
|
}
|
|
|
|
func (stringURLSettings) TableName() string { return "cabana_string_url_settings" }
|
|
func (stringURLSettings) Fillable() []string { return []string{"title", "website", "language"} }
|
|
func (stringURLSettings) Rules() map[string]string {
|
|
return map[string]string{
|
|
"title": "nullable|string|max:255",
|
|
"website": "nullable|url|max:500",
|
|
"language": "nullable|string|max:10",
|
|
}
|
|
}
|
|
|
|
const stringURLSettingsFields = "fields:\n title:\n type: text\n website:\n type: text\n language:\n type: text\n"
|
|
|
|
func compileStringURLSetting(t *testing.T) *CompiledSetting {
|
|
t.Helper()
|
|
fsys := fstest.MapFS{"models/settings/fields.yaml": &fstest.MapFile{Data: []byte(stringURLSettingsFields)}}
|
|
item := pact.SettingsItem{Code: "feed", Label: "Feed", Form: "models/settings/fields.yaml", Model: "StringURLSettings", NewModel: func() any { return &stringURLSettings{} }}
|
|
setting, err := compileSetting("acme.unit", item, fsys)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return setting
|
|
}
|
|
|
|
// TestSettingsStringURLRules runs the validation sequence of
|
|
// SettingsService.Put without a database: the string and url rules are
|
|
// accepted, valid, empty and null values pass, and an invalid URL is a
|
|
// message on the field.
|
|
func TestSettingsStringURLRules(t *testing.T) {
|
|
setting := compileStringURLSetting(t)
|
|
cases := []struct {
|
|
name string
|
|
body map[string]any
|
|
want map[string][]string
|
|
}{
|
|
{"valid", map[string]any{"title": "Feed", "website": "https://example.com/feed.png", "language": "pl"}, nil},
|
|
{"empty website", map[string]any{"website": ""}, nil},
|
|
{"null website and title", map[string]any{"website": nil, "title": nil}, nil},
|
|
{"invalid website", map[string]any{"website": "not a url"}, map[string][]string{"website": {"The website format is invalid."}}},
|
|
{"title too long", map[string]any{"title": strings.Repeat("a", 256)}, map[string][]string{"title": {"The title may not be greater than 255 characters."}}},
|
|
}
|
|
for _, tc := range cases {
|
|
model := &stringURLSettings{}
|
|
if err := lagoon.Fill(model, settingWritable(setting), projectSettingBody(setting, tc.body), false); err != nil {
|
|
t.Fatalf("%s: fill: %v", tc.name, err)
|
|
}
|
|
rules := settingRules(setting, model)
|
|
got, err := lagoon.Validate(context.Background(), nil, model, rules, valuesForRules(model, rules), nil)
|
|
if err != nil {
|
|
t.Fatalf("%s: validate: %v", tc.name, err)
|
|
}
|
|
if len(got) == 0 && len(tc.want) == 0 {
|
|
continue
|
|
}
|
|
if !reflect.DeepEqual(got, tc.want) {
|
|
t.Fatalf("%s: messages = %v, want %v", tc.name, got, tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestSettingsStringURLRulesPostgres saves the settings row through
|
|
// SettingsService.Put: a valid body is stored, an invalid URL is a 422 on
|
|
// the field and leaves the stored row as it was.
|
|
func TestSettingsStringURLRulesPostgres(t *testing.T) {
|
|
_, db := newListService(t)
|
|
if err := db.Migrator().DropTable(&stringURLSettings{}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := db.AutoMigrate(&stringURLSettings{}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
setting := compileStringURLSetting(t)
|
|
svc := SettingsService{DB: db}
|
|
ctx := context.Background()
|
|
const feedURL = "https://example.com/feed.png"
|
|
saved, err := svc.Put(ctx, setting, map[string]any{"title": "Feed", "website": feedURL, "language": "pl"})
|
|
if err != nil || !saved.Exists {
|
|
t.Fatalf("valid put = %#v err=%v", saved, err)
|
|
}
|
|
var count int64
|
|
if err := db.Model(&stringURLSettings{}).Count(&count).Error; err != nil || count != 1 {
|
|
t.Fatalf("rows = %d err=%v", count, err)
|
|
}
|
|
_, err = svc.Put(ctx, setting, map[string]any{"website": "not a url"})
|
|
var invalid *ValidationError
|
|
if !errors.As(err, &invalid) {
|
|
t.Fatalf("invalid url error = %T %v", err, err)
|
|
}
|
|
if got, _ := invalid.Details["website"].([]string); !reflect.DeepEqual(got, []string{"The website format is invalid."}) {
|
|
t.Fatalf("details = %#v", invalid.Details)
|
|
}
|
|
var stored stringURLSettings
|
|
if err := db.First(&stored, 1).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if stored.Website == nil || *stored.Website != feedURL {
|
|
t.Fatalf("stored website = %v", stored.Website)
|
|
}
|
|
_, err = svc.Put(ctx, setting, map[string]any{"title": strings.Repeat("a", 256)})
|
|
if !errors.As(err, &invalid) {
|
|
t.Fatalf("long title error = %T %v", err, err)
|
|
}
|
|
if got, _ := invalid.Details["title"].([]string); !reflect.DeepEqual(got, []string{"The title may not be greater than 255 characters."}) {
|
|
t.Fatalf("details = %#v", invalid.Details)
|
|
}
|
|
stored = stringURLSettings{}
|
|
if err := db.First(&stored, 1).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if stored.Title == nil || *stored.Title != "Feed" {
|
|
t.Fatalf("stored title = %v", stored.Title)
|
|
}
|
|
}
|