test(12.1-05): unit tests for bulk and record actions, row state, forbidden, preview and the form seams
- 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
This commit is contained in:
144
modules/cabana/phase121_preview_test.go
Normal file
144
modules/cabana/phase121_preview_test.go
Normal file
@@ -0,0 +1,144 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user