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") } }