- pact.HasAdminActionExtensions / AdminActionExtension: plugins add record actions to any admin controller by ID, merged at boot before permission validation (unknown target, no preview, duplicate name stop the boot) - internal summercms.cabana pseudo-plugin, appended only when some plugin registers admin controllers: Administrators list and preview, settings link under System, backend.manage_users and backend.manage_roles with no role grants - backend::lang user, user.role and permissions copy (en, pl) - smoke tests: compile-time seam and settings, Postgres list/preview/action
176 lines
6.5 KiB
Go
176 lines
6.5 KiB
Go
package cabana
|
|
|
|
import (
|
|
"context"
|
|
"io/fs"
|
|
"strings"
|
|
"testing"
|
|
"testing/fstest"
|
|
|
|
"git.golem15.com/golem15/summercms/modules/backpack"
|
|
"git.golem15.com/golem15/summercms/modules/bouncer"
|
|
"git.golem15.com/golem15/summercms/modules/pact"
|
|
"git.golem15.com/golem15/summercms/modules/party"
|
|
)
|
|
|
|
// screensExtender owns one list controller (so the admin is active) and adds
|
|
// record actions to other controllers through pact.HasAdminActionExtensions.
|
|
type screensExtender struct {
|
|
extensions []pact.AdminActionExtension
|
|
}
|
|
|
|
type screensExtenderController struct{}
|
|
|
|
func (screensExtenderController) ID() string { return "acme.ext.items" }
|
|
func (screensExtenderController) ModelName() string { return "Item" }
|
|
func (screensExtenderController) ConfigDir() string { return "controllers/items" }
|
|
func (screensExtenderController) RequiredPermissions() []string { return []string{"acme.ext.access"} }
|
|
|
|
func (screensExtender) ID() string { return "acme.ext" }
|
|
func (screensExtender) Requires() []string { return nil }
|
|
func (screensExtender) Register(*backpack.App) error { return nil }
|
|
func (screensExtender) Boot(*backpack.App) error { return nil }
|
|
func (screensExtender) AdminControllers() []pact.AdminController {
|
|
return []pact.AdminController{screensExtenderController{}}
|
|
}
|
|
func (screensExtender) AdminFS() fs.FS {
|
|
return fstest.MapFS{
|
|
"controllers/items/config_list.yaml": &fstest.MapFile{Data: []byte("list: ~/plugins/acme/ext/models/item/columns.yaml\nmodelClass: Item\nrecordsPerPage: 20\n")},
|
|
"models/item/columns.yaml": &fstest.MapFile{Data: []byte("columns:\n name:\n label: Name\n")},
|
|
}
|
|
}
|
|
func (screensExtender) Permissions() []pact.Permission {
|
|
return []pact.Permission{
|
|
{Code: "acme.ext.access", Roles: []string{"developer"}},
|
|
{Code: "acme.ext.ping"},
|
|
}
|
|
}
|
|
func (p screensExtender) AdminActionExtensions() []pact.AdminActionExtension { return p.extensions }
|
|
|
|
func pingAction() pact.AdminRecordAction {
|
|
return pact.AdminRecordAction{
|
|
Name: "acme_ping", Label: "Ping", Permissions: []string{"acme.ext.ping"},
|
|
Run: func(context.Context, pact.AdminRecordActionInput) (pact.AdminRecordActionResult, error) {
|
|
return pact.AdminRecordActionResult{}, nil
|
|
},
|
|
}
|
|
}
|
|
|
|
// compileWithScreens runs the compile steps of Activate that need no config
|
|
// or database on plugins plus the admin's own screens.
|
|
func compileWithScreens(t *testing.T, plugins []party.Plugin) (*Registry, error) {
|
|
t.Helper()
|
|
extended, screens := withBackendScreens(nil, plugins)
|
|
if len(extended) != len(plugins)+1 || extended[len(extended)-1] != party.Plugin(screens) {
|
|
t.Fatalf("withBackendScreens = %v", extended)
|
|
}
|
|
items, err := collectControllers(extended)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := checkReservedSegments(items); err != nil {
|
|
return nil, err
|
|
}
|
|
types, err := compileFieldTypes(extended)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
reg, err := compileRegistryWithTypes(items, types)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := compileActionExtensions(reg, extended); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := compileContributions(reg, extended); err != nil {
|
|
return nil, err
|
|
}
|
|
screens.reg = reg
|
|
return reg, nil
|
|
}
|
|
|
|
func TestBackendScreensSmoke(t *testing.T) {
|
|
extender := screensExtender{extensions: []pact.AdminActionExtension{{
|
|
Controller: AdministratorsControllerID,
|
|
RecordActions: []pact.AdminRecordAction{pingAction()},
|
|
}}}
|
|
plugins := []party.Plugin{extender}
|
|
reg, err := compileWithScreens(t, plugins)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(plugins) != 1 {
|
|
t.Fatalf("the caller's plugin slice changed: %v", plugins)
|
|
}
|
|
|
|
cc, ok := reg.Get(AdministratorsControllerID)
|
|
if !ok || cc.Form == nil || cc.Form.preview == nil {
|
|
t.Fatalf("administrators controller = %#v", cc)
|
|
}
|
|
names := cc.Form.recordActions
|
|
if len(names) == 0 || names[len(names)-1] != "acme_ping" {
|
|
t.Fatalf("administrators record actions = %v, want acme_ping last", names)
|
|
}
|
|
if _, ok := cc.RecordActions["acme_ping"]; !ok || cc.contributed["acme_ping"] != "acme.ext" {
|
|
t.Fatalf("RecordActions = %v contributed = %v", cc.RecordActions, cc.contributed)
|
|
}
|
|
if got := recordActionOfName(cc, "acme_ping"); !got {
|
|
t.Fatal("recordActionOf does not resolve the contributed action")
|
|
}
|
|
|
|
for _, code := range []string{PermissionManageUsers, PermissionManageRoles} {
|
|
if _, ok := reg.permissions[code]; !ok {
|
|
t.Fatalf("catalog lacks %s", code)
|
|
}
|
|
for _, role := range []string{"developer", "publisher"} {
|
|
if reg.rolePermissions(role)[code] {
|
|
t.Fatalf("role %s is granted %s in code", role, code)
|
|
}
|
|
}
|
|
}
|
|
if order := reg.permissionOrder; len(order) != len(reg.permissions) || order[0] != "acme.ext.access" {
|
|
t.Fatalf("permissionOrder = %v", order)
|
|
}
|
|
|
|
ctx := context.Background()
|
|
_, settings := reg.Metadata(ctx, &bouncer.Principal{ID: 1, Backend: true, IsSuperuser: true}, nil)
|
|
found := false
|
|
for _, entry := range settings {
|
|
if entry.Code == "administrators" {
|
|
found = entry.Controller == AdministratorsControllerID && entry.Category == "backend::lang.settings.category_system" && entry.Order == 100
|
|
}
|
|
}
|
|
if !found {
|
|
t.Fatalf("superuser settings = %+v, want administrators", settings)
|
|
}
|
|
_, settings = reg.Metadata(ctx, &bouncer.Principal{ID: 2, Backend: true, PermissionGrants: map[string]bool{"acme.ext.access": true}}, nil)
|
|
for _, entry := range settings {
|
|
if entry.Code == "administrators" {
|
|
t.Fatalf("an administrator without %s sees %+v", PermissionManageUsers, entry)
|
|
}
|
|
}
|
|
_, settings = reg.Metadata(ctx, &bouncer.Principal{ID: 3, Backend: true, PermissionGrants: map[string]bool{PermissionManageUsers: true}}, nil)
|
|
if len(settings) != 1 || settings[0].Code != "administrators" {
|
|
t.Fatalf("holder of %s sees %+v", PermissionManageUsers, settings)
|
|
}
|
|
|
|
missing := screensExtender{extensions: []pact.AdminActionExtension{{
|
|
Controller: "acme.missing.things",
|
|
RecordActions: []pact.AdminRecordAction{pingAction()},
|
|
}}}
|
|
if _, err := compileWithScreens(t, []party.Plugin{missing}); err == nil || !strings.Contains(err.Error(), "acme.missing.things") {
|
|
t.Fatalf("unknown controller error = %v", err)
|
|
}
|
|
|
|
routes, err := Activate(nil, []party.Plugin{contributionPlugin{id: "acme.plain", permissions: []pact.Permission{{Code: "acme.plain.use"}}}})
|
|
if routes != nil || err != nil {
|
|
t.Fatalf("Activate without admin controllers = %v, %v", routes, err)
|
|
}
|
|
}
|
|
|
|
// recordActionOfName reports whether the record action route resolves name.
|
|
func recordActionOfName(cc *CompiledController, name string) bool {
|
|
_, ok := recordActionOf(cc, name)
|
|
return ok
|
|
}
|