package migration import ( "context" "net/http" "net/http/httptest" "testing" "time" ) func TestParseRetryAfter(t *testing.T) { if got := parseRetryAfter("30"); got != 30*time.Second { t.Fatalf("seconds = %v", got) } if got := parseRetryAfter(""); got != 0 { t.Fatalf("empty = %v", got) } future := time.Now().Add(45 * time.Second).UTC().Format(http.TimeFormat) if got := parseRetryAfter(future); got < 40*time.Second || got > 50*time.Second { t.Fatalf("http date = %v", got) } } func TestRateLimitDelayUsesRetryAfter(t *testing.T) { ConfigureRateLimit(RateLimitConfig{ MaxRetries: 3, BaseDelay: 100 * time.Millisecond, MaxDelay: time.Second, }) delay := rateLimitDelay(1, 500*time.Millisecond) if delay != 500*time.Millisecond { t.Fatalf("delay = %v", delay) } delay = rateLimitDelay(3, 0) if delay != 400*time.Millisecond { t.Fatalf("exponential delay = %v", delay) } } func TestAPIGETRetries429ThenSucceeds(t *testing.T) { ConfigureRateLimit(RateLimitConfig{ MaxRetries: 5, BaseDelay: 5 * time.Millisecond, MaxDelay: 50 * time.Millisecond, }) calls := 0 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { calls++ if calls < 3 { w.Header().Set("Retry-After", "0") w.WriteHeader(http.StatusTooManyRequests) _, _ = w.Write([]byte(`{"error":"rateLimitExceeded"}`)) return } w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte(`{"ok":true}`)) })) t.Cleanup(srv.Close) body, err := apiGet(context.Background(), srv.Client(), srv.URL, "token") if err != nil { t.Fatalf("apiGet: %v", err) } if string(body) != `{"ok":true}` { t.Fatalf("body = %q", body) } if calls != 3 { t.Fatalf("calls = %d", calls) } } func TestAPIGETReturnsRateLimitErrorAfterMaxRetries(t *testing.T) { ConfigureRateLimit(RateLimitConfig{ MaxRetries: 2, BaseDelay: time.Millisecond, MaxDelay: 10 * time.Millisecond, }) srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Retry-After", "0") w.WriteHeader(http.StatusTooManyRequests) _, _ = w.Write([]byte(`{"error":"quota"}`)) })) t.Cleanup(srv.Close) _, err := apiGet(context.Background(), srv.Client(), srv.URL, "token") if !IsRateLimitError(err) { t.Fatalf("expected RateLimitError, got %v", err) } } func TestWorkerRateLimitErrorIsPending(t *testing.T) { if !IsRateLimitError(&RateLimitError{Cause: errTestRateLimit}) { t.Fatal("expected typed rate limit error") } } var errTestRateLimit = &RateLimitError{Cause: errTestRateLimitCause{}} type errTestRateLimitCause struct{} func (errTestRateLimitCause) Error() string { return "api rate limited (429): quota" }