- 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
102 lines
5.4 KiB
Go
102 lines
5.4 KiB
Go
package cabana_test
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.golem15.com/golem15/summercms/modules/pact"
|
|
)
|
|
|
|
// TestInvisibleColumnSearchAndRows: a column with invisible: true is flagged
|
|
// in the schema, searched and sorted on the server, and never part of a row
|
|
// (D-27 G6).
|
|
func TestInvisibleColumnSearchAndRows(t *testing.T) {
|
|
env, gdb := newRosterEnv(t)
|
|
rosterInsert(t, gdb, rosterPerson{Tenant: "acme", Name: "Ada", Email: "ada@hidden.example.test", Slug: "ada", Active: true})
|
|
rosterInsert(t, gdb, rosterPerson{Tenant: "acme", Name: "Bob", Email: "bob@example.test", Slug: "bob", Active: true})
|
|
rosterInsert(t, gdb, rosterPerson{Tenant: "other", Name: "Zed", Email: "zed@hidden.example.test", Active: true})
|
|
|
|
schema := env.expect(t, http.StatusOK, http.MethodGet, rosterPeople+"/schema/list", "", "bearer").Body.String()
|
|
if !strings.Contains(schema, `{"key":"email","label":"Email","searchable":true,"sortable":true,"invisible":true}`) || strings.Count(schema, `"invisible"`) != 1 {
|
|
t.Fatalf("list schema = %s", schema)
|
|
}
|
|
for _, query := range []string{"", "?search=ada", "?search=hidden.example", "?sort=email&dir=asc"} {
|
|
raw := env.expect(t, http.StatusOK, http.MethodGet, rosterPeople+query, "", "bearer").Body.String()
|
|
if strings.Contains(raw, `"email"`) || strings.Contains(raw, "example.test") {
|
|
t.Fatalf("rows of %q carry the invisible column: %s", query, raw)
|
|
}
|
|
// The visible columns are there.
|
|
if !strings.Contains(raw, `"slug":"ada"`) {
|
|
t.Fatalf("rows of %q lack a visible column: %s", query, raw)
|
|
}
|
|
}
|
|
// Search by the invisible column finds the row, inside the list scope only.
|
|
found := env.expect(t, http.StatusOK, http.MethodGet, rosterPeople+"?search=hidden.example", "", "bearer").Body.String()
|
|
if !strings.Contains(found, `"name":"Ada"`) || strings.Contains(found, `"name":"Bob"`) || strings.Contains(found, `"name":"Zed"`) || !strings.Contains(found, `"total":1`) {
|
|
t.Fatalf("search by the invisible column = %s", found)
|
|
}
|
|
// It sorts in both directions.
|
|
asc := env.expect(t, http.StatusOK, http.MethodGet, rosterPeople+"?sort=email&dir=asc", "", "bearer").Body.String()
|
|
desc := env.expect(t, http.StatusOK, http.MethodGet, rosterPeople+"?sort=email&dir=desc", "", "bearer").Body.String()
|
|
if strings.Index(asc, `"name":"Ada"`) > strings.Index(asc, `"name":"Bob"`) || strings.Index(desc, `"name":"Ada"`) < strings.Index(desc, `"name":"Bob"`) {
|
|
t.Fatalf("sort by the invisible column:\nasc %s\ndesc %s", asc, desc)
|
|
}
|
|
// The record response is the form's, not the list's: email is a form field.
|
|
id := rosterInsert(t, gdb, rosterPerson{Tenant: "acme", Name: "Cy", Email: "cy@example.test", Active: true})
|
|
if shown := env.expect(t, http.StatusOK, http.MethodGet, rosterPath(id, ""), "", "bearer").Body.String(); !strings.Contains(shown, `"email":"cy@example.test"`) {
|
|
t.Fatalf("show = %s", shown)
|
|
}
|
|
}
|
|
|
|
// TestFilterOptionsControllerFirst: a controller that implements
|
|
// pact.FilterOptions serves a scope filter's choices, from the database,
|
|
// although the model does not.
|
|
func TestFilterOptionsControllerFirst(t *testing.T) {
|
|
env, gdb := newRosterEnv(t)
|
|
news, beta := rosterTag{Name: "news"}, rosterTag{Name: "beta"}
|
|
rosterSeed(t, gdb, &news)
|
|
rosterSeed(t, gdb, &beta)
|
|
tagged := rosterInsert(t, gdb, rosterPerson{Tenant: "acme", Name: "Tagged", Active: true})
|
|
rosterInsert(t, gdb, rosterPerson{Tenant: "acme", Name: "Bare", Active: true})
|
|
rosterSeed(t, gdb, &rosterPersonTag{PersonID: tagged, TagID: news.ID})
|
|
options := env.expect(t, http.StatusOK, http.MethodGet, rosterPeople+"/filters/tagged/options", "", "limited").Body.String()
|
|
want := fmt.Sprintf(`"data":[{"value":"%d","label":"beta"},{"value":"%d","label":"news"}]`, beta.ID, news.ID)
|
|
if !strings.Contains(options, want) {
|
|
t.Fatalf("options = %s, want %s", options, want)
|
|
}
|
|
// A row added after boot is offered: the choices are read per request.
|
|
late := rosterTag{Name: "alpha"}
|
|
rosterSeed(t, gdb, &late)
|
|
if options = env.expect(t, http.StatusOK, http.MethodGet, rosterPeople+"/filters/tagged/options", "", "bearer").Body.String(); !strings.Contains(options, `"label":"alpha"`) {
|
|
t.Fatalf("options after an insert = %s", options)
|
|
}
|
|
// The scope is still the model's.
|
|
filtered := env.expect(t, http.StatusOK, http.MethodGet, fmt.Sprintf("%s?filter[tagged]=%d", rosterPeople, news.ID), "", "bearer").Body.String()
|
|
if !strings.Contains(filtered, `"name":"Tagged"`) || strings.Contains(filtered, `"name":"Bare"`) {
|
|
t.Fatalf("filtered list = %s", filtered)
|
|
}
|
|
env.expect(t, http.StatusNotFound, http.MethodGet, rosterPeople+"/filters/missing/options", "", "bearer")
|
|
if _, ok := any(rosterPerson{}).(pact.FilterOptions); ok {
|
|
t.Fatal("the fixture model serves filter options itself")
|
|
}
|
|
}
|
|
|
|
// TestFilterOptionsModelFallback: without FilterOptions on the controller the
|
|
// model serves the choices, and its labels are translated.
|
|
func TestFilterOptionsModelFallback(t *testing.T) {
|
|
env, _ := newRosterEnvWith(t, func(p *rosterPlugin) {
|
|
p.wrap = func(inner rosterController) pact.AdminController {
|
|
return rosterBare{inner: inner, newRecord: func() any { return &rosterOptionsPerson{} }}
|
|
}
|
|
})
|
|
options := env.expect(t, http.StatusOK, http.MethodGet, rosterPeople+"/filters/tagged/options", "", "bearer").Body.String()
|
|
if !strings.Contains(options, `"data":[{"value":"1","label":"Tags"},{"value":"2","label":"Plain label"}]`) {
|
|
t.Fatalf("model options = %s", options)
|
|
}
|
|
if _, ok := any(rosterBare{}).(pact.FilterOptions); ok {
|
|
t.Fatal("the bare controller serves filter options")
|
|
}
|
|
}
|