pact.SettingsItem gains an additive Controller field, the equivalent of a WinterCMS registerSettings 'url' => Backend::url(...) entry. A link entry declares no Model, Form or NewModel and needs no AdminFS; start-up fails when it combines Controller with a singleton form or a model, or names an unregistered controller. Settings codes stay one namespace. GET /settings lists a link entry with its controller only when the principal passes the item's permissions and may open the controller. Registry.Setting never returns a link entry, so the singleton settings endpoints answer 404 for its code. SettingsEntry carries controller, empty for singletons; the OpenAPI document, generated SPA types and settings fixture follow, and the pact and cabana READMEs and the settings docs describe the link.
121 lines
4.1 KiB
Go
121 lines
4.1 KiB
Go
package cabana_test
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"testing"
|
|
|
|
"git.golem15.com/golem15/summercms/modules/backpack"
|
|
"git.golem15.com/golem15/summercms/modules/bouncer"
|
|
"git.golem15.com/golem15/summercms/modules/cabana"
|
|
"git.golem15.com/golem15/summercms/modules/compass"
|
|
"git.golem15.com/golem15/summercms/modules/party"
|
|
)
|
|
|
|
func ExampleCompileList() {
|
|
// The plugin embeds its controllers/ and models/ trees; the example reads
|
|
// the same files from testdata.
|
|
fsys := os.DirFS("testdata/docs")
|
|
list, err := cabana.CompileList("acme.blog", PostsController{}, fsys)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
return
|
|
}
|
|
fmt.Println(list.Title, list.RecordsPerPage, list.PerPageOptions, list.ToolbarButtons)
|
|
for _, c := range list.Columns {
|
|
fmt.Printf("column %s type=%q searchable=%v sortable=%v\n", c.Key, c.Type, c.Searchable, c.Sortable)
|
|
}
|
|
for _, f := range list.Filters {
|
|
fmt.Printf("filter %s type=%s column=%s\n", f.Name, f.Type, f.Column)
|
|
}
|
|
// Output:
|
|
// acme.blog::lang.posts.title 20 [20 50 100] [create delete]
|
|
// column title type="" searchable=true sortable=true
|
|
// column published type="switch" searchable=false sortable=true
|
|
// column published_at type="datetime" searchable=false sortable=true
|
|
// filter published type=switch column=published
|
|
// filter published_at type=daterange column=published_at
|
|
}
|
|
|
|
func ExampleCompileForm() {
|
|
fsys := os.DirFS("testdata/docs")
|
|
form, err := cabana.CompileForm("acme.blog", PostsController{}, fsys)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
return
|
|
}
|
|
for _, f := range form.Fields {
|
|
fmt.Printf("%s %s span=%q tab=%q required=%v options=%d\n", f.Name, f.Type, f.Span, f.Tab, f.Required, len(f.Options))
|
|
}
|
|
// Output:
|
|
// title text span="left" tab="" required=true options=0
|
|
// slug text span="right" tab="" required=false options=0
|
|
// status dropdown span="left" tab="" required=false options=2
|
|
// published switch span="right" tab="" required=false options=0
|
|
// content textarea span="" tab="acme.blog::lang.posts.tab_content" required=false options=0
|
|
}
|
|
|
|
func ExampleActivate() {
|
|
cfg, err := compass.Open(compass.Options{Dir: "config", Env: "development", Environ: []string{}})
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
return
|
|
}
|
|
// Set through SUMMER_ADMIN__JWT__SECRET in a real deployment.
|
|
_ = cfg.Set("admin.jwt.secret", "test-only-secret-with-at-least-32-bytes")
|
|
_ = cfg.Set("backend.uri", "/admin")
|
|
|
|
routes, err := cabana.Activate(backpack.New(cfg), []party.Plugin{&BlogPlugin{}})
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
return
|
|
}
|
|
fmt.Println(routes.Prefix)
|
|
|
|
editor := &bouncer.Principal{ID: 7, Backend: true, PermissionGrants: map[string]bool{"acme.blog.*": true}}
|
|
fmt.Println(cabana.Allows(editor, PostsController{}.RequiredPermissions()))
|
|
fmt.Println(cabana.Allows(editor, []string{"acme.shop.access_orders"}))
|
|
// Output:
|
|
// /admin
|
|
// true
|
|
// false
|
|
}
|
|
|
|
// TestDocsDeclarations checks the plugin declarations the Backend pages show.
|
|
func TestDocsDeclarations(t *testing.T) {
|
|
p := &BlogPlugin{}
|
|
if n := len(p.Permissions()); n != 2 {
|
|
t.Fatalf("Permissions() = %d entries", n)
|
|
}
|
|
if nav := p.Navigation(); len(nav) != 1 || nav[0].Controller != "acme.blog.posts" {
|
|
t.Fatalf("Navigation() = %+v", nav)
|
|
}
|
|
settings := p.Settings()
|
|
if len(settings) != 1 {
|
|
t.Fatalf("Settings() = %+v", settings)
|
|
}
|
|
if _, ok := settings[0].NewModel().(*BlogSettings); !ok {
|
|
t.Fatal("settings model is not *BlogSettings")
|
|
}
|
|
if rule := (BlogSettings{}).Rules()["posts_per_page"]; rule == "" {
|
|
t.Fatal("BlogSettings has no posts_per_page rule")
|
|
}
|
|
}
|
|
|
|
// TestDocsSettingsLink activates the plugin with a settings entry that links
|
|
// to its posts controller, as the Settings page shows.
|
|
func TestDocsSettingsLink(t *testing.T) {
|
|
cfg, err := compass.Open(compass.Options{Dir: "config", Env: "development", Environ: []string{}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_ = cfg.Set("admin.jwt.secret", "test-only-secret-with-at-least-32-bytes")
|
|
if _, err := cabana.Activate(backpack.New(cfg), []party.Plugin{&LinkedBlogPlugin{}}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
settings := (&LinkedBlogPlugin{}).Settings()
|
|
if len(settings) != 2 || settings[1].Controller != "acme.blog.posts" || settings[1].Model != "" {
|
|
t.Fatalf("Settings() = %+v", settings)
|
|
}
|
|
}
|