| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233 |
- //go:build unit
- package service
- import (
- "context"
- "errors"
- "testing"
- "github.com/Wei-Shaw/sub2api/internal/config"
- "github.com/stretchr/testify/require"
- )
- type aliyunVerifierSpy struct {
- called int
- lastCred AliyunCaptchaCredentials
- lastParam string
- result *AliyunCaptchaVerifyResult
- err error
- }
- func (s *aliyunVerifierSpy) VerifyCaptcha(_ context.Context, cred AliyunCaptchaCredentials, param string) (*AliyunCaptchaVerifyResult, error) {
- s.called++
- s.lastCred = cred
- s.lastParam = param
- if s.err != nil {
- return nil, s.err
- }
- if s.result != nil {
- return s.result, nil
- }
- return &AliyunCaptchaVerifyResult{VerifyResult: true}, nil
- }
- func aliyunEnabledSettings() map[string]string {
- return map[string]string{
- SettingKeyAliyunCaptchaEnabled: "true",
- SettingKeyAliyunCaptchaAccessKeyID: "ak-id",
- SettingKeyAliyunCaptchaAccessKeySecret: "ak-secret",
- SettingKeyAliyunCaptchaSceneID: "scene-1",
- SettingKeyAliyunCaptchaPrefix: "prefix-1",
- }
- }
- func aliyunTestConfig() AliyunCaptchaConfig {
- return AliyunCaptchaConfig{
- Enabled: true,
- AccessKeyID: "ak-id",
- AccessKeySecret: "ak-secret",
- SceneID: "scene-1",
- Region: AliyunCaptchaRegionCN,
- }
- }
- func newAliyunAuthServiceForTest(cfg *config.Config, settings map[string]string, aliyunSpy *aliyunVerifierSpy) *AuthService {
- settingService := NewSettingService(&settingPublicRepoStub{values: settings}, cfg)
- authService := NewAuthService(
- nil, // entClient
- nil, // userRepo
- nil, // redeemRepo
- nil, // refreshTokenCache
- cfg,
- settingService,
- nil, // emailService
- NewTurnstileService(settingService, &turnstileVerifierSpy{}),
- nil, // emailQueueService
- nil, // promoService
- nil, // defaultSubAssigner
- nil, // affiliateService
- nil, // userPlatformQuotaRepo
- )
- authService.SetAliyunCaptchaService(NewAliyunCaptchaService(settingService, aliyunSpy))
- return authService
- }
- func TestAliyunCaptchaServiceVerifyParamDispatch(t *testing.T) {
- spy := &aliyunVerifierSpy{}
- svc := NewAliyunCaptchaService(nil, spy)
- err := svc.VerifyParamWithConfig(context.Background(), aliyunTestConfig(), "captcha-verify-param")
- require.NoError(t, err)
- require.Equal(t, 1, spy.called)
- require.Equal(t, "captcha-verify-param", spy.lastParam)
- require.Equal(t, "ak-id", spy.lastCred.AccessKeyID)
- require.Equal(t, "scene-1", spy.lastCred.SceneID)
- require.Equal(t, "captcha.cn-shanghai.aliyuncs.com", spy.lastCred.Endpoint)
- }
- func TestAliyunCaptchaServiceSgpEndpoint(t *testing.T) {
- spy := &aliyunVerifierSpy{}
- svc := NewAliyunCaptchaService(nil, spy)
- cfg := aliyunTestConfig()
- cfg.Region = AliyunCaptchaRegionSGP
- err := svc.VerifyParamWithConfig(context.Background(), cfg, "captcha-verify-param")
- require.NoError(t, err)
- require.Equal(t, "captcha.ap-southeast-1.aliyuncs.com", spy.lastCred.Endpoint)
- }
- func TestAliyunCaptchaServiceFailsClosedOnVerifierError(t *testing.T) {
- spy := &aliyunVerifierSpy{err: errors.New("network down")}
- svc := NewAliyunCaptchaService(nil, spy)
- err := svc.VerifyParamWithConfig(context.Background(), aliyunTestConfig(), "captcha-verify-param")
- require.ErrorIs(t, err, ErrAliyunCaptchaVerificationFailed)
- }
- func TestAliyunCaptchaServiceRejectsVerifyResultFalse(t *testing.T) {
- spy := &aliyunVerifierSpy{result: &AliyunCaptchaVerifyResult{VerifyResult: false, VerifyCode: "F001"}}
- svc := NewAliyunCaptchaService(nil, spy)
- err := svc.VerifyParamWithConfig(context.Background(), aliyunTestConfig(), "captcha-verify-param")
- require.ErrorIs(t, err, ErrAliyunCaptchaVerificationFailed)
- }
- func TestAliyunCaptchaServiceRejectsIncompleteCredentials(t *testing.T) {
- spy := &aliyunVerifierSpy{}
- svc := NewAliyunCaptchaService(nil, spy)
- cfg := aliyunTestConfig()
- cfg.AccessKeySecret = ""
- err := svc.VerifyParamWithConfig(context.Background(), cfg, "captcha-verify-param")
- require.ErrorIs(t, err, ErrAliyunCaptchaNotConfigured)
- require.Zero(t, spy.called)
- }
- func TestAliyunCaptchaServiceRejectsEmptyParam(t *testing.T) {
- spy := &aliyunVerifierSpy{}
- svc := NewAliyunCaptchaService(nil, spy)
- err := svc.VerifyParamWithConfig(context.Background(), aliyunTestConfig(), "")
- require.ErrorIs(t, err, ErrAliyunCaptchaVerificationFailed)
- require.Zero(t, spy.called)
- }
- func TestAliyunCaptchaServiceValidateCredentials(t *testing.T) {
- t.Run("invalid credential code", func(t *testing.T) {
- spy := &aliyunVerifierSpy{err: &AliyunCaptchaAPIError{Code: "SignatureDoesNotMatch", Message: "bad sk"}}
- svc := NewAliyunCaptchaService(nil, spy)
- err := svc.ValidateCredentials(context.Background(), "id", "sk", "scene", "cn")
- require.ErrorIs(t, err, ErrCaptchaInvalidCredentials)
- })
- t.Run("network error surfaces", func(t *testing.T) {
- spy := &aliyunVerifierSpy{err: errors.New("timeout")}
- svc := NewAliyunCaptchaService(nil, spy)
- err := svc.ValidateCredentials(context.Background(), "id", "sk", "scene", "cn")
- require.Error(t, err)
- require.NotErrorIs(t, err, ErrCaptchaInvalidCredentials)
- })
- t.Run("verify result false means credentials valid", func(t *testing.T) {
- spy := &aliyunVerifierSpy{result: &AliyunCaptchaVerifyResult{VerifyResult: false}}
- svc := NewAliyunCaptchaService(nil, spy)
- err := svc.ValidateCredentials(context.Background(), "id", "sk", "scene", "sgp")
- require.NoError(t, err)
- require.Equal(t, "captcha.ap-southeast-1.aliyuncs.com", spy.lastCred.Endpoint)
- })
- }
- func TestAuthServiceVerifyCaptchaDispatchesAliyun(t *testing.T) {
- spy := &aliyunVerifierSpy{}
- authService := newAliyunAuthServiceForTest(&config.Config{}, aliyunEnabledSettings(), spy)
- // 阿里云 captchaVerifyParam 复用 turnstile_token 请求字段
- err := authService.VerifyCaptcha(context.Background(), CaptchaProof{TurnstileToken: "captcha-verify-param"}, "127.0.0.1")
- require.NoError(t, err)
- require.Equal(t, 1, spy.called)
- require.Equal(t, "captcha-verify-param", spy.lastParam)
- }
- func TestAuthServiceVerifyCaptchaRejectsProviderConflict(t *testing.T) {
- settings := aliyunEnabledSettings()
- settings[SettingKeyTurnstileEnabled] = "true"
- settings[SettingKeyTurnstileSecretKey] = "secret"
- spy := &aliyunVerifierSpy{}
- authService := newAliyunAuthServiceForTest(&config.Config{}, settings, spy)
- err := authService.VerifyCaptcha(context.Background(), CaptchaProof{TurnstileToken: "param"}, "127.0.0.1")
- require.ErrorIs(t, err, ErrCaptchaProviderConflict)
- require.Zero(t, spy.called)
- }
- func TestAuthServiceVerifyCaptchaRequiredModeWithAliyun(t *testing.T) {
- cfg := &config.Config{
- Server: config.ServerConfig{Mode: "release"},
- Turnstile: config.TurnstileConfig{Required: true},
- }
- spy := &aliyunVerifierSpy{}
- authService := newAliyunAuthServiceForTest(cfg, aliyunEnabledSettings(), spy)
- // required 模式 + 阿里云启用且凭证齐全:不误报 NOT_CONFIGURED,正常走阿里云校验
- err := authService.VerifyCaptcha(context.Background(), CaptchaProof{TurnstileToken: "captcha-verify-param"}, "127.0.0.1")
- require.NoError(t, err)
- require.Equal(t, 1, spy.called)
- }
- func TestAuthServiceVerifyActionCaptchaIfEnabledDispatchesAliyun(t *testing.T) {
- spy := &aliyunVerifierSpy{}
- authService := newAliyunAuthServiceForTest(&config.Config{}, aliyunEnabledSettings(), spy)
- err := authService.VerifyActionCaptchaIfEnabled(context.Background(), CaptchaProof{TurnstileToken: "captcha-verify-param"}, "127.0.0.1")
- require.NoError(t, err)
- require.Equal(t, 1, spy.called)
- require.Equal(t, "captcha-verify-param", spy.lastParam)
- }
- func TestAuthServiceVerifyActionCaptchaIfEnabledSkipsWhenOnlyTurnstile(t *testing.T) {
- spy := &aliyunVerifierSpy{}
- authService := newAliyunAuthServiceForTest(&config.Config{}, map[string]string{
- SettingKeyTurnstileEnabled: "true",
- SettingKeyTurnstileSecretKey: "secret",
- }, spy)
- // Turnstile 不扩大既有覆盖:扩展入口不拦截
- err := authService.VerifyActionCaptchaIfEnabled(context.Background(), CaptchaProof{}, "127.0.0.1")
- require.NoError(t, err)
- require.Zero(t, spy.called)
- }
|