Files
sub2api/backend/internal/service/admin_service_role_test.go
T
shaw 7918b1a9c5 fix: 落实 #3867-#3870 合并审计的全部跟进项
透传规则(跟进 #3868/#3870,refs #3857):
- CC/Messages 4 条协议转换路径改用共享 helper,走语义状态推断 +
  body 归一化,使按错误码配置的透传规则也能命中(原先传 0 恒不命中)
- helper 增加 platform 参数:本服务同时承载 openai/grok 平台账号,
  规则须按 account.Platform 匹配,消除硬编码平台错配
- /v1/responses 两条路径命中透传规则时补记 ops 上游错误事件,
  对齐 CC/Messages 与 antigravity 先例,消除监控盲区

用户角色管理(跟进 #3869):
- 补齐 EN 语言包缺失的 admin.users.form.roleLabel
- 新增"最后一个管理员不可降级"守卫,覆盖跨管理员互降致零 admin 锁死
- 角色变更/创建管理员落审计日志(含操作者 actor_admin_id)
2026-07-09 20:06:09 +08:00

144 lines
5.3 KiB
Go

//go:build unit
package service
import (
"context"
"testing"
"github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
"github.com/stretchr/testify/require"
)
func TestAdminService_CreateUser_WithAdminRole(t *testing.T) {
repo := &userRepoStub{nextID: 30}
svc := &adminServiceImpl{userRepo: repo}
user, err := svc.CreateUser(context.Background(), &CreateUserInput{
Email: "admin@test.com",
Password: "strong-pass",
Role: RoleAdmin,
})
require.NoError(t, err)
require.Equal(t, RoleAdmin, user.Role)
}
func TestAdminService_CreateUser_DefaultsToUserRole(t *testing.T) {
repo := &userRepoStub{nextID: 31}
svc := &adminServiceImpl{userRepo: repo}
user, err := svc.CreateUser(context.Background(), &CreateUserInput{
Email: "plain@test.com",
Password: "strong-pass",
})
require.NoError(t, err)
require.Equal(t, RoleUser, user.Role)
}
func TestAdminService_CreateUser_InvalidRoleRejected(t *testing.T) {
repo := &userRepoStub{nextID: 32}
svc := &adminServiceImpl{userRepo: repo}
_, err := svc.CreateUser(context.Background(), &CreateUserInput{
Email: "bad@test.com",
Password: "strong-pass",
Role: "superuser",
})
require.Error(t, err)
require.Empty(t, repo.created, "非法角色不应写入用户")
}
func TestAdminService_UpdateUser_PromoteToAdmin(t *testing.T) {
base := &userRepoStub{user: &User{ID: 42, Email: "u@example.com", Role: RoleUser}}
repo := &rpmUserRepoStub{userRepoStub: base}
invalidator := &authCacheInvalidatorStub{}
svc := &adminServiceImpl{
userRepo: repo,
redeemCodeRepo: &redeemRepoStub{},
authCacheInvalidator: invalidator,
}
updated, err := svc.UpdateUser(context.Background(), 42, &UpdateUserInput{Role: RoleAdmin})
require.NoError(t, err)
require.Equal(t, RoleAdmin, updated.Role)
require.Equal(t, []int64{42}, invalidator.userIDs, "角色变更应失效认证缓存")
}
func TestAdminService_UpdateUser_RoleOmittedKeepsExisting(t *testing.T) {
base := &userRepoStub{user: &User{ID: 42, Email: "u@example.com", Role: RoleAdmin}}
repo := &rpmUserRepoStub{userRepoStub: base}
svc := &adminServiceImpl{userRepo: repo, redeemCodeRepo: &redeemRepoStub{}}
newName := "renamed"
updated, err := svc.UpdateUser(context.Background(), 42, &UpdateUserInput{Username: &newName})
require.NoError(t, err)
require.Equal(t, RoleAdmin, updated.Role, "未提供 role 时不应改变现有角色")
}
func TestAdminService_UpdateUser_InvalidRoleRejected(t *testing.T) {
base := &userRepoStub{user: &User{ID: 42, Email: "u@example.com", Role: RoleUser}}
repo := &rpmUserRepoStub{userRepoStub: base}
svc := &adminServiceImpl{userRepo: repo, redeemCodeRepo: &redeemRepoStub{}}
_, err := svc.UpdateUser(context.Background(), 42, &UpdateUserInput{Role: "root"})
require.Error(t, err)
require.Nil(t, repo.lastUpdated, "非法角色不应触发持久化")
}
// roleGuardUserRepoStub 在 rpmUserRepoStub 之上提供可控的管理员计数,
// 用于测试"最后一个管理员不可降级"守卫。
type roleGuardUserRepoStub struct {
*rpmUserRepoStub
adminTotal int64
listCalls int
}
func (s *roleGuardUserRepoStub) ListWithFilters(_ context.Context, _ pagination.PaginationParams, _ UserListFilters) ([]User, *pagination.PaginationResult, error) {
s.listCalls++
return nil, &pagination.PaginationResult{Total: s.adminTotal}, nil
}
func TestAdminService_UpdateUser_DemoteLastAdminRejected(t *testing.T) {
base := &userRepoStub{user: &User{ID: 42, Email: "a@example.com", Role: RoleAdmin}}
repo := &roleGuardUserRepoStub{rpmUserRepoStub: &rpmUserRepoStub{userRepoStub: base}, adminTotal: 1}
svc := &adminServiceImpl{userRepo: repo, redeemCodeRepo: &redeemRepoStub{}}
_, err := svc.UpdateUser(context.Background(), 42, &UpdateUserInput{Role: RoleUser})
require.Error(t, err)
require.Contains(t, err.Error(), "last admin")
require.Nil(t, repo.lastUpdated, "最后一个管理员不应被降级持久化")
require.Equal(t, 1, repo.listCalls, "降级路径应触发管理员计数")
}
func TestAdminService_UpdateUser_DemoteAdminAllowedWhenOthersExist(t *testing.T) {
base := &userRepoStub{user: &User{ID: 42, Email: "a@example.com", Role: RoleAdmin}}
repo := &roleGuardUserRepoStub{rpmUserRepoStub: &rpmUserRepoStub{userRepoStub: base}, adminTotal: 2}
invalidator := &authCacheInvalidatorStub{}
svc := &adminServiceImpl{
userRepo: repo,
redeemCodeRepo: &redeemRepoStub{},
authCacheInvalidator: invalidator,
}
updated, err := svc.UpdateUser(context.Background(), 42, &UpdateUserInput{Role: RoleUser})
require.NoError(t, err)
require.Equal(t, RoleUser, updated.Role)
require.NotNil(t, repo.lastUpdated)
require.Equal(t, RoleUser, repo.lastUpdated.Role, "存在其他管理员时允许降级")
}
func TestAdminService_UpdateUser_PromoteDoesNotCountAdmins(t *testing.T) {
base := &userRepoStub{user: &User{ID: 42, Email: "u@example.com", Role: RoleUser}}
repo := &roleGuardUserRepoStub{rpmUserRepoStub: &rpmUserRepoStub{userRepoStub: base}, adminTotal: 1}
svc := &adminServiceImpl{
userRepo: repo,
redeemCodeRepo: &redeemRepoStub{},
authCacheInvalidator: &authCacheInvalidatorStub{},
}
updated, err := svc.UpdateUser(context.Background(), 42, &UpdateUserInput{Role: RoleAdmin})
require.NoError(t, err)
require.Equal(t, RoleAdmin, updated.Role)
require.Equal(t, 0, repo.listCalls, "升级路径不应触发管理员计数")
}