package user import ( "context" "net/http" "strings" "testing" "git.golem15.com/golem15/sm-user-plugin/classes" "git.golem15.com/golem15/sm-user-plugin/models" "git.golem15.com/golem15/summercms/modules/backpack" "git.golem15.com/golem15/summercms/modules/pact" "git.golem15.com/golem15/summercms/modules/party" ) const acmeShopOrders = "acme.shop.orders" // acmeShopPermissions declares a code of its own and acme.blog's manage code // again, under another label: activated after acme.blog, its duplicate is // dropped. type acmeShopPermissions struct{} func (acmeShopPermissions) ID() string { return "acme.shop" } func (acmeShopPermissions) Requires() []string { return nil } func (acmeShopPermissions) Register(*backpack.App) error { return nil } func (acmeShopPermissions) Boot(*backpack.App) error { return nil } func (acmeShopPermissions) FrontendPermissions() []pact.FrontendPermission { return []pact.FrontendPermission{ {Code: acmeBlogManage, Label: "Shop's copy", Tab: "Shop"}, {Code: acmeShopOrders, Label: "Manage orders", Tab: "Shop"}, {Code: " ", Label: "Empty code", Tab: "Shop"}, } } func init() { party.Register(acmeShopPermissions{}) } // TestFrontendPermissionCatalog (D-09) extends the smoke: with two declaring // plugins the editors offer the seeded rows, then the first plugin's codes, // then the second's, a code the first already declared and an empty code // left out; a group saved with a declared code keeps it, and the member's // grants (what user.permission checks) include it. Skips under -short. func TestFrontendPermissionCatalog(t *testing.T) { env := newAdminEnvWith(t, []string{"acme.blog", "acme.shop"}) if err := env.gdb.Exec(`INSERT INTO golem15_user_frontend_permissions (code, label, tab) VALUES (?, 'Seeded label', 'Blog') ON CONFLICT (code) DO NOTHING`, acmeBlogSeeded).Error; err != nil { t.Fatal(err) } var seeded int64 if err := env.gdb.Raw(`SELECT COUNT(*) FROM golem15_user_frontend_permissions`).Scan(&seeded).Error; err != nil { t.Fatal(err) } token := env.admin(t, permAccessUsers, permAccessGroups) t.Run("second plugin's duplicate dropped", func(t *testing.T) { for _, path := range []string{adminUsersPath, adminGroupsPath} { options := env.permissionOptions(t, token, path) declared := options[seeded:] var codes []string for _, o := range declared { codes = append(codes, o.Code) } if len(declared) != 2 || declared[len(declared)-2].Code != acmeBlogManage || declared[len(declared)-2].Label != "Manage blog posts" || declared[len(declared)-1] != (offeredPermission{Code: acmeShopOrders, Label: "Manage orders", Tab: "Shop"}) { t.Fatalf("%s declared options = %+v", path, declared) } seen := map[string]int{} for _, o := range options { seen[o.Code]++ if o.Code == "" || o.Code == " " { t.Fatalf("%s offers an empty code", path) } } if seen[acmeBlogManage] != 1 || seen[acmeShopOrders] != 1 { t.Fatalf("%s codes offered %v (declared %v)", path, seen, codes) } } }) t.Run("group saved with a declared code grants it", func(t *testing.T) { stamp := adminStamp() group := env.seedGroup(t, "Orders "+stamp, "orders-"+stamp) member := env.seedUser(t, "orders", nil) env.join(t, member, group) env.expect(t, http.StatusOK, http.MethodPut, groupPath(group.ID), token, map[string]any{ "name": group.Name, "code": *group.Code, "permissions": map[string]int{acmeShopOrders: 1}, }) var saved models.UserGroup if err := env.gdb.First(&saved, group.ID).Error; err != nil || saved.Permissions == nil || !strings.Contains(*saved.Permissions, `"`+acmeShopOrders+`"`) { t.Fatalf("saved permissions = %v, %v", saved.Permissions, err) } grants, err := classes.UserPermissionGrants(context.Background(), env.gdb, member.ID) if err != nil || !grants[acmeShopOrders] { t.Fatalf("member grants = %v, %v", grants, err) } }) }