package common import ( "bytes" "context" "encoding/json" "fmt" "net/http" "net/http/httptest" "testing" "time" "github.com/alicebob/miniredis/v2" "github.com/gin-gonic/gin" "github.com/perfect-panel/server/internal/config" "github.com/perfect-panel/server/internal/middleware" "github.com/perfect-panel/server/internal/svc" "github.com/perfect-panel/server/pkg/authmethod" "github.com/perfect-panel/server/pkg/constant" "github.com/redis/go-redis/v9" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) type canonicalCheckCodeResponse struct { Code uint32 `json:"code"` Data struct { Status bool `json:"status"` Exist bool `json:"exist"` } `json:"data"` } func newCanonicalCheckCodeTestSvcCtx(t *testing.T) (*svc.ServiceContext, *redis.Client) { t.Helper() miniRedis := miniredis.RunT(t) redisClient := redis.NewClient(&redis.Options{Addr: miniRedis.Addr()}) t.Cleanup(func() { redisClient.Close() miniRedis.Close() }) svcCtx := &svc.ServiceContext{ Redis: redisClient, Config: config.Config{ VerifyCode: config.VerifyCode{ VerifyCodeExpireTime: 900, }, }, } return svcCtx, redisClient } func newCanonicalCheckCodeTestRouter(svcCtx *svc.ServiceContext) *gin.Engine { gin.SetMode(gin.TestMode) router := gin.New() router.Use(middleware.ApiVersionMiddleware(svcCtx)) router.POST("/v1/common/check_verification_code", middleware.ApiVersionSwitchHandler( CheckVerificationCodeV1Handler(svcCtx), CheckVerificationCodeV2Handler(svcCtx), )) return router } func seedCanonicalVerifyCode(t *testing.T, redisClient *redis.Client, scene string, account string, code string) string { t.Helper() cacheKey := fmt.Sprintf("%s:%s:%s", config.AuthCodeCacheKey, scene, account) payload := map[string]interface{}{ "code": code, "lastAt": time.Now().Unix(), } payloadRaw, err := json.Marshal(payload) require.NoError(t, err) err = redisClient.Set(context.Background(), cacheKey, payloadRaw, time.Minute*15).Err() require.NoError(t, err) return cacheKey } func callCanonicalCheckCode(t *testing.T, router *gin.Engine, apiHeader string, body string) canonicalCheckCodeResponse { t.Helper() reqBody := bytes.NewBufferString(body) req := httptest.NewRequest(http.MethodPost, "/v1/common/check_verification_code", reqBody) req.Header.Set("Content-Type", "application/json") if apiHeader != "" { req.Header.Set("api-header", apiHeader) } recorder := httptest.NewRecorder() router.ServeHTTP(recorder, req) require.Equal(t, http.StatusOK, recorder.Code) var resp canonicalCheckCodeResponse err := json.Unmarshal(recorder.Body.Bytes(), &resp) require.NoError(t, err) return resp } func TestCheckVerificationCodeHandler_ApiHeaderGate(t *testing.T) { tests := []struct { name string apiHeader string expectConsume bool }{ {name: "no header", apiHeader: "", expectConsume: false}, {name: "invalid header", apiHeader: "invalid", expectConsume: false}, {name: "equal threshold", apiHeader: "1.0.0", expectConsume: false}, {name: "greater threshold", apiHeader: "1.0.1", expectConsume: true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { svcCtx, redisClient := newCanonicalCheckCodeTestSvcCtx(t) router := newCanonicalCheckCodeTestRouter(svcCtx) account := "header-gate@example.com" code := "123123" cacheKey := seedCanonicalVerifyCode(t, redisClient, constant.Register.String(), account, code) body := fmt.Sprintf(`{"method":"%s","account":"%s","code":"%s","type":%d}`, authmethod.Email, account, code, constant.Register, ) resp := callCanonicalCheckCode(t, router, tt.apiHeader, body) assert.Equal(t, uint32(200), resp.Code) assert.True(t, resp.Data.Status) exists, err := redisClient.Exists(context.Background(), cacheKey).Result() require.NoError(t, err) if tt.expectConsume { assert.Equal(t, int64(0), exists) } else { assert.Equal(t, int64(1), exists) } resp = callCanonicalCheckCode(t, router, tt.apiHeader, body) if tt.expectConsume { assert.False(t, resp.Data.Status) } else { assert.True(t, resp.Data.Status) } }) } }