feat: add the bm:seed command
Validates a whole JSON file (course keys, meeting numbers, values, dates) and upserts meetings, courses, class sessions, deadlines and readings in one transaction, matched on their natural identity. Prints a created, updated and unchanged summary; --dry-run only validates. Ships a small example fixture with made-up data.
This commit is contained in:
171
plugin_test.go
Normal file
171
plugin_test.go
Normal file
@@ -0,0 +1,171 @@
|
||||
package bm_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
_ "git.golem15.com/golem15/sm-user-plugin"
|
||||
"git.golem15.com/golem15/summercms/modules/backpack"
|
||||
"git.golem15.com/golem15/summercms/modules/bonfire"
|
||||
"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/pact"
|
||||
"git.golem15.com/golem15/summercms/modules/party"
|
||||
"git.golem15.com/golem15/summercms/modules/surf"
|
||||
bm "git.golem15.com/jakub/sm-bm-plugin"
|
||||
)
|
||||
|
||||
const (
|
||||
testUserSecret = "jz-bm-test-only-user-jwt-secret-32bytes"
|
||||
testAdminSecret = "jz-bm-test-only-admin-jwt-secret-32bytes"
|
||||
)
|
||||
|
||||
// testConfig is the application config the tests boot with: the HTTP
|
||||
// limits, the golem15.user JWT secret and the admin secret.
|
||||
func testConfig(t *testing.T) *compass.Config {
|
||||
t.Helper()
|
||||
dir := t.TempDir()
|
||||
httpYAML := "body_limits:\n default_bytes: 1048576\n upload_bytes: 10485760\n"
|
||||
if err := os.WriteFile(filepath.Join(dir, "http.yaml"), []byte(httpYAML), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cfg, err := compass.Open(compass.Options{
|
||||
Dir: dir,
|
||||
Environ: []string{
|
||||
"SUMMER_ENV=testing",
|
||||
"SUMMER_GOLEM15__USER__JWT__SECRET=" + testUserSecret,
|
||||
"SUMMER_ADMIN__JWT__SECRET=" + testAdminSecret,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
// activate boots golem15.user and jz.bm the way the generated main does.
|
||||
func activate(t *testing.T, app *backpack.App) []party.Plugin {
|
||||
t.Helper()
|
||||
plugins, err := party.Activate(app, []string{"golem15.user", "jz.bm"})
|
||||
if err != nil {
|
||||
t.Fatalf("Activate: %v", err)
|
||||
}
|
||||
return plugins
|
||||
}
|
||||
|
||||
func bmPlugin(t *testing.T, plugins []party.Plugin) *bm.Plugin {
|
||||
t.Helper()
|
||||
for _, p := range plugins {
|
||||
if b, ok := p.(*bm.Plugin); ok {
|
||||
return b
|
||||
}
|
||||
}
|
||||
t.Fatal("jz.bm was not activated")
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestPluginActivates(t *testing.T) {
|
||||
app := backpack.New(testConfig(t))
|
||||
p := bmPlugin(t, activate(t, app))
|
||||
if p.ID() != "jz.bm" || !slices.Equal(p.Requires(), []string{"golem15.user"}) {
|
||||
t.Errorf("ID %q Requires %v", p.ID(), p.Requires())
|
||||
}
|
||||
if n := len(p.Models()); n != 9 {
|
||||
t.Errorf("Models = %d, want 9", n)
|
||||
}
|
||||
if n := len(p.Migrations()); n != 10 {
|
||||
t.Errorf("Migrations = %d, want 10", n)
|
||||
}
|
||||
if !slices.ContainsFunc(p.Commands(), func(c bonfire.Command) bool { return c.Name == "bm:seed" }) {
|
||||
t.Error("bm:seed is not registered")
|
||||
}
|
||||
if got := app.Config.Int("jz.bm.materials_per_page"); got != 20 {
|
||||
t.Errorf("jz.bm.materials_per_page = %d, want the default 20", got)
|
||||
}
|
||||
if got := app.Config.Int("jz.bm.dashboard_limit"); got != 5 {
|
||||
t.Errorf("jz.bm.dashboard_limit = %d, want the default 5", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoutes(t *testing.T) {
|
||||
app := backpack.New(testConfig(t))
|
||||
plugins := activate(t, app)
|
||||
router, err := surf.BuildRouter(app, plugins)
|
||||
if err != nil {
|
||||
t.Fatalf("BuildRouter: %v", err)
|
||||
}
|
||||
want := []string{
|
||||
"GET /_bm/api/v1/dashboard",
|
||||
"GET /_bm/api/v1/courses",
|
||||
"GET /_bm/api/v1/courses/{id}",
|
||||
"GET /_bm/api/v1/schedule",
|
||||
"GET /_bm/api/v1/meetings",
|
||||
"GET /_bm/api/v1/deadlines",
|
||||
"GET /_bm/api/v1/readings",
|
||||
"PUT /_bm/api/v1/readings/{id}/progress",
|
||||
"DELETE /_bm/api/v1/readings/{id}/progress",
|
||||
"GET /_bm/api/v1/materials",
|
||||
"GET /_bm/api/v1/materials/{id}",
|
||||
}
|
||||
var got []string
|
||||
for _, rt := range router.Routes() {
|
||||
if rt.PluginID != "jz.bm" {
|
||||
continue
|
||||
}
|
||||
got = append(got, rt.Method+" "+rt.Pattern)
|
||||
if !slices.Contains(rt.Middleware, "jwt.auth") {
|
||||
t.Errorf("%s %s is not behind jwt.auth: %v", rt.Method, rt.Pattern, rt.Middleware)
|
||||
}
|
||||
}
|
||||
if !slices.Equal(got, want) {
|
||||
t.Errorf("routes =\n%v\nwant\n%v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminCompiles(t *testing.T) {
|
||||
app := backpack.New(testConfig(t))
|
||||
plugins := activate(t, app)
|
||||
p := bmPlugin(t, plugins)
|
||||
routes, err := cabana.Activate(app, plugins)
|
||||
if err != nil {
|
||||
t.Fatalf("cabana.Activate: %v", err)
|
||||
}
|
||||
if routes == nil {
|
||||
t.Fatal("no admin routes")
|
||||
}
|
||||
declared := map[string]bool{}
|
||||
for _, perm := range p.Permissions() {
|
||||
declared[perm.Code] = true
|
||||
}
|
||||
for _, ctl := range p.AdminControllers() {
|
||||
perms := ctl.(pact.AdminPermissioned).RequiredPermissions()
|
||||
for _, code := range perms {
|
||||
if !declared[code] {
|
||||
t.Errorf("%s requires undeclared %s", ctl.ID(), code)
|
||||
}
|
||||
}
|
||||
if rec := ctl.(pact.AdminRecordSource).NewRecord(); rec == nil {
|
||||
t.Errorf("%s has no record model", ctl.ID())
|
||||
}
|
||||
form, err := cabana.CompileForm("jz.bm", ctl, p.AdminFS())
|
||||
if err != nil {
|
||||
t.Errorf("%s form: %v", ctl.ID(), err)
|
||||
} else if len(form.Fields) == 0 {
|
||||
t.Errorf("%s form has no fields", ctl.ID())
|
||||
}
|
||||
if _, err := cabana.CompileList("jz.bm", ctl, p.AdminFS()); err != nil {
|
||||
t.Errorf("%s list: %v", ctl.ID(), err)
|
||||
}
|
||||
editor := &bouncer.Principal{ID: 1, Backend: true, PermissionGrants: map[string]bool{"jz.bm.*": true}}
|
||||
if !cabana.Allows(editor, perms) {
|
||||
t.Errorf("jz.bm.* does not open %s", ctl.ID())
|
||||
}
|
||||
}
|
||||
nav := p.Navigation()
|
||||
if len(nav) != 1 || len(nav[0].SideMenu) != 7 {
|
||||
t.Errorf("navigation = %+v, want one group with 7 side items", nav)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user