quota_test.go 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. //go:build unit
  2. package xai
  3. import (
  4. "net/http"
  5. "testing"
  6. "time"
  7. "github.com/stretchr/testify/require"
  8. )
  9. func TestParseQuotaHeaders(t *testing.T) {
  10. t.Parallel()
  11. headers := http.Header{}
  12. headers.Set("x-ratelimit-limit-requests", "100")
  13. headers.Set("x-ratelimit-remaining-requests", "25")
  14. headers.Set("x-ratelimit-reset-requests", "1893456000")
  15. headers.Set("x-ratelimit-limit-tokens", "1000000")
  16. headers.Set("x-ratelimit-remaining-tokens", "750000")
  17. headers.Set("retry-after", "60")
  18. headers.Set("xai-subscription-tier", "supergrok")
  19. headers.Set("xai-entitlement-status", "active")
  20. headers.Set("authorization", "should-not-be-copied")
  21. snapshot := ParseQuotaHeaders(headers, http.StatusTooManyRequests)
  22. require.NotNil(t, snapshot)
  23. require.Equal(t, http.StatusTooManyRequests, snapshot.StatusCode)
  24. require.True(t, snapshot.HeadersObserved)
  25. require.NotEmpty(t, snapshot.LastHeadersSeenAt)
  26. require.Equal(t, int64(100), *snapshot.Requests.Limit)
  27. require.Equal(t, int64(25), *snapshot.Requests.Remaining)
  28. require.Equal(t, int64(1893456000), *snapshot.Requests.ResetUnix)
  29. require.Equal(t, "2030-01-01T00:00:00Z", snapshot.Requests.ResetAt)
  30. require.Equal(t, int64(1000000), *snapshot.Tokens.Limit)
  31. require.Equal(t, int64(750000), *snapshot.Tokens.Remaining)
  32. require.Equal(t, 60, *snapshot.RetryAfterSeconds)
  33. require.Equal(t, "supergrok", snapshot.SubscriptionTier)
  34. require.Equal(t, "active", snapshot.EntitlementStatus)
  35. require.Contains(t, snapshot.Headers, "x-ratelimit-limit-requests")
  36. require.NotContains(t, snapshot.Headers, "authorization")
  37. }
  38. func TestParseQuotaHeadersAcceptsXAITierAliases(t *testing.T) {
  39. t.Parallel()
  40. headers := http.Header{}
  41. headers.Set("x-xai-user-tier", "supergrok-heavy")
  42. headers.Set("x-xai-user-entitlement-status", "enabled")
  43. snapshot := ParseQuotaHeaders(headers, http.StatusOK)
  44. require.NotNil(t, snapshot)
  45. require.True(t, snapshot.HeadersObserved)
  46. require.Equal(t, "supergrok-heavy", snapshot.SubscriptionTier)
  47. require.Equal(t, "enabled", snapshot.EntitlementStatus)
  48. require.Equal(t, "supergrok-heavy", snapshot.Headers["x-xai-user-tier"])
  49. require.Equal(t, "enabled", snapshot.Headers["x-xai-user-entitlement-status"])
  50. }
  51. func TestParseQuotaHeadersAcceptsRateLimitAliases(t *testing.T) {
  52. t.Parallel()
  53. headers := http.Header{}
  54. headers.Set("x-rate-limit-limit-tokens", "500000")
  55. headers.Set("x-rate-limit-remaining-tokens", "100")
  56. headers.Set("x-rate-limit-reset-tokens", "1893456000")
  57. snapshot := ParseQuotaHeaders(headers, http.StatusOK)
  58. require.NotNil(t, snapshot)
  59. require.NotNil(t, snapshot.Tokens)
  60. require.Equal(t, int64(500000), *snapshot.Tokens.Limit)
  61. require.Equal(t, int64(100), *snapshot.Tokens.Remaining)
  62. require.Equal(t, int64(1893456000), *snapshot.Tokens.ResetUnix)
  63. require.Contains(t, snapshot.Headers, "x-rate-limit-limit-tokens")
  64. }
  65. func TestParseResetHeaderRelativeSecondsNotMisreadAsEpoch(t *testing.T) {
  66. t.Parallel()
  67. headers := http.Header{}
  68. // xAI may return the reset window as a relative number of seconds ("60").
  69. // It must resolve to ~now+60s, NOT 1970-01-01 (epoch 60).
  70. headers.Set("x-ratelimit-reset-requests", "60")
  71. headers.Set("x-ratelimit-remaining-requests", "0")
  72. before := time.Now().Unix()
  73. snapshot := ParseQuotaHeaders(headers, http.StatusTooManyRequests)
  74. require.NotNil(t, snapshot)
  75. require.NotNil(t, snapshot.Requests)
  76. require.NotNil(t, snapshot.Requests.ResetUnix)
  77. got := *snapshot.Requests.ResetUnix
  78. require.GreaterOrEqual(t, got, before+59)
  79. require.LessOrEqual(t, got, time.Now().Unix()+61)
  80. }
  81. func TestParseResetHeaderDurationWindow(t *testing.T) {
  82. t.Parallel()
  83. headers := http.Header{}
  84. headers.Set("x-ratelimit-reset-requests", "6m0s")
  85. headers.Set("x-ratelimit-remaining-requests", "0")
  86. before := time.Now().Unix()
  87. snapshot := ParseQuotaHeaders(headers, http.StatusTooManyRequests)
  88. require.NotNil(t, snapshot)
  89. require.NotNil(t, snapshot.Requests)
  90. require.NotNil(t, snapshot.Requests.ResetUnix)
  91. got := *snapshot.Requests.ResetUnix
  92. require.GreaterOrEqual(t, got, before+359)
  93. require.LessOrEqual(t, got, time.Now().Unix()+361)
  94. }
  95. func TestParseResetHeaderSubsecondDurationCeilsToFutureSecond(t *testing.T) {
  96. t.Parallel()
  97. headers := http.Header{}
  98. headers.Set("x-rate-limit-reset-tokens", "250ms")
  99. headers.Set("x-rate-limit-remaining-tokens", "0")
  100. before := time.Now().Unix()
  101. snapshot := ParseQuotaHeaders(headers, http.StatusTooManyRequests)
  102. require.NotNil(t, snapshot)
  103. require.NotNil(t, snapshot.Tokens)
  104. require.NotNil(t, snapshot.Tokens.ResetUnix)
  105. require.GreaterOrEqual(t, *snapshot.Tokens.ResetUnix, before)
  106. require.LessOrEqual(t, *snapshot.Tokens.ResetUnix, time.Now().Unix()+2)
  107. }
  108. func TestParseResetHeaderMillisecondsEpochNormalized(t *testing.T) {
  109. t.Parallel()
  110. headers := http.Header{}
  111. headers.Set("x-ratelimit-reset-tokens", "1893456000000") // ms epoch
  112. headers.Set("x-ratelimit-remaining-tokens", "0")
  113. snapshot := ParseQuotaHeaders(headers, http.StatusTooManyRequests)
  114. require.NotNil(t, snapshot)
  115. require.NotNil(t, snapshot.Tokens)
  116. require.Equal(t, int64(1893456000), *snapshot.Tokens.ResetUnix)
  117. }
  118. func TestParseQuotaHeadersReturnsNilForMissingHeaders(t *testing.T) {
  119. t.Parallel()
  120. require.Nil(t, ParseQuotaHeaders(http.Header{}, http.StatusOK))
  121. }
  122. func TestObserveQuotaHeadersRecordsNoHeaderProbe(t *testing.T) {
  123. t.Parallel()
  124. snapshot := ObserveQuotaHeaders(http.Header{}, http.StatusOK, "active_probe")
  125. require.NotNil(t, snapshot)
  126. require.False(t, snapshot.HeadersObserved)
  127. require.Equal(t, http.StatusOK, snapshot.StatusCode)
  128. require.Equal(t, "active_probe", snapshot.ObservationSource)
  129. require.NotEmpty(t, snapshot.LastProbeAt)
  130. require.Empty(t, snapshot.LastHeadersSeenAt)
  131. require.Empty(t, snapshot.Headers)
  132. require.Nil(t, snapshot.Requests)
  133. require.Nil(t, snapshot.Tokens)
  134. }
  135. func TestIsGrokFreeRolling24hTokenLimit(t *testing.T) {
  136. t.Parallel()
  137. require.True(t, IsGrokFreeRolling24hTokenLimit(GrokFreeRolling24hTokenLimit))
  138. require.True(t, IsGrokFreeRolling24hTokenLimit(500_000))
  139. require.True(t, IsGrokFreeRolling24hTokenLimit(1_000_000), "observed Free limit variants remain classifiable")
  140. require.True(t, IsGrokFreeRolling24hTokenLimit(2_000_000), "legacy snapshots remain classifiable")
  141. require.False(t, IsGrokFreeRolling24hTokenLimit(3_000_000))
  142. }