- runAction writes "cabana: admin action run" with controller, action, admin_id and kind (toolbar or field; field name and record id for a widget), never values, payload, answer or record contents - the failure and data-rejected lines carry the same keys (admin_id, kind) - actionAdminID helper shared with the bulk and record lines (unchanged) - TestAdminActionRunLog pins the line on the acme.demo fixture (Postgres) - README and the permissions docs describe the run log
120 lines
4.6 KiB
Go
120 lines
4.6 KiB
Go
package cabana_test
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// TestAdminActionRunLog pins the action run log of the admin-action-audit-log
|
|
// todo (AR-18.1-03): who ran which toolbar or widget action. A successful run
|
|
// writes one "cabana: admin action run" line with the controller, action,
|
|
// administrator id and kind (plus field and record id for a widget) and never
|
|
// the posted values, payload, answer or record contents; a refused, invalid
|
|
// or failed run writes no run line, and the failure lines carry the same
|
|
// administrator id and kind. Runs on the acme.demo.gadgets fixture
|
|
// (PostgreSQL).
|
|
func TestAdminActionRunLog(t *testing.T) {
|
|
env, gdb := newActEnv(t)
|
|
const widget = "/acme/demo/gadgets/widgets/lookup"
|
|
const toolbar = "/acme/demo/gadgets/toolbar/recount"
|
|
const runPrefix = "cabana: admin action run"
|
|
secret := actInsert(t, gdb, "Secretname", "acme")
|
|
|
|
me := profileOf(t, env.expect(t, http.StatusOK, http.MethodGet, "/auth/me", "", "bearer").Body.Bytes())
|
|
if me.ID == 0 {
|
|
t.Fatal("the signed-in administrator has id 0")
|
|
}
|
|
adminToken := fmt.Sprintf("admin_id=%d", me.ID)
|
|
logs := captureLog(t)
|
|
|
|
// A toolbar run: kind toolbar, no field and no record id.
|
|
env.expect(t, http.StatusOK, http.MethodPost, toolbar, `{}`, "bearer")
|
|
env.spy.take()
|
|
runs := logs.matching(runPrefix)
|
|
if len(runs) != 1 {
|
|
t.Fatalf("toolbar run lines = %q", runs)
|
|
}
|
|
line := runs[0]
|
|
for _, want := range []string{"controller=acme.demo.gadgets", "action=recount", "kind=toolbar", adminToken} {
|
|
if !strings.Contains(line, want) {
|
|
t.Fatalf("toolbar run line lacks %s: %q", want, line)
|
|
}
|
|
}
|
|
for _, banned := range []string{"field=", "record_id="} {
|
|
if strings.Contains(line, banned) {
|
|
t.Fatalf("toolbar run line carries %s: %q", banned, line)
|
|
}
|
|
}
|
|
|
|
// A widget run on a saved record: kind field, the field name and the
|
|
// record id, but not the record, the posted value or the answer.
|
|
env.expect(t, http.StatusOK, http.MethodPost, widget,
|
|
fmt.Sprintf(`{"record_id":%d,"values":{"name":"typedsecret"}}`, secret), "bearer")
|
|
env.spy.take()
|
|
runs = logs.matching(runPrefix)
|
|
if len(runs) != 2 {
|
|
t.Fatalf("widget run lines = %q", runs)
|
|
}
|
|
line = runs[1]
|
|
for _, want := range []string{"controller=acme.demo.gadgets", "action=lookup", "kind=field", "field=lookup", fmt.Sprintf("record_id=%d", secret), adminToken} {
|
|
if !strings.Contains(line, want) {
|
|
t.Fatalf("widget run line lacks %s: %q", want, line)
|
|
}
|
|
}
|
|
for _, banned := range []string{"Secretname", "typedsecret", "looked-up", "filled in"} {
|
|
if strings.Contains(line, banned) {
|
|
t.Fatalf("widget run line carries %s: %q", banned, line)
|
|
}
|
|
}
|
|
|
|
// A widget run on the create form: no record id and no payload.
|
|
env.expect(t, http.StatusOK, http.MethodPost, widget, `{"payload":{"note":"payloadsecret"}}`, "bearer")
|
|
env.spy.take()
|
|
runs = logs.matching(runPrefix)
|
|
if len(runs) != 3 {
|
|
t.Fatalf("create-form run lines = %q", runs)
|
|
}
|
|
line = runs[2]
|
|
for _, want := range []string{"kind=field", "field=lookup", adminToken} {
|
|
if !strings.Contains(line, want) {
|
|
t.Fatalf("create-form run line lacks %s: %q", want, line)
|
|
}
|
|
}
|
|
for _, banned := range []string{"record_id=", "payloadsecret"} {
|
|
if strings.Contains(line, banned) {
|
|
t.Fatalf("create-form run line carries %s: %q", banned, line)
|
|
}
|
|
}
|
|
|
|
// Refused, invalid and failed runs write no run line.
|
|
env.expect(t, http.StatusUnprocessableEntity, http.MethodPost, widget, `{"values":{"name":"invalid"}}`, "bearer")
|
|
env.expect(t, http.StatusForbidden, http.MethodPost, widget, `{"values":{"name":"refused"}}`, "bearer")
|
|
env.expect(t, http.StatusInternalServerError, http.MethodPost, widget, `{"values":{"name":"boom"}}`, "bearer")
|
|
env.expect(t, http.StatusInternalServerError, http.MethodPost, widget, `{"payload":"big"}`, "bearer")
|
|
env.expect(t, http.StatusForbidden, http.MethodPost, toolbar, `{}`, "limited")
|
|
env.expect(t, http.StatusForbidden, http.MethodPost, widget, `{}`, "limited")
|
|
env.spy.take()
|
|
if runs = logs.matching(runPrefix); len(runs) != 3 {
|
|
t.Fatalf("a refused, invalid or failed run was logged as run: %q", runs)
|
|
}
|
|
|
|
// The failure lines name the administrator and the kind.
|
|
failed := logs.matching("cabana: admin action failed")
|
|
if len(failed) != 1 {
|
|
t.Fatalf("failure lines = %q", failed)
|
|
}
|
|
rejected := logs.matching("cabana: admin action data rejected")
|
|
if len(rejected) != 1 {
|
|
t.Fatalf("data rejected lines = %q", rejected)
|
|
}
|
|
for _, line := range []string{failed[0], rejected[0]} {
|
|
for _, want := range []string{"action=lookup", "kind=field", "field=lookup", adminToken} {
|
|
if !strings.Contains(line, want) {
|
|
t.Fatalf("failure line lacks %s: %q", want, line)
|
|
}
|
|
}
|
|
}
|
|
}
|