auth_rate_limit_integration_test.go 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. //go:build integration
  2. package routes
  3. import (
  4. "context"
  5. "fmt"
  6. "net/http"
  7. "net/http/httptest"
  8. "os"
  9. "path/filepath"
  10. "strconv"
  11. "strings"
  12. "testing"
  13. "github.com/redis/go-redis/v9"
  14. "github.com/stretchr/testify/require"
  15. tcredis "github.com/testcontainers/testcontainers-go/modules/redis"
  16. )
  17. const authRouteRedisImageTag = "redis:8.4-alpine"
  18. func TestAuthRegisterRateLimitThresholdHitReturns429(t *testing.T) {
  19. ctx := context.Background()
  20. rdb := startAuthRouteRedis(t, ctx)
  21. router := newAuthRoutesTestRouter(rdb)
  22. const path = "/api/v1/auth/register"
  23. for i := 1; i <= 6; i++ {
  24. req := httptest.NewRequest(http.MethodPost, path, strings.NewReader(`{}`))
  25. req.Header.Set("Content-Type", "application/json")
  26. req.RemoteAddr = "198.51.100.10:23456"
  27. w := httptest.NewRecorder()
  28. router.ServeHTTP(w, req)
  29. if i <= 5 {
  30. require.Equal(t, http.StatusBadRequest, w.Code, "第 %d 次请求应先进入业务校验", i)
  31. continue
  32. }
  33. require.Equal(t, http.StatusTooManyRequests, w.Code, "第 6 次请求应命中限流")
  34. require.Contains(t, w.Body.String(), "rate limit exceeded")
  35. }
  36. }
  37. func startAuthRouteRedis(t *testing.T, ctx context.Context) *redis.Client {
  38. t.Helper()
  39. ensureAuthRouteDockerAvailable(t)
  40. redisContainer, err := tcredis.Run(ctx, authRouteRedisImageTag)
  41. require.NoError(t, err)
  42. t.Cleanup(func() {
  43. _ = redisContainer.Terminate(ctx)
  44. })
  45. redisHost, err := redisContainer.Host(ctx)
  46. require.NoError(t, err)
  47. redisPort, err := redisContainer.MappedPort(ctx, "6379/tcp")
  48. require.NoError(t, err)
  49. rdb := redis.NewClient(&redis.Options{
  50. Addr: fmt.Sprintf("%s:%d", redisHost, redisPort.Int()),
  51. DB: 0,
  52. })
  53. require.NoError(t, rdb.Ping(ctx).Err())
  54. t.Cleanup(func() {
  55. _ = rdb.Close()
  56. })
  57. return rdb
  58. }
  59. func ensureAuthRouteDockerAvailable(t *testing.T) {
  60. t.Helper()
  61. if authRouteDockerAvailable() {
  62. return
  63. }
  64. t.Skip("Docker 未启用,跳过认证限流集成测试")
  65. }
  66. func authRouteDockerAvailable() bool {
  67. if os.Getenv("DOCKER_HOST") != "" {
  68. return true
  69. }
  70. socketCandidates := []string{
  71. "/var/run/docker.sock",
  72. filepath.Join(os.Getenv("XDG_RUNTIME_DIR"), "docker.sock"),
  73. filepath.Join(authRouteUserHomeDir(), ".docker", "run", "docker.sock"),
  74. filepath.Join(authRouteUserHomeDir(), ".docker", "desktop", "docker.sock"),
  75. filepath.Join("/run/user", strconv.Itoa(os.Getuid()), "docker.sock"),
  76. }
  77. for _, socket := range socketCandidates {
  78. if socket == "" {
  79. continue
  80. }
  81. if _, err := os.Stat(socket); err == nil {
  82. return true
  83. }
  84. }
  85. return false
  86. }
  87. func authRouteUserHomeDir() string {
  88. home, err := os.UserHomeDir()
  89. if err != nil {
  90. return ""
  91. }
  92. return home
  93. }