test(17-06): framework and admin SPA unit tests for the blog companions
- 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
This commit is contained in:
113
modules/cabana/singleton_test.go
Normal file
113
modules/cabana/singleton_test.go
Normal file
@@ -0,0 +1,113 @@
|
||||
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())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user