Files
summercms/modules/cabana/action_extensions_test.go
Jakub Zych 1a56294d9b test(19.6.1-03): last-superuser race, lifecycle, revocation, seam and roles tests
- TestAdministratorsLastSuperuser (D-09): A and B reduce each other on the
  five paths while a test transaction holds the guard's advisory lock until
  pg_locks shows both requests waiting; one succeeds, one gets last_superuser
- TestAdministratorsLastSuperuserConcurrent: ten unsynchronized rounds
- TestAdministratorsLifecycle (D-10) and TestAdministratorsRevocation (D-12),
  two-row bulk selections included
- TestActionExtensionBoot (-short) and TestActionExtensionRun (D-14, D-15)
- TestRolesLifecycle (D-16, D-17), TestRolesPermissionCeiling (D-07)
- TestBackendScreensActivation (-short, D-01, D-04)
2026-10-22 12:09:12 +02:00

156 lines
6.8 KiB
Go

package cabana
import (
"context"
"fmt"
"strings"
"testing"
"git.golem15.com/golem15/summercms/modules/backpack"
"git.golem15.com/golem15/summercms/modules/pact"
"git.golem15.com/golem15/summercms/modules/party"
)
// actionContributor owns no admin controller; it declares permissions and
// adds record actions to other controllers.
type actionContributor struct {
id string
permissions []pact.Permission
extensions []pact.AdminActionExtension
}
func (p actionContributor) ID() string { return p.id }
func (actionContributor) Requires() []string { return nil }
func (actionContributor) Register(*backpack.App) error { return nil }
func (actionContributor) Boot(*backpack.App) error { return nil }
func (p actionContributor) Permissions() []pact.Permission { return p.permissions }
func (p actionContributor) AdminActionExtensions() []pact.AdminActionExtension {
return p.extensions
}
// namedAction is a valid record action named name behind permission.
func namedAction(name, permission string) pact.AdminRecordAction {
action := pingAction()
action.Name = name
action.Label = "Run " + name
action.Permissions = []string{permission}
return action
}
func toUsers(actions ...pact.AdminRecordAction) []pact.AdminActionExtension {
return []pact.AdminActionExtension{{Controller: AdministratorsControllerID, RecordActions: actions}}
}
// TestActionExtensionBoot (D-14, SC-3): every refusal of the seam stops boot
// with an error naming the plugin, the action and the controller; valid
// contributions land after the owner's actions in plugin and declared order.
func TestActionExtensionBoot(t *testing.T) {
base := screensExtender{} // owns acme.ext.items (list only) and acme.ext.ping
contributor := func(id string, ext []pact.AdminActionExtension) actionContributor {
return actionContributor{id: id, permissions: []pact.Permission{{Code: id + ".run"}}, extensions: ext}
}
noRun := namedAction("acme_norun", "acme.one.run")
noRun.Run = nil
noLabel := namedAction("acme_nolabel", "acme.one.run")
noLabel.Label = " "
for _, tc := range []struct {
name string
plugins []party.Plugin
want []string
}{
{"unknown controller", []party.Plugin{base, contributor("acme.one", []pact.AdminActionExtension{{
Controller: "acme.missing.things", RecordActions: []pact.AdminRecordAction{namedAction("acme_x", "acme.one.run")},
}})}, []string{"acme.one", "acme.missing.things", "unknown admin controller"}},
{"target without a preview block", []party.Plugin{base, contributor("acme.one", []pact.AdminActionExtension{{
Controller: "acme.ext.items", RecordActions: []pact.AdminRecordAction{namedAction("acme_x", "acme.one.run")},
}})}, []string{"acme.one", "acme.ext.items", "no preview block"}},
{"duplicate of the owner's action", []party.Plugin{base, contributor("acme.one", toUsers(namedAction("deactivate", "acme.one.run")))},
[]string{"acme.one", `"deactivate"`, AdministratorsControllerID, "plugin summercms.cabana already registers"}},
{"duplicate between two contributors", []party.Plugin{base,
contributor("acme.one", toUsers(namedAction("acme_same", "acme.one.run"))),
contributor("acme.two", toUsers(namedAction("acme_same", "acme.two.run"))),
}, []string{"plugin acme.two adds", `"acme_same"`, "plugin acme.one already registers"}},
{"reserved name create", []party.Plugin{base, contributor("acme.one", toUsers(namedAction("create", "acme.one.run")))},
[]string{"acme.one", `"create"`, "reserved"}},
{"reserved name delete", []party.Plugin{base, contributor("acme.one", toUsers(namedAction("delete", "acme.one.run")))},
[]string{"acme.one", `"delete"`, "reserved"}},
{"name is not an identifier", []party.Plugin{base, contributor("acme.one", toUsers(namedAction("acme-ping", "acme.one.run")))},
[]string{"acme.one", `"acme-ping"`, "not an identifier"}},
{"nil Run", []party.Plugin{base, contributor("acme.one", toUsers(noRun))},
[]string{"acme.one", `"acme_norun"`, "no Run function"}},
{"empty label", []party.Plugin{base, contributor("acme.one", toUsers(noLabel))},
[]string{"acme.one", `"acme_nolabel"`, "needs a label"}},
{"unknown permission", []party.Plugin{base, contributor("acme.one", toUsers(namedAction("acme_x", "acme.nobody.run")))},
[]string{"acme.nobody.run"}},
} {
t.Run(tc.name, func(t *testing.T) {
_, err := compileWithScreens(t, tc.plugins)
if err == nil {
t.Fatalf("compiled; want an error naming %v", tc.want)
}
for _, want := range tc.want {
if !strings.Contains(err.Error(), want) {
t.Fatalf("err = %v, want it to contain %q", err, want)
}
}
})
}
t.Run("valid contributions land after the owner's in order", func(t *testing.T) {
plugins := []party.Plugin{base,
contributor("acme.one", toUsers(namedAction("acme_a", "acme.one.run"), namedAction("acme_b", "acme.one.run"))),
contributor("acme.two", toUsers(namedAction("acme_c", "acme.two.run"))),
}
reg, err := compileWithScreens(t, plugins)
if err != nil {
t.Fatal(err)
}
cc, _ := reg.Get(AdministratorsControllerID)
if got := fmt.Sprint(cc.Form.recordActions); got != "[activate deactivate restore acme_a acme_b acme_c]" {
t.Fatalf("record actions = %s", got)
}
for name, plugin := range map[string]string{"acme_a": "acme.one", "acme_b": "acme.one", "acme_c": "acme.two"} {
if cc.contributed[name] != plugin || !recordActionOfName(cc, name) {
t.Fatalf("%s contributed by %q (want %s), resolvable %v", name, cc.contributed[name], plugin, recordActionOfName(cc, name))
}
}
if _, ok := cc.contributed["activate"]; ok {
t.Fatal("the owner's action is marked contributed")
}
// The roles screen is untouched.
roles, _ := reg.Get(RolesControllerID)
if roles.contributed != nil {
t.Fatalf("roles contributed = %v", roles.contributed)
}
})
t.Run("an extension without record actions is ignored", func(t *testing.T) {
plugins := []party.Plugin{base, contributor("acme.one", []pact.AdminActionExtension{{Controller: "acme.missing.things"}})}
if _, err := compileWithScreens(t, plugins); err != nil {
t.Fatal(err)
}
})
t.Run("a contributed action runs with the owner's record", func(t *testing.T) {
var got pact.AdminRecordActionInput
action := namedAction("acme_spy", "acme.one.run")
action.Run = func(_ context.Context, in pact.AdminRecordActionInput) (pact.AdminRecordActionResult, error) {
got = in
return pact.AdminRecordActionResult{}, nil
}
reg, err := compileWithScreens(t, []party.Plugin{base, contributor("acme.one", toUsers(action))})
if err != nil {
t.Fatal(err)
}
cc, _ := reg.Get(AdministratorsControllerID)
resolved, ok := recordActionOf(cc, "acme_spy")
if !ok {
t.Fatal("acme_spy not resolvable")
}
if _, err := resolved.Run(context.Background(), pact.AdminRecordActionInput{RecordID: 7, Record: &BackendUser{ID: 7}}); err != nil || got.RecordID != 7 {
t.Fatalf("run = %+v, %v", got, err)
}
})
}