package auth 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/constant" "github.com/redis/go-redis/v9" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) type legacyCheckCodeResponse struct { Code uint32 `json:"code"` Data struct { Status bool `json:"status"` Exist bool `json:"exist"` } `json:"data"` } func newLegacyCheckCodeTestRouter(svcCtx *svc.ServiceContext) *gin.Engine { gin.SetMode(gin.TestMode) router := gin.New() router.Use(middleware.ApiVersionMiddleware(svcCtx)) router.POST("/v1/auth/check-code", middleware.ApiVersionSwitchHandler( CheckCodeLegacyV1Handler(svcCtx), CheckCodeLegacyV2Handler(svcCtx), )) return router } func newLegacyCheckCodeTestSvcCtx(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 seedLegacyVerifyCode(t *testing.T, redisClient *redis.Client, scene string, email string, code string) string { t.Helper() cacheKey := fmt.Sprintf("%s:%s:%s", config.AuthCodeCacheKey, scene, email) 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 callLegacyCheckCode(t *testing.T, router *gin.Engine, apiHeader string, body string) legacyCheckCodeResponse { t.Helper() reqBody := bytes.NewBufferString(body) req := httptest.NewRequest(http.MethodPost, "/v1/auth/check-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 legacyCheckCodeResponse err := json.Unmarshal(recorder.Body.Bytes(), &resp) require.NoError(t, err) return resp } func TestCheckCodeLegacyHandler_NoHeaderNotConsumed(t *testing.T) { svcCtx, redisClient := newLegacyCheckCodeTestSvcCtx(t) router := newLegacyCheckCodeTestRouter(svcCtx) email := "legacy@example.com" code := "123456" cacheKey := seedLegacyVerifyCode(t, redisClient, constant.Security.String(), email, code) resp := callLegacyCheckCode(t, router, "", `{"email":"legacy@example.com","code":"123456","type":3}`) assert.Equal(t, uint32(200), resp.Code) assert.True(t, resp.Data.Status) assert.True(t, resp.Data.Exist) exists, err := redisClient.Exists(context.Background(), cacheKey).Result() require.NoError(t, err) assert.Equal(t, int64(1), exists) } func TestCheckCodeLegacyHandler_GreaterVersionConsumed(t *testing.T) { svcCtx, redisClient := newLegacyCheckCodeTestSvcCtx(t) router := newLegacyCheckCodeTestRouter(svcCtx) email := "latest@example.com" code := "999888" cacheKey := seedLegacyVerifyCode(t, redisClient, constant.Security.String(), email, code) resp := callLegacyCheckCode(t, router, "1.0.1", `{"email":"latest@example.com","code":"999888","type":3}`) 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) assert.Equal(t, int64(0), exists) resp = callLegacyCheckCode(t, router, "1.0.1", `{"email":"latest@example.com","code":"999888","type":3}`) assert.Equal(t, uint32(200), resp.Code) assert.False(t, resp.Data.Status) assert.False(t, resp.Data.Exist) } func TestCheckCodeLegacyHandler_EqualThresholdNotConsumed(t *testing.T) { svcCtx, redisClient := newLegacyCheckCodeTestSvcCtx(t) router := newLegacyCheckCodeTestRouter(svcCtx) email := "equal@example.com" code := "112233" cacheKey := seedLegacyVerifyCode(t, redisClient, constant.Security.String(), email, code) resp := callLegacyCheckCode(t, router, "1.0.0", `{"email":"equal@example.com","code":"112233","type":3}`) 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) assert.Equal(t, int64(1), exists) } func TestCheckCodeLegacyHandler_InvalidVersionNotConsumed(t *testing.T) { svcCtx, redisClient := newLegacyCheckCodeTestSvcCtx(t) router := newLegacyCheckCodeTestRouter(svcCtx) email := "invalid@example.com" code := "445566" cacheKey := seedLegacyVerifyCode(t, redisClient, constant.Security.String(), email, code) resp := callLegacyCheckCode(t, router, "abc", `{"email":"invalid@example.com","code":"445566","type":3}`) 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) assert.Equal(t, int64(1), exists) }