| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194 |
- //go:build unit
- package service
- import (
- "context"
- "testing"
- "time"
- "github.com/stretchr/testify/require"
- )
- type updateAccountOveragesRepoStub struct {
- mockAccountRepoForGemini
- account *Account
- updateCalls int
- }
- func (r *updateAccountOveragesRepoStub) GetByID(ctx context.Context, id int64) (*Account, error) {
- return r.account, nil
- }
- func (r *updateAccountOveragesRepoStub) Update(ctx context.Context, account *Account) error {
- r.updateCalls++
- r.account = account
- return nil
- }
- func TestUpdateAccount_DisableOveragesClearsAICreditsKey(t *testing.T) {
- accountID := int64(101)
- repo := &updateAccountOveragesRepoStub{
- account: &Account{
- ID: accountID,
- Platform: PlatformAntigravity,
- Type: AccountTypeOAuth,
- Status: StatusActive,
- Extra: map[string]any{
- "allow_overages": true,
- "mixed_scheduling": true,
- modelRateLimitsKey: map[string]any{
- "claude-sonnet-4-5": map[string]any{
- "rate_limited_at": "2026-03-15T00:00:00Z",
- "rate_limit_reset_at": "2099-03-15T00:00:00Z",
- },
- creditsExhaustedKey: map[string]any{
- "rate_limited_at": "2026-03-15T00:00:00Z",
- "rate_limit_reset_at": time.Now().Add(5 * time.Hour).UTC().Format(time.RFC3339),
- },
- },
- },
- },
- }
- svc := &adminServiceImpl{accountRepo: repo}
- updated, err := svc.UpdateAccount(context.Background(), accountID, &UpdateAccountInput{
- Extra: map[string]any{
- "mixed_scheduling": true,
- modelRateLimitsKey: map[string]any{
- "claude-sonnet-4-5": map[string]any{
- "rate_limited_at": "2026-03-15T00:00:00Z",
- "rate_limit_reset_at": "2099-03-15T00:00:00Z",
- },
- creditsExhaustedKey: map[string]any{
- "rate_limited_at": "2026-03-15T00:00:00Z",
- "rate_limit_reset_at": time.Now().Add(5 * time.Hour).UTC().Format(time.RFC3339),
- },
- },
- },
- })
- require.NoError(t, err)
- require.NotNil(t, updated)
- require.Equal(t, 1, repo.updateCalls)
- require.False(t, updated.IsOveragesEnabled())
- // 关闭 overages 后,AICredits key 应被清除
- rawLimits, ok := repo.account.Extra[modelRateLimitsKey].(map[string]any)
- if ok {
- _, exists := rawLimits[creditsExhaustedKey]
- require.False(t, exists, "关闭 overages 时应清除 AICredits 限流 key")
- }
- // 普通模型限流应保留
- require.True(t, ok)
- _, exists := rawLimits["claude-sonnet-4-5"]
- require.True(t, exists, "普通模型限流应保留")
- }
- func TestUpdateAccount_EnableOveragesClearsModelRateLimitsBeforePersist(t *testing.T) {
- accountID := int64(102)
- repo := &updateAccountOveragesRepoStub{
- account: &Account{
- ID: accountID,
- Platform: PlatformAntigravity,
- Type: AccountTypeOAuth,
- Status: StatusActive,
- Extra: map[string]any{
- "mixed_scheduling": true,
- modelRateLimitsKey: map[string]any{
- "claude-sonnet-4-5": map[string]any{
- "rate_limited_at": "2026-03-15T00:00:00Z",
- "rate_limit_reset_at": "2099-03-15T00:00:00Z",
- },
- },
- },
- },
- }
- svc := &adminServiceImpl{accountRepo: repo}
- updated, err := svc.UpdateAccount(context.Background(), accountID, &UpdateAccountInput{
- Extra: map[string]any{
- "mixed_scheduling": true,
- "allow_overages": true,
- },
- })
- require.NoError(t, err)
- require.NotNil(t, updated)
- require.Equal(t, 1, repo.updateCalls)
- require.True(t, updated.IsOveragesEnabled())
- _, exists := repo.account.Extra[modelRateLimitsKey]
- require.False(t, exists, "开启 overages 时应在持久化前清掉旧模型限流")
- }
- func TestUpdateAccount_EmptyExtraPayloadCanClearQuotaLimits(t *testing.T) {
- accountID := int64(103)
- repo := &updateAccountOveragesRepoStub{
- account: &Account{
- ID: accountID,
- Platform: PlatformAnthropic,
- Type: AccountTypeAPIKey,
- Status: StatusActive,
- Extra: map[string]any{
- "quota_limit": 100.0,
- "quota_daily_limit": 10.0,
- "quota_weekly_limit": 40.0,
- },
- },
- }
- svc := &adminServiceImpl{accountRepo: repo}
- updated, err := svc.UpdateAccount(context.Background(), accountID, &UpdateAccountInput{
- // 显式空对象:语义是“清空 extra 中的可配置键”(例如关闭配额限制)
- Extra: map[string]any{},
- })
- require.NoError(t, err)
- require.NotNil(t, updated)
- require.Equal(t, 1, repo.updateCalls)
- require.NotNil(t, repo.account.Extra)
- require.NotContains(t, repo.account.Extra, "quota_limit")
- require.NotContains(t, repo.account.Extra, "quota_daily_limit")
- require.NotContains(t, repo.account.Extra, "quota_weekly_limit")
- require.Len(t, repo.account.Extra, 0)
- }
- func TestUpdateAccount_FixedWeeklyResetClearsLegacyRollingUsage(t *testing.T) {
- now := time.Now().UTC()
- daysSinceMonday := (int(now.Weekday()) + 6) % 7
- currentWeekStart := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.UTC).AddDate(0, 0, -daysSinceMonday)
- legacyRollingStart := currentWeekStart.Add(-24 * time.Hour)
- accountID := int64(104)
- repo := &updateAccountOveragesRepoStub{
- account: &Account{
- ID: accountID,
- Platform: PlatformAnthropic,
- Type: AccountTypeAPIKey,
- Status: StatusActive,
- Extra: map[string]any{
- "quota_weekly_limit": 40.0,
- "quota_weekly_used": 12.5,
- "quota_weekly_start": legacyRollingStart.Format(time.RFC3339),
- },
- },
- }
- svc := &adminServiceImpl{accountRepo: repo}
- updated, err := svc.UpdateAccount(context.Background(), accountID, &UpdateAccountInput{
- Extra: map[string]any{
- "quota_weekly_limit": 40.0,
- "quota_weekly_reset_mode": "fixed",
- "quota_weekly_reset_day": float64(1),
- "quota_weekly_reset_hour": float64(0),
- "quota_reset_timezone": "UTC",
- },
- })
- require.NoError(t, err)
- require.NotNil(t, updated)
- require.Equal(t, 1, repo.updateCalls)
- require.InDelta(t, 0.0, updated.GetQuotaWeeklyUsed(), 1e-9)
- require.Equal(t, currentWeekStart.Format(time.RFC3339), updated.Extra["quota_weekly_start"])
- require.False(t, updated.IsWeeklyQuotaPeriodExpired())
- }
|