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) } }