- signpost: activation refusals, host overrides, D-02 pattern layers incl. PatternFrom, ReferenceOptions depths - seaglass: removal report, bluemonday bypass corpus, Check - pact Option.Depth omitempty and the menu item type contracts; party activates menu item types after Register and before the first Boot - cabana: repeater compile and decode, dependsOn compile and recheck, richeditor keys and notices, reorder and singleton boot checks (Docker-free); repeater save, dependent recheck, richeditor save, reorder and singleton routes on Postgres; conformance cases for the documented 404, 409 and 422 answers of the new routes - admin: richeditor value contract, link popover, notices and chunk failure; repeater bounds, remove and reorder, collapsed rows; dependent dropdown states and last request wins; reorder announcements, partition switch and tree rules; SingletonForm spec - gate: required names in the go, admin and postgres stages
114 lines
5.5 KiB
Go
114 lines
5.5 KiB
Go
package cabana_test
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.golem15.com/golem15/summercms/modules/pact"
|
|
)
|
|
|
|
// TestSingletonCompile pins the D-11 boot rules, Docker-free: a singleton
|
|
// controller needs config_form.yaml and no config_list.yaml, and may keep
|
|
// one (its create button is dropped then).
|
|
func TestSingletonCompile(t *testing.T) {
|
|
if err := activateCompanions(t, companionsFSPlugin{fsys: companionsOverlay(t, nil)}); err != nil {
|
|
t.Fatalf("fixture without a list: %v", err)
|
|
}
|
|
withList := companionsOverlay(t, map[string]string{
|
|
"controllers/profiles/config_list.yaml": "list: ~/plugins/acme/companions/models/profile/columns.yaml\nmodelClass: Profile\ntoolbar:\n buttons: [create]\n",
|
|
"models/profile/columns.yaml": "columns:\n title:\n label: Title\n",
|
|
})
|
|
if err := activateCompanions(t, companionsFSPlugin{fsys: withList}); err != nil {
|
|
t.Fatalf("singleton with a list: %v", err)
|
|
}
|
|
err := activateCompanions(t, companionsFSPlugin{fsys: companionsOverlay(t, map[string]string{"controllers/profiles/config_form.yaml": ""})})
|
|
if err == nil || !strings.Contains(err.Error(), "acme.companions.profiles: a singleton controller (pact.AdminSingleton) needs config_form.yaml") {
|
|
t.Fatalf("singleton without a form: err = %v", err)
|
|
}
|
|
// A controller that is not a singleton still needs its list.
|
|
err = activateCompanions(t, companionsFSPlugin{
|
|
fsys: companionsOverlay(t, nil),
|
|
controllers: []pact.AdminController{plainProfiles{}},
|
|
})
|
|
if err == nil || !strings.Contains(err.Error(), "config_list.yaml") {
|
|
t.Fatalf("plain controller without a list: err = %v", err)
|
|
}
|
|
}
|
|
|
|
// plainProfiles is the profiles controller without pact.AdminSingleton.
|
|
type plainProfiles struct{}
|
|
|
|
func (plainProfiles) ID() string { return "acme.companions.profiles" }
|
|
func (plainProfiles) ModelName() string { return "Profile" }
|
|
func (plainProfiles) ConfigDir() string { return "controllers/profiles" }
|
|
func (plainProfiles) RequiredPermissions() []string { return []string{"acme.companions.access"} }
|
|
func (plainProfiles) NewRecord() any { return &companionsProfile{} }
|
|
|
|
// TestSingleton pins the D-11 routes on Postgres: the singleton route
|
|
// creates the record once and answers its id; the record shows and updates; list, create, delete, bulk delete
|
|
// and reorder answer 404; the form schema says singleton; a controller that
|
|
// is not a singleton answers 404 on the route; anonymous requests 401.
|
|
func TestSingleton(t *testing.T) {
|
|
e := newCompanionsEnv(t)
|
|
first := e.send(t, http.MethodGet, "/acme/companions/profiles/singleton", nil, true)
|
|
id := dataID(t, first.Body.Bytes())
|
|
for range 2 {
|
|
if again := dataID(t, e.send(t, http.MethodGet, "/acme/companions/profiles/singleton", nil, true).Body.Bytes()); again != id {
|
|
t.Fatalf("id = %d, want %d", again, id)
|
|
}
|
|
}
|
|
var count int64
|
|
if err := e.db.Model(&companionsProfile{}).Count(&count).Error; err != nil || count != 1 {
|
|
t.Fatalf("profiles = %d (%v)", count, err)
|
|
}
|
|
var profile companionsProfile
|
|
if err := e.db.First(&profile, id).Error; err != nil || profile.Key != "default" || profile.Title != "Default" {
|
|
t.Fatalf("profile = %+v (%v)", profile, err)
|
|
}
|
|
|
|
path := fmt.Sprintf("/acme/companions/profiles/%d", id)
|
|
if rec := e.send(t, http.MethodGet, path, nil, true); rec.Code != http.StatusOK || !strings.Contains(rec.Body.String(), `"title":"Default"`) {
|
|
t.Fatalf("show = %d %s", rec.Code, rec.Body.String())
|
|
}
|
|
if rec := e.send(t, http.MethodPut, path, map[string]any{"title": "Renamed"}, true); rec.Code != http.StatusOK {
|
|
t.Fatalf("update = %d %s", rec.Code, rec.Body.String())
|
|
}
|
|
if err := e.db.First(&profile, id).Error; err != nil || profile.Title != "Renamed" || profile.Key != "default" {
|
|
t.Fatalf("updated profile = %+v (%v)", profile, err)
|
|
}
|
|
if again := dataID(t, e.send(t, http.MethodGet, "/acme/companions/profiles/singleton", nil, true).Body.Bytes()); again != id {
|
|
t.Fatalf("id after update = %d", again)
|
|
}
|
|
|
|
for _, tc := range []struct {
|
|
method, path string
|
|
body any
|
|
auth bool
|
|
status int
|
|
}{
|
|
{http.MethodGet, "/acme/companions/profiles", nil, true, http.StatusNotFound},
|
|
{http.MethodPost, "/acme/companions/profiles", map[string]any{"title": "x"}, true, http.StatusNotFound},
|
|
{http.MethodDelete, path, nil, true, http.StatusNotFound},
|
|
{http.MethodPost, "/acme/companions/profiles/bulk-delete", map[string]any{"ids": []uint{id}}, true, http.StatusNotFound},
|
|
{http.MethodGet, "/acme/companions/profiles/reorder", nil, true, http.StatusNotFound},
|
|
{http.MethodGet, "/acme/companions/links/singleton", nil, true, http.StatusNotFound},
|
|
{http.MethodGet, "/acme/companions/profiles/singleton", nil, false, http.StatusUnauthorized},
|
|
} {
|
|
if rec := e.send(t, tc.method, tc.path, tc.body, tc.auth); rec.Code != tc.status {
|
|
t.Fatalf("%s %s status=%d want %d body=%s", tc.method, tc.path, rec.Code, tc.status, rec.Body.String())
|
|
}
|
|
}
|
|
if err := e.db.Model(&companionsProfile{}).Count(&count).Error; err != nil || count != 1 {
|
|
t.Fatalf("profiles after refused requests = %d (%v)", count, err)
|
|
}
|
|
schema := e.send(t, http.MethodGet, "/acme/companions/profiles/schema/form", nil, true)
|
|
if !strings.Contains(schema.Body.String(), `"singleton":true`) {
|
|
t.Fatalf("form schema = %s", schema.Body.String())
|
|
}
|
|
if links := e.send(t, http.MethodGet, "/acme/companions/links/schema/form", nil, true); strings.Contains(links.Body.String(), `"singleton":true`) {
|
|
t.Fatalf("a plain form schema says singleton: %s", links.Body.String())
|
|
}
|
|
}
|