feat(admin): 用户管理 - 支持管理员添加用户 (#104)

* feat(canvas): 绘图工具 - 支持自定义 tldraw License Key

* docs(canvas): 绘图工具 - 添加 License Key 页面截图

* feat(admin): 用户管理 - 支持管理员添加用户
This commit is contained in:
ahyang
2026-08-03 22:40:10 +08:00
committed by GitHub
parent d2741f39f3
commit acaa4bb69f
6 changed files with 280 additions and 5 deletions

View File

@@ -7,9 +7,68 @@ import (
"infinite-canvas/backend/internal/repository"
"gorm.io/driver/sqlite"
"golang.org/x/crypto/bcrypt"
"gorm.io/gorm"
)
func TestCreateAdminUserCreatesActiveUserWithPasswordAndAudit(t *testing.T) {
db := newBulkUserTestDB(t)
actor := model.User{ID: "admin-1", Username: "admin", Role: model.UserRoleAdmin, Status: model.UserStatusActive}
if err := db.Create(&actor).Error; err != nil {
t.Fatal(err)
}
created, err := (&Service{repo: repository.New(db)}).CreateAdminUser(&actor, CreateAdminUserRequest{
Username: "new-user",
DisplayName: "New User",
Email: "new-user@example.com",
Password: "strong-password",
Role: model.UserRoleUser,
Status: model.UserStatusActive,
})
if err != nil {
t.Fatal(err)
}
if created.Username != "new-user" || created.DisplayName != "New User" || created.Email != "new-user@example.com" {
t.Fatalf("created user = %+v", created)
}
if created.Role != model.UserRoleUser || created.Status != model.UserStatusActive {
t.Fatalf("created user role/status = %q/%q", created.Role, created.Status)
}
if err := bcrypt.CompareHashAndPassword([]byte(created.PasswordHash), []byte("strong-password")); err != nil {
t.Fatalf("password hash does not match: %v", err)
}
var account model.CreditAccount
if err := db.First(&account, "user_id = ?", created.ID).Error; err != nil {
t.Fatal(err)
}
if account.AvailableMicrocredits <= 0 {
t.Fatalf("available credits = %d, want positive signup bonus", account.AvailableMicrocredits)
}
var audit model.AdminAuditEvent
if err := db.Where("action = ? AND target_id = ?", "user.create", created.ID).First(&audit).Error; err != nil {
t.Fatal(err)
}
if audit.ActorUserID != actor.ID {
t.Fatalf("audit actor = %q, want %q", audit.ActorUserID, actor.ID)
}
}
func TestCreateAdminUserRejectsDuplicateUsername(t *testing.T) {
db := newBulkUserTestDB(t)
actor := model.User{ID: "admin-1", Username: "admin", Role: model.UserRoleAdmin, Status: model.UserStatusActive}
if err := db.Create(&actor).Error; err != nil {
t.Fatal(err)
}
svc := &Service{repo: repository.New(db)}
input := CreateAdminUserRequest{Username: "duplicate", DisplayName: "Duplicate", Password: "strong-password", Role: model.UserRoleUser, Status: model.UserStatusActive}
if _, err := svc.CreateAdminUser(&actor, input); err != nil {
t.Fatal(err)
}
if _, err := svc.CreateAdminUser(&actor, input); err == nil {
t.Fatal("CreateAdminUser() duplicate username error = nil")
}
}
func TestBulkDisableUsersDisablesUsersSessionsAndWritesAudits(t *testing.T) {
db := newBulkUserTestDB(t)
actor := model.User{ID: "admin-1", Username: "admin", Role: model.UserRoleAdmin, Status: model.UserStatusActive}
@@ -95,7 +154,7 @@ func newBulkUserTestDB(t *testing.T) *gorm.DB {
if err != nil {
t.Fatal(err)
}
if err := db.AutoMigrate(&model.User{}, &model.AuthSession{}, &model.AdminAuditEvent{}); err != nil {
if err := db.AutoMigrate(&model.User{}, &model.AuthSession{}, &model.AdminAuditEvent{}, &model.CreditAccount{}, &model.CreditLedgerEntry{}, &model.SystemSetting{}); err != nil {
t.Fatal(err)
}
return db