tencent_captcha_service_test.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. //go:build unit
  2. package service
  3. import (
  4. "context"
  5. "errors"
  6. "testing"
  7. "github.com/Wei-Shaw/sub2api/internal/config"
  8. "github.com/stretchr/testify/require"
  9. )
  10. type tencentCaptchaVerifierStub struct {
  11. response *TencentCaptchaVerifyResponse
  12. err error
  13. calls int
  14. proof TencentCaptchaProof
  15. remoteIP string
  16. credentials TencentCaptchaCredentials
  17. }
  18. func (s *tencentCaptchaVerifierStub) VerifyTicket(_ context.Context, credentials TencentCaptchaCredentials, proof TencentCaptchaProof, remoteIP string) (*TencentCaptchaVerifyResponse, error) {
  19. s.calls++
  20. s.credentials = credentials
  21. s.proof = proof
  22. s.remoteIP = remoteIP
  23. return s.response, s.err
  24. }
  25. func newTencentCaptchaTestService(verifier TencentCaptchaVerifier) *TencentCaptchaService {
  26. return newTencentCaptchaTestServiceWithRegion(verifier, "")
  27. }
  28. func newTencentCaptchaTestServiceWithRegion(verifier TencentCaptchaVerifier, region string) *TencentCaptchaService {
  29. values := map[string]string{
  30. SettingKeyTencentCaptchaEnabled: "true",
  31. SettingKeyTencentCaptchaAppID: "123456789",
  32. SettingKeyTencentCaptchaAppSecretKey: "app-secret",
  33. SettingKeyTencentCaptchaCloudSecretID: "cloud-secret-id",
  34. SettingKeyTencentCaptchaCloudSecretKey: "cloud-secret-key",
  35. }
  36. if region != "" {
  37. values[SettingKeyTencentCaptchaRegion] = region
  38. }
  39. settings := NewSettingService(&settingPublicRepoStub{values: values}, &config.Config{})
  40. return NewTencentCaptchaService(settings, verifier)
  41. }
  42. // 站点决定服务端票据校验接入点:国际站账号的密钥在国内站接入点上无法通过鉴权,
  43. // 因此这条映射一旦错位,国际站验证码会整体失效。
  44. func TestTencentCaptchaServiceRoutesVerifyEndpointByRegion(t *testing.T) {
  45. cases := []struct {
  46. name string
  47. region string
  48. wantEndpoint string
  49. }{
  50. {"未配置回落中国站", "", "captcha.tencentcloudapi.com"},
  51. {"中国站", TencentCaptchaRegionCN, "captcha.tencentcloudapi.com"},
  52. {"国际站", TencentCaptchaRegionINTL, "captcha.intl.tencentcloudapi.com"},
  53. {"非法值回落中国站", "sgp", "captcha.tencentcloudapi.com"},
  54. }
  55. for _, tc := range cases {
  56. t.Run(tc.name, func(t *testing.T) {
  57. verifier := &tencentCaptchaVerifierStub{response: &TencentCaptchaVerifyResponse{CaptchaCode: 1}}
  58. svc := newTencentCaptchaTestServiceWithRegion(verifier, tc.region)
  59. require.NoError(t, svc.VerifyTicket(context.Background(), "ticket", "@rand", "203.0.113.10"))
  60. require.Equal(t, tc.wantEndpoint, verifier.credentials.Endpoint)
  61. })
  62. }
  63. }
  64. func TestTencentCaptchaServiceAcceptsCaptchaCodeOne(t *testing.T) {
  65. verifier := &tencentCaptchaVerifierStub{response: &TencentCaptchaVerifyResponse{CaptchaCode: 1}}
  66. svc := newTencentCaptchaTestService(verifier)
  67. err := svc.VerifyTicket(context.Background(), "ticket", "@rand", "203.0.113.10")
  68. require.NoError(t, err)
  69. require.Equal(t, 1, verifier.calls)
  70. require.Equal(t, TencentCaptchaProof{Ticket: "ticket", Randstr: "@rand"}, verifier.proof)
  71. require.Equal(t, "203.0.113.10", verifier.remoteIP)
  72. }
  73. func TestTencentCaptchaServiceRejectsDisasterRecoveryTicketWithoutCallingVerifier(t *testing.T) {
  74. verifier := &tencentCaptchaVerifierStub{response: &TencentCaptchaVerifyResponse{CaptchaCode: 1}}
  75. svc := newTencentCaptchaTestService(verifier)
  76. err := svc.VerifyTicket(context.Background(), "trerror_1001_123456789_1", "@rand", "203.0.113.10")
  77. require.ErrorIs(t, err, ErrTencentCaptchaVerificationFailed)
  78. require.Zero(t, verifier.calls)
  79. }
  80. func TestTencentCaptchaServiceRejectsEveryNonOneCode(t *testing.T) {
  81. for _, code := range []int64{0, 7, 8, 9, 15, 16, 21, 100} {
  82. t.Run(string(rune(code)), func(t *testing.T) {
  83. verifier := &tencentCaptchaVerifierStub{response: &TencentCaptchaVerifyResponse{CaptchaCode: code}}
  84. svc := newTencentCaptchaTestService(verifier)
  85. err := svc.VerifyTicket(context.Background(), "ticket", "@rand", "203.0.113.10")
  86. require.ErrorIs(t, err, ErrTencentCaptchaVerificationFailed)
  87. })
  88. }
  89. }
  90. func TestTencentCaptchaServiceFailsClosedOnVerifierError(t *testing.T) {
  91. verifier := &tencentCaptchaVerifierStub{err: errors.New("sdk unavailable")}
  92. svc := newTencentCaptchaTestService(verifier)
  93. err := svc.VerifyTicket(context.Background(), "ticket", "@rand", "203.0.113.10")
  94. require.Error(t, err)
  95. require.ErrorIs(t, err, ErrTencentCaptchaVerificationFailed)
  96. }
  97. func TestTencentCaptchaServiceRejectsIncompleteConfiguration(t *testing.T) {
  98. settings := NewSettingService(&settingPublicRepoStub{values: map[string]string{
  99. SettingKeyTencentCaptchaEnabled: "true",
  100. SettingKeyTencentCaptchaAppID: "123456789",
  101. }}, &config.Config{})
  102. verifier := &tencentCaptchaVerifierStub{response: &TencentCaptchaVerifyResponse{CaptchaCode: 1}}
  103. svc := NewTencentCaptchaService(settings, verifier)
  104. err := svc.VerifyTicket(context.Background(), "ticket", "@rand", "203.0.113.10")
  105. require.ErrorIs(t, err, ErrTencentCaptchaNotConfigured)
  106. require.Zero(t, verifier.calls)
  107. }
  108. func TestTencentCaptchaServiceFailsClosedOnSettingsReadError(t *testing.T) {
  109. settings := NewSettingService(&settingPublicRepoStub{err: errors.New("settings unavailable")}, &config.Config{})
  110. verifier := &tencentCaptchaVerifierStub{response: &TencentCaptchaVerifyResponse{CaptchaCode: 1}}
  111. svc := NewTencentCaptchaService(settings, verifier)
  112. err := svc.VerifyTicket(context.Background(), "ticket", "@rand", "203.0.113.10")
  113. require.ErrorIs(t, err, ErrServiceUnavailable)
  114. require.Zero(t, verifier.calls)
  115. }