diff --git a/backend/internal/services/admin.go b/backend/internal/services/admin.go index 97e12fd..fa2625e 100644 --- a/backend/internal/services/admin.go +++ b/backend/internal/services/admin.go @@ -1,6 +1,7 @@ package services import ( + "Noooste/garage-ui/internal/apierr" "Noooste/garage-ui/internal/config" "Noooste/garage-ui/internal/models" "Noooste/garage-ui/pkg/utils" @@ -63,13 +64,14 @@ func (s *GarageAdminService) doRequest(ctx context.Context, method, path string, return resp, nil } -// decodeResponse decodes a JSON response into the target structure +// decodeResponse decodes a JSON response into the target structure. Non-2xx +// responses are converted to *apierr.UpstreamError (Source="garage") so the +// handler layer can map them to the correct API error code and HTTP status. func decodeResponse(resp *azuretls.Response, target interface{}) error { defer resp.RawBody.Close() - if resp.StatusCode < 200 || resp.StatusCode >= 300 { - bodyBytes, _ := io.ReadAll(resp.RawBody) - return fmt.Errorf("API returned status %d: %s", resp.StatusCode, string(bodyBytes)) + if err := apierr.ParseGarage(resp); err != nil { + return err } if target != nil { @@ -77,7 +79,6 @@ func decodeResponse(resp *azuretls.Response, target interface{}) error { return fmt.Errorf("failed to decode response: %w", err) } } - return nil } diff --git a/backend/internal/services/admin_test.go b/backend/internal/services/admin_test.go index f252cfe..4f247b1 100644 --- a/backend/internal/services/admin_test.go +++ b/backend/internal/services/admin_test.go @@ -3,6 +3,7 @@ package services import ( "context" "encoding/json" + "errors" "io" "net" "net/http" @@ -11,6 +12,7 @@ import ( "testing" "time" + "Noooste/garage-ui/internal/apierr" "Noooste/garage-ui/internal/config" "Noooste/garage-ui/internal/models" ) @@ -103,8 +105,12 @@ func TestHealthCheck_Non2xxReturnsError(t *testing.T) { if err == nil { t.Fatal("expected error for 503, got nil") } - if !strings.Contains(err.Error(), "503") { - t.Errorf("error should mention status code, got %v", err) + var ue *apierr.UpstreamError + if !errors.As(err, &ue) { + t.Fatalf("expected *apierr.UpstreamError, got %T (%v)", err, err) + } + if ue.HTTPStatus != http.StatusServiceUnavailable { + t.Errorf("HTTPStatus = %d, want %d", ue.HTTPStatus, http.StatusServiceUnavailable) } } @@ -509,8 +515,15 @@ func TestDoRequest_Non2xxBodyEchoedInError(t *testing.T) { if err == nil { t.Fatal("expected error for 400, got nil") } - if !strings.Contains(err.Error(), "400") || !strings.Contains(err.Error(), "bad request") { - t.Errorf("error %v should contain 400 and the response body", err) + var ue *apierr.UpstreamError + if !errors.As(err, &ue) { + t.Fatalf("expected *apierr.UpstreamError, got %T (%v)", err, err) + } + if ue.HTTPStatus != http.StatusBadRequest { + t.Errorf("HTTPStatus = %d, want %d", ue.HTTPStatus, http.StatusBadRequest) + } + if !strings.Contains(ue.Message, "bad request") { + t.Errorf("Message = %q, want it to contain the response body", ue.Message) } }