Skip to content

Commit b3eded8

Browse files
naveensrinivasanashearin
authored andcommitted
🌱 Improve rate limit handling in roundtripper (ossf#3237)
- Add rate limit testing and handling functionality - Add tests for successful response and Retry-After header set scenarios Signed-off-by: naveensrinivasan <[email protected]> Signed-off-by: Allen Shearin <[email protected]>
1 parent 8552871 commit b3eded8

2 files changed

Lines changed: 95 additions & 1 deletion

File tree

clients/githubrepo/roundtripper/rate_limit.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ type rateLimitTransport struct {
4242
innerTransport http.RoundTripper
4343
}
4444

45-
// Roundtrip handles caching and ratelimiting of responses from GitHub.
45+
// RoundTrip handles caching and rate-limiting of responses from GitHub.
4646
func (gh *rateLimitTransport) RoundTrip(r *http.Request) (*http.Response, error) {
4747
resp, err := gh.innerTransport.RoundTrip(r)
4848
if err != nil {
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// Copyright 2023 OpenSSF Scorecard Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
package roundtripper
15+
16+
import (
17+
"net/http"
18+
"net/http/httptest"
19+
"testing"
20+
21+
"github.com/ossf/scorecard/v4/log"
22+
)
23+
24+
func TestRoundTrip(t *testing.T) {
25+
t.Parallel()
26+
var requestCount int
27+
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
28+
// Customize the response headers and body based on the test scenario
29+
switch r.URL.Path {
30+
case "/error":
31+
w.WriteHeader(http.StatusInternalServerError)
32+
w.Write([]byte("Internal Server Error")) // nolint: errcheck
33+
case "/retry":
34+
requestCount++
35+
if requestCount == 2 {
36+
// Second request: Return successful response
37+
w.Header().Set("X-RateLimit-Remaining", "10")
38+
w.WriteHeader(http.StatusOK)
39+
w.Write([]byte("Success")) // nolint: errcheck
40+
} else {
41+
// First request: Return Retry-After header
42+
w.Header().Set("Retry-After", "1")
43+
w.WriteHeader(http.StatusTooManyRequests)
44+
w.Write([]byte("Rate Limit Exceeded")) // nolint: errcheck
45+
}
46+
case "/success":
47+
w.Header().Set("X-RateLimit-Remaining", "10")
48+
w.WriteHeader(http.StatusOK)
49+
w.Write([]byte("Success")) // nolint: errcheck
50+
}
51+
}))
52+
t.Cleanup(func() {
53+
defer ts.Close()
54+
})
55+
56+
// Create the rateLimitTransport with the test server as the inner transport and a default logger
57+
transport := &rateLimitTransport{
58+
innerTransport: ts.Client().Transport,
59+
logger: log.NewLogger(log.DefaultLevel),
60+
}
61+
62+
t.Run("Successful response", func(t *testing.T) {
63+
req, err := http.NewRequest(http.MethodGet, ts.URL+"/success", nil)
64+
if err != nil {
65+
t.Fatalf("Failed to create request: %v", err)
66+
}
67+
68+
resp, err := transport.RoundTrip(req)
69+
if err != nil {
70+
t.Errorf("Unexpected error: %v", err)
71+
}
72+
if resp.StatusCode != http.StatusOK {
73+
t.Errorf("Expected status code %d, got %d", http.StatusOK, resp.StatusCode)
74+
}
75+
})
76+
77+
t.Run("Retry-After header set", func(t *testing.T) {
78+
req, err := http.NewRequest(http.MethodGet, ts.URL+"/retry", nil)
79+
if err != nil {
80+
t.Fatalf("Failed to create request: %v", err)
81+
}
82+
83+
resp, err := transport.RoundTrip(req)
84+
if err != nil {
85+
t.Errorf("Unexpected error: %v", err)
86+
}
87+
if resp.StatusCode != http.StatusOK {
88+
t.Errorf("Expected status code %d, got %d", http.StatusOK, resp.StatusCode)
89+
}
90+
if requestCount != 2 {
91+
t.Errorf("Expected 2 requests, got %d", requestCount)
92+
}
93+
})
94+
}

0 commit comments

Comments
 (0)