- min and max compare the characters of a string or a number's decimal form and the element count of a slice or map on any field without numeric or integer (Laravel's getSize), with the min.array and max.array messages for lists - blank values, booleans, dates and byte slices are not measured; a malformed bound is an error on every non-numeric field - reverse the 261104-f4x pin, prove it through the cabana settings save sequence, and update the README, docs and the storePassword comment
144 lines
5.6 KiB
Go
144 lines
5.6 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); one rule (code) has no string token, so its max is a
|
|
// length all the same.
|
|
type stringURLSettings struct {
|
|
ID uint `gorm:"column:id;primaryKey"`
|
|
Title *string `gorm:"column:title"`
|
|
Website *string `gorm:"column:website"`
|
|
Language string `gorm:"column:language"`
|
|
Code *string `gorm:"column:code"`
|
|
}
|
|
|
|
func (stringURLSettings) TableName() string { return "cabana_string_url_settings" }
|
|
func (stringURLSettings) Fillable() []string { return []string{"title", "website", "language", "code"} }
|
|
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",
|
|
"code": "nullable|max:5",
|
|
}
|
|
}
|
|
|
|
const stringURLSettingsFields = "fields:\n title:\n type: text\n website:\n type: text\n language:\n type: text\n code:\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 or an
|
|
// over-long value (with or without the string token) 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."}}},
|
|
{"code at max", map[string]any{"code": "abcde"}, nil},
|
|
{"empty code", map[string]any{"code": ""}, nil},
|
|
{"code too long", map[string]any{"code": "abcdef"}, map[string][]string{"code": {"The code may not be greater than 5 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)
|
|
}
|
|
}
|