summaryrefslogtreecommitdiff
path: root/t/t5584-http-429-retry.sh
blob: a22007b2cff4167468abfd92d437b66782eb67e0 (plain)
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
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
#!/bin/sh

test_description='test HTTP 429 Too Many Requests retry logic'

. ./test-lib.sh

. "$TEST_DIRECTORY"/lib-httpd.sh

start_httpd

test_expect_success 'setup test repository' '
	test_commit initial &&
	git clone --bare . "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
	git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/repo.git" config http.receivepack true
'

# This test suite uses a special HTTP 429 endpoint at /http_429/ that simulates
# rate limiting. The endpoint format is:
#   /http_429/<test-context>/<retry-after-value>/<repo-path>
# The http-429.sh script (in t/lib-httpd) returns a 429 response with the
# specified Retry-After header on the first request for each test context,
# then forwards subsequent requests to git-http-backend. Each test context
# is isolated, allowing multiple tests to run independently.

test_expect_success 'HTTP 429 with retries disabled (maxRetries=0) fails immediately' '
	# Set maxRetries to 0 (disabled)
	test_config http.maxRetries 0 &&
	test_config http.retryAfter 1 &&

	# Should fail immediately without any retry attempt
	test_must_fail git ls-remote "$HTTPD_URL/http_429/retries-disabled/1/repo.git" 2>err &&

	# Verify no retry happened (no "waiting" message in stderr)
	test_grep ! -i "waiting.*retry" err
'

test_expect_success 'HTTP 429 permanent should fail after max retries' '
	# Enable retries with a limit
	test_config http.maxRetries 2 &&

	# Git should retry but eventually fail when 429 persists
	test_must_fail git ls-remote "$HTTPD_URL/http_429/permanent-fail/permanent/repo.git" 2>err
'

test_expect_success 'HTTP 429 with Retry-After is retried and succeeds' '
	# Enable retries
	test_config http.maxRetries 3 &&

	# Git should retry after receiving 429 and eventually succeed
	git ls-remote "$HTTPD_URL/http_429/retry-succeeds/1/repo.git" >output 2>err &&
	test_grep "refs/heads/" output
'

test_expect_success 'HTTP 429 without Retry-After uses configured default' '
	# Enable retries and configure default delay
	test_config http.maxRetries 3 &&
	test_config http.retryAfter 1 &&

	# Git should retry using configured default and succeed
	git ls-remote "$HTTPD_URL/http_429/no-retry-after-header/none/repo.git" >output 2>err &&
	test_grep "refs/heads/" output
'

test_expect_success 'HTTP 429 retry delays are respected' '
	# Enable retries
	test_config http.maxRetries 3 &&

	# Time the operation - it should take at least 2 seconds due to retry delay
	start=$(test-tool date getnanos) &&
	git ls-remote "$HTTPD_URL/http_429/retry-delays-respected/2/repo.git" >output 2>err &&
	duration=$(test-tool date getnanos $start) &&

	# Verify it took at least 2 seconds (allowing some tolerance)
	duration_int=${duration%.*} &&
	test "$duration_int" -ge 1 &&
	test_grep "refs/heads/" output
'

test_expect_success 'HTTP 429 fails immediately if Retry-After exceeds http.maxRetryTime' '
	# Configure max retry time to 3 seconds (much less than requested 100)
	test_config http.maxRetries 3 &&
	test_config http.maxRetryTime 3 &&

	# Should fail immediately without waiting
	start=$(test-tool date getnanos) &&
	test_must_fail git ls-remote "$HTTPD_URL/http_429/retry-after-exceeds-max-time/100/repo.git" 2>err &&
	duration=$(test-tool date getnanos $start) &&

	# Should fail quickly (no 100 second wait)
	duration_int=${duration%.*} &&
	test "$duration_int" -lt 99 &&
	test_grep "greater than http.maxRetryTime" err
'

test_expect_success 'HTTP 429 fails if configured http.retryAfter exceeds http.maxRetryTime' '
	# Test misconfiguration: retryAfter > maxRetryTime
	# Configure retryAfter larger than maxRetryTime
	test_config http.maxRetries 3 &&
	test_config http.retryAfter 100 &&
	test_config http.maxRetryTime 5 &&

	# Should fail immediately with configuration error
	start=$(test-tool date getnanos) &&
	test_must_fail git ls-remote "$HTTPD_URL/http_429/config-retry-after-exceeds-max-time/none/repo.git" 2>err &&
	duration=$(test-tool date getnanos $start) &&

	# Should fail quickly (no 100 second wait)
	duration_int=${duration%.*} &&
	test "$duration_int" -lt 99 &&
	test_grep "configured http.retryAfter.*exceeds.*http.maxRetryTime" err
'

test_expect_success 'HTTP 429 with Retry-After HTTP-date format' '
	# Test HTTP-date format (RFC 2822) in Retry-After header
	raw=$(test-tool date timestamp now) &&
	now="${raw#* -> }" &&
	future_time=$((now + 2)) &&
	raw=$(test-tool date show:rfc2822 $future_time) &&
	future_date="${raw#* -> }" &&
	future_date_encoded=$(echo "$future_date" | sed "s/ /%20/g") &&

	# Enable retries
	test_config http.maxRetries 3 &&

	# Git should parse the HTTP-date and retry after the delay
	start=$(test-tool date getnanos) &&
	git ls-remote "$HTTPD_URL/http_429/http-date-format/$future_date_encoded/repo.git" >output 2>err &&
	duration=$(test-tool date getnanos $start) &&

	# Should take at least 1 second (allowing tolerance for processing time)
	duration_int=${duration%.*} &&
	test "$duration_int" -ge 1 &&
	test_grep "refs/heads/" output
