fix(migration): return proper error message when request fails

Related to https://github.com/go-vikunja/vikunja/issues/1788
This commit is contained in:
kolaente
2025-11-12 20:25:17 +01:00
parent 9efc0baf50
commit 8862b6f69d
2 changed files with 26 additions and 5 deletions

View File

@@ -20,6 +20,8 @@ import (
"bytes"
"context"
"crypto/rand"
"fmt"
"io"
"math"
"math/big"
"net/http"
@@ -92,9 +94,19 @@ func DoPostWithHeaders(url string, form url.Values, headers map[string]string) (
return resp, nil
}
// Don't retry on last attempt
// Return error on last attempt if still getting 5xx
if attempt == maxRetries-1 {
return resp, nil
bodyBytes, readErr := io.ReadAll(resp.Body)
resp.Body.Close()
// Re-create the body so the caller can still read it if needed
resp.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
if readErr != nil {
return resp, fmt.Errorf("request failed after %d attempts with status code %d (could not read response body: %w)", maxRetries, resp.StatusCode, readErr)
}
return resp, fmt.Errorf("request failed after %d attempts with status code %d: %s", maxRetries, resp.StatusCode, string(bodyBytes))
}
// Close the body before retrying
@@ -108,5 +120,5 @@ func DoPostWithHeaders(url string, form url.Values, headers map[string]string) (
time.Sleep(delay + jitter)
}
return resp, nil
return nil, fmt.Errorf("request failed after %d attempts", maxRetries)
}

View File

@@ -20,6 +20,7 @@ import (
"net/http"
"net/http/httptest"
"net/url"
"strings"
"sync/atomic"
"testing"
)
@@ -53,17 +54,25 @@ func TestDoPostWithHeaders_RetriesOn500(t *testing.T) {
func TestDoPostWithHeaders_GivesUpAfter3Retries(t *testing.T) {
var attempts atomic.Int32
expectedBody := "Internal Server Error: database connection failed"
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
attempts.Add(1)
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte(expectedBody))
}))
defer server.Close()
form := url.Values{"key": {"value"}}
resp, err := DoPostWithHeaders(server.URL, form, map[string]string{})
if err != nil {
t.Fatalf("expected no error, got %v", err)
if err == nil {
t.Fatal("expected error after exhausted retries, got nil")
}
if !strings.Contains(err.Error(), expectedBody) {
t.Errorf("expected error message to contain response body %q, got: %s", expectedBody, err.Error())
}
if resp == nil {
t.Fatal("expected response to be returned with error, got nil")
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusInternalServerError {