为用户在 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>
101 lines
2.8 KiB
Go
101 lines
2.8 KiB
Go
//go:build unit
|
|
|
|
package service
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/Wei-Shaw/sub2api/internal/config"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
type turnstileVerifierSpy struct {
|
|
called int
|
|
lastToken string
|
|
result *TurnstileVerifyResponse
|
|
err error
|
|
}
|
|
|
|
func (s *turnstileVerifierSpy) VerifyToken(_ context.Context, _ string, token, _ string) (*TurnstileVerifyResponse, error) {
|
|
s.called++
|
|
s.lastToken = token
|
|
if s.err != nil {
|
|
return nil, s.err
|
|
}
|
|
if s.result != nil {
|
|
return s.result, nil
|
|
}
|
|
return &TurnstileVerifyResponse{Success: true}, nil
|
|
}
|
|
|
|
func newAuthServiceForRegisterTurnstileTest(settings map[string]string, verifier TurnstileVerifier) *AuthService {
|
|
cfg := &config.Config{
|
|
Server: config.ServerConfig{
|
|
Mode: "release",
|
|
},
|
|
Turnstile: config.TurnstileConfig{
|
|
Required: true,
|
|
},
|
|
}
|
|
|
|
settingService := NewSettingService(&settingRepoStub{values: settings}, cfg)
|
|
turnstileService := NewTurnstileService(settingService, verifier)
|
|
|
|
return NewAuthService(
|
|
nil, // entClient
|
|
&userRepoStub{},
|
|
nil, // redeemRepo
|
|
nil, // refreshTokenCache
|
|
cfg,
|
|
settingService,
|
|
nil, // emailService
|
|
turnstileService,
|
|
nil, // emailQueueService
|
|
nil, // promoService
|
|
nil, // defaultSubAssigner
|
|
nil, // affiliateService
|
|
nil, // userPlatformQuotaRepo
|
|
)
|
|
}
|
|
|
|
func TestAuthService_VerifyTurnstileForRegister_SkipWhenEmailVerifyCodeProvided(t *testing.T) {
|
|
verifier := &turnstileVerifierSpy{}
|
|
service := newAuthServiceForRegisterTurnstileTest(map[string]string{
|
|
SettingKeyEmailVerifyEnabled: "true",
|
|
SettingKeyTurnstileEnabled: "true",
|
|
SettingKeyTurnstileSecretKey: "secret",
|
|
SettingKeyRegistrationEnabled: "true",
|
|
}, verifier)
|
|
|
|
err := service.VerifyTurnstileForRegister(context.Background(), "", "127.0.0.1", "123456")
|
|
require.NoError(t, err)
|
|
require.Equal(t, 0, verifier.called)
|
|
}
|
|
|
|
func TestAuthService_VerifyTurnstileForRegister_RequireWhenVerifyCodeMissing(t *testing.T) {
|
|
verifier := &turnstileVerifierSpy{}
|
|
service := newAuthServiceForRegisterTurnstileTest(map[string]string{
|
|
SettingKeyEmailVerifyEnabled: "true",
|
|
SettingKeyTurnstileEnabled: "true",
|
|
SettingKeyTurnstileSecretKey: "secret",
|
|
}, verifier)
|
|
|
|
err := service.VerifyTurnstileForRegister(context.Background(), "", "127.0.0.1", "")
|
|
require.ErrorIs(t, err, ErrTurnstileVerificationFailed)
|
|
}
|
|
|
|
func TestAuthService_VerifyTurnstileForRegister_NoSkipWhenEmailVerifyDisabled(t *testing.T) {
|
|
verifier := &turnstileVerifierSpy{}
|
|
service := newAuthServiceForRegisterTurnstileTest(map[string]string{
|
|
SettingKeyEmailVerifyEnabled: "false",
|
|
SettingKeyTurnstileEnabled: "true",
|
|
SettingKeyTurnstileSecretKey: "secret",
|
|
}, verifier)
|
|
|
|
err := service.VerifyTurnstileForRegister(context.Background(), "turnstile-token", "127.0.0.1", "123456")
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, verifier.called)
|
|
require.Equal(t, "turnstile-token", verifier.lastToken)
|
|
}
|