mirror of
https://gitee.com/wanwujie/sub2api
synced 2026-04-03 15:02:13 +08:00
注册邮箱域名白名单策略上线,后台大数据场景性能大幅优化。 - 注册邮箱域名白名单:支持管理员配置允许注册的邮箱域名策略 - Keys 页面表单筛选:用户 /keys 页面支持按条件筛选 API Key - Settings 页面分 Tab 拆分:管理后台设置页面按功能模块分 Tab 展示 - 后台大数据场景加载性能优化:仪表盘/用户/账号/Ops 页面大数据集加载显著提速 - Usage 大表分页优化:默认避免全量 COUNT(*),大幅降低分页查询耗时 - 消除重复的 normalizeAccountIDList,补充新增组件的单元测试 - 清理无用文件和过时文档,精简项目结构 - EmailVerifyView 硬编码英文字符串替换为 i18n 调用 - 修复 Anthropic 平台无限流重置时间的 429 误标记账号限流问题 - 修复自定义菜单页面管理员视角菜单不生效问题 - 修复 Ops 错误详情弹窗未展示真实上游 payload 的问题 - 修复充值/订阅菜单 icon 显示问题 # Conflicts: # .gitignore # backend/cmd/server/VERSION # backend/ent/group.go # backend/ent/runtime/runtime.go # backend/ent/schema/group.go # backend/go.sum # backend/internal/handler/admin/account_handler.go # backend/internal/handler/admin/dashboard_handler.go # backend/internal/pkg/usagestats/usage_log_types.go # backend/internal/repository/group_repo.go # backend/internal/repository/usage_log_repo.go # backend/internal/server/middleware/security_headers.go # backend/internal/server/router.go # backend/internal/service/account_usage_service.go # backend/internal/service/admin_service_bulk_update_test.go # backend/internal/service/dashboard_service.go # backend/internal/service/gateway_service.go # frontend/src/api/admin/dashboard.ts # frontend/src/components/account/BulkEditAccountModal.vue # frontend/src/components/charts/GroupDistributionChart.vue # frontend/src/components/layout/AppSidebar.vue # frontend/src/i18n/locales/en.ts # frontend/src/i18n/locales/zh.ts # frontend/src/views/admin/GroupsView.vue # frontend/src/views/admin/SettingsView.vue # frontend/src/views/admin/UsageView.vue # frontend/src/views/user/PurchaseSubscriptionView.vue
173 lines
5.1 KiB
Go
173 lines
5.1 KiB
Go
//go:build unit
|
|
|
|
package service
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
type accountRepoStubForBulkUpdate struct {
|
|
accountRepoStub
|
|
bulkUpdateErr error
|
|
bulkUpdateIDs []int64
|
|
bindGroupErrByID map[int64]error
|
|
bindGroupsCalls []int64
|
|
getByIDsAccounts []*Account
|
|
getByIDsErr error
|
|
getByIDsCalled bool
|
|
getByIDsIDs []int64
|
|
getByIDAccounts map[int64]*Account
|
|
getByIDErrByID map[int64]error
|
|
getByIDCalled []int64
|
|
listByGroupData map[int64][]Account
|
|
listByGroupErr map[int64]error
|
|
}
|
|
|
|
func (s *accountRepoStubForBulkUpdate) BulkUpdate(_ context.Context, ids []int64, _ AccountBulkUpdate) (int64, error) {
|
|
s.bulkUpdateIDs = append([]int64{}, ids...)
|
|
if s.bulkUpdateErr != nil {
|
|
return 0, s.bulkUpdateErr
|
|
}
|
|
return int64(len(ids)), nil
|
|
}
|
|
|
|
func (s *accountRepoStubForBulkUpdate) BindGroups(_ context.Context, accountID int64, _ []int64) error {
|
|
s.bindGroupsCalls = append(s.bindGroupsCalls, accountID)
|
|
if err, ok := s.bindGroupErrByID[accountID]; ok {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *accountRepoStubForBulkUpdate) ListByGroup(_ context.Context, groupID int64) ([]Account, error) {
|
|
if err, ok := s.listByGroupErr[groupID]; ok {
|
|
return nil, err
|
|
}
|
|
if rows, ok := s.listByGroupData[groupID]; ok {
|
|
return rows, nil
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func (s *accountRepoStubForBulkUpdate) GetByIDs(_ context.Context, ids []int64) ([]*Account, error) {
|
|
s.getByIDsCalled = true
|
|
s.getByIDsIDs = append([]int64{}, ids...)
|
|
if s.getByIDsErr != nil {
|
|
return nil, s.getByIDsErr
|
|
}
|
|
return s.getByIDsAccounts, nil
|
|
}
|
|
|
|
func (s *accountRepoStubForBulkUpdate) GetByID(_ context.Context, id int64) (*Account, error) {
|
|
s.getByIDCalled = append(s.getByIDCalled, id)
|
|
if err, ok := s.getByIDErrByID[id]; ok {
|
|
return nil, err
|
|
}
|
|
if account, ok := s.getByIDAccounts[id]; ok {
|
|
return account, nil
|
|
}
|
|
return nil, errors.New("account not found")
|
|
}
|
|
|
|
// TestAdminService_BulkUpdateAccounts_AllSuccessIDs 验证批量更新成功时返回 success_ids/failed_ids。
|
|
func TestAdminService_BulkUpdateAccounts_AllSuccessIDs(t *testing.T) {
|
|
repo := &accountRepoStubForBulkUpdate{}
|
|
svc := &adminServiceImpl{accountRepo: repo}
|
|
|
|
schedulable := true
|
|
input := &BulkUpdateAccountsInput{
|
|
AccountIDs: []int64{1, 2, 3},
|
|
Schedulable: &schedulable,
|
|
}
|
|
|
|
result, err := svc.BulkUpdateAccounts(context.Background(), input)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 3, result.Success)
|
|
require.Equal(t, 0, result.Failed)
|
|
require.ElementsMatch(t, []int64{1, 2, 3}, result.SuccessIDs)
|
|
require.Empty(t, result.FailedIDs)
|
|
require.Len(t, result.Results, 3)
|
|
}
|
|
|
|
// TestAdminService_BulkUpdateAccounts_PartialFailureIDs 验证部分失败时 success_ids/failed_ids 正确。
|
|
func TestAdminService_BulkUpdateAccounts_PartialFailureIDs(t *testing.T) {
|
|
repo := &accountRepoStubForBulkUpdate{
|
|
bindGroupErrByID: map[int64]error{
|
|
2: errors.New("bind failed"),
|
|
},
|
|
}
|
|
svc := &adminServiceImpl{
|
|
accountRepo: repo,
|
|
groupRepo: &groupRepoStubForAdmin{getByID: &Group{ID: 10, Name: "g10"}},
|
|
}
|
|
|
|
groupIDs := []int64{10}
|
|
schedulable := false
|
|
input := &BulkUpdateAccountsInput{
|
|
AccountIDs: []int64{1, 2, 3},
|
|
GroupIDs: &groupIDs,
|
|
Schedulable: &schedulable,
|
|
SkipMixedChannelCheck: true,
|
|
}
|
|
|
|
result, err := svc.BulkUpdateAccounts(context.Background(), input)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 2, result.Success)
|
|
require.Equal(t, 1, result.Failed)
|
|
require.ElementsMatch(t, []int64{1, 3}, result.SuccessIDs)
|
|
require.ElementsMatch(t, []int64{2}, result.FailedIDs)
|
|
require.Len(t, result.Results, 3)
|
|
}
|
|
|
|
func TestAdminService_BulkUpdateAccounts_NilGroupRepoReturnsError(t *testing.T) {
|
|
repo := &accountRepoStubForBulkUpdate{}
|
|
svc := &adminServiceImpl{accountRepo: repo}
|
|
|
|
groupIDs := []int64{10}
|
|
input := &BulkUpdateAccountsInput{
|
|
AccountIDs: []int64{1},
|
|
GroupIDs: &groupIDs,
|
|
}
|
|
|
|
result, err := svc.BulkUpdateAccounts(context.Background(), input)
|
|
require.Nil(t, result)
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "group repository not configured")
|
|
}
|
|
|
|
// TestAdminService_BulkUpdateAccounts_MixedChannelPreCheckBlocksOnExistingConflict verifies
|
|
// that the global pre-check detects a conflict with existing group members and returns an
|
|
// error before any DB write is performed.
|
|
func TestAdminService_BulkUpdateAccounts_MixedChannelPreCheckBlocksOnExistingConflict(t *testing.T) {
|
|
repo := &accountRepoStubForBulkUpdate{
|
|
getByIDsAccounts: []*Account{
|
|
{ID: 1, Platform: PlatformAntigravity},
|
|
},
|
|
// Group 10 already contains an Anthropic account.
|
|
listByGroupData: map[int64][]Account{
|
|
10: {{ID: 99, Platform: PlatformAnthropic}},
|
|
},
|
|
}
|
|
svc := &adminServiceImpl{
|
|
accountRepo: repo,
|
|
groupRepo: &groupRepoStubForAdmin{getByID: &Group{ID: 10, Name: "target-group"}},
|
|
}
|
|
|
|
groupIDs := []int64{10}
|
|
input := &BulkUpdateAccountsInput{
|
|
AccountIDs: []int64{1},
|
|
GroupIDs: &groupIDs,
|
|
}
|
|
|
|
result, err := svc.BulkUpdateAccounts(context.Background(), input)
|
|
require.Nil(t, result)
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "mixed channel")
|
|
// No BindGroups should have been called since the check runs before any write.
|
|
require.Empty(t, repo.bindGroupsCalls)
|
|
}
|