feat: add config-driven super-admin bootstrap to Go backend
This commit is contained in:
1 parent
8affa4b25a
commit
ff055c972d
5 files changed
+345
No files matched your search
@@ -0,0 +1,206 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"git.nianxx.cn/wangxuming/NianAIGC/backend/internal/administration"
|
||||
"git.nianxx.cn/wangxuming/NianAIGC/backend/internal/postgres"
|
||||
)
|
||||
|
||||
// bootstrapFakeStore is a minimal in-memory administration.Store for
|
||||
// bootstrap tests. Only ListAccounts and CreateAccount carry behavior; the
|
||||
// remaining methods exist to satisfy the interface.
|
||||
type bootstrapFakeStore struct {
|
||||
accounts map[string]administration.Account
|
||||
}
|
||||
|
||||
func newBootstrapFakeStore() *bootstrapFakeStore {
|
||||
return &bootstrapFakeStore{accounts: map[string]administration.Account{}}
|
||||
}
|
||||
|
||||
func (s *bootstrapFakeStore) ListAccounts(_ context.Context, filters administration.AccountFilters) ([]administration.Account, error) {
|
||||
out := []administration.Account{}
|
||||
for _, account := range s.accounts {
|
||||
if filters.OrganizationID != "" && account.OrganizationID != filters.OrganizationID {
|
||||
continue
|
||||
}
|
||||
if filters.Role != "" && account.Role != filters.Role {
|
||||
continue
|
||||
}
|
||||
if !filters.IncludeDisabled && account.Status != administration.StatusActive {
|
||||
continue
|
||||
}
|
||||
out = append(out, account)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s *bootstrapFakeStore) GetAccount(_ context.Context, id string) (administration.Account, bool, error) {
|
||||
account, ok := s.accounts[id]
|
||||
return account, ok, nil
|
||||
}
|
||||
|
||||
func (s *bootstrapFakeStore) CreateAccount(_ context.Context, account administration.Account) (administration.Account, error) {
|
||||
if _, exists := s.accounts[account.ID]; exists {
|
||||
return administration.Account{}, errors.New("account already exists")
|
||||
}
|
||||
s.accounts[account.ID] = account
|
||||
return account, nil
|
||||
}
|
||||
|
||||
func (s *bootstrapFakeStore) UpdateAccount(_ context.Context, account administration.Account) (administration.Account, error) {
|
||||
s.accounts[account.ID] = account
|
||||
return account, nil
|
||||
}
|
||||
|
||||
func (s *bootstrapFakeStore) DeleteAccount(_ context.Context, id, _ string) error {
|
||||
delete(s.accounts, id)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *bootstrapFakeStore) ListOrganizations(context.Context, bool) ([]administration.Organization, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s *bootstrapFakeStore) GetOrganization(context.Context, string) (administration.Organization, bool, error) {
|
||||
return administration.Organization{}, false, nil
|
||||
}
|
||||
|
||||
func (s *bootstrapFakeStore) CreateOrganization(_ context.Context, organization administration.Organization) (administration.Organization, error) {
|
||||
return organization, nil
|
||||
}
|
||||
|
||||
func (s *bootstrapFakeStore) UpdateOrganization(_ context.Context, organization administration.Organization) (administration.Organization, error) {
|
||||
return organization, nil
|
||||
}
|
||||
|
||||
func (s *bootstrapFakeStore) DeleteOrganization(context.Context, string) error { return nil }
|
||||
|
||||
func (s *bootstrapFakeStore) CountOrganizationMembers(context.Context, string) (int, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func configuredBootstrap() BootstrapAdminConfig {
|
||||
return BootstrapAdminConfig{Phone: "13800138000", Password: "change-me-now", DisplayName: "平台超级管理员"}
|
||||
}
|
||||
|
||||
func TestParseBootstrapAdminConfig(t *testing.T) {
|
||||
env := map[string]string{
|
||||
"ZHINIAN_BOOTSTRAP_ADMIN_PHONE": " 13800138000 ",
|
||||
"ZHINIAN_BOOTSTRAP_ADMIN_PASSWORD": "change-me-now",
|
||||
"ZHINIAN_BOOTSTRAP_ADMIN_NAME": " 平台超级管理员 ",
|
||||
}
|
||||
config := ParseBootstrapAdminConfig(func(name string) string { return env[name] })
|
||||
if config.Phone != "13800138000" || config.Password != "change-me-now" || config.DisplayName != "平台超级管理员" {
|
||||
t.Fatalf("unexpected parsed config: %+v", config)
|
||||
}
|
||||
if !config.Configured() {
|
||||
t.Fatalf("expected parsed config to be complete")
|
||||
}
|
||||
|
||||
defaulted := ParseBootstrapAdminConfig(func(name string) string {
|
||||
if name == "ZHINIAN_BOOTSTRAP_ADMIN_NAME" {
|
||||
return ""
|
||||
}
|
||||
return env[name]
|
||||
})
|
||||
if defaulted.DisplayName != "平台超级管理员" {
|
||||
t.Fatalf("expected default display name, got %q", defaulted.DisplayName)
|
||||
}
|
||||
|
||||
if config := ParseBootstrapAdminConfig(nil); config.Configured() {
|
||||
t.Fatalf("nil getenv must produce an incomplete config")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBootstrapSuperAdminNoopWithoutConfig(t *testing.T) {
|
||||
store := newBootstrapFakeStore()
|
||||
service := administration.NewService(store)
|
||||
created, err := BootstrapSuperAdmin(context.Background(), postgres.BackendPostgres, service, BootstrapAdminConfig{})
|
||||
if err != nil || created {
|
||||
t.Fatalf("expected no-op without config, got created=%v err=%v", created, err)
|
||||
}
|
||||
if len(store.accounts) != 0 {
|
||||
t.Fatalf("expected no accounts, got %d", len(store.accounts))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBootstrapSuperAdminNoopOnLocalBackend(t *testing.T) {
|
||||
store := newBootstrapFakeStore()
|
||||
service := administration.NewService(store)
|
||||
created, err := BootstrapSuperAdmin(context.Background(), postgres.BackendLocal, service, configuredBootstrap())
|
||||
if err != nil || created {
|
||||
t.Fatalf("expected no-op on local backend, got created=%v err=%v", created, err)
|
||||
}
|
||||
if len(store.accounts) != 0 {
|
||||
t.Fatalf("expected no accounts, got %d", len(store.accounts))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBootstrapSuperAdminCreatesOnce(t *testing.T) {
|
||||
store := newBootstrapFakeStore()
|
||||
service := administration.NewService(store)
|
||||
|
||||
created, err := BootstrapSuperAdmin(context.Background(), postgres.BackendPostgres, service, configuredBootstrap())
|
||||
if err != nil || !created {
|
||||
t.Fatalf("expected first bootstrap to create, got created=%v err=%v", created, err)
|
||||
}
|
||||
if len(store.accounts) != 1 {
|
||||
t.Fatalf("expected exactly one account, got %d", len(store.accounts))
|
||||
}
|
||||
for _, account := range store.accounts {
|
||||
if account.Role != administration.RoleSuperAdmin || account.Status != administration.StatusActive {
|
||||
t.Fatalf("unexpected bootstrapped account: %+v", account)
|
||||
}
|
||||
if account.PasswordHash == "change-me-now" || account.PasswordHash == "" {
|
||||
t.Fatalf("password must be hashed, got %q", account.PasswordHash)
|
||||
}
|
||||
}
|
||||
|
||||
created, err = BootstrapSuperAdmin(context.Background(), postgres.BackendPostgres, service, configuredBootstrap())
|
||||
if err != nil || created {
|
||||
t.Fatalf("expected second bootstrap to skip, got created=%v err=%v", created, err)
|
||||
}
|
||||
if len(store.accounts) != 1 {
|
||||
t.Fatalf("expected still exactly one account, got %d", len(store.accounts))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBootstrapSuperAdminSkipsDisabledSuperAdmin(t *testing.T) {
|
||||
store := newBootstrapFakeStore()
|
||||
existing := administration.Account{
|
||||
ID: "user-existing", Phone: "13900139000", DisplayName: "已有超管",
|
||||
Role: administration.RoleSuperAdmin, Status: administration.StatusDisabled,
|
||||
SessionVersion: 1,
|
||||
}
|
||||
store.accounts[existing.ID] = existing
|
||||
service := administration.NewService(store)
|
||||
|
||||
created, err := BootstrapSuperAdmin(context.Background(), postgres.BackendPostgres, service, configuredBootstrap())
|
||||
if err != nil || created {
|
||||
t.Fatalf("expected skip with existing disabled super admin, got created=%v err=%v", created, err)
|
||||
}
|
||||
if len(store.accounts) != 1 {
|
||||
t.Fatalf("expected no new account, got %d", len(store.accounts))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBootstrapSuperAdminRejectsShortPassword(t *testing.T) {
|
||||
store := newBootstrapFakeStore()
|
||||
service := administration.NewService(store)
|
||||
config := configuredBootstrap()
|
||||
config.Password = "short"
|
||||
|
||||
created, err := BootstrapSuperAdmin(context.Background(), postgres.BackendPostgres, service, config)
|
||||
if err == nil {
|
||||
t.Fatalf("expected short password to fail")
|
||||
}
|
||||
if created {
|
||||
t.Fatalf("expected no account on failed bootstrap")
|
||||
}
|
||||
if len(store.accounts) != 0 {
|
||||
t.Fatalf("expected no accounts, got %d", len(store.accounts))
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user