diff --git a/internal/auth/bootstrap.go b/internal/auth/bootstrap.go index 50f56d21..de7b5a75 100644 --- a/internal/auth/bootstrap.go +++ b/internal/auth/bootstrap.go @@ -4,8 +4,8 @@ import ( "context" "fmt" - "github.com/jackc/pgx/v5/pgxpool" "chat-agent-system/internal/config" + "github.com/jackc/pgx/v5/pgxpool" ) // BootstrapAdminFromConfig 确保配置的管理员账户在 PG 中存在且具备 role-admin。 @@ -25,7 +25,9 @@ func BootstrapAdminFromConfig(ctx context.Context, store AuthStore, cfg *config. existing, err := store.GetUserByUsername(ctx, adminUser) if err == nil && existing != nil { if existing.RoleID != "role-admin" { - _, _ = store.UpdateUserRole(ctx, existing.ID, "role-admin", "系统管理员") + if _, roleErr := store.UpdateUserRole(ctx, existing.ID, "role-admin", "系统管理员"); roleErr != nil { + return fmt.Errorf("bootstrap admin UpdateUserRole: %w", roleErr) + } } if existing.PasswordHash == "" { hash, hashErr := HashPassword(adminPass) @@ -86,14 +88,14 @@ func BootstrapDevSeedUsers(ctx context.Context, store AuthStore, cfg *config.Con continue } _, err := store.CreateUser(ctx, &UserAccount{ - Username: su.username, - Name: su.name, - PasswordHash: passHash, - Status: StatusActive, - RoleID: su.roleID, - Position: su.position, - Department: su.dept, - EmployeeID: su.empID, + Username: su.username, + Name: su.name, + PasswordHash: passHash, + Status: StatusActive, + RoleID: su.roleID, + Position: su.position, + Department: su.dept, + EmployeeID: su.empID, }) if err != nil { return fmt.Errorf("seed user %s: %w", su.username, err) diff --git a/internal/auth/bootstrap_test.go b/internal/auth/bootstrap_test.go new file mode 100644 index 00000000..8a714f03 --- /dev/null +++ b/internal/auth/bootstrap_test.go @@ -0,0 +1,94 @@ +package auth_test + +import ( + "context" + "errors" + "strings" + "testing" + + "chat-agent-system/internal/auth" + "chat-agent-system/internal/config" +) + +// failRoleStore wraps MemoryStore and forces UpdateUserRole to fail. +type failRoleStore struct { + *auth.MemoryStore + failRole bool +} + +func (s *failRoleStore) UpdateUserRole(ctx context.Context, id int64, roleID, position string) (*auth.UserAccount, error) { + if s.failRole { + return nil, errors.New("forced UpdateUserRole failure") + } + return s.MemoryStore.UpdateUserRole(ctx, id, roleID, position) +} + +func TestBootstrapAdminFromConfig_PromotesExistingUserToAdmin(t *testing.T) { + cfg := &config.Config{ + AdminUsername: "opsadmin", + AdminPassword: "bootstrap-pass-999", + Env: "dev", + } + base := auth.NewMemoryStore(&config.Config{Env: "dev", AdminUsername: "other", AdminPassword: "x"}) + ctx := context.Background() + created, err := base.CreateUser(ctx, &auth.UserAccount{ + Username: "opsadmin", + Name: "临时账号", + PasswordHash: "hash", + Status: auth.StatusActive, + RoleID: "role-guest", + Position: "访客/待指派", + EmployeeID: "TMP-1", + }) + if err != nil { + t.Fatalf("CreateUser: %v", err) + } + if created.RoleID == "role-admin" { + t.Fatal("precondition: seed must not already be role-admin") + } + + if err := auth.BootstrapAdminFromConfig(ctx, base, cfg); err != nil { + t.Fatalf("BootstrapAdminFromConfig: %v", err) + } + got, err := base.GetUserByUsername(ctx, "opsadmin") + if err != nil { + t.Fatal(err) + } + if got.RoleID != "role-admin" { + t.Fatalf("want role-admin after bootstrap, got %s", got.RoleID) + } + if got.Position != "系统管理员" { + t.Fatalf("want 系统管理员 position, got %s", got.Position) + } +} + +func TestBootstrapAdminFromConfig_UpdateUserRoleErrorNotSwallowed(t *testing.T) { + cfg := &config.Config{ + AdminUsername: "opsadmin2", + AdminPassword: "bootstrap-pass-999", + Env: "dev", + } + base := auth.NewMemoryStore(&config.Config{Env: "dev", AdminUsername: "other", AdminPassword: "x"}) + ctx := context.Background() + _, err := base.CreateUser(ctx, &auth.UserAccount{ + Username: "opsadmin2", + Name: "临时账号", + PasswordHash: "hash", + Status: auth.StatusActive, + RoleID: "role-guest", + Position: "访客/待指派", + EmployeeID: "TMP-2", + }) + if err != nil { + t.Fatalf("CreateUser: %v", err) + } + + wrapped := &failRoleStore{MemoryStore: base, failRole: true} + err = auth.BootstrapAdminFromConfig(ctx, wrapped, cfg) + if err == nil { + t.Fatal("expected BootstrapAdminFromConfig to surface UpdateUserRole error") + } + if !strings.Contains(err.Error(), "UpdateUserRole") { + t.Fatalf("error should mention UpdateUserRole, got %v", err) + } +}