- bulk action: empty, duplicate, unordered, absent, partial, out-of-scope, rollback, concurrent runs, permissions, CSRF, body cap - record action: scope, Applies, strict body, offered order, rollback, Applies error - ForbiddenError from every Form hook, the bulk delete and the relation link and child hooks - permission editor modes, locked codes and provider errors; relation locks on create, update and belongsTo - TestPhase121BootErrors: every boot error of plans 01 and 02 with plugin, controller and file - pact: the action, row state and filter contracts on a sample controller
145 lines
7.1 KiB
Go
145 lines
7.1 KiB
Go
package cabana_test
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.golem15.com/golem15/summercms/modules/cabana"
|
|
)
|
|
|
|
// TestPreviewSchema: the form schema reports the preview block, the
|
|
// preview-only field with its context, and nothing of either for a form
|
|
// without a preview (D-11).
|
|
func TestPreviewSchema(t *testing.T) {
|
|
env, _ := newRosterEnv(t)
|
|
view, raw := rosterFormSchema(t, env, "bearer")
|
|
if view.Preview == nil || view.Preview.HeaderPartial != "status" || !strings.Contains(raw, `"preview":{"headerPartial":"status"}`) {
|
|
t.Fatalf("preview block = %+v in %s", view.Preview, raw)
|
|
}
|
|
if !strings.Contains(raw, `"name":"joined_ip","type":"text","label":"Joined from IP address","context":"preview"}`) {
|
|
t.Fatalf("the preview-only field is not in the schema: %s", raw)
|
|
}
|
|
if !strings.Contains(raw, `{"name":"name","type":"text","label":"Name","span":"left"}`) {
|
|
t.Fatalf("a field without a context got one: %s", raw)
|
|
}
|
|
// The same schema for the limited administrator: the preview is part of
|
|
// the form, not a permission.
|
|
if limited, _ := rosterFormSchema(t, env, "limited"); limited.Preview == nil {
|
|
t.Fatal("the limited administrator gets no preview block")
|
|
}
|
|
// preview: {} enables the screen without a header partial.
|
|
bare, _ := newRosterEnvWith(t, func(p *rosterPlugin) {
|
|
p.fsys = rosterTree(t, map[string]string{"controllers/people/config_form.yaml": rosterFormHead + "preview: {}\n"})
|
|
})
|
|
view, raw = rosterFormSchema(t, bare, "bearer")
|
|
if view.Preview == nil || view.Preview.HeaderPartial != "" || !strings.Contains(raw, `"preview":{}`) {
|
|
t.Fatalf("preview: {} = %+v in %s", view.Preview, raw)
|
|
}
|
|
// A form without the key has no preview at all.
|
|
none, _ := newRosterEnvWith(t, func(p *rosterPlugin) {
|
|
p.fsys = rosterTree(t, map[string]string{"controllers/people/config_form.yaml": rosterFormHead})
|
|
})
|
|
view, raw = rosterFormSchema(t, none, "bearer")
|
|
if view.Preview != nil || strings.Contains(raw, `"preview":{}`) || strings.Contains(raw, "headerPartial") {
|
|
t.Fatalf("a form without preview reports one: %s", raw)
|
|
}
|
|
}
|
|
|
|
// TestPreviewFieldNeverWritten: a field with context preview is returned by
|
|
// show and ignored by create and update (T-12.1-14).
|
|
func TestPreviewFieldNeverWritten(t *testing.T) {
|
|
env, gdb := newRosterEnv(t)
|
|
ip := "203.0.113.7"
|
|
id := rosterInsert(t, gdb, rosterPerson{Tenant: "acme", Name: "Ada", Active: true, JoinedIP: &ip})
|
|
rec := env.expect(t, http.StatusOK, http.MethodGet, rosterPath(id, ""), "", "bearer")
|
|
if got := rosterRecord(t, rec.Body.Bytes()).Data["joined_ip"]; got != ip {
|
|
t.Fatalf("show joined_ip = %v", got)
|
|
}
|
|
for _, body := range []string{`{"joined_ip":"198.51.100.1"}`, `{"name":"Ada L","joined_ip":null}`, `{"name":"Ada L","joined_ip":""}`} {
|
|
rec = env.expect(t, http.StatusOK, http.MethodPut, rosterPath(id, ""), body, "bearer")
|
|
if got := rosterRecord(t, rec.Body.Bytes()).Data["joined_ip"]; got != ip {
|
|
t.Fatalf("update %s answered joined_ip = %v", body, got)
|
|
}
|
|
if stored := rosterLoad(t, gdb, id); stored.JoinedIP == nil || *stored.JoinedIP != ip {
|
|
t.Fatalf("update %s wrote joined_ip = %v", body, stored.JoinedIP)
|
|
}
|
|
}
|
|
if rosterLoad(t, gdb, id).Name != "Ada L" {
|
|
t.Fatal("the writable part of the body was not saved")
|
|
}
|
|
rec = env.expect(t, http.StatusCreated, http.MethodPost, rosterPeople, `{"name":"New","joined_ip":"198.51.100.2",`+rosterPair+`}`, "bearer")
|
|
created, _ := rosterRecord(t, rec.Body.Bytes()).Data["id"].(float64)
|
|
if stored := rosterLoad(t, gdb, uint(created)); stored.JoinedIP != nil {
|
|
t.Fatalf("create wrote joined_ip = %q", *stored.JoinedIP)
|
|
}
|
|
// A nested value for it is ignored like any other value.
|
|
env.expect(t, http.StatusOK, http.MethodPut, rosterPath(id, ""), `{"joined_ip":{"x":1}}`, "bearer")
|
|
}
|
|
|
|
// TestPreviewHeaderPartialScoped: the status hint is served through the
|
|
// partial route with the form scope and the controller's permission, as
|
|
// nodes (T-12.1-15).
|
|
func TestPreviewHeaderPartialScoped(t *testing.T) {
|
|
env, gdb := newRosterEnv(t)
|
|
banned := rosterInsert(t, gdb, rosterPerson{Tenant: "acme", Name: "Bea", Active: true, Banned: true})
|
|
gone := rosterInsert(t, gdb, rosterPerson{Tenant: "acme", Name: "Gone", Active: true, DeletedAt: rosterDeleted()})
|
|
idle := rosterInsert(t, gdb, rosterPerson{Tenant: "acme", Name: "Idle"})
|
|
foreign := rosterInsert(t, gdb, rosterPerson{Tenant: "other", Name: "Zed", Banned: true})
|
|
hint := func(id uint) string { return fmt.Sprintf("%s/partials/status?id=%d", rosterPeople, id) }
|
|
for id, want := range map[uint][2]string{
|
|
banned: {"summer-callout--danger", "This person is banned"},
|
|
gone: {"summer-callout--danger", "This person is archived"},
|
|
idle: {"summer-callout--warning", "This person is not active"},
|
|
} {
|
|
rec := env.expect(t, http.StatusOK, http.MethodGet, hint(id), "", "limited")
|
|
if body := rec.Body.String(); !strings.Contains(body, want[0]) || !strings.Contains(body, want[1]) || strings.Contains(body, "<") {
|
|
t.Fatalf("hint of %d = %s", id, body)
|
|
}
|
|
var view cabana.Envelope[cabana.PartialView]
|
|
if err := json.Unmarshal(rec.Body.Bytes(), &view); err != nil || len(view.Data.Nodes) != 1 || view.Data.Nodes[0].Attrs["role"] != "status" {
|
|
t.Fatalf("hint nodes = %+v (%v)", view.Data.Nodes, err)
|
|
}
|
|
}
|
|
rec := env.expect(t, http.StatusNotFound, http.MethodGet, hint(foreign), "", "bearer")
|
|
actErrorCode(t, rec.Body.Bytes(), "not_found")
|
|
env.expect(t, http.StatusNotFound, http.MethodGet, hint(999999), "", "bearer")
|
|
env.expect(t, http.StatusNotFound, http.MethodGet, fmt.Sprintf("%s/partials/missing?id=%d", rosterPeople, banned), "", "bearer")
|
|
// The Polish request gets Polish text.
|
|
if pl := rosterLocale(env, http.MethodGet, hint(banned), "", "pl"); !strings.Contains(pl.Body.String(), "Ta osoba jest zablokowana") {
|
|
t.Fatalf("pl hint = %s", pl.Body.String())
|
|
}
|
|
// Without a token there is no hint.
|
|
req := httptest.NewRequest(http.MethodGet, adminAPI(hint(banned)), nil)
|
|
anonymous := httptest.NewRecorder()
|
|
env.h.ServeHTTP(anonymous, req)
|
|
if anonymous.Code != http.StatusUnauthorized {
|
|
t.Fatalf("anonymous hint = %d", anonymous.Code)
|
|
}
|
|
}
|
|
|
|
// TestPreviewMessagesDefaults: a form that names no preview or edit message
|
|
// gets the framework's, in the request locale.
|
|
func TestPreviewMessagesDefaults(t *testing.T) {
|
|
env, _ := newRosterEnvWith(t, func(p *rosterPlugin) {
|
|
p.fsys = rosterTree(t, map[string]string{"controllers/people/config_form.yaml": rosterFormHead + "preview: {}\n"})
|
|
})
|
|
view, _ := rosterFormSchema(t, env, "bearer")
|
|
if view.Messages.Preview["other"] != "Record preview" || view.Messages.Edit["other"] != "Edit record" {
|
|
t.Fatalf("default messages = %v / %v", view.Messages.Preview, view.Messages.Edit)
|
|
}
|
|
pl := rosterLocale(env, http.MethodGet, rosterPeople+"/schema/form", "", "pl")
|
|
if !strings.Contains(pl.Body.String(), `"preview":{"other":"Podgląd rekordu"}`) {
|
|
t.Fatalf("pl default messages = %s", pl.Body.String())
|
|
}
|
|
// The fixture's own messages replace them.
|
|
own, _ := newRosterEnv(t)
|
|
view, _ = rosterFormSchema(t, own, "bearer")
|
|
if view.Messages.Preview["other"] != "Person details" || view.Messages.Edit["other"] != "Edit person" {
|
|
t.Fatalf("plugin messages = %v / %v", view.Messages.Preview, view.Messages.Edit)
|
|
}
|
|
}
|