Merge pull request #4358 from DanisJiang/feat/admin-recharge-affiliate-rebate

feat(affiliate): support rebates for admin balance deposits
This commit is contained in:
Wesley Liddick
2026-07-15 20:54:48 +08:00
committed by GitHub
20 changed files with 229 additions and 3 deletions
+1 -1
View File
@@ -176,7 +176,7 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
dashboardHandler := admin.NewDashboardHandler(dashboardService, dashboardAggregationService)
proxyExitInfoProber := repository.NewProxyExitInfoProber(configConfig)
proxyLatencyCache := repository.NewProxyLatencyCache(redisClient)
adminService := service.NewAdminService(userRepository, groupRepository, adminAccountRepository, proxyRepository, apiKeyRepository, redeemCodeRepository, userGroupRateRepository, userRPMCache, billingCacheService, proxyExitInfoProber, proxyLatencyCache, apiKeyAuthCacheInvalidator, client, settingService, subscriptionService, userSubscriptionRepository, privacyClientFactory, openAIGatewayService)
adminService := service.NewAdminService(userRepository, groupRepository, adminAccountRepository, proxyRepository, apiKeyRepository, redeemCodeRepository, userGroupRateRepository, userRPMCache, billingCacheService, proxyExitInfoProber, proxyLatencyCache, apiKeyAuthCacheInvalidator, client, settingService, subscriptionService, userSubscriptionRepository, privacyClientFactory, openAIGatewayService, affiliateService)
adminUserHandler := admin.NewUserHandler(adminService, concurrencyService, serviceUserPlatformQuotaRepository, billingCache)
groupCapacityService := service.NewGroupCapacityService(accountRepository, groupRepository, concurrencyService, sessionLimitCache, rpmCache)
groupHandler := admin.NewGroupHandler(adminService, dashboardService, groupCapacityService)
@@ -229,6 +229,7 @@ func (h *SettingHandler) GetSettings(c *gin.Context) {
AffiliateRebateFreezeHours: settings.AffiliateRebateFreezeHours,
AffiliateRebateDurationDays: settings.AffiliateRebateDurationDays,
AffiliateRebatePerInviteeCap: settings.AffiliateRebatePerInviteeCap,
AdminRechargeRebateEnabled: settings.AdminRechargeRebateEnabled,
DefaultUserRPMLimit: settings.DefaultUserRPMLimit,
DefaultSubscriptions: defaultSubscriptions,
EnableModelFallback: settings.EnableModelFallback,
@@ -308,6 +308,9 @@ func diffSettings(before *service.SystemSettings, after *service.SystemSettings,
if before.AffiliateRebatePerInviteeCap != after.AffiliateRebatePerInviteeCap {
changed = append(changed, "affiliate_rebate_per_invitee_cap")
}
if before.AdminRechargeRebateEnabled != after.AdminRechargeRebateEnabled {
changed = append(changed, "affiliate_admin_recharge_enabled")
}
if !equalDefaultSubscriptions(before.DefaultSubscriptions, after.DefaultSubscriptions) {
changed = append(changed, "default_subscriptions")
}
@@ -149,6 +149,7 @@ type UpdateSettingsRequest struct {
AffiliateRebateFreezeHours *int `json:"affiliate_rebate_freeze_hours"`
AffiliateRebateDurationDays *int `json:"affiliate_rebate_duration_days"`
AffiliateRebatePerInviteeCap *float64 `json:"affiliate_rebate_per_invitee_cap"`
AdminRechargeRebateEnabled *bool `json:"affiliate_admin_recharge_enabled"`
DefaultUserRPMLimit int `json:"default_user_rpm_limit"`
DefaultSubscriptions []dto.DefaultSubscriptionSetting `json:"default_subscriptions"`
AuthSourceDefaultEmailBalance *float64 `json:"auth_source_default_email_balance"`
@@ -391,6 +392,10 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
if affiliateRebatePerInviteeCap < 0 {
affiliateRebatePerInviteeCap = service.AffiliateRebatePerInviteeCapDefault
}
adminRechargeRebateEnabled := previousSettings.AdminRechargeRebateEnabled
if req.AdminRechargeRebateEnabled != nil {
adminRechargeRebateEnabled = *req.AdminRechargeRebateEnabled
}
// 通用表格配置:兼容旧客户端未传字段时保留当前值。
if req.TableDefaultPageSize <= 0 {
req.TableDefaultPageSize = previousSettings.TableDefaultPageSize
@@ -1279,6 +1284,7 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
AffiliateRebateFreezeHours: affiliateRebateFreezeHours,
AffiliateRebateDurationDays: affiliateRebateDurationDays,
AffiliateRebatePerInviteeCap: affiliateRebatePerInviteeCap,
AdminRechargeRebateEnabled: adminRechargeRebateEnabled,
DefaultUserRPMLimit: req.DefaultUserRPMLimit,
DefaultSubscriptions: defaultSubscriptions,
EnableModelFallback: req.EnableModelFallback,
@@ -1786,6 +1792,7 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
AffiliateRebateFreezeHours: updatedSettings.AffiliateRebateFreezeHours,
AffiliateRebateDurationDays: updatedSettings.AffiliateRebateDurationDays,
AffiliateRebatePerInviteeCap: updatedSettings.AffiliateRebatePerInviteeCap,
AdminRechargeRebateEnabled: updatedSettings.AdminRechargeRebateEnabled,
DefaultUserRPMLimit: updatedSettings.DefaultUserRPMLimit,
DefaultSubscriptions: updatedDefaultSubscriptions,
EnableModelFallback: updatedSettings.EnableModelFallback,
+1
View File
@@ -148,6 +148,7 @@ type SystemSettings struct {
AffiliateRebateFreezeHours int `json:"affiliate_rebate_freeze_hours"`
AffiliateRebateDurationDays int `json:"affiliate_rebate_duration_days"`
AffiliateRebatePerInviteeCap float64 `json:"affiliate_rebate_per_invitee_cap"`
AdminRechargeRebateEnabled bool `json:"affiliate_admin_recharge_enabled"`
DefaultUserRPMLimit int `json:"default_user_rpm_limit"`
DefaultSubscriptions []DefaultSubscriptionSetting `json:"default_subscriptions"`
+3 -1
View File
@@ -836,6 +836,7 @@ func TestAPIContracts(t *testing.T) {
"affiliate_rebate_freeze_hours": 0,
"affiliate_rebate_duration_days": 0,
"affiliate_rebate_per_invitee_cap": 0,
"affiliate_admin_recharge_enabled": false,
"default_user_rpm_limit": 0,
"default_subscriptions": [],
"enable_model_fallback": false,
@@ -1111,6 +1112,7 @@ func TestAPIContracts(t *testing.T) {
"affiliate_rebate_freeze_hours": 0,
"affiliate_rebate_duration_days": 0,
"affiliate_rebate_per_invitee_cap": 0,
"affiliate_admin_recharge_enabled": false,
"default_user_rpm_limit": 0,
"default_subscriptions": [],
"enable_model_fallback": false,
@@ -1372,7 +1374,7 @@ func newContractDeps(t *testing.T) *contractDeps {
settingRepo := newStubSettingRepo()
settingService := service.NewSettingService(settingRepo, cfg)
adminService := service.NewAdminService(userRepo, groupRepo, &accountRepo, proxyRepo, apiKeyRepo, redeemRepo, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
adminService := service.NewAdminService(userRepo, groupRepo, &accountRepo, proxyRepo, apiKeyRepo, redeemRepo, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
authHandler := handler.NewAuthHandler(cfg, nil, userService, settingService, nil, redeemService, nil, nil)
apiKeyHandler := handler.NewAPIKeyHandler(apiKeyService)
usageHandler := handler.NewUsageHandler(usageService, apiKeyService, nil, nil)
@@ -599,6 +599,11 @@ type adminServiceImpl struct {
userSubRepo UserSubscriptionRepository
privacyClientFactory PrivacyClientFactory
runtimeBlocker AccountRuntimeBlocker
affiliateService adminRechargeAffiliateAccruer
}
type adminRechargeAffiliateAccruer interface {
AccrueInviteRebate(ctx context.Context, inviteeUserID int64, baseRechargeAmount float64) (float64, error)
}
type userGroupRateBatchReader interface {
@@ -625,6 +630,7 @@ func NewAdminService(
userSubRepo UserSubscriptionRepository,
privacyClientFactory PrivacyClientFactory,
runtimeBlocker AccountRuntimeBlocker,
affiliateService *AffiliateService,
) AdminService {
return &adminServiceImpl{
userRepo: userRepo,
@@ -646,5 +652,6 @@ func NewAdminService(
userSubRepo: userSubRepo,
privacyClientFactory: privacyClientFactory,
runtimeBlocker: runtimeBlocker,
affiliateService: affiliateService,
}
}
@@ -4,6 +4,7 @@ package service
import (
"context"
"errors"
"testing"
"github.com/stretchr/testify/require"
@@ -50,6 +51,30 @@ type authCacheInvalidatorStub struct {
keys []string
}
type adminRechargeAffiliateAccruerStub struct {
calls []adminRechargeAffiliateAccrual
rebate float64
err error
}
type adminRechargeAffiliateAccrual struct {
userID int64
amount float64
}
func (s *adminRechargeAffiliateAccruerStub) AccrueInviteRebate(_ context.Context, userID int64, amount float64) (float64, error) {
s.calls = append(s.calls, adminRechargeAffiliateAccrual{userID: userID, amount: amount})
return s.rebate, s.err
}
func adminRechargeSettingService(enabled bool) *SettingService {
values := map[string]string{}
if enabled {
values[SettingKeyAffiliateAdminRechargeEnabled] = "true"
}
return NewSettingService(&settingRepoStub{values: values}, nil)
}
func (s *authCacheInvalidatorStub) InvalidateAuthCacheByKey(ctx context.Context, key string) {
s.keys = append(s.keys, key)
}
@@ -95,3 +120,76 @@ func TestAdminService_UpdateUserBalance_NoChangeNoInvalidate(t *testing.T) {
require.Empty(t, invalidator.userIDs)
require.Empty(t, redeemRepo.created)
}
func TestAdminService_UpdateUserBalance_AdminRechargeAffiliateRebate(t *testing.T) {
tests := []struct {
name string
enabled bool
operation string
amount float64
wantCalls []adminRechargeAffiliateAccrual
}{
{
name: "disabled by default",
operation: "add",
amount: 5,
},
{
name: "enabled add",
enabled: true,
operation: "add",
amount: 0.1,
wantCalls: []adminRechargeAffiliateAccrual{{userID: 7, amount: 0.1}},
},
{
name: "enabled set increase",
enabled: true,
operation: "set",
amount: 15,
},
{
name: "enabled subtract",
enabled: true,
operation: "subtract",
amount: 5,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
baseRepo := &userRepoStub{user: &User{ID: 7, Balance: 10}}
repo := &balanceUserRepoStub{userRepoStub: baseRepo}
redeemRepo := &balanceRedeemRepoStub{redeemRepoStub: &redeemRepoStub{}}
affiliate := &adminRechargeAffiliateAccruerStub{}
svc := &adminServiceImpl{
userRepo: repo,
redeemCodeRepo: redeemRepo,
settingService: adminRechargeSettingService(tt.enabled),
affiliateService: affiliate,
}
_, err := svc.UpdateUserBalance(context.Background(), 7, tt.amount, tt.operation, "")
require.NoError(t, err)
require.Equal(t, tt.wantCalls, affiliate.calls)
})
}
}
func TestAdminService_UpdateUserBalance_AffiliateFailureDoesNotRollbackRecharge(t *testing.T) {
baseRepo := &userRepoStub{user: &User{ID: 7, Balance: 10}}
repo := &balanceUserRepoStub{userRepoStub: baseRepo}
redeemRepo := &balanceRedeemRepoStub{redeemRepoStub: &redeemRepoStub{}}
affiliate := &adminRechargeAffiliateAccruerStub{err: errors.New("affiliate unavailable")}
svc := &adminServiceImpl{
userRepo: repo,
redeemCodeRepo: redeemRepo,
settingService: adminRechargeSettingService(true),
affiliateService: affiliate,
}
user, err := svc.UpdateUserBalance(context.Background(), 7, 5, "add", "")
require.NoError(t, err)
require.Equal(t, 15.0, user.Balance)
require.Equal(t, []adminRechargeAffiliateAccrual{{userID: 7, amount: 5}}, affiliate.calls)
require.Len(t, redeemRepo.created, 1)
}
+19
View File
@@ -487,6 +487,7 @@ func (s *adminServiceImpl) UpdateUserBalance(ctx context.Context, userID int64,
if s.authCacheInvalidator != nil && balanceDiff != 0 {
s.authCacheInvalidator.InvalidateAuthCacheByUserID(ctx, userID)
}
s.tryAccrueAffiliateRebateForAdminRecharge(ctx, userID, operation, balance)
if s.billingCacheService != nil {
go func() {
@@ -524,6 +525,24 @@ func (s *adminServiceImpl) UpdateUserBalance(ctx context.Context, userID int64,
return user, nil
}
func (s *adminServiceImpl) tryAccrueAffiliateRebateForAdminRecharge(ctx context.Context, userID int64, operation string, amount float64) {
if operation != "add" || amount <= 0 || s.settingService == nil || s.affiliateService == nil {
return
}
if !s.settingService.IsAffiliateAdminRechargeEnabled(ctx) {
return
}
rebate, err := s.affiliateService.AccrueInviteRebate(ctx, userID, amount)
if err != nil {
logger.LegacyPrintf("service.admin", "affiliate rebate failed for admin recharge: user_id=%d amount=%.8f err=%v", userID, amount, err)
return
}
if rebate > 0 {
logger.LegacyPrintf("service.admin", "affiliate rebate accrued for admin recharge: user_id=%d amount=%.8f rebate=%.8f", userID, amount, rebate)
}
}
func (s *adminServiceImpl) GetUserAPIKeys(ctx context.Context, userID int64, page, pageSize int, sortBy, sortOrder string) ([]APIKey, int64, error) {
params := pagination.PaginationParams{Page: page, PageSize: pageSize, SortBy: sortBy, SortOrder: sortOrder}
keys, result, err := s.apiKeyRepo.ListByUserID(ctx, userID, params, APIKeyListFilters{})
@@ -33,6 +33,7 @@ const (
AffiliateRebateDurationDaysDefault = 0 // 0 = 永久有效
AffiliateRebateDurationDaysMax = 3650 // ~10 年
AffiliateRebatePerInviteeCapDefault = 0.0 // 0 = 无上限
AdminRechargeRebateEnabledDefault = false // 管理员充值默认不产生返利
)
// Platform constants
@@ -138,6 +139,7 @@ const (
SettingKeyAffiliateRebateFreezeHours = "affiliate_rebate_freeze_hours" // 返利冻结期(小时,0=不冻结)
SettingKeyAffiliateRebateDurationDays = "affiliate_rebate_duration_days" // 返利有效期(天,0=永久)
SettingKeyAffiliateRebatePerInviteeCap = "affiliate_rebate_per_invitee_cap" // 单人返利上限(0=无上限)
SettingKeyAffiliateAdminRechargeEnabled = "affiliate_admin_recharge_enabled" // 管理员充值是否产生返利
SettingKeyRiskControlEnabled = "risk_control_enabled" // 是否启用风控中心入口与审计链路
SettingKeyContentModerationConfig = "content_moderation_config" // 内容审计配置(JSON)
SettingKeyCyberSessionBlockEnabled = "cyber_session_block_enabled" // cyber 命中后会话级自动屏蔽总开关(默认关)
@@ -77,6 +77,16 @@ func (s *SettingService) IsAffiliateEnabled(ctx context.Context) bool {
return value == "true"
}
// IsAffiliateAdminRechargeEnabled reports whether admin balance
// deposits should participate in the affiliate rebate program.
func (s *SettingService) IsAffiliateAdminRechargeEnabled(ctx context.Context) bool {
value, err := s.settingRepo.GetValue(ctx, SettingKeyAffiliateAdminRechargeEnabled)
if err != nil {
return AdminRechargeRebateEnabledDefault
}
return value == "true"
}
// GetAffiliateRebateRatePercent 读取并 clamp 全局返利比例。
// 解析失败、缺失或越界都回退到 AffiliateRebateRateDefault — 该比例从不抛错,
// 调用方只关心一个可用的数值。
+3 -1
View File
@@ -181,7 +181,8 @@ func (s *SettingService) InitializeDefaultSettings(ctx context.Context) error {
SettingKeyAvailableChannelsEnabled: "false",
// Affiliate (邀请返利) feature (default disabled; opt-in)
SettingKeyAffiliateEnabled: "false",
SettingKeyAffiliateEnabled: "false",
SettingKeyAffiliateAdminRechargeEnabled: strconv.FormatBool(AdminRechargeRebateEnabledDefault),
// 风控中心功能(默认关闭,显式启用)
SettingKeyRiskControlEnabled: "false",
@@ -332,6 +333,7 @@ func (s *SettingService) parseSettings(settings map[string]string) *SystemSettin
if perInviteeCap, err := strconv.ParseFloat(settings[SettingKeyAffiliateRebatePerInviteeCap], 64); err == nil && perInviteeCap >= 0 {
result.AffiliateRebatePerInviteeCap = perInviteeCap
}
result.AdminRechargeRebateEnabled = settings[SettingKeyAffiliateAdminRechargeEnabled] == "true"
result.DefaultSubscriptions = parseDefaultSubscriptions(settings[SettingKeyDefaultSubscriptions])
// 敏感信息直接返回,方便测试连接时使用
@@ -126,6 +126,37 @@ type defaultSubGroupReaderStub struct {
calls []int64
}
func TestSettingService_AffiliateAdminRechargeSetting(t *testing.T) {
t.Run("missing value defaults to disabled", func(t *testing.T) {
svc := NewSettingService(&settingGetAllRepoStub{values: map[string]string{}}, &config.Config{})
settings, err := svc.GetAllSettings(context.Background())
require.NoError(t, err)
require.False(t, settings.AdminRechargeRebateEnabled)
})
t.Run("explicit value is parsed", func(t *testing.T) {
svc := NewSettingService(&settingGetAllRepoStub{values: map[string]string{
SettingKeyAffiliateAdminRechargeEnabled: "true",
}}, &config.Config{})
settings, err := svc.GetAllSettings(context.Background())
require.NoError(t, err)
require.True(t, settings.AdminRechargeRebateEnabled)
})
t.Run("value is persisted", func(t *testing.T) {
repo := &settingUpdateRepoStub{}
svc := NewSettingService(repo, &config.Config{})
err := svc.UpdateSettings(context.Background(), &SystemSettings{
AdminRechargeRebateEnabled: true,
})
require.NoError(t, err)
require.Equal(t, "true", repo.updates[SettingKeyAffiliateAdminRechargeEnabled])
})
}
func (s *defaultSubGroupReaderStub) GetByID(ctx context.Context, id int64) (*Group, error) {
s.calls = append(s.calls, id)
if err, ok := s.errBy[id]; ok {
@@ -297,6 +297,7 @@ func (s *SettingService) buildSystemSettingsUpdates(ctx context.Context, setting
settings.AffiliateRebatePerInviteeCap = AffiliateRebatePerInviteeCapDefault
}
updates[SettingKeyAffiliateRebatePerInviteeCap] = strconv.FormatFloat(settings.AffiliateRebatePerInviteeCap, 'f', 8, 64)
updates[SettingKeyAffiliateAdminRechargeEnabled] = strconv.FormatBool(settings.AdminRechargeRebateEnabled)
updates[SettingKeyDefaultUserRPMLimit] = strconv.Itoa(settings.DefaultUserRPMLimit)
defaultSubsJSON, err := json.Marshal(settings.DefaultSubscriptions)
if err != nil {
@@ -152,6 +152,7 @@ type SystemSettings struct {
AffiliateRebateFreezeHours int
AffiliateRebateDurationDays int
AffiliateRebatePerInviteeCap float64
AdminRechargeRebateEnabled bool
DefaultUserRPMLimit int
DefaultSubscriptions []DefaultSubscriptionSetting
+2
View File
@@ -376,6 +376,7 @@ export interface SystemSettings {
affiliate_rebate_freeze_hours: number;
affiliate_rebate_duration_days: number;
affiliate_rebate_per_invitee_cap: number;
affiliate_admin_recharge_enabled: boolean;
default_concurrency: number;
default_user_rpm_limit: number;
default_subscriptions: DefaultSubscriptionSetting[];
@@ -673,6 +674,7 @@ export interface UpdateSettingsRequest {
affiliate_rebate_freeze_hours?: number;
affiliate_rebate_duration_days?: number;
affiliate_rebate_per_invitee_cap?: number;
affiliate_admin_recharge_enabled?: boolean;
default_concurrency?: number;
default_user_rpm_limit?: number;
default_subscriptions?: DefaultSubscriptionSetting[];
@@ -45,6 +45,8 @@ export default {
description: 'Existing users invite new ones; the inviter earns a percentage rebate on the invitee’s recharges. Disabled by default.',
enabled: 'Enable Affiliate',
enabledHint: 'When off, the affiliate menu is hidden, the aff parameter is ignored at signup, and new recharges generate no rebate. Existing rebate balances can still be transferred.',
adminRechargeRebate: 'Rebate Admin Deposits',
adminRechargeRebateHint: 'When enabled, balance added through User Management > Deposit generates affiliate rebates. Setting a balance or withdrawing funds does not.',
rebateRate: 'Global Rebate Rate',
rebateRateHint: 'Default percentage given back to the inviter on recharges (0-100, e.g. 10 = 10%).',
freezeHours: 'Rebate Freeze Period (hours)',
@@ -45,6 +45,8 @@ export default {
description: '老用户邀请新用户注册,新用户充值后老用户按比例获得返利额度。默认关闭。',
enabled: '启用邀请返利',
enabledHint: '关闭后用户菜单中的邀请页面入口隐藏、注册时忽略邀请码、新充值不再产生返利。已有返利额度仍可转入余额。',
adminRechargeRebate: '管理员充值参与返利',
adminRechargeRebateHint: '开启后,通过“用户管理 > 充值”增加的余额会产生邀请返利;设置余额和扣款不参与返利。',
rebateRate: '全局返利比例',
rebateRateHint: '充值后返给邀请人的默认比例(0-100%,例如填写 10 表示返利 10%)。',
freezeHours: '返利冻结期(小时)',
+14
View File
@@ -5938,6 +5938,18 @@
</div>
<div v-if="form.affiliate_enabled" class="space-y-6">
<div class="flex items-center justify-between">
<div>
<label class="text-sm font-medium text-gray-700 dark:text-gray-300">
{{ t('admin.settings.features.affiliate.adminRechargeRebate') }}
</label>
<p class="mt-0.5 text-xs text-gray-500 dark:text-gray-400">
{{ t('admin.settings.features.affiliate.adminRechargeRebateHint') }}
</p>
</div>
<Toggle v-model="form.affiliate_admin_recharge_enabled" />
</div>
<div>
<label class="input-label">
{{ t('admin.settings.features.affiliate.rebateRate') }}
@@ -8063,6 +8075,7 @@ const form = reactive<SettingsForm>({
affiliate_rebate_freeze_hours: 0,
affiliate_rebate_duration_days: 0,
affiliate_rebate_per_invitee_cap: 0,
affiliate_admin_recharge_enabled: false,
default_concurrency: 1,
default_subscriptions: [],
force_email_on_third_party_signup: false,
@@ -9418,6 +9431,7 @@ async function saveSettings() {
affiliate_rebate_freeze_hours: Math.max(0, Math.min(720, Number(form.affiliate_rebate_freeze_hours) || 0)),
affiliate_rebate_duration_days: Math.max(0, Math.min(3650, Math.floor(Number(form.affiliate_rebate_duration_days) || 0))),
affiliate_rebate_per_invitee_cap: Math.max(0, Number(form.affiliate_rebate_per_invitee_cap) || 0),
affiliate_admin_recharge_enabled: form.affiliate_admin_recharge_enabled,
default_concurrency: form.default_concurrency,
default_subscriptions: normalizedDefaultSubscriptions,
force_email_on_third_party_signup: form.force_email_on_third_party_signup,
@@ -646,6 +646,27 @@ describe("admin SettingsView payment visible method controls", () => {
expect(payload).not.toHaveProperty("payment_visible_method_wxpay_enabled");
});
it("submits the admin recharge affiliate rebate setting", async () => {
getSettings.mockResolvedValueOnce({
...baseSettingsResponse,
affiliate_enabled: true,
affiliate_admin_recharge_enabled: true,
});
const wrapper = mountView();
await flushPromises();
await wrapper.find("form").trigger("submit.prevent");
await flushPromises();
expect(updateSettings).toHaveBeenCalledTimes(1);
expect(updateSettings).toHaveBeenCalledWith(
expect.objectContaining({
affiliate_admin_recharge_enabled: true,
}),
);
});
it("submits Anthropic cache TTL injection gateway setting", async () => {
getSettings.mockResolvedValueOnce({
...baseSettingsResponse,