Files
summercms/cabana/metadata_settings_test.go

214 lines
8.4 KiB
Go

package cabana
import (
"context"
"errors"
"io/fs"
"reflect"
"strings"
"testing"
"testing/fstest"
"time"
"git.golem15.com/golem15/summercms/backpack"
"git.golem15.com/golem15/summercms/bouncer"
"git.golem15.com/golem15/summercms/pact"
"git.golem15.com/golem15/summercms/party"
"gorm.io/gorm"
)
type contributionPlugin struct {
id string
permissions []pact.Permission
navigation []pact.NavigationItem
settings []pact.SettingsItem
fsys fs.FS
}
func (p contributionPlugin) ID() string { return p.id }
func (p contributionPlugin) Requires() []string { return nil }
func (p contributionPlugin) Register(*backpack.App) error { return nil }
func (p contributionPlugin) Boot(*backpack.App) error { return nil }
func (p contributionPlugin) Permissions() []pact.Permission { return p.permissions }
func (p contributionPlugin) Navigation() []pact.NavigationItem { return p.navigation }
func (p contributionPlugin) Settings() []pact.SettingsItem { return p.settings }
func (p contributionPlugin) AdminFS() fs.FS { return p.fsys }
type metadataController struct{ id string }
func (c metadataController) ID() string { return c.id }
func (metadataController) ModelName() string { return "Metadata" }
func (metadataController) ConfigDir() string { return "controllers/metadata" }
func (metadataController) RequiredPermissions() []string { return []string{"acme.demo.access"} }
type singletonSetting struct {
ID uint `gorm:"column:id;primaryKey"`
Enabled bool `gorm:"column:enabled"`
CreatedAt time.Time `gorm:"column:created_at"`
UpdatedAt time.Time `gorm:"column:updated_at"`
}
func (singletonSetting) TableName() string { return "cabana_singleton_settings" }
func (singletonSetting) Fillable() []string { return []string{"enabled"} }
func (singletonSetting) Rules() map[string]string { return map[string]string{"enabled": "boolean"} }
func TestMetadataPermissionRegistry(t *testing.T) {
plugin := metadataPlugin()
reg := metadataRegistry()
if err := compileContributions(reg, []party.Plugin{plugin}); err != nil {
t.Fatal(err)
}
grants := reg.rolePermissions("developer")
if !grants["acme.demo.access"] || !grants["acme.demo.manage_settings"] {
t.Fatalf("developer grants = %#v", grants)
}
if grants := reg.rolePermissions("publisher"); len(grants) != 0 {
t.Fatalf("publisher grants = %#v", grants)
}
bad := metadataRegistry()
bad.byID["acme.demo.widgets"].Controller = metadataController{id: "acme.demo.widgets"}
bad.byID["acme.demo.widgets"].Controller = unknownPermissionController{}
if err := compileContributions(bad, []party.Plugin{plugin}); err == nil || !strings.Contains(err.Error(), "unknown permission") {
t.Fatalf("unknown permission error = %v", err)
}
duplicate := contributionPlugin{id: "other.demo", permissions: plugin.permissions}
if err := compileContributions(metadataRegistry(), []party.Plugin{plugin, duplicate}); err == nil || !strings.Contains(err.Error(), "duplicate permission") {
t.Fatalf("duplicate permission error = %v", err)
}
}
func TestMetadataFiltering(t *testing.T) {
plugin := metadataPlugin()
reg := metadataRegistry()
if err := compileContributions(reg, []party.Plugin{plugin}); err != nil {
t.Fatal(err)
}
developer := &bouncer.Principal{ID: 1, Backend: true, PermissionGrants: reg.rolePermissions("developer")}
navigation, settings := reg.Metadata(context.Background(), developer, nil)
if len(navigation) != 1 || len(navigation[0].SideMenu) != 1 || navigation[0].Code != "demo" || navigation[0].SideMenu[0].Code != "widgets" {
t.Fatalf("developer navigation = %#v", navigation)
}
if len(settings) != 1 || settings[0].Code != "demo" || !reflect.DeepEqual(settings[0].Keywords, []string{"demo", "settings"}) {
t.Fatalf("developer settings = %#v", settings)
}
denied := &bouncer.Principal{ID: 2, Backend: true, PermissionGrants: map[string]bool{}}
navigation, settings = reg.Metadata(context.Background(), denied, nil)
if navigation == nil || settings == nil || len(navigation) != 0 || len(settings) != 0 {
t.Fatalf("denied metadata = %#v %#v", navigation, settings)
}
frontend := &bouncer.Principal{ID: 1, PermissionGrants: reg.rolePermissions("developer")}
navigation, settings = reg.Metadata(context.Background(), frontend, nil)
if len(navigation) != 0 || len(settings) != 0 {
t.Fatalf("frontend principal leaked metadata = %#v %#v", navigation, settings)
}
}
func TestSettingsSingletonLifecycle(t *testing.T) {
_, db := newListService(t)
if err := db.Migrator().DropTable(&singletonSetting{}); err != nil {
t.Fatal(err)
}
if err := db.AutoMigrate(&singletonSetting{}); err != nil {
t.Fatal(err)
}
setting, err := compileSetting("acme.demo", metadataPlugin().settings[0], metadataFS())
if err != nil {
t.Fatal(err)
}
svc := SettingsService{DB: db}
missing, err := svc.Get(context.Background(), setting)
if err != nil || missing.Exists || missing.Data["enabled"] != false {
t.Fatalf("missing = %#v err=%v", missing, err)
}
assertSettingCount(t, db, 0)
if _, err := svc.Put(context.Background(), setting, map[string]any{}); err == nil {
t.Fatal("missing required setting succeeded")
}
created, err := svc.Put(context.Background(), setting, map[string]any{"enabled": true, "id": 99})
if err != nil || !created.Exists || created.Data["enabled"] != true {
t.Fatalf("created = %#v err=%v", created, err)
}
assertSettingCount(t, db, 1)
var before singletonSetting
if err := db.First(&before, 1).Error; err != nil {
t.Fatal(err)
}
replayed, err := svc.Put(context.Background(), setting, map[string]any{"enabled": true})
if err != nil || replayed.Data["enabled"] != true {
t.Fatalf("replay = %#v err=%v", replayed, err)
}
var after singletonSetting
if err := db.First(&after, 1).Error; err != nil {
t.Fatal(err)
}
if !after.UpdatedAt.Equal(before.UpdatedAt) || after.ID != 1 {
t.Fatalf("replay mutated singleton before=%+v after=%+v", before, after)
}
if _, err := svc.Put(context.Background(), setting, map[string]any{"enabled": "not-bool"}); err == nil {
t.Fatal("invalid boolean succeeded")
} else {
var invalid *ValidationError
if !errors.As(err, &invalid) {
t.Fatalf("invalid boolean error = %T %v", err, err)
}
}
var preserved singletonSetting
if err := db.First(&preserved, 1).Error; err != nil || !preserved.Enabled {
t.Fatalf("failed update was not rolled back: %+v err=%v", preserved, err)
}
}
type unknownPermissionController struct{}
func (unknownPermissionController) ID() string { return "acme.demo.widgets" }
func (unknownPermissionController) ModelName() string { return "Metadata" }
func (unknownPermissionController) ConfigDir() string { return "controllers/metadata" }
func (unknownPermissionController) RequiredPermissions() []string {
return []string{"acme.demo.unknown"}
}
func metadataRegistry() *Registry {
return &Registry{byID: map[string]*CompiledController{
"acme.demo.widgets": {Controller: metadataController{id: "acme.demo.widgets"}, Relations: map[string]*CompiledRelation{}},
}}
}
func metadataPlugin() contributionPlugin {
return contributionPlugin{
id: "acme.demo",
permissions: []pact.Permission{
{Code: "acme.demo.access", Roles: []string{"developer"}},
{Code: "acme.demo.manage_settings", Roles: []string{"developer"}},
},
navigation: []pact.NavigationItem{{
Code: "demo", Label: "Demo", Icon: "icon", Order: 20, Controller: "acme.demo.widgets",
Permissions: []string{"acme.demo.*"},
SideMenu: []pact.NavigationItem{{Code: "widgets", Label: "Widgets", Controller: "acme.demo.widgets", Permissions: []string{"acme.demo.access"}}},
}},
settings: []pact.SettingsItem{{
Code: "demo", Label: "Settings", Description: "Description", Category: "Demo", Icon: "icon-cog",
Model: "Settings", Order: 20, Keywords: []string{"demo", "settings"}, Permissions: []string{"acme.demo.manage_settings"},
Form: "models/settings/fields.yaml", NewModel: func() any { return &singletonSetting{} },
}},
fsys: metadataFS(),
}
}
func metadataFS() fs.FS {
return fstest.MapFS{"models/settings/fields.yaml": {Data: []byte("fields:\n enabled:\n type: switch\n default: 0\n required: true\n")}}
}
func assertSettingCount(t *testing.T, db *gorm.DB, want int64) {
t.Helper()
var got int64
if err := db.Model(&singletonSetting{}).Count(&got).Error; err != nil {
t.Fatal(err)
}
if got != want {
t.Fatalf("settings count=%d want=%d", got, want)
}
}