Files
sm-user-plugin/updates/user_groups_test.go
Jakub Zych e22f7660f3 feat: add the admin columns and the frontend permissions table
- users.permissions and users.last_seen as additive columns
- golem15_user_frontend_permissions (code, label, tab, comment)
- User gains LastSeen, read-only CreatedAt and UpdatedAt and the
  filterByGroup list scope; none of them is serialized
- migration tests roll back by position past the three new steps
2026-10-05 12:51:41 +02:00

151 lines
4.7 KiB
Go

package updates
import (
"encoding/json"
"reflect"
"strings"
"testing"
"git.golem15.com/golem15/sm-user-plugin/classes"
"git.golem15.com/golem15/sm-user-plugin/models"
"git.golem15.com/golem15/summercms/modules/lagoon"
"github.com/go-gormigrate/gormigrate/v2"
)
func TestUserGroupsMigration(t *testing.T) {
sqlDB := dedicatedDB(t, "user_groups")
gdb, err := lagoon.Use(t.Context(), sqlDB)
if err != nil {
t.Fatal(err)
}
m := gormigrate.New(gdb, &gormigrate.Options{
TableName: "summer_migrations_golem15_user",
UseTransaction: true,
}, All())
if err := m.Migrate(); err != nil {
t.Fatal(err)
}
for _, table := range []string{"user_groups", "users_groups"} {
if !gdb.Migrator().HasTable(table) {
t.Fatalf("%s missing after migrate", table)
}
}
for _, col := range []string{"id", "name", "code", "description", "permissions", "created_at", "updated_at"} {
if !gdb.Migrator().HasColumn("user_groups", col) {
t.Fatalf("user_groups.%s missing", col)
}
}
var seeded []models.UserGroup
if err := gdb.Order("id").Find(&seeded).Error; err != nil {
t.Fatal(err)
}
if len(seeded) != 2 || seeded[0].Name != "Guest" || *seeded[0].Code != models.GroupGuest ||
*seeded[0].Description != "Default group for guest users." ||
seeded[1].Name != "Registered" || *seeded[1].Code != models.GroupRegistered ||
*seeded[1].Description != "Default group for registered users." ||
seeded[0].CreatedAt == nil || seeded[0].Permissions != nil {
t.Fatalf("seed rows = %+v", seeded)
}
var pk string
if err := gdb.Raw(`SELECT conname FROM pg_constraint WHERE conrelid = 'users_groups'::regclass AND contype = 'p'`).Scan(&pk).Error; err != nil {
t.Fatal(err)
}
if pk != "user_group" {
t.Fatalf("users_groups primary key = %q", pk)
}
// Frontend permissions, last seen, API tokens, user permissions, then user groups.
for range 5 {
if err := m.RollbackLast(); err != nil {
t.Fatal(err)
}
}
if gdb.Migrator().HasTable("user_groups") || gdb.Migrator().HasTable("users_groups") {
t.Fatal("user groups tables survived rollback")
}
if !gdb.Migrator().HasTable("jwt_blacklist") || !gdb.Migrator().HasTable("users") {
t.Fatal("rollback dropped an earlier table")
}
if err := m.Migrate(); err != nil {
t.Fatalf("re-up: %v", err)
}
var n int64
if err := gdb.Table("user_groups").Count(&n).Error; err != nil || n != 2 {
t.Fatalf("re-up rows = %d err = %v", n, err)
}
}
func TestUserGroupsCodesAndRelation(t *testing.T) {
sqlDB := dedicatedDB(t, "user_groups_codes")
gdb, err := lagoon.Use(t.Context(), sqlDB)
if err != nil {
t.Fatal(err)
}
m := gormigrate.New(gdb, &gormigrate.Options{
TableName: "summer_migrations_golem15_user",
UseTransaction: true,
}, All())
if err := m.Migrate(); err != nil {
t.Fatal(err)
}
admin := "admin"
adminGroup := models.UserGroup{Name: "Admins", Code: &admin}
if err := gdb.Create(&adminGroup).Error; err != nil {
t.Fatal(err)
}
noCode := models.UserGroup{Name: "Uncoded"}
if err := gdb.Create(&noCode).Error; err != nil {
t.Fatal(err)
}
site := models.User{Email: "site-admin@example.test"}
plain := models.User{Email: "plain@example.test"}
for _, u := range []*models.User{&site, &plain} {
if err := gdb.Omit("Groups").Create(u).Error; err != nil {
t.Fatal(err)
}
}
for _, gid := range []uint{noCode.ID, adminGroup.ID, 2} {
if err := gdb.Exec(`INSERT INTO users_groups (user_id, user_group_id) VALUES (?, ?)`, site.ID, gid).Error; err != nil {
t.Fatal(err)
}
}
codes, err := classes.UserGroupCodes(t.Context(), gdb, site.ID)
if err != nil {
t.Fatal(err)
}
if want := []string{"registered", "admin"}; !reflect.DeepEqual(codes, want) {
t.Fatalf("codes = %v, want %v", codes, want)
}
codes, err = classes.UserGroupCodes(t.Context(), gdb, plain.ID)
if err != nil || codes == nil || len(codes) != 0 {
t.Fatalf("plain codes = %#v err = %v", codes, err)
}
for _, c := range []struct {
user uint
code string
want bool
}{{site.ID, "admin", true}, {plain.ID, "admin", false}, {site.ID, "guest", false}} {
got, err := classes.HasGroupCode(t.Context(), gdb, c.user, c.code)
if err != nil || got != c.want {
t.Fatalf("HasGroupCode(%d, %s) = %v, %v", c.user, c.code, got, err)
}
}
// The relation loads through the pivot and is never serialized.
var loaded models.User
if err := gdb.Preload("Groups").Take(&loaded, site.ID).Error; err != nil {
t.Fatal(err)
}
if len(loaded.Groups) != 3 {
t.Fatalf("groups = %+v", loaded.Groups)
}
raw, err := json.Marshal(loaded)
if err != nil || strings.Contains(string(raw), "Groups") || strings.Contains(string(raw), "Admins") {
t.Fatalf("user JSON = %s err = %v", raw, err)
}
if _, err := classes.UserGroupCodes(t.Context(), nil, site.ID); err == nil {
t.Fatal("nil handle must be an error")
}
}