-
Notifications
You must be signed in to change notification settings - Fork 402
Expand file tree
/
Copy pathtest_rate_limit_utils.py
More file actions
205 lines (167 loc) · 9.25 KB
/
test_rate_limit_utils.py
File metadata and controls
205 lines (167 loc) · 9.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
"""
Unit tests for rate limit handling functions.
Tests the parse_retry_after(), is_rate_limit_error(), and backoff calculation
functions from rate_limit_utils.py (shared module).
"""
import unittest
from rate_limit_utils import (
calculate_error_backoff,
calculate_rate_limit_backoff,
clamp_retry_delay,
is_rate_limit_error,
parse_retry_after,
)
class TestParseRetryAfter(unittest.TestCase):
"""Tests for parse_retry_after() function."""
def test_retry_after_colon_format(self):
"""Test 'Retry-After: 60' format."""
assert parse_retry_after("Retry-After: 60") == 60
assert parse_retry_after("retry-after: 120") == 120
assert parse_retry_after("retry after: 30 seconds") == 30
def test_retry_after_space_format(self):
"""Test 'retry after 60 seconds' format."""
assert parse_retry_after("retry after 60 seconds") == 60
assert parse_retry_after("Please retry after 120 seconds") == 120
assert parse_retry_after("Retry after 30") == 30
def test_try_again_in_format(self):
"""Test 'try again in X seconds' format."""
assert parse_retry_after("try again in 120 seconds") == 120
assert parse_retry_after("Please try again in 60s") == 60
assert parse_retry_after("Try again in 30 seconds") == 30
def test_seconds_remaining_format(self):
"""Test 'X seconds remaining' format."""
assert parse_retry_after("30 seconds remaining") == 30
assert parse_retry_after("60 seconds left") == 60
assert parse_retry_after("120 seconds until reset") == 120
def test_retry_after_zero(self):
"""Test 'Retry-After: 0' returns 0 (not None)."""
assert parse_retry_after("Retry-After: 0") == 0
assert parse_retry_after("retry after 0 seconds") == 0
def test_no_match(self):
"""Test messages that don't contain retry-after info."""
assert parse_retry_after("no match here") is None
assert parse_retry_after("Connection refused") is None
assert parse_retry_after("Internal server error") is None
assert parse_retry_after("") is None
def test_minutes_not_supported(self):
"""Test that minutes are not parsed (by design)."""
# We only support seconds to avoid complexity
# These patterns should NOT match when followed by minute/hour units
assert parse_retry_after("wait 5 minutes") is None
assert parse_retry_after("try again in 2 minutes") is None
assert parse_retry_after("retry after 5 minutes") is None
assert parse_retry_after("retry after 1 hour") is None
assert parse_retry_after("try again in 30 min") is None
class TestIsRateLimitError(unittest.TestCase):
"""Tests for is_rate_limit_error() function."""
def test_rate_limit_patterns(self):
"""Test various rate limit error messages."""
assert is_rate_limit_error("Rate limit exceeded") is True
assert is_rate_limit_error("rate_limit_exceeded") is True
assert is_rate_limit_error("Too many requests") is True
assert is_rate_limit_error("HTTP 429 Too Many Requests") is True
assert is_rate_limit_error("API quota exceeded") is True
assert is_rate_limit_error("Server is overloaded") is True
def test_specific_429_patterns(self):
"""Test that 429 is detected with proper context."""
assert is_rate_limit_error("http 429") is True
assert is_rate_limit_error("HTTP429") is True
assert is_rate_limit_error("status 429") is True
assert is_rate_limit_error("error 429") is True
assert is_rate_limit_error("429 too many requests") is True
def test_case_insensitive(self):
"""Test that detection is case-insensitive."""
assert is_rate_limit_error("RATE LIMIT") is True
assert is_rate_limit_error("Rate Limit") is True
assert is_rate_limit_error("rate limit") is True
assert is_rate_limit_error("RaTe LiMiT") is True
def test_non_rate_limit_errors(self):
"""Test non-rate-limit error messages."""
assert is_rate_limit_error("Connection refused") is False
assert is_rate_limit_error("Authentication failed") is False
assert is_rate_limit_error("Invalid API key") is False
assert is_rate_limit_error("Internal server error") is False
assert is_rate_limit_error("Network timeout") is False
assert is_rate_limit_error("") is False
class TestFalsePositives(unittest.TestCase):
"""Verify non-rate-limit messages don't trigger detection."""
def test_version_numbers_with_429(self):
"""Version numbers should not trigger."""
assert is_rate_limit_error("Node v14.29.0") is False
assert is_rate_limit_error("Python 3.12.429") is False
assert is_rate_limit_error("Version 2.429 released") is False
def test_issue_and_pr_numbers(self):
"""Issue/PR numbers should not trigger."""
assert is_rate_limit_error("See PR #429") is False
assert is_rate_limit_error("Fixed in issue 429") is False
assert is_rate_limit_error("Closes #429") is False
def test_line_numbers(self):
"""Line numbers in errors should not trigger."""
assert is_rate_limit_error("Error at line 429") is False
assert is_rate_limit_error("See file.py:429") is False
def test_port_numbers(self):
"""Port numbers should not trigger."""
assert is_rate_limit_error("port 4293") is False
assert is_rate_limit_error("localhost:4290") is False
def test_legitimate_wait_messages(self):
"""Legitimate wait instructions should not trigger."""
# These would fail if "please wait" pattern still exists
assert is_rate_limit_error("Please wait for the build to complete") is False
assert is_rate_limit_error("Please wait while I analyze this") is False
def test_retry_discussion_messages(self):
"""Messages discussing retry logic should not trigger."""
# These would fail if "try again later" pattern still exists
assert is_rate_limit_error("Try again later after maintenance") is False
assert is_rate_limit_error("The user should try again later") is False
def test_limit_discussion_messages(self):
"""Messages discussing limits should not trigger (removed pattern)."""
# These would fail if "limit reached" pattern still exists
assert is_rate_limit_error("File size limit reached") is False
assert is_rate_limit_error("Memory limit reached, consider optimization") is False
def test_overloaded_in_programming_context(self):
"""Method/operator overloading discussions should not trigger."""
assert is_rate_limit_error("I will create an overloaded constructor") is False
assert is_rate_limit_error("The + operator is overloaded") is False
assert is_rate_limit_error("Here is the overloaded version of the function") is False
assert is_rate_limit_error("The method is overloaded to accept different types") is False
# But actual API overload messages should still match
assert is_rate_limit_error("Server is overloaded") is True
assert is_rate_limit_error("API overloaded") is True
assert is_rate_limit_error("system is overloaded") is True
class TestBackoffFunctions(unittest.TestCase):
"""Test backoff calculation functions from rate_limit_utils."""
def test_rate_limit_backoff_sequence(self):
"""Test that rate limit backoff follows expected exponential sequence with jitter.
Base formula: 15 * 2^retries with 0-30% jitter.
Base values: 15, 30, 60, 120, 240, 480, 960, 1920, 3600, 3600
With jitter the result should be in [base, base * 1.3].
"""
base_values = [15, 30, 60, 120, 240, 480, 960, 1920, 3600, 3600]
for retries, base in enumerate(base_values):
delay = calculate_rate_limit_backoff(retries)
# Delay must be at least the base value (jitter is non-negative)
assert delay >= base, f"Retry {retries}: {delay} < base {base}"
# Delay must not exceed base + 30% jitter (int truncation means <= base * 1.3)
max_with_jitter = int(base * 1.3)
assert delay <= max_with_jitter, f"Retry {retries}: {delay} > max {max_with_jitter}"
def test_error_backoff_sequence(self):
"""Test that error backoff follows expected linear sequence."""
expected = [30, 60, 90, 120, 150, 180, 210, 240, 270, 300, 300] # Caps at 300
for retries in range(1, len(expected) + 1):
delay = calculate_error_backoff(retries)
expected_delay = expected[retries - 1]
assert delay == expected_delay, f"Retry {retries}: expected {expected_delay}, got {delay}"
def test_clamp_retry_delay(self):
"""Test that retry delay is clamped to valid range."""
# Values within range stay the same
assert clamp_retry_delay(60) == 60
assert clamp_retry_delay(1800) == 1800
assert clamp_retry_delay(3600) == 3600
# Values below minimum get clamped to 1
assert clamp_retry_delay(0) == 1
assert clamp_retry_delay(-10) == 1
# Values above maximum get clamped to 3600
assert clamp_retry_delay(7200) == 3600
assert clamp_retry_delay(86400) == 3600
if __name__ == "__main__":
unittest.main()