You've already forked agentic-coding-workflow
修改
This commit is contained in:
@@ -48,7 +48,97 @@ func (r *fakeRepo) GetUserByID(_ context.Context, id uuid.UUID) (*User, error) {
|
||||
}
|
||||
|
||||
func (r *fakeRepo) CountUsers(_ context.Context) (int, error) { return len(r.users), nil }
|
||||
func (r *fakeRepo) ListAdmins(_ context.Context) ([]*User, error) { return nil, nil }
|
||||
func (r *fakeRepo) ListAdmins(_ context.Context) ([]*User, error) {
|
||||
out := make([]*User, 0)
|
||||
for _, u := range r.users {
|
||||
if u.IsAdmin {
|
||||
out = append(out, u)
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (r *fakeRepo) CountAdmins(_ context.Context) (int, error) {
|
||||
n := 0
|
||||
for _, u := range r.users {
|
||||
if u.IsAdmin && u.Enabled {
|
||||
n++
|
||||
}
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (r *fakeRepo) ListUsers(_ context.Context) ([]*User, error) {
|
||||
out := make([]*User, 0, len(r.users))
|
||||
for _, u := range r.users {
|
||||
out = append(out, u)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (r *fakeRepo) findByID(id uuid.UUID) (*User, bool) {
|
||||
for _, u := range r.users {
|
||||
if u.ID == id {
|
||||
return u, true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (r *fakeRepo) UpdateProfile(_ context.Context, id uuid.UUID, displayName string) (*User, error) {
|
||||
u, ok := r.findByID(id)
|
||||
if !ok {
|
||||
return nil, errs.New(errs.CodeNotFound, "no user")
|
||||
}
|
||||
u.DisplayName = displayName
|
||||
return u, nil
|
||||
}
|
||||
|
||||
func (r *fakeRepo) SetAdmin(_ context.Context, id uuid.UUID, isAdmin bool) (*User, error) {
|
||||
u, ok := r.findByID(id)
|
||||
if !ok {
|
||||
return nil, errs.New(errs.CodeNotFound, "no user")
|
||||
}
|
||||
u.IsAdmin = isAdmin
|
||||
return u, nil
|
||||
}
|
||||
|
||||
func (r *fakeRepo) SetEnabled(_ context.Context, id uuid.UUID, enabled bool) (*User, error) {
|
||||
u, ok := r.findByID(id)
|
||||
if !ok {
|
||||
return nil, errs.New(errs.CodeNotFound, "no user")
|
||||
}
|
||||
u.Enabled = enabled
|
||||
return u, nil
|
||||
}
|
||||
|
||||
func (r *fakeRepo) UpdatePassword(_ context.Context, id uuid.UUID, passwordHash string) error {
|
||||
u, ok := r.findByID(id)
|
||||
if !ok {
|
||||
return errs.New(errs.CodeNotFound, "no user")
|
||||
}
|
||||
u.PasswordHash = passwordHash
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *fakeRepo) DeleteUser(_ context.Context, id uuid.UUID) error {
|
||||
for email, u := range r.users {
|
||||
if u.ID == id {
|
||||
delete(r.users, email)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *fakeRepo) DeleteSessionsByUserID(_ context.Context, userID uuid.UUID) error {
|
||||
for k, s := range r.sessions {
|
||||
if s.UserID == userID {
|
||||
delete(r.sessions, k)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *fakeRepo) CreateSession(_ context.Context, s *Session) error {
|
||||
r.sessions[string(s.TokenHash)] = s
|
||||
@@ -168,3 +258,124 @@ func TestLogout_RecordsAudit(t *testing.T) {
|
||||
require.Len(t, spy.entries, 1)
|
||||
require.Equal(t, "user.logout", spy.entries[0].Action)
|
||||
}
|
||||
|
||||
// ===== 管理员用户管理测试 =====
|
||||
|
||||
// newAdminRepo 构造一个含一个启用管理员的 fakeRepo + service。
|
||||
func newAdminRepo(t *testing.T) (*service, *fakeRepo, *User) {
|
||||
t.Helper()
|
||||
repo := newFakeRepo()
|
||||
hash, err := HashPassword("password123")
|
||||
require.NoError(t, err)
|
||||
admin := &User{ID: uuid.New(), Email: "admin@b.c", DisplayName: "Admin", PasswordHash: hash, IsAdmin: true, Enabled: true}
|
||||
repo.users[admin.Email] = admin
|
||||
svc := NewService(repo, nil).(*service)
|
||||
return svc, repo, admin
|
||||
}
|
||||
|
||||
func TestCreateUser_AndListUsers(t *testing.T) {
|
||||
svc, _, _ := newAdminRepo(t)
|
||||
u, err := svc.CreateUser(context.Background(), CreateUserInput{
|
||||
Email: "new@b.c", DisplayName: "New", Password: "password123", IsAdmin: false,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "new@b.c", u.Email)
|
||||
users, err := svc.ListUsers(context.Background())
|
||||
require.NoError(t, err)
|
||||
require.Len(t, users, 2)
|
||||
}
|
||||
|
||||
func TestCreateUser_ShortPassword(t *testing.T) {
|
||||
svc, _, _ := newAdminRepo(t)
|
||||
_, err := svc.CreateUser(context.Background(), CreateUserInput{
|
||||
Email: "new@b.c", DisplayName: "New", Password: "short",
|
||||
})
|
||||
ae, ok := errs.As(err)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, errs.CodeInvalidInput, ae.Code)
|
||||
}
|
||||
|
||||
func TestSetAdmin_CannotDemoteSelf(t *testing.T) {
|
||||
svc, _, admin := newAdminRepo(t)
|
||||
_, err := svc.SetAdmin(context.Background(), admin.ID, admin.ID, false)
|
||||
ae, ok := errs.As(err)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, errs.CodeConflict, ae.Code)
|
||||
}
|
||||
|
||||
func TestSetAdmin_CannotDemoteLastAdmin(t *testing.T) {
|
||||
svc, repo, admin := newAdminRepo(t)
|
||||
// 加一个普通用户作 actor,避免触发自锁分支
|
||||
actor := &User{ID: uuid.New(), Email: "actor@b.c", DisplayName: "A", IsAdmin: true, Enabled: true}
|
||||
repo.users[actor.Email] = actor
|
||||
// 现在有 2 个 admin;降级其中一个应成功
|
||||
_, err := svc.SetAdmin(context.Background(), actor.ID, admin.ID, false)
|
||||
require.NoError(t, err)
|
||||
// 只剩 actor 一个 admin;再降级 actor(由自己以外触发不可能,构造另一普通用户)
|
||||
plain := &User{ID: uuid.New(), Email: "p@b.c", DisplayName: "P", IsAdmin: false, Enabled: true}
|
||||
repo.users[plain.Email] = plain
|
||||
_, err = svc.SetAdmin(context.Background(), plain.ID, actor.ID, false)
|
||||
ae, ok := errs.As(err)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, errs.CodeConflict, ae.Code)
|
||||
}
|
||||
|
||||
func TestSetEnabled_DisableRevokesSessions(t *testing.T) {
|
||||
svc, repo, _ := newAdminRepo(t)
|
||||
target := &User{ID: uuid.New(), Email: "t@b.c", DisplayName: "T", IsAdmin: false, Enabled: true}
|
||||
repo.users[target.Email] = target
|
||||
repo.sessions["s1"] = &Session{UserID: target.ID, TokenHash: []byte("s1")}
|
||||
actor := &User{ID: uuid.New(), Email: "act@b.c", IsAdmin: true, Enabled: true}
|
||||
repo.users[actor.Email] = actor
|
||||
_, err := svc.SetEnabled(context.Background(), actor.ID, target.ID, false)
|
||||
require.NoError(t, err)
|
||||
require.False(t, repo.users["t@b.c"].Enabled)
|
||||
require.Empty(t, repo.sessions)
|
||||
}
|
||||
|
||||
func TestChangePassword_WrongOld(t *testing.T) {
|
||||
svc, _, admin := newAdminRepo(t)
|
||||
err := svc.ChangePassword(context.Background(), admin.ID, "wrong-old", "newpassword123")
|
||||
ae, ok := errs.As(err)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, errs.CodeUnauthorized, ae.Code)
|
||||
}
|
||||
|
||||
func TestChangePassword_Success(t *testing.T) {
|
||||
svc, repo, admin := newAdminRepo(t)
|
||||
repo.sessions["s1"] = &Session{UserID: admin.ID, TokenHash: []byte("s1")}
|
||||
err := svc.ChangePassword(context.Background(), admin.ID, "password123", "newpassword123")
|
||||
require.NoError(t, err)
|
||||
ok, err := VerifyPassword("newpassword123", repo.users["admin@b.c"].PasswordHash)
|
||||
require.NoError(t, err)
|
||||
require.True(t, ok)
|
||||
require.Empty(t, repo.sessions) // 改密撤销全部会话
|
||||
}
|
||||
|
||||
func TestAdminResetPassword_ReturnsTemp(t *testing.T) {
|
||||
svc, repo, admin := newAdminRepo(t)
|
||||
temp, err := svc.AdminResetPassword(context.Background(), admin.ID)
|
||||
require.NoError(t, err)
|
||||
require.GreaterOrEqual(t, len(temp), minPasswordLen)
|
||||
ok, err := VerifyPassword(temp, repo.users["admin@b.c"].PasswordHash)
|
||||
require.NoError(t, err)
|
||||
require.True(t, ok)
|
||||
}
|
||||
|
||||
func TestDeleteUser_CannotDeleteSelf(t *testing.T) {
|
||||
svc, _, admin := newAdminRepo(t)
|
||||
err := svc.DeleteUser(context.Background(), admin.ID, admin.ID)
|
||||
ae, ok := errs.As(err)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, errs.CodeConflict, ae.Code)
|
||||
}
|
||||
|
||||
func TestDeleteUser_CannotDeleteLastAdmin(t *testing.T) {
|
||||
svc, repo, admin := newAdminRepo(t)
|
||||
actor := &User{ID: uuid.New(), Email: "act@b.c", IsAdmin: false, Enabled: true}
|
||||
repo.users[actor.Email] = actor
|
||||
err := svc.DeleteUser(context.Background(), actor.ID, admin.ID)
|
||||
ae, ok := errs.As(err)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, errs.CodeConflict, ae.Code)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user