sub2api/backend/internal/service/auth_email_oauth_auto_test.go
DaydreamCoding 6b39b344d8 feat(quota): 用户 × 平台 USD 配额
为用户在 anthropic/openai/gemini/antigravity 四个平台上提供日/周/月
三个窗口的 USD 配额管控。配额语义:未设置=不限制,0=禁用,>0=美元上限。

两层模型:
- 配置层:系统默认配额,以及 email/linuxdo/oidc/wechat/github/google/
  dingtalk 七个鉴权来源的默认配额,存于 settings,以嵌套 JSON 整体读写
  (系统 1 个 key + 每个来源 1 个 key),整体替换语义。
- 运行时层:user_platform_quota 表按用户记录实际配额,与配置层解耦。

后端:新增 ent schema 与 140_user_platform_quotas.sql 迁移、repository
与 service 端口、计费链路集成、管理端与用户端读写接口。
前端:管理端设置页配额编辑、用户配额管理 Modal、用户 Dashboard 展示、
中英文案。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 10:49:20 +08:00

89 lines
2.2 KiB
Go

//go:build unit
package service
import (
"context"
"testing"
"github.com/Wei-Shaw/sub2api/internal/config"
"github.com/stretchr/testify/require"
)
func newEmailOAuthAutoAuthService(
userRepo UserRepository,
settings map[string]string,
quotaRepo UserPlatformQuotaRepository,
) *AuthService {
cfg := &config.Config{
JWT: config.JWTConfig{
Secret: "test-secret",
ExpireHour: 1,
AccessTokenExpireMinutes: 60,
RefreshTokenExpireDays: 7,
},
Default: config.DefaultConfig{
UserBalance: 3.5,
UserConcurrency: 2,
},
}
settingService := NewSettingService(&settingRepoStub{values: settings}, cfg)
return NewAuthService(
nil, // entClient — nil, updateUserSignupSource early return
userRepo,
nil, // redeemRepo — invitationCode="" 时不触发
&refreshTokenCacheStub{},
cfg,
settingService,
nil, // emailService
nil, // turnstileService
nil, // emailQueueService
nil, // promoService
nil, // defaultSubAssigner — nil, assignSubscriptions early return
nil, // affiliateService — nil, bindOAuthAffiliate early return
quotaRepo,
)
}
func TestEmailOAuthAuto_SnapshotsPlatformQuotaDefaults(t *testing.T) {
userRepo := &userRepoStub{nextID: 88}
quotaRepo := &userPlatformQuotaRepoStub{}
svc := newEmailOAuthAutoAuthService(
userRepo,
map[string]string{
SettingKeyRegistrationEnabled: "true",
SettingKeyDefaultPlatformQuotas: `{"gemini": {"monthly": 100.0}}`,
},
quotaRepo,
)
user, err := svc.createEmailOAuthUser(
context.Background(),
"newoauth@example.com",
"newoauth",
"github",
"", // invitationCode
"", // affiliateCode
)
require.NoError(t, err)
require.NotNil(t, user)
require.Equal(t, int64(88), user.ID)
require.Len(t, quotaRepo.bulkInsertCalls, 1, "createEmailOAuthUser must snapshot platform quotas via BulkInsertInitial")
records := quotaRepo.bulkInsertCalls[0]
var geminiRecord *UserPlatformQuotaRecord
for i := range records {
if records[i].Platform == "gemini" {
geminiRecord = &records[i]
break
}
}
require.NotNil(t, geminiRecord, "expected gemini platform record")
require.NotNil(t, geminiRecord.MonthlyLimitUSD)
require.InDelta(t, 100.0, *geminiRecord.MonthlyLimitUSD, 0.0001)
}