Files
summercms/modules/cabana/action_extensions_postgres_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

64 lines
3.2 KiB
Go

package cabana_test
import (
"net/http"
"testing"
"git.golem15.com/golem15/summercms/modules/cabana"
)
// TestActionExtensionRun (D-14, D-15): a contributed record action on the
// Administrators screen is offered and runs only for principals holding both
// the screen's and the action's permissions, answers 409 once it no longer
// applies, and runs in the row-locked transaction with the record.
func TestActionExtensionRun(t *testing.T) {
env := newScreensEnv(t)
both := env.seedAdmin(t, adminSeed{Name: "ext-both", RolePermissions: map[string]int{cabana.PermissionManageUsers: 1, pingPermission: 1}})
bothToken := env.login(t, both.Login)
screenOnly := env.seedAdmin(t, adminSeed{Name: "ext-screen", RolePermissions: manageUsers})
screenToken := env.login(t, screenOnly.Login)
actionOnly := env.seedAdmin(t, adminSeed{Name: "ext-action", RolePermissions: map[string]int{pingPermission: 1}})
actionToken := env.login(t, actionOnly.Login)
target := env.seedAdmin(t, adminSeed{Name: "ext-target"})
ping := userURL(target.ID, "actions", "acme_ping")
names := recordActionNames(env.expect(t, http.StatusOK, http.MethodGet, userURL(target.ID), bothToken, nil))
if len(names) == 0 || names[len(names)-1] != "acme_ping" || names[0] != "deactivate" {
t.Fatalf("offered to the holder of both = %v, want the owner's actions then acme_ping", names)
}
if names := recordActionNames(env.expect(t, http.StatusOK, http.MethodGet, userURL(target.ID), screenToken, nil)); containsName(names, "acme_ping") {
t.Fatalf("offered without %s = %v", pingPermission, names)
}
env.expectRefusal(t, http.StatusForbidden, http.MethodPost, ping, screenToken, map[string]any{}, "")
env.expectRefusal(t, http.StatusForbidden, http.MethodGet, userURL(target.ID), actionToken, nil, "")
env.expectRefusal(t, http.StatusForbidden, http.MethodPost, ping, actionToken, map[string]any{}, "")
if runs, _ := env.spy.runs(); len(runs) != 0 {
t.Fatalf("refused requests ran the action: %v", runs)
}
// Shown while it applies, then Applies turns false before the POST.
env.spy.refuse.Store(true)
if rec := env.call(t, http.MethodPost, ping, bothToken, map[string]any{}); rec.Code != http.StatusConflict {
t.Fatalf("POST after Applies turned false status=%d body=%s", rec.Code, rec.Body.String())
}
if names := recordActionNames(env.expect(t, http.StatusOK, http.MethodGet, userURL(target.ID), bothToken, nil)); containsName(names, "acme_ping") {
t.Fatalf("offered while it does not apply: %v", names)
}
env.spy.refuse.Store(false)
if runs, _ := env.spy.runs(); len(runs) != 0 {
t.Fatalf("a 409 ran the action: %v", runs)
}
result := dataOf(env.expect(t, http.StatusOK, http.MethodPost, ping, bothToken, map[string]any{}))
if result["message"] != "Pinged" {
t.Fatalf("result = %v", result)
}
runs, withTx := env.spy.runs()
if len(runs) != 1 || runs[0] != uint64(target.ID) || !withTx[0] {
t.Fatalf("runs = %v tx = %v, want one run on %d inside the transaction", runs, withTx, target.ID)
}
// A record the screen hides is not found for the action either.
hidden := env.seedAdmin(t, adminSeed{Name: "ext-hidden", Superuser: true})
env.expectRefusal(t, http.StatusNotFound, http.MethodPost, userURL(hidden.ID, "actions", "acme_ping"), bothToken, map[string]any{}, "")
}