|
7 | 7 | "fmt" |
8 | 8 | "net/http" |
9 | 9 | "net/http/httptest" |
| 10 | + "sync/atomic" |
| 11 | + "testing" |
| 12 | + "time" |
10 | 13 |
|
11 | 14 | "github.com/worldline-go/klient" |
12 | 15 | ) |
@@ -123,3 +126,141 @@ func Example() { |
123 | 126 | // Output: |
124 | 127 | // 123+ |
125 | 128 | } |
| 129 | + |
| 130 | +func ExampleWithRetryTimeout() { |
| 131 | + // Create a client with retry timeout of 2 seconds per attempt |
| 132 | + client, err := klient.New( |
| 133 | + klient.WithDisableBaseURLCheck(true), |
| 134 | + klient.WithRetryMax(3), // Will retry up to 3 times |
| 135 | + klient.WithRetryWaitMin(500*time.Millisecond), |
| 136 | + klient.WithRetryWaitMax(1*time.Second), |
| 137 | + klient.WithRetryTimeout(2*time.Second), // Each attempt times out after 2 seconds |
| 138 | + ) |
| 139 | + if err != nil { |
| 140 | + panic(err) |
| 141 | + } |
| 142 | + |
| 143 | + // If a request takes longer than 2 seconds, it will timeout and retry |
| 144 | + // Total possible time: ~2s (first attempt) + 0.5s (wait) + 2s (retry) + ... |
| 145 | + _ = client |
| 146 | + fmt.Println("Client created with 2 second timeout per retry attempt") |
| 147 | + // Output: Client created with 2 second timeout per retry attempt |
| 148 | +} |
| 149 | + |
| 150 | +func TestRetryTimeout(t *testing.T) { |
| 151 | + var attemptCount atomic.Int32 |
| 152 | + |
| 153 | + // Create a server that delays responses |
| 154 | + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| 155 | + attempt := attemptCount.Add(1) |
| 156 | + t.Logf("Attempt %d received", attempt) |
| 157 | + |
| 158 | + // First 2 attempts take too long (will timeout) |
| 159 | + // Third attempt is fast (will succeed) |
| 160 | + if attempt < 3 { |
| 161 | + time.Sleep(3 * time.Second) // Longer than RetryTimeout |
| 162 | + } |
| 163 | + |
| 164 | + w.WriteHeader(http.StatusOK) |
| 165 | + w.Write([]byte("success")) |
| 166 | + })) |
| 167 | + defer server.Close() |
| 168 | + |
| 169 | + // Create client with retry timeout |
| 170 | + client, err := klient.New( |
| 171 | + klient.WithDisableBaseURLCheck(true), |
| 172 | + klient.WithRetryMax(3), |
| 173 | + klient.WithRetryWaitMin(100*time.Millisecond), |
| 174 | + klient.WithRetryWaitMax(200*time.Millisecond), |
| 175 | + klient.WithRetryTimeout(2*time.Second), // Each attempt times out after 2 seconds |
| 176 | + ) |
| 177 | + if err != nil { |
| 178 | + t.Fatal(err) |
| 179 | + } |
| 180 | + |
| 181 | + req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, server.URL, nil) |
| 182 | + if err != nil { |
| 183 | + t.Fatal(err) |
| 184 | + } |
| 185 | + |
| 186 | + start := time.Now() |
| 187 | + resp, err := client.HTTP.Do(req) |
| 188 | + elapsed := time.Since(start) |
| 189 | + |
| 190 | + if err != nil { |
| 191 | + t.Fatalf("Request failed after %v: %v", elapsed, err) |
| 192 | + } |
| 193 | + defer resp.Body.Close() |
| 194 | + |
| 195 | + attempts := attemptCount.Load() |
| 196 | + t.Logf("Request completed in %v with %d attempts", elapsed, attempts) |
| 197 | + |
| 198 | + // Should have made 3 attempts (2 timeouts + 1 success) |
| 199 | + if attempts != 3 { |
| 200 | + t.Errorf("Expected 3 attempts, got %d", attempts) |
| 201 | + } |
| 202 | + |
| 203 | + // Total time should be approximately: |
| 204 | + // 2s (timeout) + 0.1-0.2s (wait) + 2s (timeout) + 0.1-0.2s (wait) + <1s (success) |
| 205 | + // = approximately 4-5 seconds |
| 206 | + if elapsed < 4*time.Second || elapsed > 6*time.Second { |
| 207 | + t.Logf("Warning: elapsed time %v outside expected range (4-6s)", elapsed) |
| 208 | + } |
| 209 | +} |
| 210 | + |
| 211 | +func TestRetryTimeoutAllAttemptsTimeout(t *testing.T) { |
| 212 | + var attemptCount atomic.Int32 |
| 213 | + |
| 214 | + // Create a server that always delays |
| 215 | + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| 216 | + attempt := attemptCount.Add(1) |
| 217 | + t.Logf("Attempt %d received", attempt) |
| 218 | + time.Sleep(5 * time.Second) // Always too slow |
| 219 | + w.WriteHeader(http.StatusOK) |
| 220 | + })) |
| 221 | + defer server.Close() |
| 222 | + |
| 223 | + client, err := klient.New( |
| 224 | + klient.WithDisableBaseURLCheck(true), |
| 225 | + klient.WithRetryMax(2), // Will try 3 times total (initial + 2 retries) |
| 226 | + klient.WithRetryWaitMin(100*time.Millisecond), |
| 227 | + klient.WithRetryWaitMax(200*time.Millisecond), |
| 228 | + klient.WithRetryTimeout(1*time.Second), // Each attempt times out after 1 second |
| 229 | + ) |
| 230 | + if err != nil { |
| 231 | + t.Fatal(err) |
| 232 | + } |
| 233 | + |
| 234 | + req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, server.URL, nil) |
| 235 | + if err != nil { |
| 236 | + t.Fatal(err) |
| 237 | + } |
| 238 | + |
| 239 | + start := time.Now() |
| 240 | + resp, err := client.HTTP.Do(req) |
| 241 | + elapsed := time.Since(start) |
| 242 | + |
| 243 | + if resp != nil { |
| 244 | + resp.Body.Close() |
| 245 | + } |
| 246 | + |
| 247 | + attempts := attemptCount.Load() |
| 248 | + t.Logf("Request failed after %v with %d attempts (error: %v)", elapsed, attempts, err) |
| 249 | + |
| 250 | + // Should have made 3 attempts (all timeouts) |
| 251 | + if attempts != 3 { |
| 252 | + t.Errorf("Expected 3 attempts, got %d", attempts) |
| 253 | + } |
| 254 | + |
| 255 | + // Should have an error (context deadline exceeded) |
| 256 | + if err == nil { |
| 257 | + t.Error("Expected error due to timeouts, got nil") |
| 258 | + } |
| 259 | + |
| 260 | + // Total time should be approximately: |
| 261 | + // 1s (timeout) + 0.1-0.2s (wait) + 1s (timeout) + 0.1-0.2s (wait) + 1s (timeout) |
| 262 | + // = approximately 3-4 seconds |
| 263 | + if elapsed < 3*time.Second || elapsed > 4*time.Second { |
| 264 | + t.Logf("Warning: elapsed time %v outside expected range (3-4s)", elapsed) |
| 265 | + } |
| 266 | +} |
0 commit comments