'

test_expect_success 'HTTP 429 with HTTP-date exceeding maxRetryTime fails immediately' '
	raw=$(test-tool date timestamp now) &&
	now="${raw#* -> }" &&
	future_time=$((now + 200)) &&
	raw=$(test-tool date show:rfc2822 $future_time) &&
	future_date="${raw#* -> }" &&
	future_date_encoded=$(echo "$future_date" | sed "s/ /%20/g") &&

	# Configure max retry time much less than the 200 second delay
	test_config http.maxRetries 3 &&
	test_config http.maxRetryTime 10 &&

	# Should fail immediately without waiting 200 seconds
	start=$(test-tool date getnanos) &&
	test_must_fail git ls-remote "$HTTPD_URL/http_429/http-date-exceeds-max-time/$future_date_encoded/repo.git" 2>err &&
	duration=$(test-tool date getnanos $start) &&

	# Should fail quickly (not wait 200 seconds)
	duration_int=${duration%.*} &&
	test "$duration_int" -lt 199 &&
	test_grep "http.maxRetryTime" err
'

test_expect_success 'HTTP 429 with past HTTP-date should not wait' '
	raw=$(test-tool date timestamp now) &&
	now="${raw#* -> }" &&
	past_time=$((now - 10)) &&
	raw=$(test-tool date show:rfc2822 $past_time) &&
	past_date="${raw#* -> }" &&
	past_date_encoded=$(echo "$past_date" | sed "s/ /%20/g") &&

	# Enable retries
	test_config http.maxRetries 3 &&

	# Git should retry immediately without waiting
	start=$(test-tool date getnanos) &&
	git ls-remote "$HTTPD_URL/http_429/past-http-date/$past_date_encoded/repo.git" >output 2>err &&
	duration=$(test-tool date getnanos $start) &&

	# Should complete quickly (no wait for a past-date Retry-After)
	duration_int=${duration%.*} &&
	test "$duration_int" -lt 5 &&
	test_grep "refs/heads/" output
'

test_expect_success 'HTTP 429 with invalid Retry-After format uses configured default' '
	# Configure default retry-after
	test_config http.maxRetries 3 &&
	test_config http.retryAfter 1 &&

	# Should use configured default (1 second) since header is invalid
	start=$(test-tool date getnanos) &&
	git ls-remote "$HTTPD_URL/http_429/invalid-retry-after-format/invalid/repo.git" >output 2>err &&
	duration=$(test-tool date getnanos $start) &&

	# Should take at least 1 second (the configured default)
	duration_int=${duration%.*} &&
	test "$duration_int" -ge 1 &&
	test_grep "refs/heads/" output &&
	test_grep "waiting.*retry" err
'

test_expect_success 'HTTP 429 will not be retried without config' '
	# Default config means http.maxRetries=0 (retries disabled)
	# When 429 is received, it should fail immediately without retry
	# Do NOT configure anything - use defaults (http.maxRetries defaults to 0)

	# Should fail immediately without retry
	test_must_fail git ls-remote "$HTTPD_URL/http_429/no-retry-without-config/1/repo.git" 2>err &&

	# Verify no retry happened (no "waiting" message)
	test_grep ! -i "waiting.*retry" err &&

	# Should get 429 error
	test_grep "429" err
'

test_expect_success 'GIT_HTTP_RETRY_AFTER overrides http.retryAfter config' '
	# Configure retryAfter to 10 seconds
	test_config http.maxRetries 3 &&
	test_config http.retryAfter 10 &&

	# Override with environment variable to 1 second
	start=$(test-tool date getnanos) &&
	GIT_HTTP_RETRY_AFTER=1 git ls-remote "$HTTPD_URL/http_429/env-retry-after-override/none/repo.git" >output 2>err &&
	duration=$(test-tool date getnanos $start) &&

	# Should use env var (1 second), not config (10 seconds)
	duration_int=${duration%.*} &&
	test "$duration_int" -ge 1 &&
	test "$duration_int" -lt 5 &&
	test_grep "refs/heads/" output &&
	test_grep "waiting.*retry" err
'

test_expect_success 'GIT_HTTP_MAX_RETRIES overrides http.maxRetries config' '
	# Configure maxRetries to 0 (disabled)
	test_config http.maxRetries 0 &&
	test_config http.retryAfter 1 &&

	# Override with environment variable to enable retries
	GIT_HTTP_MAX_RETRIES=3 git ls-remote "$HTTPD_URL/http_429/env-max-retries-override/1/repo.git" >output 2>err &&

	# Should retry (env var enables it despite config saying disabled)
	test_grep "refs/heads/" output &&
	test_grep "waiting.*retry" err
'

test_expect_success 'GIT_HTTP_MAX_RETRY_TIME overrides http.maxRetryTime config' '
	# Configure maxRetryTime to 100 seconds (would accept 50 second delay)
	test_config http.maxRetries 3 &&
	test_config http.maxRetryTime 100 &&

	# Override with environment variable to 10 seconds (should reject 50 second delay)
	start=$(test-tool date getnanos) &&
	test_must_fail env GIT_HTTP_MAX_RETRY_TIME=10 \
		git ls-remote "$HTTPD_URL/http_429/env-max-retry-time-override/50/repo.git" 2>err &&
	duration=$(test-tool date getnanos $start) &&

	# Should fail quickly (not wait 50 seconds) because env var limits to 10
	duration_int=${duration%.*} &&
	test "$duration_int" -lt 49 &&
	test_grep "greater than http.maxRetryTime" err
'

test_expect_success 'verify normal repository access still works' '
	git ls-remote "$HTTPD_URL/smart/repo.git" >output &&
	test_grep "refs/heads/" output
'

test_done